Compare commits
	
		
			62 Commits
		
	
	
		
			bb19158aa0
			...
			dthomas_me
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						5ef8795eb4
	
				 | 
					
					
						|||
| 
						
						
							
						
						a63faba2aa
	
				 | 
					
					
						|||
| dd0ac46662 | |||
| 
						 | 
					1a07501d53 | ||
| 
						 | 
					0b97079cfd | ||
| 
						 | 
					a6d557d1c6 | ||
| 5a11acfa42 | |||
| 
						
						
							
						
						0f2c19e811
	
				 | 
					
					
						|||
| 
						
						
							
						
						b18355fc14
	
				 | 
					
					
						|||
| 
						 | 
					6921d5c4b4 | ||
| 
						 | 
					b861a61f07 | ||
| 
						 | 
					925ceb4b5a | ||
| 
						 | 
					dd2559130d | ||
| 
						 | 
					1fa7654da5 | ||
| 
						 | 
					051cc1d003 | ||
| 
						 | 
					e1af00e1db | ||
| 
						
						
							
						
						aadb6ba24d
	
				 | 
					
					
						|||
| 
						
						
							
						
						4561b1c1a3
	
				 | 
					
					
						|||
| 
						
						
							
						
						6edd0b4ef0
	
				 | 
					
					
						|||
| 
						
						
							
						
						2c242aac29
	
				 | 
					
					
						|||
| af6ffe451d | |||
| 
						
						
							
						
						b585f8cdb7
	
				 | 
					
					
						|||
| 50bf835d13 | |||
| 
						
						
							
						
						f12d8a8062
	
				 | 
					
					
						|||
| 
						
						
							
						
						fc9ff4c8b2
	
				 | 
					
					
						|||
| 
						
						
							
						
						e903150fd4
	
				 | 
					
					
						|||
| 
						
						
							
						
						d0ea696274
	
				 | 
					
					
						|||
| 
						
						
							
						
						eea8c9f5fa
	
				 | 
					
					
						|||
| fe5de4e54c | |||
| 
						
						
							
						
						68b95bfe17
	
				 | 
					
					
						|||
| 
						
						
							
						
						e3e9b855f9
	
				 | 
					
					
						|||
| 523915feb0 | |||
| b5b2a936c1 | |||
| afd144bd32 | |||
| eb221a5206 | |||
| 0299b3877d | |||
| 
						 | 
					fc6baa8826 | ||
| 20a970025d | |||
| 0d597a1866 | |||
| 329c3e5108 | |||
| 13b1c87b94 | |||
| 2a9a6c1576 | |||
| c3fc682123 | |||
| 0bbfa2d49c | |||
| 9ac8f95982 | |||
| 3371f4031c | |||
| d30831fc55 | |||
| 1659d38726 | |||
| db7c8c4577 | |||
| 
						 | 
					21b1bf7992 | ||
| d376dba67c | |||
| 95631dbdbe | |||
| 9aef296763 | |||
| 2b287492de | |||
| d2a4d93590 | |||
| 58605dbe85 | |||
| 7bf3276ce9 | |||
| 818688452b | |||
| 01526524d4 | |||
| 33671683ea | |||
| fad885c610 | |||
| 
						
						
							
						
						75fad013c0
	
				 | 
					
					
						
							
								
								
									
										17
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,18 @@
 | 
				
			|||||||
 | 
					# python
 | 
				
			||||||
venv
 | 
					venv
 | 
				
			||||||
__pycache__
 | 
					__pycache__
 | 
				
			||||||
cables
 | 
					# cable data folder(s)
 | 
				
			||||||
 | 
					cables
 | 
				
			||||||
 | 
					cables-sample.zip
 | 
				
			||||||
 | 
					# meilisearch (mainly where I've put the data volume for the container)
 | 
				
			||||||
 | 
					meili_data
 | 
				
			||||||
 | 
					# IDE things
 | 
				
			||||||
 | 
					.vscode
 | 
				
			||||||
 | 
					.idea
 | 
				
			||||||
 | 
					# videos
 | 
				
			||||||
 | 
					*.webm
 | 
				
			||||||
 | 
					output.mp4
 | 
				
			||||||
 | 
					# log files
 | 
				
			||||||
 | 
					output.log
 | 
				
			||||||
 | 
					# images
 | 
				
			||||||
 | 
					*.png
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					FROM python:latest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN apt-get update && apt-get install -y libgl1-mesa-glx ghostscript && apt-get clean && rm -rf /var/lib/apt/lists
 | 
				
			||||||
 | 
					COPY . .
 | 
				
			||||||
 | 
					#COPY config-server.yml config.yml
 | 
				
			||||||
 | 
					RUN pip3 install -r requirements.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["python3", "run.py"]
 | 
				
			||||||
 | 
					EXPOSE 5000
 | 
				
			||||||
 | 
					EXPOSE 8000
 | 
				
			||||||
 | 
					EXPOSE 9000
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								badapple.mp4
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								badapple.mp4
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										166
									
								
								banner_ivu_export.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										166
									
								
								banner_ivu_export.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,166 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					from time import sleep
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from: https://github.com/MoisesBrito31/ve_data_log/blob/main/serverContagem/VE/drive.py
 | 
				
			||||||
 | 
					(no license)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(partially) adapted to English language & iVu camera instead of classic VE by Cole Deck
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def gravaLog(ip="1",tipo="Evento", msg="", file="log_imagem.txt"):
 | 
				
			||||||
 | 
					    # removed full logging
 | 
				
			||||||
 | 
					    fprint(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriveImg():
 | 
				
			||||||
 | 
					    HEADERSIZE = 100
 | 
				
			||||||
 | 
					    ip = "192.168.0.1"
 | 
				
			||||||
 | 
					    port = 32200
 | 
				
			||||||
 | 
					    onLine = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, ip, port, pasta = "media/"):
 | 
				
			||||||
 | 
					        self.pasta = pasta
 | 
				
			||||||
 | 
					        self.ip=ip
 | 
				
			||||||
 | 
					        self.port = port
 | 
				
			||||||
 | 
					        self.trans = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					        self.trans.settimeout(5)
 | 
				
			||||||
 | 
					        fprint("Trying to connect...")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.trans.connect((self.ip,self.port))
 | 
				
			||||||
 | 
					            self.onLine = True
 | 
				
			||||||
 | 
					            fprint("Camera Online")
 | 
				
			||||||
 | 
					            #self.trans.close()
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self.onLine = False 
 | 
				
			||||||
 | 
					            fprint("Offline")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_img(self):
 | 
				
			||||||
 | 
					        resposta = 'Falha'
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if not self.onLine:
 | 
				
			||||||
 | 
					                #print(f'tentando Conectar camera {self.ip}...')
 | 
				
			||||||
 | 
					                gravaLog(ip=self.ip,msg=f'Trying to connect...')
 | 
				
			||||||
 | 
					                sleep(2)
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    self.trans = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					                    self.trans.connect((self.ip,self.PORT))
 | 
				
			||||||
 | 
					                    self.onLine = True
 | 
				
			||||||
 | 
					                    gravaLog(ip=self.ip,msg=f'Connection established.')
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    self.onLine = False
 | 
				
			||||||
 | 
					                    self.trans.close()
 | 
				
			||||||
 | 
					                    return resposta
 | 
				
			||||||
 | 
					            ret = self.trans.recv(64)
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                valida = str(ret[0:15].decode('UTF-8'))
 | 
				
			||||||
 | 
					                #print(valida)
 | 
				
			||||||
 | 
					                if valida.find("TC IMAGE")<0:
 | 
				
			||||||
 | 
					                    self.onLine = False
 | 
				
			||||||
 | 
					                    self.trans.close()
 | 
				
			||||||
 | 
					                    sleep(2)
 | 
				
			||||||
 | 
					                    gravaLog(ip=self.ip,tipo="Falha",msg=f'Unable to find TC IMAGE bookmark')
 | 
				
			||||||
 | 
					                    return "Error"
 | 
				
			||||||
 | 
					            except Exception as ex:
 | 
				
			||||||
 | 
					                self.onLine = False
 | 
				
			||||||
 | 
					                self.trans.close()
 | 
				
			||||||
 | 
					                sleep(2)
 | 
				
			||||||
 | 
					                gravaLog(ip=self.ip,tipo="Falha",msg=f'Error - {str(ex)}')
 | 
				
			||||||
 | 
					                return "Error"
 | 
				
			||||||
 | 
					            if ret:
 | 
				
			||||||
 | 
					                frame = int.from_bytes(ret[24:27],"little")
 | 
				
			||||||
 | 
					                isJpeg = int.from_bytes(ret[32:33],"little")
 | 
				
			||||||
 | 
					                img_size = int.from_bytes(ret[20:23],"little")
 | 
				
			||||||
 | 
					                data = self.trans.recv(5000)
 | 
				
			||||||
 | 
					                while img_size>len(data) and ret:
 | 
				
			||||||
 | 
					                    ret = self.trans.recv(10000)
 | 
				
			||||||
 | 
					                    if ret:
 | 
				
			||||||
 | 
					                        data = data+ret
 | 
				
			||||||
 | 
					                        #print(f'{len(ret)}b dados recebidos, total de: {len(data)+64}b')
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        gravaLog(ip=self.ip,tipo="Falha",msg="Unable to recieve the image")
 | 
				
			||||||
 | 
					                        self.onLine = False
 | 
				
			||||||
 | 
					                        return "Unable to recieve the image"
 | 
				
			||||||
 | 
					                hoje = datetime.now()
 | 
				
			||||||
 | 
					                idcam = self.ip.split('.')
 | 
				
			||||||
 | 
					                """try:
 | 
				
			||||||
 | 
					                    nomeFile = f'{hoje.day}{hoje.month}{hoje.year}-{idcam[3]}-{frame}'
 | 
				
			||||||
 | 
					                    if isJpeg==1:
 | 
				
			||||||
 | 
					                        file = open(f'{self.pasta}{nomeFile}.jpg','wb')
 | 
				
			||||||
 | 
					                        nomeFile = f'{nomeFile}.jpg'
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        file = open(f'{self.pasta}{nomeFile}.bmp','wb')
 | 
				
			||||||
 | 
					                        nomeFile = f'{nomeFile}.bmp'
 | 
				
			||||||
 | 
					                    file.write(data)
 | 
				
			||||||
 | 
					                    file.close()
 | 
				
			||||||
 | 
					                except Exception as ex:
 | 
				
			||||||
 | 
					                    sleep(2)
 | 
				
			||||||
 | 
					                    gravaLog(ip=self.ip,tipo="Falha",msg=f'Error - {str(ex)}')
 | 
				
			||||||
 | 
					                    return "Falha" 
 | 
				
			||||||
 | 
					                    """
 | 
				
			||||||
 | 
					                if isJpeg==1:
 | 
				
			||||||
 | 
					                    return "jpeg",data
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    return "bmp",data
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					        except Exception as ex:
 | 
				
			||||||
 | 
					            gravaLog(ip=self.ip,tipo="Falha Generica",msg=f'Error - {str(ex)}')
 | 
				
			||||||
 | 
					            #print(f'erro {str(ex)}')
 | 
				
			||||||
 | 
					            self.onLine = False
 | 
				
			||||||
 | 
					            self.trans.close()
 | 
				
			||||||
 | 
					            sleep(2)
 | 
				
			||||||
 | 
					            return resposta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DriveData():
 | 
				
			||||||
 | 
					    HEADERSIZE = 100
 | 
				
			||||||
 | 
					    ip = "192.168.0.1"
 | 
				
			||||||
 | 
					    port = 32100
 | 
				
			||||||
 | 
					    onLine = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, ip, port):
 | 
				
			||||||
 | 
					        gravaLog(ip=self.ip,msg=f'iniciou drive',file="log_data.txt")
 | 
				
			||||||
 | 
					        self.ip=ip
 | 
				
			||||||
 | 
					        self.port = port
 | 
				
			||||||
 | 
					        self.trans = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.trans.connect((self.ip,self.port))
 | 
				
			||||||
 | 
					            self.onLine = True
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            self.onLine = False 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_data(self):
 | 
				
			||||||
 | 
					        resposta = 'falha'
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if not self.onLine:
 | 
				
			||||||
 | 
					                #print(f'tentando Conectar...\n')
 | 
				
			||||||
 | 
					                gravaLog(ip=self.ip,msg=f'tentando Conectar...',file="log_data.txt")
 | 
				
			||||||
 | 
					                sleep(2)
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    self.trans = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					                    self.trans.connect((self.ip,self.PORT))
 | 
				
			||||||
 | 
					                    self.onLine = True
 | 
				
			||||||
 | 
					                    gravaLog(ip=self.ip,msg=f'Conexão restabelecida...',file="log_data.txt")
 | 
				
			||||||
 | 
					                except:
 | 
				
			||||||
 | 
					                    self.onLine = False
 | 
				
			||||||
 | 
					                    return resposta
 | 
				
			||||||
 | 
					            resposta = self.trans.recv(self.HEADERSIZE).decode("utf-8")
 | 
				
			||||||
 | 
					            resposta = str(resposta).split(',')
 | 
				
			||||||
 | 
					            return resposta
 | 
				
			||||||
 | 
					        except Exception as ex:
 | 
				
			||||||
 | 
					            self.onLine = False 
 | 
				
			||||||
 | 
					            gravaLog(ip=self.ip,tipo="Falha Generica",msg=f'erro {str(ex)}',file="log_data.txt")
 | 
				
			||||||
 | 
					            sleep(2)
 | 
				
			||||||
 | 
					            return resposta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    test = DriveImg("192.168.1.125", 32200)
 | 
				
			||||||
 | 
					    x = 0
 | 
				
			||||||
 | 
					    while x < 100:
 | 
				
			||||||
 | 
					        x=x+1
 | 
				
			||||||
 | 
					        imgtype, img = test.read_img()
 | 
				
			||||||
							
								
								
									
										13
									
								
								compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								compose.yml
									
									
									
									
									
										Normal file
									
								
							@@ -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:
 | 
				
			||||||
							
								
								
									
										535
									
								
								config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										535
									
								
								config.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,535 @@
 | 
				
			|||||||
 | 
					core:
 | 
				
			||||||
 | 
					  mode: linuxserver
 | 
				
			||||||
 | 
					  serverip: 172.26.178.114
 | 
				
			||||||
 | 
					  clientip: 172.26.176.1
 | 
				
			||||||
 | 
					  server: Hyper-Vd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					arm:
 | 
				
			||||||
 | 
					  ip: 192.168.1.145
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#cable_map:
 | 
				
			||||||
 | 
					cameras:
 | 
				
			||||||
 | 
					  banner:
 | 
				
			||||||
 | 
					    ip: 192.168.1.125
 | 
				
			||||||
 | 
					    port: 32200
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					led: 
 | 
				
			||||||
 | 
					  fps: 90
 | 
				
			||||||
 | 
					  timeout: 0
 | 
				
			||||||
 | 
					  controllers:
 | 
				
			||||||
 | 
					    - universe: 9
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 0
 | 
				
			||||||
 | 
					      ledend: 143
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 3
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 144
 | 
				
			||||||
 | 
					      ledend: 287
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 2
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 288
 | 
				
			||||||
 | 
					      ledend: 431
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 4
 | 
				
			||||||
 | 
					      ip: 192.168.5.40
 | 
				
			||||||
 | 
					      ledstart: 432
 | 
				
			||||||
 | 
					      ledend: 575
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 1
 | 
				
			||||||
 | 
					      ip: 192.168.5.4
 | 
				
			||||||
 | 
					      ledstart: 576
 | 
				
			||||||
 | 
					      ledend: 719
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 5
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 720
 | 
				
			||||||
 | 
					      ledend: 863
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 6
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 864
 | 
				
			||||||
 | 
					      ledend: 1007
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 7
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 1008
 | 
				
			||||||
 | 
					      ledend: 1151
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 8
 | 
				
			||||||
 | 
					      ip: 192.168.68.131
 | 
				
			||||||
 | 
					      ledstart: 1152
 | 
				
			||||||
 | 
					      ledend: 1295
 | 
				
			||||||
 | 
					      mode: rgb
 | 
				
			||||||
 | 
					    - universe: 0
 | 
				
			||||||
 | 
					      ip: 192.168.68.130
 | 
				
			||||||
 | 
					      ledstart: 1296
 | 
				
			||||||
 | 
					      ledend: 1365
 | 
				
			||||||
 | 
					      mode: rgbw
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  map:
 | 
				
			||||||
 | 
					    # total for 54x rings: 1296 LEDs (0-1295), 24 ea
 | 
				
			||||||
 | 
					    # controller 1
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 0
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [0, 304.8]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 24
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-65.991, 266.7]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 48
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, 228.6]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 72
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-197.973, 190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 96
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-263.965, 152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 120
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-263.965, 76.2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 2
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 144
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [0, 228.6]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 168
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-65.991, 190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 192
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, 152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 216
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-197.973, 114.3]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 240
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-197.973, 38.1]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 264
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-263.965, 0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 3
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 288
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [0, 152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 312
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-65.991, 114.3]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 336
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, 76.2]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 360
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, 0]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 384
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-197.973, -38.1]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 408
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-263.965, -76.2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 4
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 432
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, 76.2]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 456
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, 152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 480
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, 228.6]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 504
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [65.991, 266.7]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 528
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [65.991, 190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 552
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [65.991, 114.3]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 5
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 576
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, 0]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 600
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [197.973, 38.1]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 624
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [197.973, 114.3]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 648
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [197.973, 190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 672
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [263.965, 152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 696
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [263.965, 76.2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 6
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 720
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, -76.2]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 744
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [197.973, -38.1]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 768
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [263.965, 0]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 792
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [263.965, -76.2]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 816
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [263.965, -152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 840
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [197.973, -114.3]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 7
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 864
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [65.991, -114.3]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 888
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [0, -152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 912
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-65.991, -114.3]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 936
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, -76.2]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 960
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-197.973, -114.3]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 984
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, -152.4]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 8
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1008
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [0, -228.6]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1032
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-65.991, -190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1056
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-65.991, -266.7]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1080
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-131.982, -228.6]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1104
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-197.973, -190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1128
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [-263.965, -152.4]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller 9
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1152
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [0, -304.8]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1176
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [65.991, -266.7]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1200
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, -228.6]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1224
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [197.973, -190.5]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1248
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [131.982, -152.4]
 | 
				
			||||||
 | 
					    - type: circle
 | 
				
			||||||
 | 
					      start: 1272
 | 
				
			||||||
 | 
					      size: 24
 | 
				
			||||||
 | 
					      diameter: 63.5
 | 
				
			||||||
 | 
					      angle: 0
 | 
				
			||||||
 | 
					      pos: [65.991, -190.5]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Strips
 | 
				
			||||||
 | 
					    - type: strip
 | 
				
			||||||
 | 
					      start: 1296
 | 
				
			||||||
 | 
					      size: 70
 | 
				
			||||||
 | 
					      length: 600
 | 
				
			||||||
 | 
					      angle: 270 # down
 | 
				
			||||||
 | 
					      pos: [375, 300]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					global_position_offset: [0,0] # default coordinate spce below as center of arm at 0,0 - adjust if necessary
 | 
				
			||||||
 | 
					animation_time: 40
 | 
				
			||||||
 | 
					position_map:
 | 
				
			||||||
 | 
					  - index: 0
 | 
				
			||||||
 | 
					    pos: [-152.4, 263.965]
 | 
				
			||||||
 | 
					  - index: 1
 | 
				
			||||||
 | 
					    pos: [-76.2, 263.965]
 | 
				
			||||||
 | 
					  - index: 2
 | 
				
			||||||
 | 
					    pos: [0, 263.965]
 | 
				
			||||||
 | 
					  - index: 3
 | 
				
			||||||
 | 
					    pos: [76.2, 263.965]
 | 
				
			||||||
 | 
					  - index: 4
 | 
				
			||||||
 | 
					    pos: [152.4, 263.965]
 | 
				
			||||||
 | 
					  - index: 5
 | 
				
			||||||
 | 
					    pos: [-190.5, 197.973]
 | 
				
			||||||
 | 
					  - index: 6
 | 
				
			||||||
 | 
					    pos: [-114.3, 197.973]
 | 
				
			||||||
 | 
					  - index: 7
 | 
				
			||||||
 | 
					    pos: [-38.1, 197.973]
 | 
				
			||||||
 | 
					  - index: 8
 | 
				
			||||||
 | 
					    pos: [38.1, 197.973]
 | 
				
			||||||
 | 
					  - index: 9
 | 
				
			||||||
 | 
					    pos: [114.3, 197.973]
 | 
				
			||||||
 | 
					  - index: 10
 | 
				
			||||||
 | 
					    pos: [190.5, 197.973]
 | 
				
			||||||
 | 
					  - index: 11
 | 
				
			||||||
 | 
					    pos: [-228.6, 131.982]
 | 
				
			||||||
 | 
					  - index: 12
 | 
				
			||||||
 | 
					    pos: [-152.4, 131.982]
 | 
				
			||||||
 | 
					  - index: 13
 | 
				
			||||||
 | 
					    pos: [-76.2, 131.982]
 | 
				
			||||||
 | 
					  - index: 14
 | 
				
			||||||
 | 
					    pos: [0, 131.982]
 | 
				
			||||||
 | 
					  - index: 15
 | 
				
			||||||
 | 
					    pos: [76.2, 131.982]
 | 
				
			||||||
 | 
					  - index: 16
 | 
				
			||||||
 | 
					    pos: [152.4, 131.982]
 | 
				
			||||||
 | 
					  - index: 17
 | 
				
			||||||
 | 
					    pos: [228.6, 131.982]
 | 
				
			||||||
 | 
					  - index: 18
 | 
				
			||||||
 | 
					    pos: [-266.7, 65.991]
 | 
				
			||||||
 | 
					  - index: 19
 | 
				
			||||||
 | 
					    pos: [-190.5, 65.991]
 | 
				
			||||||
 | 
					  - index: 20
 | 
				
			||||||
 | 
					    pos: [-114.3, 65.991]
 | 
				
			||||||
 | 
					  - index: 21
 | 
				
			||||||
 | 
					    pos: [114.3, 65.991]
 | 
				
			||||||
 | 
					  - index: 22
 | 
				
			||||||
 | 
					    pos: [190.5, 65.991]
 | 
				
			||||||
 | 
					  - index: 23
 | 
				
			||||||
 | 
					    pos: [266.7, 65.991]
 | 
				
			||||||
 | 
					  - index: 24
 | 
				
			||||||
 | 
					    pos: [-304.8, 0]
 | 
				
			||||||
 | 
					  - index: 25
 | 
				
			||||||
 | 
					    pos: [-228.6, 0]
 | 
				
			||||||
 | 
					  - index: 26
 | 
				
			||||||
 | 
					    pos: [-152.4, 0]
 | 
				
			||||||
 | 
					  - index: 27
 | 
				
			||||||
 | 
					    pos: [152.4, 0]
 | 
				
			||||||
 | 
					  - index: 28
 | 
				
			||||||
 | 
					    pos: [228.6, 0]
 | 
				
			||||||
 | 
					  - index: 29
 | 
				
			||||||
 | 
					    pos: [304.8, 0]
 | 
				
			||||||
 | 
					  - index: 30
 | 
				
			||||||
 | 
					    pos: [-266.7, -65.991]
 | 
				
			||||||
 | 
					  - index: 31
 | 
				
			||||||
 | 
					    pos: [-190.5, -65.991]
 | 
				
			||||||
 | 
					  - index: 32
 | 
				
			||||||
 | 
					    pos: [-114.3, -65.991]
 | 
				
			||||||
 | 
					  - index: 33
 | 
				
			||||||
 | 
					    pos: [114.3, -65.991]
 | 
				
			||||||
 | 
					  - index: 34
 | 
				
			||||||
 | 
					    pos: [190.5, -65.991]
 | 
				
			||||||
 | 
					  - index: 35
 | 
				
			||||||
 | 
					    pos: [266.7, -65.991]
 | 
				
			||||||
 | 
					  - index: 36
 | 
				
			||||||
 | 
					    pos: [-228.6, -131.982]
 | 
				
			||||||
 | 
					  - index: 37
 | 
				
			||||||
 | 
					    pos: [-152.4, -131.982]
 | 
				
			||||||
 | 
					  - index: 38
 | 
				
			||||||
 | 
					    pos: [-76.2, -131.982]
 | 
				
			||||||
 | 
					  - index: 39
 | 
				
			||||||
 | 
					    pos: [0, -131.982]
 | 
				
			||||||
 | 
					  - index: 40
 | 
				
			||||||
 | 
					    pos: [76.2, -131.982]
 | 
				
			||||||
 | 
					  - index: 41
 | 
				
			||||||
 | 
					    pos: [152.4, -131.982]
 | 
				
			||||||
 | 
					  - index: 42
 | 
				
			||||||
 | 
					    pos: [228.6, -131.982]
 | 
				
			||||||
 | 
					  - index: 43
 | 
				
			||||||
 | 
					    pos: [-190.5, -197.973]
 | 
				
			||||||
 | 
					  - index: 44
 | 
				
			||||||
 | 
					    pos: [-114.3, -197.973]
 | 
				
			||||||
 | 
					  - index: 45
 | 
				
			||||||
 | 
					    pos: [-38.1, -197.973]
 | 
				
			||||||
 | 
					  - index: 46
 | 
				
			||||||
 | 
					    pos: [38.1, -197.973]
 | 
				
			||||||
 | 
					  - index: 47
 | 
				
			||||||
 | 
					    pos: [114.3, -197.973]
 | 
				
			||||||
 | 
					  - index: 48
 | 
				
			||||||
 | 
					    pos: [190.5, -197.973]
 | 
				
			||||||
 | 
					  - index: 49
 | 
				
			||||||
 | 
					    pos: [-152.4, -263.965]
 | 
				
			||||||
 | 
					  - index: 50
 | 
				
			||||||
 | 
					    pos: [-76.2, -263.965]
 | 
				
			||||||
 | 
					  - index: 51
 | 
				
			||||||
 | 
					    pos: [0, -263.965]
 | 
				
			||||||
 | 
					  - index: 52
 | 
				
			||||||
 | 
					    pos: [76.2, -263.965]
 | 
				
			||||||
 | 
					  - index: 53
 | 
				
			||||||
 | 
					    pos: [152.4, -263.965]
 | 
				
			||||||
							
								
								
									
										134
									
								
								database.py
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								database.py
									
									
									
									
									
								
							@@ -1,134 +0,0 @@
 | 
				
			|||||||
"""This module contains functionality for interacting with a PostgreSQL database. It will automatically handle error
 | 
					 | 
				
			||||||
conditions (i.e. missing columns) without terminating the entire program. Use the :py:class:`DBConnector` class to
 | 
					 | 
				
			||||||
handle database interactions, either as a standalone object or in a context manager."""
 | 
					 | 
				
			||||||
from __future__ import annotations
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import os
 | 
					 | 
				
			||||||
import psycopg2
 | 
					 | 
				
			||||||
from psycopg2 import DatabaseError, OperationalError
 | 
					 | 
				
			||||||
from psycopg2.errors import UndefinedColumn
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
DB_ADDRESS = os.getenv('DB_ADDRESS', 'localhost')
 | 
					 | 
				
			||||||
DB_PORT = os.getenv('DB_PORT', 5432)
 | 
					 | 
				
			||||||
DB_USER = os.getenv('DB_USER', 'postgres')
 | 
					 | 
				
			||||||
DB_PASSWORD = os.getenv('DB_PASSWORD', '')
 | 
					 | 
				
			||||||
DB_NAME = os.getenv('DB_NAME', 'postgres')
 | 
					 | 
				
			||||||
DB_TABLE = os.getenv('DB_TABLE', 'cables')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class DBConnector:
 | 
					 | 
				
			||||||
    """Context managed database class. Use with statements to automatically open and close the database connection, like
 | 
					 | 
				
			||||||
    so:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .. code-block:: python
 | 
					 | 
				
			||||||
       with DBConnector() as db:
 | 
					 | 
				
			||||||
           db.read()
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _db_start(self):
 | 
					 | 
				
			||||||
        """Setup the database connection and cursor."""
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self.conn = psycopg2.connect(
 | 
					 | 
				
			||||||
                f"host={DB_ADDRESS} port={DB_PORT} dbname={DB_NAME} user={DB_USER} password={DB_PASSWORD}")
 | 
					 | 
				
			||||||
            self.cur = self.conn.cursor()
 | 
					 | 
				
			||||||
        except OperationalError as e:
 | 
					 | 
				
			||||||
            raise e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _db_stop(self):
 | 
					 | 
				
			||||||
        """Close the cursor and connection."""
 | 
					 | 
				
			||||||
        self.cur.close()
 | 
					 | 
				
			||||||
        self.conn.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self):
 | 
					 | 
				
			||||||
        self._db_start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __del__(self):
 | 
					 | 
				
			||||||
        self._db_stop()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __enter__(self):
 | 
					 | 
				
			||||||
        self._db_start()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __exit__(self):
 | 
					 | 
				
			||||||
        self._db_stop()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _get_cols(self) -> set[str]:
 | 
					 | 
				
			||||||
        """Get the list of columns in the database.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :return: A list of column names."""
 | 
					 | 
				
			||||||
        query = f"select COLUMN_NAME from information_schema.columns where table_name={DB_TABLE}"
 | 
					 | 
				
			||||||
        rows = {x["COLUMN_NAME"] for x in self._query(query)}
 | 
					 | 
				
			||||||
        return rows
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _column_parity(self, columns: list[str] | set[str]) -> set[str]:
 | 
					 | 
				
			||||||
        """If the listed columns are not in the database, add them."""
 | 
					 | 
				
			||||||
        cols = set(columns)
 | 
					 | 
				
			||||||
        existing = self._get_cols()
 | 
					 | 
				
			||||||
        needs = cols.difference(existing.intersection(cols))
 | 
					 | 
				
			||||||
        query = f"ALTER TABLE {DB_TABLE} {', '.join([f'ADD COLUMN {c}' for c in needs])}"
 | 
					 | 
				
			||||||
        self._query(query)
 | 
					 | 
				
			||||||
        return self._get_cols()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _query(self, sql) -> list[dict]:
 | 
					 | 
				
			||||||
        """Basic function for running queries.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param sql: SQL query as plaintext.
 | 
					 | 
				
			||||||
        :return: Results of the query, or an empty list if none."""
 | 
					 | 
				
			||||||
        result = []
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            self.cur.execute(sql)
 | 
					 | 
				
			||||||
            result = self._read_dict()
 | 
					 | 
				
			||||||
        except DatabaseError as e:
 | 
					 | 
				
			||||||
            print(f"ERROR {e.pgcode}: {e.pgerror}\n"
 | 
					 | 
				
			||||||
                  f"Caused by query: {sql}")
 | 
					 | 
				
			||||||
        finally:
 | 
					 | 
				
			||||||
            return result
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def _read_dict(self) -> list[dict]:
 | 
					 | 
				
			||||||
        """Read the cursor as a list of dictionaries. psycopg2 defaults to using a list of tuples, so we want to convert
 | 
					 | 
				
			||||||
        each row into a dictionary before we return it."""
 | 
					 | 
				
			||||||
        cols = [i.name for i in self.cur.description]
 | 
					 | 
				
			||||||
        results = []
 | 
					 | 
				
			||||||
        for row in self.cur:
 | 
					 | 
				
			||||||
            row_dict = {}
 | 
					 | 
				
			||||||
            for i in range(0, len(row)):
 | 
					 | 
				
			||||||
                if row[i]:
 | 
					 | 
				
			||||||
                    row_dict = {**row_dict, cols[i]: row[i]}
 | 
					 | 
				
			||||||
            results.append(row_dict)
 | 
					 | 
				
			||||||
        return results
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def read(self, **kwargs) -> list[dict]:
 | 
					 | 
				
			||||||
        """Read rows from a database that match the specified filters.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param kwargs: Column constraints; i.e. what value to filter by in what column.
 | 
					 | 
				
			||||||
        :returns: A list of dictionaries of all matching rows, or an empty list if no match."""
 | 
					 | 
				
			||||||
        args = []
 | 
					 | 
				
			||||||
        for kw in kwargs.keys():
 | 
					 | 
				
			||||||
            args.append(f"{kw} ILIKE {kwargs['kw']}")
 | 
					 | 
				
			||||||
        query = f"SELECT * FROM {DB_TABLE}"
 | 
					 | 
				
			||||||
        if len(args) > 0:
 | 
					 | 
				
			||||||
            query += f" WHERE {' AND '.join(args)}"
 | 
					 | 
				
			||||||
        return self._query(query)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def write(self, **kwargs) -> dict:
 | 
					 | 
				
			||||||
        """Write a row to the database.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param kwargs: Values to write for each database; specify each column separately!
 | 
					 | 
				
			||||||
        :returns: The row you just added."""
 | 
					 | 
				
			||||||
        values = []
 | 
					 | 
				
			||||||
        for val in kwargs.keys():
 | 
					 | 
				
			||||||
            values.append(kwargs[val])
 | 
					 | 
				
			||||||
        query = f"INSERT INTO {DB_TABLE} ({', '.join(kwargs.keys())}) VALUES ({', '.join(values)})"
 | 
					 | 
				
			||||||
        self._query(query)
 | 
					 | 
				
			||||||
        return kwargs
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def write_all(self, items: list[dict]) -> list[dict]:
 | 
					 | 
				
			||||||
        """Write multiple rows to the database.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        :param items: Rows to write, as a list of dictionaries.
 | 
					 | 
				
			||||||
        :returns: The rows that were added successfully."""
 | 
					 | 
				
			||||||
        successes = []
 | 
					 | 
				
			||||||
        for i in items:
 | 
					 | 
				
			||||||
            res0 = self.write(**i)
 | 
					 | 
				
			||||||
            if res0:
 | 
					 | 
				
			||||||
                successes.append(res0)
 | 
					 | 
				
			||||||
        return successes
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								download-vid.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										11
									
								
								download-vid.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					set -euxo pipefail 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p tmpdl
 | 
				
			||||||
 | 
					cd tmpdl
 | 
				
			||||||
 | 
					yt-dlp $1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ffmpeg -i * -vf "scale=800x480" -r $2 ../output.mp4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rm -rf tmpdl
 | 
				
			||||||
							
								
								
									
										349
									
								
								get_specs.py
									
									
									
									
									
								
							
							
						
						
									
										349
									
								
								get_specs.py
									
									
									
									
									
								
							@@ -5,9 +5,10 @@ import sys
 | 
				
			|||||||
import read_datasheet
 | 
					import read_datasheet
 | 
				
			||||||
from alive_progress import alive_bar
 | 
					from alive_progress import alive_bar
 | 
				
			||||||
import requests
 | 
					import requests
 | 
				
			||||||
#import time
 | 
					import time
 | 
				
			||||||
import json
 | 
					import json
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bartext = ""
 | 
					bartext = ""
 | 
				
			||||||
failed = []
 | 
					failed = []
 | 
				
			||||||
@@ -25,38 +26,123 @@ def check_internet(url='https://belden.com', timeout=5):
 | 
				
			|||||||
    
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def query_search(partnum):
 | 
					def query_search(partnum, source):
 | 
				
			||||||
    """token_url = "https://www.belden.com/coveo/rest/token?t=" + str(int(time.time()))
 | 
					    if source == "Belden":
 | 
				
			||||||
    with requests.get(token_url) as r:
 | 
					        token_url = "https://www.belden.com/coveo/rest/token?t=" + str(int(time.time()))
 | 
				
			||||||
        out = json.loads(r.content)
 | 
					        with requests.get(token_url) as r:
 | 
				
			||||||
    token = out["token"]
 | 
					            out = json.loads(r.content)
 | 
				
			||||||
    search_url = "https://www.belden.com/coveo/rest/search"
 | 
					        token = out["token"]
 | 
				
			||||||
    search_data ='{ "q": "' + str(partnum) + '", "sortCriteria": "relevancy", "numberOfResults": "250", "sortCriteria": "@catalogitemwebdisplaypriority ascending", "searchHub": "products-only-search", "pipeline": "Site Search", "maximumAge": "900000", "tab": "products-search", "locale": "en" }'
 | 
					        search_url = "https://www.belden.com/coveo/rest/search"
 | 
				
			||||||
    #"aq": "", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))", "firstResult": "0", "categoryFacets": "[{\\"field\\":\\"@catalogitemcategories\\",\\"path\\":[],\\"injectionDepth\\":1000,\\"maximumNumberOfValues\\":6,\\"delimitingCharacter\\":\\"|\\"}]", "facetOptions": "{}", "groupBy": "" }'
 | 
					 | 
				
			||||||
    #print(search_data)
 | 
					 | 
				
			||||||
    print(json.loads(search_data))
 | 
					 | 
				
			||||||
    #search_data = '{ "q": "' + str(partnum) + '" }'
 | 
					 | 
				
			||||||
    print(search_data)
 | 
					 | 
				
			||||||
    headers = headers = {
 | 
					 | 
				
			||||||
        'Authorization': f'Bearer {token}',
 | 
					 | 
				
			||||||
        'Content-Type': 'application/json'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    with requests.post(search_url, headers=headers, data=search_data) as r:
 | 
					 | 
				
			||||||
        print(r.text)"""
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    # TODO: Reimplement in python
 | 
					 | 
				
			||||||
    # Bash script uses some crazy json formatting that I could not figure out
 | 
					 | 
				
			||||||
    # Despite the fact that I wrote it
 | 
					 | 
				
			||||||
    # So I'll just leave it, becuase it works.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    command = ["./query-search.sh", partnum]
 | 
					        # Ridiculous search parameters extracted from website. Do not touch
 | 
				
			||||||
    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
 | 
					        search_data = r"""{ "q": "{QUERY}", "sortCriteria": "relevancy", "numberOfResults": "250", "sortCriteria": "@catalogitemwebdisplaypriority ascending", "searchHub": "products-only-search", "pipeline": "Site Search", "maximumAge": "900000", "tab": "products-search", "locale": "en", "aq": "(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((@syssource==\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\" @catalogitemprimarycategorypublished==true)) ((@catalogitemregionavailable=Global) (@z95xlanguage==en))", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\"Coveo_web_index - rg-nc-prod-sitecore-prod\")) OR (@source==(\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\",\"website_001002_Category_index-rg-nc-prod-sitecore-prod\"))", "firstResult": "0" }, "categoryFacets": "[{\"field\":\"@catalogitemcategories\",\"path\":[],\"injectionDepth\":1000,\"maximumNumberOfValues\":6,\"delimitingCharacter\":\"|\"}]", "facetOptions": "{}", "groupBy": "    [{\"field\":\"@contenttype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[\"Products\"],\"queryOverride\":\"{QUERY}\",\"advancedQueryOverride\":\"(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((((((((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) ((@z95xpath=C292F3A37B3A4E6BAB345DF87ADDE516 @z95xid<>C292F3A37B3A4E6BAB345DF87ADDE516) @z95xtemplate==E4EFEB787BDC4B1A908EFC64D56CB2A4)) OR ((@z95xpath=723501A864754FEEB8AE377E4C710271 @z95xid<>723501A864754FEEB8AE377E4C710271) ((@z95xpath=600114EAB0E5407A84AAA9F0985B6575 @z95xid<>600114EAB0E5407A84AAA9F0985B6575) @z95xtemplate==2BE4FD6B3B2C49EBBD9E1F6C92238B05))) OR (@syssource==\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\" @catalogitemprimarycategorypublished==true)) OR ((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) @z95xpath<>C292F3A37B3A4E6BAB345DF87ADDE516)) OR @syssource==\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\") NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B))) ((@catalogitemregionavailable=Global) (@z95xlanguage==en) OR (@contenttype=(Blogs,Resources,Other)) (NOT @ez120xcludefromcoveo==1))\",\"constantQueryOverride\":\"((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))\"},{\"field\":\"@catalogitembrand\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemenvironment\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemregionalavailability\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@prez45xtez120xt\",\"maximumNumberOfValues\":5,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@tags\",\"maximumNumberOfValues\":4,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetassettype\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetbrand\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetmarket\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsolution\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsearchcontentpagetype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]}]" }"""
 | 
				
			||||||
    if result.returncode != 0: # error
 | 
					        search_data = search_data.replace(r"{QUERY}", partnum)
 | 
				
			||||||
        print("No results found in search database for " + partnum + ". No hi-res part image available.", result.stderr)
 | 
					        #"aq": "", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))", "firstResult": "0", "categoryFacets": "[{\\"field\\":\\"@catalogitemcategories\\",\\"path\\":[],\\"injectionDepth\\":1000,\\"maximumNumberOfValues\\":6,\\"delimitingCharacter\\":\\"|\\"}]", "facetOptions": "{}", "groupBy": "" }'
 | 
				
			||||||
 | 
					        #fprint(search_data)
 | 
				
			||||||
 | 
					        #fprint(json.loads(search_data))
 | 
				
			||||||
 | 
					        #search_data = '{ "q": "' + str(partnum) + '" }'
 | 
				
			||||||
 | 
					        #fprint(search_data)
 | 
				
			||||||
 | 
					        headers = headers = {
 | 
				
			||||||
 | 
					            'Authorization': f'Bearer {token}',
 | 
				
			||||||
 | 
					            'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with requests.post(search_url, headers=headers, data=search_data) as r:
 | 
				
			||||||
 | 
					                a = r.text
 | 
				
			||||||
 | 
					                a = json.loads(a)
 | 
				
			||||||
 | 
					                idx = -1
 | 
				
			||||||
 | 
					                name = ""
 | 
				
			||||||
 | 
					                for partid in range(len(a["results"])):
 | 
				
			||||||
 | 
					                    name = a["results"][partid]["title"]
 | 
				
			||||||
 | 
					                    if name != partnum:
 | 
				
			||||||
 | 
					                        if name.find(partnum) >= 0:
 | 
				
			||||||
 | 
					                            idx = partid
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					                        elif partnum.find(name) >= 0:
 | 
				
			||||||
 | 
					                            idx = partid
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        idx = partid
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                if idx < 0:
 | 
				
			||||||
 | 
					                    fprint("Could not find part in API: " + partnum)
 | 
				
			||||||
 | 
					                    return False
 | 
				
			||||||
 | 
					                fprint("Search result found: result " + str(idx) + ", for ID " + name)
 | 
				
			||||||
 | 
					                #urlname = a["results"][0]["raw"]["catalogitemurlname"]
 | 
				
			||||||
 | 
					                img = a["results"][idx]["raw"]["catalogitemimageurl"]
 | 
				
			||||||
 | 
					                img = img[0:img.index("?")]
 | 
				
			||||||
 | 
					                uri = a["results"][idx]["raw"]["clickableuri"]
 | 
				
			||||||
 | 
					                dsid = a["results"][idx]["raw"]["catalogitemdatasheetid"]
 | 
				
			||||||
 | 
					                brand = a["results"][idx]["raw"]["catalogitembrand"]
 | 
				
			||||||
 | 
					                desc = a["results"][idx]["raw"]["catalogitemlongdesc"]
 | 
				
			||||||
 | 
					                shortdesc = a["results"][idx]["raw"]["catalogitemshortdesc"]
 | 
				
			||||||
 | 
					                a = json.dumps(a["results"][idx], indent=2)
 | 
				
			||||||
 | 
					                #print(a, urlname, img, uri, dsurl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                out = dict()
 | 
				
			||||||
 | 
					                out["url"] = "https://www.belden.com/products/" + uri
 | 
				
			||||||
 | 
					                out["datasheet"] = "https://catalog.belden.com/techdata/EN/" + dsid + "_techdata.pdf"
 | 
				
			||||||
 | 
					                out["brand"] = brand
 | 
				
			||||||
 | 
					                out["name"] = shortdesc
 | 
				
			||||||
 | 
					                out["description"] = desc
 | 
				
			||||||
 | 
					                out["image"] = "https://www.belden.com" + img
 | 
				
			||||||
 | 
					                out["partnum"] = name
 | 
				
			||||||
 | 
					                #print(out)
 | 
				
			||||||
 | 
					                return out
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            print("falied to search with API. Falling back to datasheet lookup.")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Original bash script
 | 
				
			||||||
 | 
					    # superceded by above
 | 
				
			||||||
 | 
					    if source == "Belden_shell":
 | 
				
			||||||
 | 
					        command = ["./query-search.sh", partnum]
 | 
				
			||||||
 | 
					        result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
 | 
				
			||||||
 | 
					        if result.returncode != 0: # error
 | 
				
			||||||
 | 
					            fprint("No results found in search database for " + partnum + ". No hi-res part image available.", result.stderr)
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            data_out = json.loads(result.stdout)
 | 
				
			||||||
 | 
					            return data_out
 | 
				
			||||||
 | 
					    elif source == "Alphawire":
 | 
				
			||||||
 | 
					        alphaurl = "https://www.alphawire.com//sxa/search/results/?l=en&s={4A774076-6068-460C-9CC6-A2D8E85E407F}&itemid={BF82F58C-EFD9-4D8B-AE3E-097DD12CF7DA}&sig=&autoFireSearch=true&productpartnumber=*" + partnum + "*&v={B22CD56D-AB95-4048-8AA1-5BBDF2F2D17F}&p=10&e=0&o=ProductPartNumber%2CAscending"
 | 
				
			||||||
 | 
					        r = requests.get(url=alphaurl)
 | 
				
			||||||
 | 
					        data = r.json()
 | 
				
			||||||
 | 
					        output = dict()
 | 
				
			||||||
 | 
					        #print(data)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            if data["Count"] > 0:
 | 
				
			||||||
 | 
					                #print(data["Results"][0]["Url"])
 | 
				
			||||||
 | 
					                for result in data["Results"]:
 | 
				
			||||||
 | 
					                    if result["Url"].split("/")[-1] == partnum:
 | 
				
			||||||
 | 
					                        #print(partnum)
 | 
				
			||||||
 | 
					                        #print(result["Html"])
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            imgidx = result["Html"].index("<img src=") + 10
 | 
				
			||||||
 | 
					                            imgidx2 = result["Html"].index("?", imgidx)
 | 
				
			||||||
 | 
					                            output["image"] = result["Html"][imgidx:imgidx2]
 | 
				
			||||||
 | 
					                            if output["image"].index("http") != 0:
 | 
				
			||||||
 | 
					                                output["image"] = ""
 | 
				
			||||||
 | 
					                                print("No cable image found.")
 | 
				
			||||||
 | 
					                        except:
 | 
				
			||||||
 | 
					                            print("No cable image found.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        dsidx = result["Html"].index("<a href=\"/disteAPI/") + 9
 | 
				
			||||||
 | 
					                        dsidx2 = result["Html"].index(partnum, dsidx) + len(partnum)
 | 
				
			||||||
 | 
					                        output["datasheet"] = "https://www.alphawire.com" + result["Html"][dsidx:dsidx2]
 | 
				
			||||||
 | 
					                        output["partnum"] = partnum
 | 
				
			||||||
 | 
					                        #"test".index()
 | 
				
			||||||
 | 
					                        #print(output)
 | 
				
			||||||
 | 
					                        return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
        return False
 | 
					        return False
 | 
				
			||||||
    else:
 | 
					
 | 
				
			||||||
        data_out = json.loads(result.stdout)
 | 
					 | 
				
			||||||
        return data_out
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
def touch(path):
 | 
					def touch(path):
 | 
				
			||||||
    with open(path, 'a'):
 | 
					    with open(path, 'a'):
 | 
				
			||||||
@@ -64,7 +150,7 @@ def touch(path):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_multi(partnums):
 | 
					def get_multi(partnums, delay=0.25):
 | 
				
			||||||
    with alive_bar(len(partnums) * 2, dual_line=True, calibrate=30, bar="classic2", spinner="classic") as bar:
 | 
					    with alive_bar(len(partnums) * 2, dual_line=True, calibrate=30, bar="classic2", spinner="classic") as bar:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _try_download_datasheet(partnum, output_dir): # Guess datasheet URL
 | 
					        def _try_download_datasheet(partnum, output_dir): # Guess datasheet URL
 | 
				
			||||||
@@ -72,7 +158,7 @@ def get_multi(partnums):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            sanitized_name = partnum.replace(" ", "")
 | 
					            sanitized_name = partnum.replace(" ", "")
 | 
				
			||||||
            url = "https://catalog.belden.com/techdata/EN/" + sanitized_name + "_techdata.pdf"
 | 
					            url = "https://catalog.belden.com/techdata/EN/" + sanitized_name + "_techdata.pdf"
 | 
				
			||||||
            #print(url)
 | 
					            #fprint(url)
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                with requests.get(url, stream=True) as r:
 | 
					                with requests.get(url, stream=True) as r:
 | 
				
			||||||
                    #r.raise_for_status()
 | 
					                    #r.raise_for_status()
 | 
				
			||||||
@@ -89,10 +175,10 @@ def get_multi(partnums):
 | 
				
			|||||||
                            bartext = bartext + "."
 | 
					                            bartext = bartext + "."
 | 
				
			||||||
                            bar.text = bartext
 | 
					                            bar.text = bartext
 | 
				
			||||||
                            f.write(chunk)
 | 
					                            f.write(chunk)
 | 
				
			||||||
                #print("")
 | 
					                #fprint("")
 | 
				
			||||||
                return output_dir + "/datasheet.pdf"
 | 
					                return output_dir + "/datasheet.pdf"
 | 
				
			||||||
            except KeyboardInterrupt:
 | 
					            except KeyboardInterrupt:
 | 
				
			||||||
                print("Quitting!")
 | 
					                fprint("Quitting!")
 | 
				
			||||||
                os.remove(output_dir + "/datasheet.pdf")
 | 
					                os.remove(output_dir + "/datasheet.pdf")
 | 
				
			||||||
                sys.exit()
 | 
					                sys.exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -100,7 +186,7 @@ def get_multi(partnums):
 | 
				
			|||||||
        def _download_datasheet(url, output_dir): # Download datasheet with known URL
 | 
					        def _download_datasheet(url, output_dir): # Download datasheet with known URL
 | 
				
			||||||
            global bartext
 | 
					            global bartext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            #print(url)
 | 
					            #fprint(url)
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                with requests.get(url, stream=True) as r:
 | 
					                with requests.get(url, stream=True) as r:
 | 
				
			||||||
                    #r.raise_for_status()
 | 
					                    #r.raise_for_status()
 | 
				
			||||||
@@ -117,18 +203,18 @@ def get_multi(partnums):
 | 
				
			|||||||
                            bartext = bartext + "."
 | 
					                            bartext = bartext + "."
 | 
				
			||||||
                            bar.text = bartext
 | 
					                            bar.text = bartext
 | 
				
			||||||
                            f.write(chunk)
 | 
					                            f.write(chunk)
 | 
				
			||||||
                #print("")
 | 
					                #fprint("")
 | 
				
			||||||
                return output_dir + "/datasheet.pdf"
 | 
					                return output_dir + "/datasheet.pdf"
 | 
				
			||||||
            except KeyboardInterrupt:
 | 
					            except KeyboardInterrupt:
 | 
				
			||||||
                print("Quitting!")
 | 
					                fprint("Quitting!")
 | 
				
			||||||
                os.remove(output_dir + "/datasheet.pdf")
 | 
					                os.remove(output_dir + "/datasheet.pdf")
 | 
				
			||||||
                sys.exit()
 | 
					                sys.exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _download_image(url, output_dir): # Download datasheet with known URL
 | 
					        def _download_image(url, output_dir): # Download image with known URL
 | 
				
			||||||
            global bartext
 | 
					            global bartext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            #print(url)
 | 
					            #fprint(url)
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                with requests.get(url, stream=True) as r:
 | 
					                with requests.get(url, stream=True) as r:
 | 
				
			||||||
                    #r.raise_for_status()
 | 
					                    #r.raise_for_status()
 | 
				
			||||||
@@ -143,32 +229,39 @@ def get_multi(partnums):
 | 
				
			|||||||
                            bartext = bartext + "."
 | 
					                            bartext = bartext + "."
 | 
				
			||||||
                            bar.text = bartext
 | 
					                            bar.text = bartext
 | 
				
			||||||
                            f.write(chunk)
 | 
					                            f.write(chunk)
 | 
				
			||||||
                #print("")
 | 
					                #fprint("")
 | 
				
			||||||
                return output_dir + "/part-hires." + url.split(".")[-1]
 | 
					                return output_dir + "/part-hires." + url.split(".")[-1]
 | 
				
			||||||
            except KeyboardInterrupt:
 | 
					            except KeyboardInterrupt:
 | 
				
			||||||
                print("Quitting!")
 | 
					                fprint("Quitting!")
 | 
				
			||||||
                os.remove(partnum + "/datasheet.pdf")
 | 
					                os.remove(partnum + "/datasheet.pdf")
 | 
				
			||||||
                sys.exit()
 | 
					                sys.exit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def __use_cached_datasheet(partnum, path, output_dir):
 | 
					        def __use_cached_datasheet(partnum, path, output_dir, dstype):
 | 
				
			||||||
            print("Using cached datasheet for " + partnum, end='')
 | 
					            fprint("Using cached datasheet for " + partnum)
 | 
				
			||||||
            bar.text = "Using cached datasheet for " + partnum
 | 
					            bar.text = "Using cached datasheet for " + partnum
 | 
				
			||||||
            bar(skipped=True)
 | 
					            bar(skipped=True)
 | 
				
			||||||
            print("Parsing Datasheet contents of " + partnum, end='')
 | 
					            if not os.path.exists(output_dir + "/parsed"):
 | 
				
			||||||
            bar.text = "Parsing Datasheet contents of " + partnum + ".pdf..."
 | 
					                
 | 
				
			||||||
            read_datasheet.parse(path, output_dir)
 | 
					                fprint("Parsing Datasheet contents of " + partnum)
 | 
				
			||||||
            bar(skipped=False)
 | 
					                bar.text = "Parsing Datasheet contents of " + partnum + ".pdf..."
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					                read_datasheet.parse(path, output_dir, partnum, dstype)
 | 
				
			||||||
 | 
					                bar(skipped=False) 
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fprint("Datasheet already parsed for " + partnum)
 | 
				
			||||||
 | 
					                bar.text = "Datasheet already parsed for " + partnum + ".pdf"
 | 
				
			||||||
 | 
					                bar(skipped=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def __downloaded_datasheet(partnum, path, output_dir):
 | 
					        def __downloaded_datasheet(partnum, path, output_dir, dstype):
 | 
				
			||||||
            print("Downloaded " + path, end='')
 | 
					            fprint("Downloaded " + path)
 | 
				
			||||||
            bar.text = "Downloaded " + path
 | 
					            bar.text = "Downloaded " + path
 | 
				
			||||||
            bar(skipped=False)
 | 
					            bar(skipped=False)
 | 
				
			||||||
            print("Parsing Datasheet contents of " + partnum, end='')
 | 
					            fprint("Parsing Datasheet contents of " + partnum)
 | 
				
			||||||
            bar.text = "Parsing Datasheet contents of " + partnum + ".pdf..."
 | 
					            bar.text = "Parsing Datasheet contents of " + partnum + ".pdf..."
 | 
				
			||||||
            read_datasheet.parse(path, output_dir)
 | 
					            read_datasheet.parse(path, output_dir, partnum, dstype)
 | 
				
			||||||
            bar(skipped=False)
 | 
					            bar(skipped=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for partnum in partnums:
 | 
					        def run_search(partnum):
 | 
				
			||||||
            output_dir = "cables/" + partnum
 | 
					            output_dir = "cables/" + partnum
 | 
				
			||||||
            path = output_dir + "/datasheet.pdf"
 | 
					            path = output_dir + "/datasheet.pdf"
 | 
				
			||||||
            bartext = "Downloading files for part " + partnum
 | 
					            bartext = "Downloading files for part " + partnum
 | 
				
			||||||
@@ -176,48 +269,85 @@ def get_multi(partnums):
 | 
				
			|||||||
            #
 | 
					            #
 | 
				
			||||||
            if (not os.path.exists(output_dir + "/found_part_hires")) or not (os.path.exists(path) and os.path.getsize(path) > 1):
 | 
					            if (not os.path.exists(output_dir + "/found_part_hires")) or not (os.path.exists(path) and os.path.getsize(path) > 1):
 | 
				
			||||||
                # Use query
 | 
					                # Use query
 | 
				
			||||||
                search_result = query_search(partnum.replace(" ", ""))
 | 
					                search_result = query_search(partnum, dstype)
 | 
				
			||||||
                # Try to use belden.com search
 | 
					                # Try to use belden.com search
 | 
				
			||||||
                if search_result is not False:
 | 
					                if search_result is not False:
 | 
				
			||||||
                    # Download high resolution part image if available and needed
 | 
					                    # Download high resolution part image if available and needed
 | 
				
			||||||
 | 
					                    partnum = search_result["partnum"]
 | 
				
			||||||
 | 
					                    output_dir = "cables/" + partnum
 | 
				
			||||||
 | 
					                    path = output_dir + "/datasheet.pdf"
 | 
				
			||||||
 | 
					                    bartext = "Downloading files for part " + partnum
 | 
				
			||||||
 | 
					                    bar.text = bartext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if not os.path.exists(output_dir + "/found_part_hires"):
 | 
					                    if not os.path.exists(output_dir + "/found_part_hires"):
 | 
				
			||||||
                        if _download_image(search_result["image"], output_dir):
 | 
					                        if _download_image(search_result["image"], output_dir):
 | 
				
			||||||
                            print("Downloaded hi-res part image for " + partnum)
 | 
					                            fprint("Downloaded hi-res part image for " + partnum)
 | 
				
			||||||
                        touch(output_dir + "/found_part_hires")
 | 
					                        touch(output_dir + "/found_part_hires")
 | 
				
			||||||
                    else:
 | 
					                    else:
 | 
				
			||||||
                        print("Using cached hi-res part image for " + partnum)
 | 
					                        fprint("Using cached hi-res part image for " + partnum)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # Download datasheet from provided URL if needed
 | 
					                    # Download datasheet from provided URL if needed
 | 
				
			||||||
                    if os.path.exists(path) and os.path.getsize(path) > 1:
 | 
					                    if os.path.exists(path) and os.path.getsize(path) > 1:
 | 
				
			||||||
                        __use_cached_datasheet(partnum, path, output_dir)
 | 
					                        __use_cached_datasheet(partnum, path, output_dir, dstype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    elif _download_datasheet(search_result["datasheet"], output_dir) is not False:
 | 
					                    elif _download_datasheet(search_result["datasheet"], output_dir) is not False:
 | 
				
			||||||
                        __downloaded_datasheet(partnum, path, output_dir)
 | 
					                        __downloaded_datasheet(partnum, path, output_dir, dstype)
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                elif os.path.exists(path) and os.path.getsize(path) > 1:
 | 
					                elif os.path.exists(path) and os.path.getsize(path) > 1:
 | 
				
			||||||
                    __use_cached_datasheet(partnum, path, output_dir)
 | 
					                    __use_cached_datasheet(partnum, path, output_dir, dstype)
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                # If search fails, and we don't already have the datasheet, guess datasheet URL and skip the hires image download
 | 
					                # If search fails, and we don't already have the datasheet, guess datasheet URL and skip the hires image download
 | 
				
			||||||
                elif _try_download_datasheet(partnum, output_dir) is not False:
 | 
					                elif _try_download_datasheet(partnum, output_dir) is not False:
 | 
				
			||||||
                    __downloaded_datasheet(partnum, path, output_dir)
 | 
					                    __downloaded_datasheet(partnum, path, output_dir, dstype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # Failed to download with search or guess :(
 | 
					                # Failed to download with search or guess :(
 | 
				
			||||||
                else: 
 | 
					                else: 
 | 
				
			||||||
                    print("Failed to download datasheet for part " + partnum, end='')
 | 
					                    return False
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # We already have a hi-res image and the datasheet - perfect!
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fprint("Using cached hi-res part image for " + partnum)
 | 
				
			||||||
 | 
					                __use_cached_datasheet(partnum, path, output_dir, dstype)
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        for fullpartnum in partnums:
 | 
				
			||||||
 | 
					            if fullpartnum[0:2] == "BL": # catalog.belden.com entry
 | 
				
			||||||
 | 
					                partnum = fullpartnum[2:]
 | 
				
			||||||
 | 
					                dstype = "Belden"
 | 
				
			||||||
 | 
					            elif fullpartnum[0:2] == "AW":
 | 
				
			||||||
 | 
					                partnum = fullpartnum[2:]
 | 
				
			||||||
 | 
					                dstype = "Alphawire"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                dstype = "Belden" # guess
 | 
				
			||||||
 | 
					                partnum = fullpartnum
 | 
				
			||||||
 | 
					            if not run_search(partnum):
 | 
				
			||||||
 | 
					                success = False
 | 
				
			||||||
 | 
					                if len(partnum.split(" ")) > 1:
 | 
				
			||||||
 | 
					                    for name in partnum.split(" "):
 | 
				
			||||||
 | 
					                        fprint("Retrying with alternate name: " + name)
 | 
				
			||||||
 | 
					                        if(run_search(name)):
 | 
				
			||||||
 | 
					                            success = True
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					                        time.sleep(delay)
 | 
				
			||||||
 | 
					                    if not success:
 | 
				
			||||||
 | 
					                        namestripped = partnum.strip(" ")
 | 
				
			||||||
 | 
					                        fprint("Retrying with alternate name: " + namestripped)
 | 
				
			||||||
 | 
					                        if(run_search(namestripped)):
 | 
				
			||||||
 | 
					                            success = True
 | 
				
			||||||
 | 
					                            time.sleep(delay)
 | 
				
			||||||
 | 
					                if not success:
 | 
				
			||||||
 | 
					                    fprint("Failed to download datasheet for part " + partnum)
 | 
				
			||||||
                    bar.text = "Failed to download datasheet for part " + partnum
 | 
					                    bar.text = "Failed to download datasheet for part " + partnum
 | 
				
			||||||
                    failed.append(partnum)
 | 
					                    failed.append(partnum)
 | 
				
			||||||
                    bar(skipped=True)
 | 
					                    bar(skipped=True)
 | 
				
			||||||
                    bar(skipped=True)
 | 
					                    bar(skipped=True)
 | 
				
			||||||
 | 
					            time.sleep(delay)
 | 
				
			||||||
            # We already have a hi-res image and the datasheet - perfect!
 | 
					            
 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                print("Using cached hi-res part image for " + partnum)
 | 
					 | 
				
			||||||
                __use_cached_datasheet(partnum, path, output_dir)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if len(failed) > 0:
 | 
					    if len(failed) > 0:
 | 
				
			||||||
        print("Failed to download:")
 | 
					        fprint("Failed to download:")
 | 
				
			||||||
        for partnum in failed:
 | 
					        for partnum in failed:
 | 
				
			||||||
            print(partnum)
 | 
					            fprint(partnum)
 | 
				
			||||||
        return False # Go to manual review upload page
 | 
					        return False # Go to manual review upload page
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        return True # All cables downloaded; we are good to go
 | 
					        return True # All cables downloaded; we are good to go
 | 
				
			||||||
@@ -226,20 +356,73 @@ def get_multi(partnums):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    partnums = ["10GXS12", "RST 5L-RKT 5L-949", 
 | 
					    # partnums = ["BLFISX012W0", "BL7958A", "BL10GXS12", "BLRST 5L-RKT 5L-949", 
 | 
				
			||||||
"10GXS13",
 | 
					    # "BL10GXS13",
 | 
				
			||||||
"10GXW12",
 | 
					    # "BL10GXW12",
 | 
				
			||||||
"10GXW13",
 | 
					    # "BL10GXW13",
 | 
				
			||||||
"2412",
 | 
					    # "BL2412",
 | 
				
			||||||
"2413",
 | 
					    # "BL2413",
 | 
				
			||||||
"OSP6AU",
 | 
					    # "BLOSP6AU",
 | 
				
			||||||
"FI4D024P9",
 | 
					    # "BLFI4D024P9",
 | 
				
			||||||
"FISD012R9",
 | 
					    # "BLFISD012R9",
 | 
				
			||||||
"FDSD012A9",
 | 
					    # "BLFDSD012A9",
 | 
				
			||||||
"FSSL024NG",
 | 
					    # "BLFSSL024NG",
 | 
				
			||||||
"FISX006W0",
 | 
					    # "BLFISX006W0",
 | 
				
			||||||
"FISX00103"
 | 
					    # "BLFISX00103",
 | 
				
			||||||
 | 
					    # "BLC6D1100007"
 | 
				
			||||||
 | 
					    # ]
 | 
				
			||||||
 | 
					    partnums = [
 | 
				
			||||||
 | 
					    # Actual cables in Jukebox
 | 
				
			||||||
 | 
					    "AW86104CY",
 | 
				
			||||||
 | 
					    "AW3050",
 | 
				
			||||||
 | 
					    "AW6714",
 | 
				
			||||||
 | 
					    "AW1172C",
 | 
				
			||||||
 | 
					    "AW2211/4",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "BLTF-1LF-006-RS5N",
 | 
				
			||||||
 | 
					    "BLTF-SD9-006-RI5N",
 | 
				
			||||||
 | 
					    "BLTT-SLG-024-HTNN",
 | 
				
			||||||
 | 
					    "BLFISX012W0",
 | 
				
			||||||
 | 
					    "BLFI4X012W0",
 | 
				
			||||||
 | 
					    "BLSPE101 006Q",
 | 
				
			||||||
 | 
					    "BLSPE102 006Q",
 | 
				
			||||||
 | 
					    "BL7922A 010Q",
 | 
				
			||||||
 | 
					    "BL7958A 008Q",
 | 
				
			||||||
 | 
					    "BLIOP6U 010Q",
 | 
				
			||||||
 | 
					    "BL10GXW13 D15Q",
 | 
				
			||||||
 | 
					    "BL10GXW53 D15Q",
 | 
				
			||||||
 | 
					    "BL29501F 010Q",
 | 
				
			||||||
 | 
					    "BL29512 010Q",
 | 
				
			||||||
 | 
					    "BL3106A 010Q",
 | 
				
			||||||
 | 
					    "BL9841 060Q",
 | 
				
			||||||
 | 
					    "BL3105A 010Q",
 | 
				
			||||||
 | 
					    "BL3092A 010Q",
 | 
				
			||||||
 | 
					    "BL8760 060Q",
 | 
				
			||||||
 | 
					    "BL6300UE 008Q",
 | 
				
			||||||
 | 
					    "BL6300FE 009Q",
 | 
				
			||||||
 | 
					    "BLRA500P 006Q",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Some ones I picked, including some invalid ones
 | 
				
			||||||
 | 
					    "BL10GXS12", 
 | 
				
			||||||
 | 
					    "BLRST 5L-RKT 5L-949", 
 | 
				
			||||||
 | 
					    "BL10GXS13",
 | 
				
			||||||
 | 
					    "BL10GXW12",
 | 
				
			||||||
 | 
					    "BL10GXW13",
 | 
				
			||||||
 | 
					    "BL2412",
 | 
				
			||||||
 | 
					    "BL2413",
 | 
				
			||||||
 | 
					    "BLOSP6AU",
 | 
				
			||||||
 | 
					    "BLFI4D024P9",
 | 
				
			||||||
 | 
					    "BLFISD012R9",
 | 
				
			||||||
 | 
					    "BLFDSD012A9",
 | 
				
			||||||
 | 
					    "BLFSSL024NG",
 | 
				
			||||||
 | 
					    "BLFISX006W0",
 | 
				
			||||||
 | 
					    "BLFISX00103",
 | 
				
			||||||
 | 
					    "BLC6D1100007"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    get_multi(partnums)
 | 
					    #query_search("86104CY", "Alphawire")
 | 
				
			||||||
 | 
					    get_multi(partnums, 0.25)
 | 
				
			||||||
 | 
					    #query_search("10GXS13", "Belden")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					<html>  <head>    <title>RGB Controller Configuration</title>    <style>      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }    </style>  </head>  <body>    <h1>RGB Controller Configuration</h1><br>    <h2>Set IP address</h2>    Needs reboot to apply<br>    Set to 0.0.0.0 for DHCP    <form method="post" enctype="application/x-www-form-urlencoded" action="/postform/">      <input type="text" name="ipa" value="0" size="3">.      <input type="text" name="ipb" value="0" size="3">.      <input type="text" name="ipc" value="0" size="3">.      <input type="text" name="ipd" value="0" size="3">      <input type="submit" value="Set">    </form><br>    <h2>Set Hostname</h2>    Needs reboot to apply<br>    Max 64 characters    <form method="post" enctype="application/x-www-form-urlencoded" action="/postform/">      <input type="text" name="hostname" value="RGBController" size="20">      <input type="submit" value="Set">    </form><br>    <h2>DMX512 Start Universe</h2>    Applies immediately<br>    Between (inclusive) 1-65000    <form method="post" enctype="application/x-www-form-urlencoded" action="/postform/">      <input type="text" name="universe" value="1" size="5">      <input type="submit" value="Set">    </form><br>    <form method="post" enctype="application/x-www-form-urlencoded" action="/postform/">      <input type="submit" name="reboot" value="Reboot">    </form><br>  </body></html>
 | 
				
			||||||
@@ -1,4 +1,5 @@
 | 
				
			|||||||
#!/bin/sh
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					# change this to #!/bin/bash for windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if ! [ -d "venv" ]; then
 | 
					if ! [ -d "venv" ]; then
 | 
				
			||||||
    ./venv-setup.sh
 | 
					    ./venv-setup.sh
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										359
									
								
								inv_kin_testing.ipynb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										359
									
								
								inv_kin_testing.ipynb
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										94
									
								
								keyboard-down.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								keyboard-down.ps1
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					# A script to toggle the Touch Keyboard of Windows 11,
 | 
				
			||||||
 | 
					# compatible with both Windows PowerShell and PowerShell 7.
 | 
				
			||||||
 | 
					# Based on code by @torvin (https://stackoverflow.com/users/332528/torvin): https://stackoverflow.com/a/40921638
 | 
				
			||||||
 | 
					# Based on code by @Andrea S. (https://stackoverflow.com/users/5887913/andrea-s): https://stackoverflow.com/a/55513524
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Warning: Relies on undocumented behaviour of the Windows Shell
 | 
				
			||||||
 | 
					# and may break with any update.
 | 
				
			||||||
 | 
					# Last tested on Windows 11 Home 22000.978.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Add-Type -ReferencedAssemblies $(if ($PSVersionTable.PSEdition -eq "Desktop") {"System.Drawing.dll"} else {$null}) -Language CSharp -TypeDefinition @'
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.Drawing;
 | 
				
			||||||
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class TouchKeyboardController
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static void ToggleTouchKeyboard()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            UIHostNoLaunch uiHostNoLaunch = new UIHostNoLaunch();
 | 
				
			||||||
 | 
					            ((ITipInvocation)uiHostNoLaunch).Toggle(GetDesktopWindow());
 | 
				
			||||||
 | 
					            Marshal.ReleaseComObject(uiHostNoLaunch);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (COMException exc) 
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (exc.HResult == unchecked((int)0x80040154)) // REGDB_E_CLASSNOTREG
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ProcessStartInfo processStartInfo = new ProcessStartInfo("TabTip.exe")
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    UseShellExecute = true
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                using (Process process = Process.Start(processStartInfo))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                throw;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
 | 
				
			||||||
 | 
					    class UIHostNoLaunch
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
 | 
				
			||||||
 | 
					    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
				
			||||||
 | 
					    interface ITipInvocation
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        void Toggle(IntPtr hwnd);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [DllImport("user32.dll", SetLastError = false)]
 | 
				
			||||||
 | 
					    static extern IntPtr GetDesktopWindow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static bool IsInputPaneOpen()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        FrameworkInputPane frameworkInputPane = new FrameworkInputPane();
 | 
				
			||||||
 | 
					        Rectangle rect;
 | 
				
			||||||
 | 
					        ((IFrameworkInputPane)frameworkInputPane).Location(out rect);
 | 
				
			||||||
 | 
					        Marshal.ReleaseComObject(frameworkInputPane);
 | 
				
			||||||
 | 
					        return !rect.IsEmpty;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("d5120aa3-46ba-44c5-822d-ca8092c1fc72")]
 | 
				
			||||||
 | 
					    public class FrameworkInputPane
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("5752238b-24f0-495a-82f1-2fd593056796")]
 | 
				
			||||||
 | 
					    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
				
			||||||
 | 
					    public interface IFrameworkInputPane
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        int Advise([MarshalAs(UnmanagedType.IUnknown)] object pWindow, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
 | 
				
			||||||
 | 
					        int AdviseWithHWND(IntPtr hwnd, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
 | 
				
			||||||
 | 
					        int Unadvise(int pdwCookie);
 | 
				
			||||||
 | 
					        int Location(out Rectangle prcInputPaneScreenLocation);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					'@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Toggle the Touch Keyboard regardless of whether it is currently shown or not.
 | 
				
			||||||
 | 
					#[TouchKeyboardController]::ToggleTouchKeyboard()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Alternatively, if you only want to show the Touch Keyboard
 | 
				
			||||||
 | 
					# and not hide it if it is already active:
 | 
				
			||||||
 | 
					if ([TouchKeyboardController]::IsInputPaneOpen()) {
 | 
				
			||||||
 | 
					    [TouchKeyboardController]::ToggleTouchKeyboard()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										94
									
								
								keyboard-up.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								keyboard-up.ps1
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					# A script to toggle the Touch Keyboard of Windows 11,
 | 
				
			||||||
 | 
					# compatible with both Windows PowerShell and PowerShell 7.
 | 
				
			||||||
 | 
					# Based on code by @torvin (https://stackoverflow.com/users/332528/torvin): https://stackoverflow.com/a/40921638
 | 
				
			||||||
 | 
					# Based on code by @Andrea S. (https://stackoverflow.com/users/5887913/andrea-s): https://stackoverflow.com/a/55513524
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Warning: Relies on undocumented behaviour of the Windows Shell
 | 
				
			||||||
 | 
					# and may break with any update.
 | 
				
			||||||
 | 
					# Last tested on Windows 11 Home 22000.978.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Add-Type -ReferencedAssemblies $(if ($PSVersionTable.PSEdition -eq "Desktop") {"System.Drawing.dll"} else {$null}) -Language CSharp -TypeDefinition @'
 | 
				
			||||||
 | 
					using System;
 | 
				
			||||||
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.Drawing;
 | 
				
			||||||
 | 
					using System.Runtime.InteropServices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class TouchKeyboardController
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static void ToggleTouchKeyboard()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        try
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            UIHostNoLaunch uiHostNoLaunch = new UIHostNoLaunch();
 | 
				
			||||||
 | 
					            ((ITipInvocation)uiHostNoLaunch).Toggle(GetDesktopWindow());
 | 
				
			||||||
 | 
					            Marshal.ReleaseComObject(uiHostNoLaunch);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (COMException exc) 
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (exc.HResult == unchecked((int)0x80040154)) // REGDB_E_CLASSNOTREG
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ProcessStartInfo processStartInfo = new ProcessStartInfo("TabTip.exe")
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    UseShellExecute = true
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					                using (Process process = Process.Start(processStartInfo))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                throw;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("4ce576fa-83dc-4F88-951c-9d0782b4e376")]
 | 
				
			||||||
 | 
					    class UIHostNoLaunch
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("37c994e7-432b-4834-a2f7-dce1f13b834b")]
 | 
				
			||||||
 | 
					    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
				
			||||||
 | 
					    interface ITipInvocation
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        void Toggle(IntPtr hwnd);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [DllImport("user32.dll", SetLastError = false)]
 | 
				
			||||||
 | 
					    static extern IntPtr GetDesktopWindow();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static bool IsInputPaneOpen()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        FrameworkInputPane frameworkInputPane = new FrameworkInputPane();
 | 
				
			||||||
 | 
					        Rectangle rect;
 | 
				
			||||||
 | 
					        ((IFrameworkInputPane)frameworkInputPane).Location(out rect);
 | 
				
			||||||
 | 
					        Marshal.ReleaseComObject(frameworkInputPane);
 | 
				
			||||||
 | 
					        return !rect.IsEmpty;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("d5120aa3-46ba-44c5-822d-ca8092c1fc72")]
 | 
				
			||||||
 | 
					    public class FrameworkInputPane
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [ComImport, Guid("5752238b-24f0-495a-82f1-2fd593056796")]
 | 
				
			||||||
 | 
					    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
 | 
				
			||||||
 | 
					    public interface IFrameworkInputPane
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        int Advise([MarshalAs(UnmanagedType.IUnknown)] object pWindow, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
 | 
				
			||||||
 | 
					        int AdviseWithHWND(IntPtr hwnd, [MarshalAs(UnmanagedType.IUnknown)] object pHandler, out int pdwCookie);
 | 
				
			||||||
 | 
					        int Unadvise(int pdwCookie);
 | 
				
			||||||
 | 
					        int Location(out Rectangle prcInputPaneScreenLocation);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					'@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Toggle the Touch Keyboard regardless of whether it is currently shown or not.
 | 
				
			||||||
 | 
					#[TouchKeyboardController]::ToggleTouchKeyboard()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Alternatively, if you only want to show the Touch Keyboard
 | 
				
			||||||
 | 
					# and not hide it if it is already active:
 | 
				
			||||||
 | 
					if (-not [TouchKeyboardController]::IsInputPaneOpen()) {
 | 
				
			||||||
 | 
					    [TouchKeyboardController]::ToggleTouchKeyboard()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										683
									
								
								led_control.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										683
									
								
								led_control.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,683 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sacn
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import yaml
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					import platform    # For getting the operating system name
 | 
				
			||||||
 | 
					import subprocess  # For executing a shell command
 | 
				
			||||||
 | 
					from util import win32
 | 
				
			||||||
 | 
					import cv2
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					from uptime import uptime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sender = None
 | 
				
			||||||
 | 
					debug = True
 | 
				
			||||||
 | 
					config = None
 | 
				
			||||||
 | 
					leds = None
 | 
				
			||||||
 | 
					leds_size = None
 | 
				
			||||||
 | 
					leds_normalized = None
 | 
				
			||||||
 | 
					controllers = None
 | 
				
			||||||
 | 
					data = None
 | 
				
			||||||
 | 
					exactdata = None
 | 
				
			||||||
 | 
					rings = None
 | 
				
			||||||
 | 
					ringstatus = None
 | 
				
			||||||
 | 
					mode = "Startup"
 | 
				
			||||||
 | 
					firstrun = True
 | 
				
			||||||
 | 
					changecount = 0
 | 
				
			||||||
 | 
					animation_time = 0
 | 
				
			||||||
 | 
					start = uptime()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def ping(host):
 | 
				
			||||||
 | 
					    #Returns True if host (str) responds to a ping request.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Option for the number of packets as a function of
 | 
				
			||||||
 | 
					    if win32:
 | 
				
			||||||
 | 
					        param1 = '-n'
 | 
				
			||||||
 | 
					        param2 = '-w'
 | 
				
			||||||
 | 
					        param3 = '250'
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        param1 = '-c'
 | 
				
			||||||
 | 
					        param2 = '-W'
 | 
				
			||||||
 | 
					        param3 = '0.25'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Building the command. Ex: "ping -c 1 google.com"
 | 
				
			||||||
 | 
					    command = ['ping', param1, '1', param2, param3, host]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return subprocess.call(command, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def map():
 | 
				
			||||||
 | 
					    global config
 | 
				
			||||||
 | 
					    global leds
 | 
				
			||||||
 | 
					    global leds_size
 | 
				
			||||||
 | 
					    global leds_normalized
 | 
				
			||||||
 | 
					    global controllers
 | 
				
			||||||
 | 
					    global rings
 | 
				
			||||||
 | 
					    global ringstatus
 | 
				
			||||||
 | 
					    global animation_time
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open('config.yml', 'r') as fileread:
 | 
				
			||||||
 | 
					        #global config
 | 
				
			||||||
 | 
					        config = yaml.safe_load(fileread)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    animation_time = config["animation_time"]
 | 
				
			||||||
 | 
					    leds = list()
 | 
				
			||||||
 | 
					    leds_size = list()
 | 
				
			||||||
 | 
					    controllers = list()
 | 
				
			||||||
 | 
					    rings = list(range(len(config["position_map"])))
 | 
				
			||||||
 | 
					    ringstatus = list(range(len(config["position_map"])))
 | 
				
			||||||
 | 
					    #print(rings)
 | 
				
			||||||
 | 
					    #fprint(config["led"]["map"])
 | 
				
			||||||
 | 
					    generate_map = False
 | 
				
			||||||
 | 
					    map = list()
 | 
				
			||||||
 | 
					    for shape in config["led"]["map"]:
 | 
				
			||||||
 | 
					        if shape["type"] == "circle":
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if generate_map:
 | 
				
			||||||
 | 
					                map.append((shape["pos"][1],shape["pos"][0]))
 | 
				
			||||||
 | 
					            #fprint(shape["pos"])
 | 
				
			||||||
 | 
					            anglediv = 360.0 / shape["size"]
 | 
				
			||||||
 | 
					            angle = 0
 | 
				
			||||||
 | 
					            radius = shape["diameter"] / 2
 | 
				
			||||||
 | 
					            lednum = shape["start"]
 | 
				
			||||||
 | 
					            for item in config['position_map']:
 | 
				
			||||||
 | 
					            # Check if the current item's position matches the target position
 | 
				
			||||||
 | 
					                #print(item['pos'],(shape["pos"][1],shape["pos"][0]))
 | 
				
			||||||
 | 
					                if tuple(item['pos']) == (shape["pos"][1],shape["pos"][0]):
 | 
				
			||||||
 | 
					                    rings[item["index"]] = (shape["pos"][1],shape["pos"][0],lednum,lednum+shape["size"]) # rings[index] = x, y, startpos, endpos
 | 
				
			||||||
 | 
					                    ringstatus[item["index"]] = [None, None]
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            if len(leds) < lednum + shape["size"]:
 | 
				
			||||||
 | 
					                for x in range(lednum + shape["size"] - len(leds)):
 | 
				
			||||||
 | 
					                    leds.append(None)
 | 
				
			||||||
 | 
					                    leds_size.append(None)
 | 
				
			||||||
 | 
					            while angle < 359.999:
 | 
				
			||||||
 | 
					                tmpangle = angle + shape["angle"]
 | 
				
			||||||
 | 
					                x = math.cos(tmpangle * (math.pi / 180.0)) * radius + shape["pos"][1] # flip by 90 degress when we changed layout
 | 
				
			||||||
 | 
					                y = math.sin(tmpangle * (math.pi / 180.0)) * radius + shape["pos"][0]
 | 
				
			||||||
 | 
					                leds[lednum] = (x,y)
 | 
				
			||||||
 | 
					                lednum = lednum + 1
 | 
				
			||||||
 | 
					                angle = angle + anglediv
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        elif shape["type"] == "strip":
 | 
				
			||||||
 | 
					            angle = shape["angle"]
 | 
				
			||||||
 | 
					            lednum = shape["start"]
 | 
				
			||||||
 | 
					            length = shape["length"]
 | 
				
			||||||
 | 
					            distdiv = length / shape["size"]
 | 
				
			||||||
 | 
					            dist = distdiv / 2
 | 
				
			||||||
 | 
					            xmov = math.cos(angle * (math.pi / 180.0)) * distdiv
 | 
				
			||||||
 | 
					            ymov = math.sin(angle * (math.pi / 180.0)) * distdiv
 | 
				
			||||||
 | 
					            pos = shape["pos"]
 | 
				
			||||||
 | 
					            if len(leds) < lednum + shape["size"]:
 | 
				
			||||||
 | 
					                for x in range(lednum + shape["size"] - len(leds)):
 | 
				
			||||||
 | 
					                    leds.append(None)
 | 
				
			||||||
 | 
					                    leds_size.append(None)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            while dist < length:
 | 
				
			||||||
 | 
					                leds[lednum] = (pos[0], pos[1])
 | 
				
			||||||
 | 
					                pos[0] += xmov
 | 
				
			||||||
 | 
					                pos[1] += ymov
 | 
				
			||||||
 | 
					                dist += distdiv
 | 
				
			||||||
 | 
					                lednum = lednum + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if generate_map:
 | 
				
			||||||
 | 
					        map = sorted(map, key=lambda x: (-x[1], x[0]))
 | 
				
			||||||
 | 
					        print(map)
 | 
				
			||||||
 | 
					        import matplotlib.pyplot as plt
 | 
				
			||||||
 | 
					        plt.axis('equal')
 | 
				
			||||||
 | 
					        x, y = zip(*map)
 | 
				
			||||||
 | 
					        plt.scatter(x, y, s=12)
 | 
				
			||||||
 | 
					        #plt.plot(x, y, marker='o')
 | 
				
			||||||
 | 
					        #plt.scatter(*zip(*leds), s=3)
 | 
				
			||||||
 | 
					        for i, (x_pos, y_pos) in enumerate(map):
 | 
				
			||||||
 | 
					            plt.text(x_pos, y_pos, str(i), color="red", fontsize=12)
 | 
				
			||||||
 | 
					        plt.savefig("map2.png", dpi=600, bbox_inches="tight")
 | 
				
			||||||
 | 
					        data = {"map": [{"index": i, "pos": str(list(pos))} for i, pos in enumerate(map)]}
 | 
				
			||||||
 | 
					        yaml_str = yaml.dump(data, default_flow_style=False)
 | 
				
			||||||
 | 
					        print(yaml_str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    print(rings)
 | 
				
			||||||
 | 
					    flag = 0
 | 
				
			||||||
 | 
					    for x in leds:
 | 
				
			||||||
 | 
					        if x is None:
 | 
				
			||||||
 | 
					            flag = flag + 1
 | 
				
			||||||
 | 
					    if flag > 0:
 | 
				
			||||||
 | 
					        fprint("Warning: Imperfect LED map ordering. Hiding undefined lights.")
 | 
				
			||||||
 | 
					        for x in range(len(leds)):
 | 
				
			||||||
 | 
					            if leds[x] is None:
 | 
				
			||||||
 | 
					                leds[x] = (0, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #leds = tmpleds.reverse()
 | 
				
			||||||
 | 
					    #fprint(leds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # controller mapping
 | 
				
			||||||
 | 
					    for ctrl in config["led"]["controllers"]:
 | 
				
			||||||
 | 
					        if len(controllers) < ctrl["universe"]+1:
 | 
				
			||||||
 | 
					            for x in range(ctrl["universe"]+1 - len(controllers)):
 | 
				
			||||||
 | 
					                controllers.append(None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        controllers[ctrl["universe"]] = (ctrl["ledstart"],ctrl["ledend"]+1,ctrl["ip"])
 | 
				
			||||||
 | 
					        for x in range(ctrl["ledstart"],ctrl["ledend"]+1):
 | 
				
			||||||
 | 
					            leds_size[x] = len(ctrl["mode"])
 | 
				
			||||||
 | 
					    #fprint(controllers)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if(debug):
 | 
				
			||||||
 | 
					        import matplotlib.pyplot as plt
 | 
				
			||||||
 | 
					        plt.axis('equal')
 | 
				
			||||||
 | 
					        for ctrl in controllers:
 | 
				
			||||||
 | 
					            plt.scatter(*zip(*leds[ctrl[0]:ctrl[1]]), s=2)
 | 
				
			||||||
 | 
					        #plt.scatter(*zip(*leds), s=3)
 | 
				
			||||||
 | 
					        plt.savefig("map.png", dpi=600, bbox_inches="tight")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    leds_adj = [(x-min([led[0] for led in leds]), # push to zero start
 | 
				
			||||||
 | 
					                    y-min([led[1] for led in leds]) )
 | 
				
			||||||
 | 
					                   for x, y in leds]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    leds_normalized = [(x / max([led[0] for led in leds_adj]), 
 | 
				
			||||||
 | 
					                    y / max([led[1] for led in leds_adj]))
 | 
				
			||||||
 | 
					                   for x, y in leds_adj]
 | 
				
			||||||
 | 
					    #return leds, controllers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def init():
 | 
				
			||||||
 | 
					    map()
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    global config
 | 
				
			||||||
 | 
					    global leds
 | 
				
			||||||
 | 
					    global leds_size
 | 
				
			||||||
 | 
					    global controllers
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    global exactdata
 | 
				
			||||||
 | 
					    sender = sacn.sACNsender(fps=config["led"]["fps"], universeDiscovery=False)
 | 
				
			||||||
 | 
					    sender.start()  # start the sending thread
 | 
				
			||||||
 | 
					    """for x in range(len(controllers)):
 | 
				
			||||||
 | 
					        print("Waiting for the controller at", controllers[x][2], "to be online...", end="")
 | 
				
			||||||
 | 
					        count = 0
 | 
				
			||||||
 | 
					        while not ping(controllers[x][2]):
 | 
				
			||||||
 | 
					            count = count + 1
 | 
				
			||||||
 | 
					            if count >= config["led"]["timeout"]:
 | 
				
			||||||
 | 
					                fprint(" ERROR: controller still offline after " + str(count) + " seconds, continuing...")
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					        if count < config["led"]["timeout"]:
 | 
				
			||||||
 | 
					            fprint(" done")"""
 | 
				
			||||||
 | 
					    for x in range(len(controllers)):
 | 
				
			||||||
 | 
					        print("Activating controller", x, "at", controllers[x][2], "with", controllers[x][1]-controllers[x][0], "LEDs.")
 | 
				
			||||||
 | 
					        sender.activate_output(x+1)  # start sending out data
 | 
				
			||||||
 | 
					        sender[x+1].destination = controllers[x][2]
 | 
				
			||||||
 | 
					    sender.manual_flush = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # initialize global pixel data list
 | 
				
			||||||
 | 
					    data = list()
 | 
				
			||||||
 | 
					    exactdata = list()
 | 
				
			||||||
 | 
					    for x in range(len(leds)):
 | 
				
			||||||
 | 
					        if leds_size[x] == 3:
 | 
				
			||||||
 | 
					            exactdata.append(None)
 | 
				
			||||||
 | 
					            data.append((20,20,127))
 | 
				
			||||||
 | 
					        elif leds_size[x] == 4:
 | 
				
			||||||
 | 
					            exactdata.append(None)
 | 
				
			||||||
 | 
					            data.append((50,50,255,0))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            exactdata.append(None)
 | 
				
			||||||
 | 
					            data.append((0,0,0))
 | 
				
			||||||
 | 
					    sendall(data)
 | 
				
			||||||
 | 
					    #time.sleep(50000)    
 | 
				
			||||||
 | 
					    fprint("Running start-up test sequence...")
 | 
				
			||||||
 | 
					    for y in range(1):
 | 
				
			||||||
 | 
					        for x in range(len(leds)):
 | 
				
			||||||
 | 
					            setpixel(0,60,144,x)
 | 
				
			||||||
 | 
					        sendall(data)
 | 
				
			||||||
 | 
					        #time.sleep(2)
 | 
				
			||||||
 | 
					    alloffsmooth()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def sendall(datain):
 | 
				
			||||||
 | 
					    # send all LED data to all controllers
 | 
				
			||||||
 | 
					    # data must have all LED data in it as [(R,G,B,)] tuples in an array, 1 tuple per pixel
 | 
				
			||||||
 | 
					    global controllers
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    sender.manual_flush = True
 | 
				
			||||||
 | 
					    for x in range(len(controllers)):
 | 
				
			||||||
 | 
					        sender[x+1].dmx_data = list(sum(datain[controllers[x][0]:controllers[x][1]] , ())) # flatten the subsection of the data array
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    sender.flush()
 | 
				
			||||||
 | 
					    time.sleep(0.002)
 | 
				
			||||||
 | 
					    #sender.flush() # 100% reliable with 2 flushes, often fails with 1
 | 
				
			||||||
 | 
					    #time.sleep(0.002)
 | 
				
			||||||
 | 
					    #sender.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def fastsendall(datain):
 | 
				
			||||||
 | 
					    # send all LED data to all controllers
 | 
				
			||||||
 | 
					    # data must have all LED data in it as [(R,G,B,)] tuples in an array, 1 tuple per pixel
 | 
				
			||||||
 | 
					    global controllers
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    sender.manual_flush = False
 | 
				
			||||||
 | 
					    print(datain[controllers[0][0]:controllers[0][1]])
 | 
				
			||||||
 | 
					    for x in range(len(controllers)):
 | 
				
			||||||
 | 
					        sender[x+1].dmx_data = list(sum(datain[controllers[x][0]:controllers[x][1]] , ())) # flatten the subsection of the data array
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    sender.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def senduniverse(datain, lednum):
 | 
				
			||||||
 | 
					    # send all LED data for 1 controller/universe
 | 
				
			||||||
 | 
					    # data must have all LED data in it as [(R,G,B,)] tuples in an array, 1 tuple per pixel
 | 
				
			||||||
 | 
					    global controllers
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    for x in range(len(controllers)):
 | 
				
			||||||
 | 
					        if lednum >= controllers[x][0] and lednum < controllers[x][1]:
 | 
				
			||||||
 | 
					            sender[x+1].dmx_data = list(sum(datain[controllers[x][0]:controllers[x][1]] , ())) # flatten the subsection of the data array
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    sender.flush()
 | 
				
			||||||
 | 
					    time.sleep(0.004)
 | 
				
			||||||
 | 
					    #sender.flush() # 100% reliable with 2 flushes, often fails with 1
 | 
				
			||||||
 | 
					    #time.sleep(0.002)
 | 
				
			||||||
 | 
					    #sender.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def alloff():
 | 
				
			||||||
 | 
					    tmpdata = list()
 | 
				
			||||||
 | 
					    for x in range(len(leds)):
 | 
				
			||||||
 | 
					        if leds_size[x] == 3:
 | 
				
			||||||
 | 
					            tmpdata.append((0,0,0))
 | 
				
			||||||
 | 
					        elif leds_size[x] == 4:
 | 
				
			||||||
 | 
					            tmpdata.append((0,0,0,0))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            tmpdata.append((0,0,0))
 | 
				
			||||||
 | 
					    sendall(tmpdata)
 | 
				
			||||||
 | 
					    #sendall(tmpdata)
 | 
				
			||||||
 | 
					    #sendall(tmpdata) #definitely make sure it's off
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def allon():
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    sendall(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def alloffsmooth():
 | 
				
			||||||
 | 
					    tmpdata = data
 | 
				
			||||||
 | 
					    for x in range(256):
 | 
				
			||||||
 | 
					        for x in range(len(data)):
 | 
				
			||||||
 | 
					            setpixel(tmpdata[x][0]-1,tmpdata[x][1]-1,tmpdata[x][2]-1, x)
 | 
				
			||||||
 | 
					        sendall(tmpdata)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    alloff()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setpixelnow(r, g, b, num):
 | 
				
			||||||
 | 
					    # slight optimization: send only changed universe
 | 
				
			||||||
 | 
					    # unfortunately no way to manual flush data packets to only 1 controller with this sACN library
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    setpixel(r,g,b,num)
 | 
				
			||||||
 | 
					    senduniverse(data, num)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setmode(stmode, r=0,g=0,b=0):
 | 
				
			||||||
 | 
					    global mode
 | 
				
			||||||
 | 
					    global firstrun
 | 
				
			||||||
 | 
					    if stmode is not None:
 | 
				
			||||||
 | 
					        if mode != stmode:
 | 
				
			||||||
 | 
					            firstrun = True
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        mode = stmode
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setring(r,g,b,idx):
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    ring = rings[idx]
 | 
				
			||||||
 | 
					    for pixel in range(ring[2],ring[3]):
 | 
				
			||||||
 | 
					        setpixel(r,g,b,pixel)
 | 
				
			||||||
 | 
					    #global data
 | 
				
			||||||
 | 
					    #senduniverse(data, ring[2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def runmodes(ring = -1, speed = 1):
 | 
				
			||||||
 | 
					    global mode
 | 
				
			||||||
 | 
					    global firstrun
 | 
				
			||||||
 | 
					    global changecount
 | 
				
			||||||
 | 
					    fprint("Mode: " + str(mode))
 | 
				
			||||||
 | 
					    if mode == "Startup":
 | 
				
			||||||
 | 
					        # loading animation. cable check
 | 
				
			||||||
 | 
					        if firstrun:
 | 
				
			||||||
 | 
					            changecount = animation_time * 3
 | 
				
			||||||
 | 
					            firstrun = False
 | 
				
			||||||
 | 
					            for x in range(len(ringstatus)):
 | 
				
			||||||
 | 
					                ringstatus[x] = [True, animation_time]
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        if changecount > 0:
 | 
				
			||||||
 | 
					            fprint(changecount)
 | 
				
			||||||
 | 
					            changecount = fadeorder(0,len(leds), changecount, 0,50,100)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            setmode("Startup2")
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					    elif mode == "Startup2":
 | 
				
			||||||
 | 
					        if firstrun:
 | 
				
			||||||
 | 
					            firstrun = False
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            for x in range(len(ringstatus)):
 | 
				
			||||||
 | 
					                if ringstatus[x][0]:
 | 
				
			||||||
 | 
					                    setring(0, 50, 100, x)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    ringstatus[x][1] = fadeall(rings[x][2],rings[x][3], ringstatus[x][1], 100,0,0) # not ready
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif mode == "StartupCheck":
 | 
				
			||||||
 | 
					        if firstrun:
 | 
				
			||||||
 | 
					            firstrun = False
 | 
				
			||||||
 | 
					            for x in range(len(ringstatus)):
 | 
				
			||||||
 | 
					                ringstatus[x] = [False, animation_time]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            for x in range(len(ringstatus)):
 | 
				
			||||||
 | 
					                if ringstatus[x][0]:
 | 
				
			||||||
 | 
					                    ringstatus[x][1] = fadeall(rings[x][2],rings[x][3], ringstatus[x][1], 0,50,100) # ready  
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    setring(100, 0, 0, x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    elif mode == "GrabA":
 | 
				
			||||||
 | 
					        if firstrun:
 | 
				
			||||||
 | 
					            firstrun = False
 | 
				
			||||||
 | 
					            changecount = animation_time # 100hz
 | 
				
			||||||
 | 
					        if changecount > 0:
 | 
				
			||||||
 | 
					            changecount = fadeall(rings[ring][2],rings[ring][3], changecount, 100,0,0)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            setring(100,0,0,ring)
 | 
				
			||||||
 | 
					            setmode("GrabB")
 | 
				
			||||||
 | 
					    elif mode == "GrabB":
 | 
				
			||||||
 | 
					        if firstrun:
 | 
				
			||||||
 | 
					            firstrun = False
 | 
				
			||||||
 | 
					            changecount = animation_time # 100hz
 | 
				
			||||||
 | 
					        if changecount > 0:
 | 
				
			||||||
 | 
					            changecount = fadeorder(rings[ring][2],rings[ring][3], changecount, 0,100,0)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            setring(0,100,0,ring)
 | 
				
			||||||
 | 
					            setmode("idle")
 | 
				
			||||||
 | 
					    elif mode == "GrabC":
 | 
				
			||||||
 | 
					        if firstrun:
 | 
				
			||||||
 | 
					            firstrun = False
 | 
				
			||||||
 | 
					            changecount = animation_time # 100hz
 | 
				
			||||||
 | 
					        if changecount > 0:
 | 
				
			||||||
 | 
					            changecount = fadeall(rings[ring][2],rings[ring][3], changecount, 0,50,100)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            setring(0,50,100,ring)
 | 
				
			||||||
 | 
					            setmode("idle")
 | 
				
			||||||
 | 
					    elif mode == "idle":
 | 
				
			||||||
 | 
					        time.sleep(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sendall(data)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					def fadeall(idxa,idxb,sizerem,r,g,b):
 | 
				
			||||||
 | 
					    if sizerem < 1:
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					    global exactdata
 | 
				
			||||||
 | 
					    sum = 0
 | 
				
			||||||
 | 
					    for x in range(idxa,idxb):
 | 
				
			||||||
 | 
					        if exactdata[x] is None:
 | 
				
			||||||
 | 
					            exactdata[x] = data[x]
 | 
				
			||||||
 | 
					        old = exactdata[x]
 | 
				
			||||||
 | 
					        dr = (r - old[0])/sizerem
 | 
				
			||||||
 | 
					        sum += abs(dr)
 | 
				
			||||||
 | 
					        dr += old[0]
 | 
				
			||||||
 | 
					        dg = (g - old[1])/sizerem
 | 
				
			||||||
 | 
					        sum += abs(dg)
 | 
				
			||||||
 | 
					        dg += old[1]
 | 
				
			||||||
 | 
					        db = (b - old[2])/sizerem 
 | 
				
			||||||
 | 
					        db += old[2]
 | 
				
			||||||
 | 
					        sum += abs(db)
 | 
				
			||||||
 | 
					        exactdata[x] = (dr, dg, db)
 | 
				
			||||||
 | 
					        #print(new)
 | 
				
			||||||
 | 
					        setpixel(dr, dg, db, x)
 | 
				
			||||||
 | 
					        if sizerem == 1:
 | 
				
			||||||
 | 
					            exactdata[x] = None
 | 
				
			||||||
 | 
					    if sum == 0 and sizerem > 2:
 | 
				
			||||||
 | 
					        sizerem = 2
 | 
				
			||||||
 | 
					    return sizerem - 1
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					def fadeorder(idxa,idxb,sizerem,r,g,b):
 | 
				
			||||||
 | 
					    if sizerem < 1:
 | 
				
			||||||
 | 
					        return 0
 | 
				
			||||||
 | 
					    global exactdata
 | 
				
			||||||
 | 
					    drs = 0
 | 
				
			||||||
 | 
					    dgs = 0
 | 
				
			||||||
 | 
					    dbs = 0
 | 
				
			||||||
 | 
					    sum = 0
 | 
				
			||||||
 | 
					    for x in range(idxa,idxb):
 | 
				
			||||||
 | 
					        if exactdata[x] is None:
 | 
				
			||||||
 | 
					            exactdata[x] = data[x]
 | 
				
			||||||
 | 
					        old = exactdata[x]
 | 
				
			||||||
 | 
					        dr = (r - old[0])
 | 
				
			||||||
 | 
					        dg = (g - old[1])
 | 
				
			||||||
 | 
					        db = (b - old[2])
 | 
				
			||||||
 | 
					        drs += dr
 | 
				
			||||||
 | 
					        dgs += dg
 | 
				
			||||||
 | 
					        dbs += db
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    drs /= sizerem
 | 
				
			||||||
 | 
					    dgs /= sizerem
 | 
				
			||||||
 | 
					    dbs /= sizerem
 | 
				
			||||||
 | 
					    sum += abs(drs) + abs(dgs) + abs(dbs)
 | 
				
			||||||
 | 
					    print(drs,dgs,dbs)
 | 
				
			||||||
 | 
					    for x in range(idxa,idxb):
 | 
				
			||||||
 | 
					        old = exactdata[x]
 | 
				
			||||||
 | 
					        new = list(old)
 | 
				
			||||||
 | 
					        if drs > 0:
 | 
				
			||||||
 | 
					            if old[0] + drs > r:
 | 
				
			||||||
 | 
					                new[0] = r
 | 
				
			||||||
 | 
					                drs -= r - old[0]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new[0] = old[0] + drs
 | 
				
			||||||
 | 
					                drs = 0
 | 
				
			||||||
 | 
					        if dgs > 0:
 | 
				
			||||||
 | 
					            if old[1] + dgs > g:
 | 
				
			||||||
 | 
					                new[1] = g
 | 
				
			||||||
 | 
					                dgs -= g - old[1]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new[1] = old[1] + dgs
 | 
				
			||||||
 | 
					                dgs = 0
 | 
				
			||||||
 | 
					        if dbs > 0:
 | 
				
			||||||
 | 
					            if old[2] + dbs > b:
 | 
				
			||||||
 | 
					                new[2] = b
 | 
				
			||||||
 | 
					                dbs -= b - old[2]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new[2] = old[2] + dbs
 | 
				
			||||||
 | 
					                dbs = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if drs < 0:
 | 
				
			||||||
 | 
					            if old[0] + drs < r:
 | 
				
			||||||
 | 
					                new[0] = r
 | 
				
			||||||
 | 
					                drs -= r - old[0]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new[0] = old[0] + drs
 | 
				
			||||||
 | 
					                drs = 0
 | 
				
			||||||
 | 
					        if dgs < 0:
 | 
				
			||||||
 | 
					            if old[1] + dgs < g:
 | 
				
			||||||
 | 
					                new[1] = g
 | 
				
			||||||
 | 
					                dgs -= g - old[1]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new[1] = old[1] + dgs
 | 
				
			||||||
 | 
					                dgs = 0
 | 
				
			||||||
 | 
					        if dbs < 0:
 | 
				
			||||||
 | 
					            if old[2] + dbs < b:
 | 
				
			||||||
 | 
					                new[2] = b
 | 
				
			||||||
 | 
					                dbs -= b - old[2]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                new[2] = old[2] + dbs
 | 
				
			||||||
 | 
					                dbs = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if drs != 0 or dgs != 0 or dbs != 0:
 | 
				
			||||||
 | 
					            exactdata[x] = new
 | 
				
			||||||
 | 
					            setpixel(new[0],new[1],new[2],x)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if sizerem == 1:
 | 
				
			||||||
 | 
					            exactdata[x] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if sum == 0 and sizerem > 2:
 | 
				
			||||||
 | 
					        sizerem = 2
 | 
				
			||||||
 | 
					    return sizerem - 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setpixel(r, g, b, num):
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    global leds_size
 | 
				
			||||||
 | 
					    # constrain values
 | 
				
			||||||
 | 
					    if r < 0:
 | 
				
			||||||
 | 
					        r = 0
 | 
				
			||||||
 | 
					    elif r > 255:
 | 
				
			||||||
 | 
					        r = 255
 | 
				
			||||||
 | 
					    if g < 0:
 | 
				
			||||||
 | 
					        g = 0
 | 
				
			||||||
 | 
					    elif g > 255:
 | 
				
			||||||
 | 
					        g = 255
 | 
				
			||||||
 | 
					    if b < 0:
 | 
				
			||||||
 | 
					        b = 0
 | 
				
			||||||
 | 
					    elif b > 255:
 | 
				
			||||||
 | 
					        b = 255
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if leds_size[num] == 3:
 | 
				
			||||||
 | 
					        data[num] = (int(r), int(g), int(b))
 | 
				
			||||||
 | 
					    elif leds_size[num] == 4: # cut out matching white and turn on white pixel instead
 | 
				
			||||||
 | 
					        data[num] = (( int(r) - int(min(r,g,b)), int(g) - int(min(r,g,b)), int(b) - int(min(r,g,b)), int(min(r,g,b))) )
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        data[num] = (int(r), int(g), int(b))
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def close():
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    time.sleep(0.5)
 | 
				
			||||||
 | 
					    sender.stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mapimage(image, fps=90):
 | 
				
			||||||
 | 
					    global start
 | 
				
			||||||
 | 
					    while uptime() - start < 1/fps:
 | 
				
			||||||
 | 
					        time.sleep(0.00001)
 | 
				
			||||||
 | 
					    fprint(1 / (uptime() - start))
 | 
				
			||||||
 | 
					    start = uptime()
 | 
				
			||||||
 | 
					    minsize = min(image.shape[0:2])
 | 
				
			||||||
 | 
					    leds_normalized2 = [(x * minsize, 
 | 
				
			||||||
 | 
					                        y * minsize)
 | 
				
			||||||
 | 
					                        for x, y in leds_normalized]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    cv2.imshow("video", image)
 | 
				
			||||||
 | 
					    cv2.waitKey(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #im_rgb = image #cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # OpenCV uses BGR format by default
 | 
				
			||||||
 | 
					    avgx = 0
 | 
				
			||||||
 | 
					    avgy = 0
 | 
				
			||||||
 | 
					    for xx in range(len(leds_normalized2)):
 | 
				
			||||||
 | 
					        led = leds_normalized2[xx]
 | 
				
			||||||
 | 
					        x, y = int(round(led[0])), int(round(led[1]))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if x < image.shape[1] and y < image.shape[0]:
 | 
				
			||||||
 | 
					            #avgx += x
 | 
				
			||||||
 | 
					            #avgy += y
 | 
				
			||||||
 | 
					            color = tuple(image[y, x])
 | 
				
			||||||
 | 
					            setpixel(color[2]/2,color[1]/2,color[0]/2,xx) # swap b & r
 | 
				
			||||||
 | 
					            #print(color)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            #avgx += x
 | 
				
			||||||
 | 
					            #avgy += y
 | 
				
			||||||
 | 
					            setpixel(0,0,0,xx)
 | 
				
			||||||
 | 
					    #avgx /= len(leds)
 | 
				
			||||||
 | 
					    #avgy /= len(leds)
 | 
				
			||||||
 | 
					    #print((avgx,avgy, max([led[0] for led in leds_adj]), max([led[1] for led in leds_adj]) , min(image.shape[0:2]) ))
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    fastsendall(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mainloop(stmode, ring = -1, fps = 100, preview = False):
 | 
				
			||||||
 | 
					    global start
 | 
				
			||||||
 | 
					    while uptime() - start < 1/fps:
 | 
				
			||||||
 | 
					        time.sleep(0.00001)
 | 
				
			||||||
 | 
					    fprint(1 / (uptime() - start))
 | 
				
			||||||
 | 
					    start = uptime()
 | 
				
			||||||
 | 
					    if mode is not None:
 | 
				
			||||||
 | 
					        setmode(stmode)
 | 
				
			||||||
 | 
					    runmodes(ring)
 | 
				
			||||||
 | 
					    if preview:
 | 
				
			||||||
 | 
					        drawdata()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def drawdata():
 | 
				
			||||||
 | 
					    #tmp = list()
 | 
				
			||||||
 | 
					    #for x in len(leds):
 | 
				
			||||||
 | 
					    #    led = leds[x]
 | 
				
			||||||
 | 
					    #    tmp.append((led[0], led[1], data[x]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    x = [led[0] for led in leds]
 | 
				
			||||||
 | 
					    y = [led[1] for led in leds]
 | 
				
			||||||
 | 
					    colors = data
 | 
				
			||||||
 | 
					    colors_normalized = [(x[0]/255, x[1]/255, x[2]/255) for x in colors]
 | 
				
			||||||
 | 
					    # Plot the points
 | 
				
			||||||
 | 
					    plt.scatter(x, y, c=colors_normalized)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Optional: add grid, title, and labels
 | 
				
			||||||
 | 
					    plt.grid(True)
 | 
				
			||||||
 | 
					    plt.title('Colored Points')
 | 
				
			||||||
 | 
					    plt.xlabel('X')
 | 
				
			||||||
 | 
					    plt.ylabel('Y')
 | 
				
			||||||
 | 
					    plt.show()
 | 
				
			||||||
 | 
					    plt.savefig("map3.png", dpi=50, bbox_inches="tight")
 | 
				
			||||||
 | 
					    plt.clf()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def startup_animation(show):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    stmode = "Startup"
 | 
				
			||||||
 | 
					    mainloop(stmode, preview=show)
 | 
				
			||||||
 | 
					    while mode == "Startup":
 | 
				
			||||||
 | 
					        mainloop(None, preview=show)
 | 
				
			||||||
 | 
					    for x in range(54):
 | 
				
			||||||
 | 
					        ringstatus[x][0] = False
 | 
				
			||||||
 | 
					        mainloop(None, preview=show)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for x in range(animation_time):
 | 
				
			||||||
 | 
					        mainloop(None, preview=show)
 | 
				
			||||||
 | 
					    clear_animations()
 | 
				
			||||||
 | 
					    stmode = "StartupCheck"
 | 
				
			||||||
 | 
					    mainloop(stmode, preview=show)
 | 
				
			||||||
 | 
					    clear_animations()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def clear_animations():
 | 
				
			||||||
 | 
					    for x in range(len(leds)):
 | 
				
			||||||
 | 
					        exactdata[x] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def do_animation(stmode, ring=-1):
 | 
				
			||||||
 | 
					    mainloop(stmode, ring, preview=show)
 | 
				
			||||||
 | 
					    wait_for_animation(ring)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def start_animation(stmode, ring=-1):
 | 
				
			||||||
 | 
					    mainloop(stmode, ring, preview=show)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def wait_for_animation(ring=-1):
 | 
				
			||||||
 | 
					    while mode != "idle":
 | 
				
			||||||
 | 
					        mainloop(None, ring, preview=show)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    init()
 | 
				
			||||||
 | 
					    import matplotlib.pyplot as plt
 | 
				
			||||||
 | 
					    """cap = cv2.VideoCapture('badapple.mp4')
 | 
				
			||||||
 | 
					    while cap.isOpened():
 | 
				
			||||||
 | 
					        ret, frame = cap.read()
 | 
				
			||||||
 | 
					        if not ret:
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					        mapimage(frame, fps=30)"""
 | 
				
			||||||
 | 
					    show = True
 | 
				
			||||||
 | 
					    ring = 1
 | 
				
			||||||
 | 
					    startup_animation(show)
 | 
				
			||||||
 | 
					    for x in range(54):
 | 
				
			||||||
 | 
					        ringstatus[x][0] = True
 | 
				
			||||||
 | 
					        mainloop(None, preview=show)
 | 
				
			||||||
 | 
					    for x in range(animation_time):
 | 
				
			||||||
 | 
					        mainloop(None, preview=show)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    do_animation("GrabA", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do_animation("GrabA", 5)
 | 
				
			||||||
 | 
					    start_animation("GrabC", 1)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    wait_for_animation(1)
 | 
				
			||||||
 | 
					    do_animation("GrabC", 5)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    close()
 | 
				
			||||||
 | 
					    #sys.exit(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # blue : default
 | 
				
			||||||
 | 
					    # green : target
 | 
				
			||||||
 | 
					    # yellow : crosshair
 | 
				
			||||||
 | 
					    # red : missing 
 | 
				
			||||||
 | 
					    # uninitialized : red/purple?
 | 
				
			||||||
							
								
								
									
										45
									
								
								process_video.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								process_video.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cv2
 | 
				
			||||||
 | 
					import banner_ivu_export
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class qr_reader():
 | 
				
			||||||
 | 
					    camera = None
 | 
				
			||||||
 | 
					    def __init__(self, ip, port):
 | 
				
			||||||
 | 
					        self.camera = banner_ivu_export.DriveImg(ip, port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_qr(self, tries=1):
 | 
				
			||||||
 | 
					        print("Trying " + str(tries) + " frames.")
 | 
				
			||||||
 | 
					        for x in range(tries):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                imgtype, img = self.camera.read_img()
 | 
				
			||||||
 | 
					                #fprint(imgtype)
 | 
				
			||||||
 | 
					                image_array = np.frombuffer(img, np.uint8)
 | 
				
			||||||
 | 
					                img = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
 | 
				
			||||||
 | 
					                #cv2.imshow('Image', img)
 | 
				
			||||||
 | 
					                #cv2.waitKey(1)
 | 
				
			||||||
 | 
					                detect = cv2.QRCodeDetector()
 | 
				
			||||||
 | 
					                value, points, straight_qrcode = detect.detectAndDecode(img)
 | 
				
			||||||
 | 
					                return value
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class video_streamer():
 | 
				
			||||||
 | 
					    camera = None
 | 
				
			||||||
 | 
					    def __init__(self, ip, port):
 | 
				
			||||||
 | 
					        self.camera = banner_ivu_export.DriveImg(ip, port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_frame(self):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            return self.camera.read_img()
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    test = qr_reader("192.168.1.125", 32200)
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        fprint(test.read_qr(5))
 | 
				
			||||||
@@ -2,34 +2,47 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Parse Belden catalog techdata datasheets 
 | 
					# Parse Belden catalog techdata datasheets 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pandas as pd
 | 
				
			||||||
 | 
					pd.set_option('future.no_silent_downcasting', True)
 | 
				
			||||||
from PyPDF2 import PdfReader
 | 
					from PyPDF2 import PdfReader
 | 
				
			||||||
import camelot
 | 
					import camelot
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
from PIL import Image
 | 
					from PIL import Image
 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					from util import run_cmd
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def parse(filename, output_dir):
 | 
					def touch(path):
 | 
				
			||||||
 | 
					    with open(path, 'a'):
 | 
				
			||||||
 | 
					        os.utime(path, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def parse(filename, output_dir, partnum, dstype):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Extract table data
 | 
					    # Extract table data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tables = camelot.read_pdf(filename, pages="1-end", flavor='lattice', backend="poppler", split_text=False, line_scale=100, process_background=True, resolution=600, interations=1, layout_kwargs={'detect_vertical': False, 'char_margin': 0.5}, shift_text=['r', 't'])
 | 
					    tables = camelot.read_pdf(filename, pages="1-end", flavor='lattice', backend="poppler", split_text=False, line_scale=100, process_background=True, resolution=600, interations=1, layout_kwargs={'detect_vertical': False, 'char_margin': 0.5}, shift_text=['r', 't'])
 | 
				
			||||||
    #print("Total tables extracted:", tables.n)
 | 
					    #fprint("Total tables extracted:", tables.n)
 | 
				
			||||||
    n = 0
 | 
					    n = 0
 | 
				
			||||||
    pagenum = 0
 | 
					    pagenum = 0
 | 
				
			||||||
    reader = PdfReader(filename)
 | 
					    reader = PdfReader(filename)
 | 
				
			||||||
    page = reader.pages[0]
 | 
					    page = reader.pages[0]
 | 
				
			||||||
    table_list = {}
 | 
					    table_list = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for table in tables:
 | 
					    for table in tables:
 | 
				
			||||||
 | 
					        table.df.infer_objects(copy=False)
 | 
				
			||||||
        table.df.replace('', np.nan, inplace=True)
 | 
					        table.df.replace('', np.nan, inplace=True)
 | 
				
			||||||
        table.df.dropna(inplace=True, how="all")
 | 
					        table.df.dropna(inplace=True, how="all")
 | 
				
			||||||
        table.df.dropna(inplace=True, axis="columns", how="all")
 | 
					        table.df.dropna(inplace=True, axis="columns", how="all")
 | 
				
			||||||
        table.df.replace(np.nan, '', inplace=True)
 | 
					        table.df.replace(np.nan, '', inplace=True)
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if not table.df.empty:
 | 
					        if not table.df.empty:
 | 
				
			||||||
            #print("\nTable " + str(n))
 | 
					            #fprint("\nTable " + str(n))
 | 
				
			||||||
            # Extract table names
 | 
					            # Extract table names
 | 
				
			||||||
            table_start = table.cells[0][0].lt[1] # Read top-left cell's top-left coordinate
 | 
					            table_start = table.cells[0][0].lt[1] # Read top-left cell's top-left coordinate
 | 
				
			||||||
            #print(table_start)
 | 
					            #fprint(table_start)
 | 
				
			||||||
            ymin = table_start
 | 
					            ymin = table_start
 | 
				
			||||||
            ymax = table_start + 10
 | 
					            ymax = table_start + 10
 | 
				
			||||||
            if pagenum != table.page - 1:
 | 
					            if pagenum != table.page - 1:
 | 
				
			||||||
@@ -43,21 +56,22 @@ def parse(filename, output_dir):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            page.extract_text(visitor_text=visitor_body)
 | 
					            page.extract_text(visitor_text=visitor_body)
 | 
				
			||||||
            text_body = "".join(parts).strip('\n')
 | 
					            text_body = "".join(parts).strip('\n')
 | 
				
			||||||
            #print(text_body)
 | 
					            if len(text_body) == 0:
 | 
				
			||||||
 | 
					                text_body = str(n)
 | 
				
			||||||
 | 
					            #fprint(text_body)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            table_list[text_body] = table.df
 | 
					            table_list[text_body] = table.df
 | 
				
			||||||
            #table.to_html("table" + str(n) + ".html")
 | 
					            #table.to_html("table" + str(n) + ".html")
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            #print(table.df)
 | 
					            #fprint(table.df)
 | 
				
			||||||
            #camelot.plot(table, kind='grid').savefig("test" + str(n) + ".png")
 | 
					            #camelot.plot(table, kind='grid').savefig("test" + str(n) + ".png")
 | 
				
			||||||
            n=n+1
 | 
					            n=n+1
 | 
				
			||||||
    #camelot.plot(tables[0], kind='grid').savefig("test.png")
 | 
					    #camelot.plot(tables[0], kind='grid').savefig("test.png")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    tables.export(output_dir + '/techdata.json', f='json')
 | 
					    #tables.export(output_dir + '/techdata.json', f='json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # print(table_list)
 | 
					    # fprint(table_list)
 | 
				
			||||||
    # Extract Basic details - part name & description, image, etc
 | 
					    # Extract Basic details - part name & description, image, etc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    reader = PdfReader(filename)
 | 
					    reader = PdfReader(filename)
 | 
				
			||||||
@@ -66,7 +80,7 @@ def parse(filename, output_dir):
 | 
				
			|||||||
    skip = False
 | 
					    skip = False
 | 
				
			||||||
    for image_file_object in page.images:
 | 
					    for image_file_object in page.images:
 | 
				
			||||||
        if image_file_object.name == "img0.png" and skip == False:
 | 
					        if image_file_object.name == "img0.png" and skip == False:
 | 
				
			||||||
            #print(Image.open(io.BytesIO(image_file_object.data)).mode)
 | 
					            #fprint(Image.open(io.BytesIO(image_file_object.data)).mode)
 | 
				
			||||||
            if Image.open(io.BytesIO(image_file_object.data)).mode == "P":
 | 
					            if Image.open(io.BytesIO(image_file_object.data)).mode == "P":
 | 
				
			||||||
                skip = True
 | 
					                skip = True
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
@@ -81,9 +95,174 @@ def parse(filename, output_dir):
 | 
				
			|||||||
                with open(output_dir + "/brand.png", "wb") as fp:
 | 
					                with open(output_dir + "/brand.png", "wb") as fp:
 | 
				
			||||||
                    fp.write(image_file_object.data)
 | 
					                    fp.write(image_file_object.data)
 | 
				
			||||||
                    count += 1
 | 
					                    count += 1
 | 
				
			||||||
    return table_list
 | 
					
 | 
				
			||||||
 | 
					    # Table parsing and reordring
 | 
				
			||||||
 | 
					    tables = dict()
 | 
				
			||||||
 | 
					    torename = dict()
 | 
				
			||||||
 | 
					    previous_table = ""
 | 
				
			||||||
 | 
					    for table_name in table_list.keys():
 | 
				
			||||||
 | 
					        # determine shape: horizontal or vertical
 | 
				
			||||||
 | 
					        table = table_list[table_name]
 | 
				
			||||||
 | 
					        rows = table.shape[0]
 | 
				
			||||||
 | 
					        cols = table.shape[1]
 | 
				
			||||||
 | 
					        vertical = None
 | 
				
			||||||
 | 
					        if rows > 2 and cols == 2:
 | 
				
			||||||
 | 
					            vertical = True
 | 
				
			||||||
 | 
					        elif cols == 1:
 | 
				
			||||||
 | 
					            vertical = False
 | 
				
			||||||
 | 
					        elif rows == 1:
 | 
				
			||||||
 | 
					            vertical = True
 | 
				
			||||||
 | 
					        elif cols == 2: # and rows <= 2
 | 
				
			||||||
 | 
					            # inconsistent
 | 
				
			||||||
 | 
					            if table.iloc[0, 0].find(":") == len(table.iloc[0, 0]) - 1: # check if last character is ":" indicating a vertical table
 | 
				
			||||||
 | 
					                vertical = True
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                vertical = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif cols > 2: # and rows <= 2
 | 
				
			||||||
 | 
					            vertical = False
 | 
				
			||||||
 | 
					        elif rows > 2 and cols > 2: # big table
 | 
				
			||||||
 | 
					            vertical = False
 | 
				
			||||||
 | 
					        else: # 1 column, <= 2 rows
 | 
				
			||||||
 | 
					            vertical = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # missing name check
 | 
				
			||||||
 | 
					        for table_name_2 in table_list.keys(): 
 | 
				
			||||||
 | 
					            if table_name_2.find(table.iloc[-1, 0]) >= 0:
 | 
				
			||||||
 | 
					                # Name taken from table directly above - this table does not have a name
 | 
				
			||||||
 | 
					                torename[table_name_2] = "Specs " + str(len(tables))
 | 
				
			||||||
 | 
					                #table_list["Specs " + str(len(tables))] = table_list[table_name_2] # rename table to arbitrary altername name
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if vertical:
 | 
				
			||||||
 | 
					            out = dict()
 | 
				
			||||||
 | 
					            for row in table.itertuples(index=False, name=None):
 | 
				
			||||||
 | 
					                out[row[0].replace("\n", " ").replace(":", "")] = row[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else: # horizontal
 | 
				
			||||||
 | 
					            out = dict()
 | 
				
			||||||
 | 
					            for col in table.columns:
 | 
				
			||||||
 | 
					                col_data = tuple(table[col])
 | 
				
			||||||
 | 
					                out[col_data[0].replace("\n", " ")] = col_data[1:]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        tables[table_name] = out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # multi-page table check
 | 
				
			||||||
 | 
					        if dstype == "Belden":
 | 
				
			||||||
 | 
					            if table_name.isdigit() and len(tables) > 1:
 | 
				
			||||||
 | 
					                #fprint(table_name)
 | 
				
			||||||
 | 
					                #fprint(previous_table)
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                main_key = previous_table
 | 
				
			||||||
 | 
					                cont_key = table_name
 | 
				
			||||||
 | 
					                #fprint(tables)
 | 
				
			||||||
 | 
					                if vertical == False:
 | 
				
			||||||
 | 
					                    main_keys = list(tables[main_key].keys())
 | 
				
			||||||
 | 
					                    for i, (cont_key, cont_values) in enumerate(tables[cont_key].items()):
 | 
				
			||||||
 | 
					                        if i < len(main_keys):
 | 
				
			||||||
 | 
					                            #fprint(tables[main_key][main_keys[i]])
 | 
				
			||||||
 | 
					                            tables[main_key][main_keys[i]] = (tuple(tables[main_key][main_keys[i]]) + (cont_key,) + cont_values)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					                    del tables[table_name]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    for key in tables[cont_key].keys():
 | 
				
			||||||
 | 
					                        tables[main_key][key] = tables[cont_key][key]
 | 
				
			||||||
 | 
					                    del tables[table_name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        previous_table = table_name
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # remove renamed tables
 | 
				
			||||||
 | 
					    for table_name in torename.keys():
 | 
				
			||||||
 | 
					        tables[torename[table_name]] = tables[table_name]
 | 
				
			||||||
 | 
					        del tables[table_name]
 | 
				
			||||||
 | 
					    # remove multi-line values that occasionally squeak through
 | 
				
			||||||
 | 
					    def replace_newlines_in_dict(d):
 | 
				
			||||||
 | 
					        for key, value in d.items():
 | 
				
			||||||
 | 
					            if isinstance(value, str):
 | 
				
			||||||
 | 
					                # Replace \n with " " if the value is a string
 | 
				
			||||||
 | 
					                d[key] = value.replace('\n', ' ')
 | 
				
			||||||
 | 
					            elif isinstance(value, dict):
 | 
				
			||||||
 | 
					                # Recursively call the function if the value is another dictionary
 | 
				
			||||||
 | 
					                replace_newlines_in_dict(value)
 | 
				
			||||||
 | 
					        return d
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    tables = replace_newlines_in_dict(tables)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # summary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    output_table = dict()
 | 
				
			||||||
 | 
					    output_table["partnum"] = partnum
 | 
				
			||||||
 | 
					    id = str(uuid.uuid4())
 | 
				
			||||||
 | 
					    output_table["id"] = id
 | 
				
			||||||
 | 
					    #output_table["position"] = id
 | 
				
			||||||
 | 
					    #output_table["brand"] = brand
 | 
				
			||||||
 | 
					    output_table["fullspecs"] = tables
 | 
				
			||||||
 | 
					    output_table["searchspecs"] = {"partnum": partnum, **flatten(tables)}
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    output_table["searchspecs"]["id"] = id
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #print(output_table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    run_cmd("rm \"" + output_dir + "\"/*.json") # not reliable!
 | 
				
			||||||
 | 
					    with open(output_dir + "/" + output_table["searchspecs"]["id"] + ".json", 'w') as json_file:
 | 
				
			||||||
 | 
					        json.dump(output_table["searchspecs"], json_file)
 | 
				
			||||||
 | 
					    touch(output_dir + "/parsed")
 | 
				
			||||||
 | 
					    return output_table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def flatten(tables):
 | 
				
			||||||
 | 
					    def convert_to_number(s):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            # First, try converting to an integer.
 | 
				
			||||||
 | 
					            return int(s)
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            # If that fails, try converting to a float.
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                return float(s)
 | 
				
			||||||
 | 
					            except ValueError:
 | 
				
			||||||
 | 
					                # If it fails again, return the original string.
 | 
				
			||||||
 | 
					                return s
 | 
				
			||||||
 | 
					    out = dict()
 | 
				
			||||||
 | 
					    #print("{")
 | 
				
			||||||
 | 
					    for table in tables.keys():
 | 
				
			||||||
 | 
					        for key in tables[table].keys():
 | 
				
			||||||
 | 
					            if len(key) < 64:
 | 
				
			||||||
 | 
					                keyname = key
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                keyname = key[0:64]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fullkeyname = (table + ": " + keyname).replace(".","")
 | 
				
			||||||
 | 
					            if type(tables[table][key]) is not tuple:
 | 
				
			||||||
 | 
					                out[fullkeyname] = convert_to_number(tables[table][key])
 | 
				
			||||||
 | 
					                #print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
 | 
				
			||||||
 | 
					            elif len(tables[table][key]) == 1:
 | 
				
			||||||
 | 
					                out[fullkeyname] = convert_to_number(tables[table][key][0])
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                #print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # if the item has at least two commas in it, split it
 | 
				
			||||||
 | 
					            if tables[table][key].count(',') > 0:
 | 
				
			||||||
 | 
					                out[fullkeyname] = list(map(lambda x: x.strip(), tables[table][key].split(",")))
 | 
				
			||||||
 | 
					                #print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # if the item has at least two commas in it, split it
 | 
				
			||||||
 | 
					            if tables[table][key].count(',') > 0:
 | 
				
			||||||
 | 
					                out[fullkeyname] = list(map(lambda x: x.strip(), tables[table][key].split(",")))
 | 
				
			||||||
 | 
					                print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #print("}")
 | 
				
			||||||
 | 
					    return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == "__main__":
 | 
					if __name__ == "__main__":
 | 
				
			||||||
    parse("test2.pdf", "10GXS13")
 | 
					    parse("test2.pdf", "cables/10GXS13", "10GXS13")
 | 
				
			||||||
@@ -4,7 +4,17 @@ opencv-python
 | 
				
			|||||||
pypdf2==2.12.1
 | 
					pypdf2==2.12.1
 | 
				
			||||||
alive-progress
 | 
					alive-progress
 | 
				
			||||||
requests
 | 
					requests
 | 
				
			||||||
psycopg2
 | 
					git+https://github.com/Byeongdulee/python-urx.git
 | 
				
			||||||
 | 
					meilisearch
 | 
				
			||||||
 | 
					pyyaml
 | 
				
			||||||
 | 
					Flask
 | 
				
			||||||
 | 
					selenium
 | 
				
			||||||
 | 
					sacn
 | 
				
			||||||
 | 
					uptime
 | 
				
			||||||
 | 
					websockets
 | 
				
			||||||
 | 
					numpy
 | 
				
			||||||
 | 
					scipy
 | 
				
			||||||
 | 
					ipywidgets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Development
 | 
					# Development
 | 
				
			||||||
matplotlib
 | 
					matplotlib
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										426
									
								
								run.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										426
									
								
								run.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,426 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import get_specs
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
 | 
					#import logging
 | 
				
			||||||
 | 
					import yaml
 | 
				
			||||||
 | 
					from multiprocessing import Process, Manager, Pool, TimeoutError, active_children, log_to_stderr, Pipe, Queue
 | 
				
			||||||
 | 
					from multiprocessing.pool import Pool
 | 
				
			||||||
 | 
					import multiprocessing
 | 
				
			||||||
 | 
					from time import sleep
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					from util import run_cmd
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import ur5_control
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					from flask import Flask, render_template, request
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					import led_control
 | 
				
			||||||
 | 
					import server
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import process_video
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					config = None
 | 
				
			||||||
 | 
					keeprunning = True
 | 
				
			||||||
 | 
					arm_ready = False
 | 
				
			||||||
 | 
					led_ready = False
 | 
				
			||||||
 | 
					camera_ready = False
 | 
				
			||||||
 | 
					sensor_ready = False
 | 
				
			||||||
 | 
					vm_ready = False
 | 
				
			||||||
 | 
					killme = None
 | 
				
			||||||
 | 
					#pool = None
 | 
				
			||||||
 | 
					serverproc = None
 | 
				
			||||||
 | 
					camera = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					to_server_queue = Queue()
 | 
				
			||||||
 | 
					from_server_queue = Queue()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def arm_start_callback(res):
 | 
				
			||||||
 | 
					    global arm_ready
 | 
				
			||||||
 | 
					    arm_ready = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def led_start_callback(res):
 | 
				
			||||||
 | 
					    global led_ready
 | 
				
			||||||
 | 
					    led_ready = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def camera_start_callback(res):
 | 
				
			||||||
 | 
					    global camera_ready
 | 
				
			||||||
 | 
					    camera_ready = True
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					def sensor_start_callback(res):
 | 
				
			||||||
 | 
					    global sensor_ready
 | 
				
			||||||
 | 
					    sensor_ready = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def vm_start_callback(res):
 | 
				
			||||||
 | 
					    global vm_ready
 | 
				
			||||||
 | 
					    vm_ready = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def wait_for(val, name):
 | 
				
			||||||
 | 
					    #global val
 | 
				
			||||||
 | 
					    if val is False:
 | 
				
			||||||
 | 
					        fprint("waiting for " + name + " to complete...")
 | 
				
			||||||
 | 
					        while val is False:
 | 
				
			||||||
 | 
					            sleep(0.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def start_server_socket():
 | 
				
			||||||
 | 
					    """app = Flask(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @app.route('/report_ip', methods=['POST'])
 | 
				
			||||||
 | 
					    def report_ip():
 | 
				
			||||||
 | 
					        client_ip = request.json.get('ip')
 | 
				
			||||||
 | 
					        fprint(f"Received IP: {client_ip}")
 | 
				
			||||||
 | 
					        # You can store or process the IP address as needed
 | 
				
			||||||
 | 
					        return "IP Received", 200
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    app.run(host='0.0.0.0', port=5000)"""
 | 
				
			||||||
 | 
					    global to_server_queue
 | 
				
			||||||
 | 
					    global from_server_queue
 | 
				
			||||||
 | 
					    fprint("Starting WebSocket server...")
 | 
				
			||||||
 | 
					    websocket_process = server.start_websocket_server(to_server_queue, from_server_queue)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Example
 | 
				
			||||||
 | 
					    #to_server_queue.put("Hello, WebSocket clients!")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        #print("HI")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Handeling Server Requests Loop, will run forever
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not from_server_queue.empty():
 | 
				
			||||||
 | 
					            client_id, message = from_server_queue.get()
 | 
				
			||||||
 | 
					            fprint(f"Message from client {client_id}: {message}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # if we get here, we have a "valid" data packet
 | 
				
			||||||
 | 
					                data = decoded["data"]
 | 
				
			||||||
 | 
					                call = decoded["call"]
 | 
				
			||||||
 | 
					                match decoded["type"]:
 | 
				
			||||||
 | 
					                    case "log":
 | 
				
			||||||
 | 
					                        fprint("log message")
 | 
				
			||||||
 | 
					                        if call == "send":
 | 
				
			||||||
 | 
					                            fprint("webapp: " + str(data), sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					                        elif call == "request":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case "cable_map":
 | 
				
			||||||
 | 
					                        fprint("cable_map message")
 | 
				
			||||||
 | 
					                        if call == "send":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					                        elif call == "request":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    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("")
 | 
				
			||||||
 | 
					                        elif call == "request":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case "cable_search":
 | 
				
			||||||
 | 
					                        fprint("cable_search message")
 | 
				
			||||||
 | 
					                        if call == "send":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					                        elif call == "request":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case "keyboard":
 | 
				
			||||||
 | 
					                        fprint("keyboard message")
 | 
				
			||||||
 | 
					                        if call == "send":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					                        elif call == "request":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					                            if data["enabled"] == True:
 | 
				
			||||||
 | 
					                                # todo : send this to client
 | 
				
			||||||
 | 
					                                p = Process(target=run_cmd, args=("./keyboard-up.ps1",))
 | 
				
			||||||
 | 
					                                p.start()
 | 
				
			||||||
 | 
					                            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("")
 | 
				
			||||||
 | 
					                        elif call == "request":
 | 
				
			||||||
 | 
					                            fprint("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    case _:
 | 
				
			||||||
 | 
					                        fprint("Unknown/unimplemented data type: " + decoded["type"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                fprint("Non-JSON message recieved")
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sleep(0.001)  # Sleep to prevent tight loop
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def start_client_socket():
 | 
				
			||||||
 | 
					    app = Flask(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @app.route('/control_client', methods=['POST'])
 | 
				
			||||||
 | 
					    def message_from_server():
 | 
				
			||||||
 | 
					        # Handle message from server
 | 
				
			||||||
 | 
					        data = request.json
 | 
				
			||||||
 | 
					        fprint(f"Message from server: {data.get('message')}")
 | 
				
			||||||
 | 
					        return "Message received", 200
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    app.run(host='0.0.0.0', port=6000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_server_online(serverip, clientip):
 | 
				
			||||||
 | 
					    def send_ip_to_server(server_url, client_ip):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            response = requests.post(server_url, json={'ip': client_ip}, timeout=1)
 | 
				
			||||||
 | 
					            fprint(f"Server response: {response.text}")
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        except requests.exceptions.RequestException as e:
 | 
				
			||||||
 | 
					            fprint(f"Error sending IP to server: {e}")
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    server_url = 'http://' + serverip + ':5000/report_ip'
 | 
				
			||||||
 | 
					    while not send_ip_to_server(server_url, clientip):
 | 
				
			||||||
 | 
					        sleep(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fprint("Successfully connected to server.")
 | 
				
			||||||
 | 
					    return True
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					def setup_server(pool):
 | 
				
			||||||
 | 
					    # linux server setup
 | 
				
			||||||
 | 
					    global config
 | 
				
			||||||
 | 
					    global counter
 | 
				
			||||||
 | 
					    global sensor_ready
 | 
				
			||||||
 | 
					    global camera_ready
 | 
				
			||||||
 | 
					    global led_ready
 | 
				
			||||||
 | 
					    global arm_ready
 | 
				
			||||||
 | 
					    global serverproc
 | 
				
			||||||
 | 
					    global camera
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pool.apply_async(ur5_control.init, (config["arm"]["ip"],), callback=arm_start_callback)
 | 
				
			||||||
 | 
					    pool.apply_async(led_control.init, callback=led_start_callback)
 | 
				
			||||||
 | 
					    #pool.apply_async(sensor_control.init, callback=sensor_start_callback)
 | 
				
			||||||
 | 
					    serverproc = Process(target=start_server_socket)
 | 
				
			||||||
 | 
					    serverproc.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if led_ready is False:
 | 
				
			||||||
 | 
					        fprint("waiting for " + "LED controller initialization" + " to complete...", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					        while led_ready is False:
 | 
				
			||||||
 | 
					            sleep(0.1)
 | 
				
			||||||
 | 
					    fprint("LED controllers initialized.", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					    #to_server_queue.put("[log] LED controllers initialized.")
 | 
				
			||||||
 | 
					    sensor_ready = True
 | 
				
			||||||
 | 
					    if sensor_ready is False:
 | 
				
			||||||
 | 
					        fprint("waiting for " + "Sensor Initialization" + " to complete...", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					        while sensor_ready is False:
 | 
				
			||||||
 | 
					            sleep(0.1)
 | 
				
			||||||
 | 
					    fprint("Sensors initialized.", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if camera_ready is False:
 | 
				
			||||||
 | 
					        fprint("waiting for " + "Camera initilization" + " to complete...", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					        # camera = process_video.qr_reader(config["cameras"]["banner"]["ip"], config["cameras"]["banner"]["port"])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fprint("Camera initialized.", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    arm_ready = True
 | 
				
			||||||
 | 
					    if arm_ready is False:
 | 
				
			||||||
 | 
					        fprint("waiting for " + "UR5 initilization" + " to complete...", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					        while arm_ready is False:
 | 
				
			||||||
 | 
					            sleep(0.1)
 | 
				
			||||||
 | 
					    fprint("Arm initialized.", sendqueue=to_server_queue)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mainloop_server(pool):
 | 
				
			||||||
 | 
					    global config
 | 
				
			||||||
 | 
					    global counter
 | 
				
			||||||
 | 
					    global killme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if killme.value > 0:
 | 
				
			||||||
 | 
					        killall()
 | 
				
			||||||
 | 
					    counter = counter + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # fprint("Looking for QR code...")
 | 
				
			||||||
 | 
					    # print(camera.read_qr(30))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_loading_app():
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    app = Flask(__name__)
 | 
				
			||||||
 | 
					    @app.route('/')
 | 
				
			||||||
 | 
					    def index():
 | 
				
			||||||
 | 
					        return render_template('index.html')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    app.run(debug=True, use_reloader=False, port=7000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_client(pool):
 | 
				
			||||||
 | 
					    # Windows client setup
 | 
				
			||||||
 | 
					    fprint("Opening browser...")
 | 
				
			||||||
 | 
					    firefox = webdriver.Firefox()
 | 
				
			||||||
 | 
					    firefox.fullscreen_window()
 | 
				
			||||||
 | 
					    global config
 | 
				
			||||||
 | 
					    global vm_ready
 | 
				
			||||||
 | 
					    global serverproc
 | 
				
			||||||
 | 
					    # Open loading wepage
 | 
				
			||||||
 | 
					    p = Process(target=run_loading_app)
 | 
				
			||||||
 | 
					    p.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    firefox.get('http://localhost:7000')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # start Linux server VM
 | 
				
			||||||
 | 
					    if config["core"]["server"] == "Hyper-V":
 | 
				
			||||||
 | 
					        run_cmd("Start-VM -Name Jukebox*") # any and all VMs starting with "Jukebox"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Wait for VM to start and be reachable over the network
 | 
				
			||||||
 | 
					    serverproc = Process(target=start_client_socket)
 | 
				
			||||||
 | 
					    serverproc.start()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    pool.apply_async(check_server_online, (config["core"]["serverip"],config["core"]["clientip"]), callback=vm_start_callback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #wait_for(vm_ready, "VM Startup")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #global vm_ready
 | 
				
			||||||
 | 
					    if vm_ready is False:
 | 
				
			||||||
 | 
					        fprint("waiting for " + "VM Startup" + " to complete...")
 | 
				
			||||||
 | 
					        while vm_ready is False:
 | 
				
			||||||
 | 
					            sleep(0.1)
 | 
				
			||||||
 | 
					    p.terminate()
 | 
				
			||||||
 | 
					    firefox.get("http://" + config["core"]["serverip"] + ":8000")
 | 
				
			||||||
 | 
					    return True
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mainloop_client(pool):
 | 
				
			||||||
 | 
					    sleep(0.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # listen for & act on commands from VM, if needed
 | 
				
			||||||
 | 
					    # mainly just shut down, possibly connect to wifi or something
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""class Logger(object):
 | 
				
			||||||
 | 
					    def __init__(self, filename="output.log"):
 | 
				
			||||||
 | 
					        self.log = open(filename, "a")
 | 
				
			||||||
 | 
					        self.terminal = sys.stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, message):
 | 
				
			||||||
 | 
					        self.log.write(message)
 | 
				
			||||||
 | 
					        #close(filename)
 | 
				
			||||||
 | 
					        #self.log = open(filename, "a")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.terminal.write(message)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            sleep(0)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        print("",end="")"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def killall():
 | 
				
			||||||
 | 
					    procs = active_children()
 | 
				
			||||||
 | 
					    for proc in procs:
 | 
				
			||||||
 | 
					        proc.kill()
 | 
				
			||||||
 | 
					    fprint("All child processes killed")
 | 
				
			||||||
 | 
					    os.kill(os.getpid(), 9) # dirty kill of self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					def killall_signal(a, b):
 | 
				
			||||||
 | 
					    global config
 | 
				
			||||||
 | 
					    if config["core"]["server"] == "Hyper-V":
 | 
				
			||||||
 | 
					        run_cmd("Stop-VM -Name Jukebox*") # any and all VMs starting with "Jukebox"
 | 
				
			||||||
 | 
					    killall()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def error(msg, *args):
 | 
				
			||||||
 | 
					    return multiprocessing.get_logger().error(msg, *args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogExceptions(object):
 | 
				
			||||||
 | 
					    def __init__(self, callable):
 | 
				
			||||||
 | 
					        self.__callable = callable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __call__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            result = self.__callable(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            # Here we add some debugging help. If multiprocessing's
 | 
				
			||||||
 | 
					            # debugging is on, it will arrange to log the traceback
 | 
				
			||||||
 | 
					            error(traceback.format_exc())
 | 
				
			||||||
 | 
					            # Re-raise the original exception so the Pool worker can
 | 
				
			||||||
 | 
					            # clean up
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # It was fine, give a normal answer
 | 
				
			||||||
 | 
					        return result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LoggingPool(Pool):
 | 
				
			||||||
 | 
					    def apply_async(self, func, args=(), kwds={}, callback=None):
 | 
				
			||||||
 | 
					        return Pool.apply_async(self, LogExceptions(func), args, kwds, callback)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    #sys.stdout = Logger(filename="output.log")
 | 
				
			||||||
 | 
					    #sys.stderr = Logger(filename="output.log")
 | 
				
			||||||
 | 
					    #log_to_stderr(logging.DEBUG)
 | 
				
			||||||
 | 
					    fprint("Starting Jukebox control system...")
 | 
				
			||||||
 | 
					    with open('config.yml', 'r') as fileread:
 | 
				
			||||||
 | 
					        #global config
 | 
				
			||||||
 | 
					        config = yaml.safe_load(fileread)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with Manager() as manager:
 | 
				
			||||||
 | 
					        fprint("Spawning threads...")
 | 
				
			||||||
 | 
					        pool = LoggingPool(processes=10)
 | 
				
			||||||
 | 
					        counter = 0
 | 
				
			||||||
 | 
					        killme = manager.Value('d', 0)
 | 
				
			||||||
 | 
					        signal.signal(signal.SIGINT, killall_signal)
 | 
				
			||||||
 | 
					        if config["core"]["mode"] == "winclient":
 | 
				
			||||||
 | 
					            fprint("Starting in client mode.")
 | 
				
			||||||
 | 
					            from selenium import webdriver
 | 
				
			||||||
 | 
					            if setup_client(pool):
 | 
				
			||||||
 | 
					                fprint("Entering main loop...")
 | 
				
			||||||
 | 
					                while(keeprunning):
 | 
				
			||||||
 | 
					                    mainloop_client(pool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif config["core"]["mode"] == "linuxserver":
 | 
				
			||||||
 | 
					            fprint("Starting in server mode.")
 | 
				
			||||||
 | 
					            if setup_server(pool):
 | 
				
			||||||
 | 
					                fprint("Entering main loop...")
 | 
				
			||||||
 | 
					                while(keeprunning):
 | 
				
			||||||
 | 
					                    mainloop_server(pool)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										117
									
								
								search.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								search.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					"""Interactions with the Meilisearch API for adding and searching cables."""
 | 
				
			||||||
 | 
					from meilisearch import Client
 | 
				
			||||||
 | 
					from meilisearch.task import TaskInfo
 | 
				
			||||||
 | 
					from meilisearch.errors import MeilisearchApiError
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFAULT_URL = "http://localhost:7700"
 | 
				
			||||||
 | 
					DEFAULT_APIKEY = "fluffybunnyrabbit" # I WOULD RECOMMEND SOMETHING MORE SECURE
 | 
				
			||||||
 | 
					DEFAULT_INDEX = "cables"
 | 
				
			||||||
 | 
					DEFAULT_FILTERABLE_ATTRS = ["partnum", "uuid", "position"] # default filterable attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class JukeboxSearch:
 | 
				
			||||||
 | 
					    """Class for interacting with the Meilisearch API."""
 | 
				
			||||||
 | 
					    def __init__(self,
 | 
				
			||||||
 | 
					                 url: str = None,
 | 
				
			||||||
 | 
					                 api_key: str = None,
 | 
				
			||||||
 | 
					                 index: str = None,
 | 
				
			||||||
 | 
					                 filterable_attrs: list = None):
 | 
				
			||||||
 | 
					        """Connect to Meilisearch and perform first-run tasks as necessary.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param url: Address of the Meilisearch server. Defaults to ``http://localhost:7700`` if unspecified.
 | 
				
			||||||
 | 
					        :param api_key: API key used to authenticate with Meilisearch. It is highly recommended to set this as something
 | 
				
			||||||
 | 
					        secure if you can access this endpoint publicly, but you can ignore this and set Meilisearch's default API key
 | 
				
			||||||
 | 
					        to ``fluffybunnyrabbit``.
 | 
				
			||||||
 | 
					        :param index: The name of the index to configure. Defaults to ``cables`` if unspecified.
 | 
				
			||||||
 | 
					        :param filterable_attrs: List of all the attributes we want to filter by."""
 | 
				
			||||||
 | 
					        # connect to Meilisearch
 | 
				
			||||||
 | 
					        url = url or DEFAULT_URL
 | 
				
			||||||
 | 
					        api_key = api_key or DEFAULT_APIKEY
 | 
				
			||||||
 | 
					        filterable_attrs = filterable_attrs or DEFAULT_FILTERABLE_ATTRS
 | 
				
			||||||
 | 
					        self.index = index or DEFAULT_INDEX
 | 
				
			||||||
 | 
					        self.client = Client(url, api_key)
 | 
				
			||||||
 | 
					        # create the index if it does not exist already
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.client.get_index(self.index)
 | 
				
			||||||
 | 
					        except MeilisearchApiError as _:
 | 
				
			||||||
 | 
					            self.client.create_index(self.index)
 | 
				
			||||||
 | 
					        # make a variable to easily reference the index
 | 
				
			||||||
 | 
					        self.idxref = self.client.index(self.index)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # update filterable attributes if needed
 | 
				
			||||||
 | 
					        self.update_filterables(filterable_attrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_document(self, document: dict) -> TaskInfo:
 | 
				
			||||||
 | 
					        """Add a cable to the Meilisearch index.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param document: Dictionary containing all the cable data.
 | 
				
			||||||
 | 
					        :returns: A TaskInfo object for the addition of the new document."""
 | 
				
			||||||
 | 
					        return self.idxref.add_documents(document)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_documents(self, documents: list):
 | 
				
			||||||
 | 
					        """Add a list of cables to the Meilisearch index.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param documents: List of dictionaries containing all the cable data.
 | 
				
			||||||
 | 
					        :returns: A TaskInfo object for the last new document."""
 | 
				
			||||||
 | 
					        taskinfo = None
 | 
				
			||||||
 | 
					        for i in documents:
 | 
				
			||||||
 | 
					            taskinfo = self.add_document(i)
 | 
				
			||||||
 | 
					        return taskinfo
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def update_filterables(self, filterables: list):
 | 
				
			||||||
 | 
					        """Update filterable attributes and wait for database to fully index. If the filterable attributes matches the
 | 
				
			||||||
 | 
					        current attributes in the database, don't update (saves reindexing).
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        :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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def search(self, query: str, filters: str = None):
 | 
				
			||||||
 | 
					        """Execute a search query on the Meilisearch index.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        :param query: Seach query
 | 
				
			||||||
 | 
					        :param filters: A meilisearch compatible filter statement.
 | 
				
			||||||
 | 
					        :returns: The search results dict. Actual results are in a list under "hits", but there are other nice values that are useful in the root element."""
 | 
				
			||||||
 | 
					        if filters:
 | 
				
			||||||
 | 
					            q = self.idxref.search(query, {"filter": filters})
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            q = self.idxref.search(query)
 | 
				
			||||||
 | 
					        return q
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def _filter_one(self, filter: str):
 | 
				
			||||||
 | 
					        """Get the first item to match a filter.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        :param filter: A meilisearch compatible filter statement.
 | 
				
			||||||
 | 
					        :returns: A dict containing the results; If no results found, an empty dict."""
 | 
				
			||||||
 | 
					        q = self.search("", filter)
 | 
				
			||||||
 | 
					        if q["estimatedTotalHits"] != 0:
 | 
				
			||||||
 | 
					            return ["hits"][0]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return dict()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_position(self, position: str):
 | 
				
			||||||
 | 
					        """Get a part by position.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        :param partnum: The position to search for."""
 | 
				
			||||||
 | 
					        return self._filter_one(f"position = {position}")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def get_uuid(self, uuid: str):
 | 
				
			||||||
 | 
					        """Get a specific UUID.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        :param uuid: The UUID to search for."""
 | 
				
			||||||
 | 
					        return self._filter_one(f"uuid = {uuid}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_partnum(self, partnum: str):
 | 
				
			||||||
 | 
					        """Get a specific part number.
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        :param partnum: The part number to search for."""
 | 
				
			||||||
 | 
					        return self._filter_one(f"partnum = {partnum}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# entrypoint
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    jbs = JukeboxSearch()
 | 
				
			||||||
							
								
								
									
										95
									
								
								server.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										95
									
								
								server.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# WebSocket server for communicating with all other nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""import websockets
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebSocketServer:
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.start_server = websockets.serve(self.handler, "0.0.0.0", 9000)
 | 
				
			||||||
 | 
					        self.received_messages = asyncio.Queue()
 | 
				
			||||||
 | 
					        self.connected_clients = set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def handler(self, websocket, path):
 | 
				
			||||||
 | 
					        self.connected_clients.add(websocket)
 | 
				
			||||||
 | 
					        fprint(self.connected_clients)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            async for message in websocket:
 | 
				
			||||||
 | 
					                fprint(message)
 | 
				
			||||||
 | 
					                await self.received_messages.put(message)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            self.connected_clients.remove(websocket)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def send_message(self, message):
 | 
				
			||||||
 | 
					        disconnected_clients = set()
 | 
				
			||||||
 | 
					        for websocket in self.connected_clients:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                await websocket.send(message)
 | 
				
			||||||
 | 
					            except websockets.exceptions.ConnectionClosed:
 | 
				
			||||||
 | 
					                disconnected_clients.add(websocket)
 | 
				
			||||||
 | 
					        self.connected_clients.difference_update(disconnected_clients)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def run(self):
 | 
				
			||||||
 | 
					        fprint("Starting WebSocket server...")
 | 
				
			||||||
 | 
					        asyncio.get_event_loop().run_until_complete(self.start_server)
 | 
				
			||||||
 | 
					        #asyncio.run(self.start_server)
 | 
				
			||||||
 | 
					        asyncio.get_event_loop().run_forever()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def get_received_message(self):
 | 
				
			||||||
 | 
					        if not self.received_messages.empty():
 | 
				
			||||||
 | 
					            return await self.received_messages.get()
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Function to be used by multiprocessing to run the server
 | 
				
			||||||
 | 
					def run_server():
 | 
				
			||||||
 | 
					    server = WebSocketServer()
 | 
				
			||||||
 | 
					    asyncio.run(server.run())"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import websockets
 | 
				
			||||||
 | 
					from multiprocessing import Process, Queue
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					connected_clients = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def handler(websocket, path, to_server_queue, from_server_queue):
 | 
				
			||||||
 | 
					    # Register websocket connection
 | 
				
			||||||
 | 
					    client_id = str(uuid.uuid4())
 | 
				
			||||||
 | 
					    connected_clients[client_id] = websocket
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        # Handle incoming messages
 | 
				
			||||||
 | 
					        async for message in websocket:
 | 
				
			||||||
 | 
					            #print(f"Received message: {message}")
 | 
				
			||||||
 | 
					            print(client_id)
 | 
				
			||||||
 | 
					            from_server_queue.put((client_id, message))
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        # Unregister websocket connection
 | 
				
			||||||
 | 
					        if client_id in connected_clients:
 | 
				
			||||||
 | 
					            del connected_clients[client_id]
 | 
				
			||||||
 | 
					            print(f"Client {client_id} connection closed")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def send_messages(to_server_queue):
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        if not to_server_queue.empty():
 | 
				
			||||||
 | 
					            client_id, message = to_server_queue.get()
 | 
				
			||||||
 | 
					            if client_id in connected_clients:  # Send message to specific client
 | 
				
			||||||
 | 
					                await connected_clients[client_id].send(message)
 | 
				
			||||||
 | 
					            elif len(connected_clients) > 0:  # Broadcast message to all clients
 | 
				
			||||||
 | 
					                for client in connected_clients.values():
 | 
				
			||||||
 | 
					                    await client.send(message)
 | 
				
			||||||
 | 
					        await asyncio.sleep(0.001)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def websocket_server(to_server_queue, from_server_queue):
 | 
				
			||||||
 | 
					    start_server = websockets.serve(lambda ws, path: handler(ws, path, to_server_queue, from_server_queue), "localhost", 9000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    asyncio.get_event_loop().run_until_complete(start_server)
 | 
				
			||||||
 | 
					    asyncio.get_event_loop().create_task(send_messages(to_server_queue))
 | 
				
			||||||
 | 
					    asyncio.get_event_loop().run_forever()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def start_websocket_server(to_server_queue, from_server_queue):
 | 
				
			||||||
 | 
					    p = Process(target=websocket_server, args=(to_server_queue, from_server_queue))
 | 
				
			||||||
 | 
					    p.start()
 | 
				
			||||||
 | 
					    return p
 | 
				
			||||||
							
								
								
									
										1
									
								
								source.fish
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								source.fish
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					source venv/bin/activate.fish
 | 
				
			||||||
							
								
								
									
										24
									
								
								static/style.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								static/style.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					body {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    padding-top: 100px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.spinner {
 | 
				
			||||||
 | 
					    border: 16px solid #f3f3f3;
 | 
				
			||||||
 | 
					    border-top: 16px solid #002554;
 | 
				
			||||||
 | 
					    border-radius: 50%;
 | 
				
			||||||
 | 
					    width: 120px;
 | 
				
			||||||
 | 
					    height: 120px;
 | 
				
			||||||
 | 
					    animation: spin 2s linear infinite;
 | 
				
			||||||
 | 
					    margin: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes spin {
 | 
				
			||||||
 | 
					    0% { transform: rotate(0deg); }
 | 
				
			||||||
 | 
					    100% { transform: rotate(360deg); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					p {
 | 
				
			||||||
 | 
					    margin-top: 20px;
 | 
				
			||||||
 | 
					    font-size: 20px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								templates/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>Loading</title>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div class="spinner"></div>
 | 
				
			||||||
 | 
					    <p>Waiting for backend server to start...</p>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										106
									
								
								test.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								test.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
				
			|||||||
 | 
					print("\u001b[37m")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Ring:
 | 
				
			||||||
 | 
					    def __init__(self) -> None:
 | 
				
			||||||
 | 
					        self.leds = [0] * 24
 | 
				
			||||||
 | 
					        self.id = 0
 | 
				
			||||||
 | 
					        self.dirty = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __iter__(self) -> iter:
 | 
				
			||||||
 | 
					        yield from self.leds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __repr__(self) -> str:
 | 
				
			||||||
 | 
					        return f"Ring<id={self.id}, led_state={' '.join(list(map(lambda x: str(x+1), self.leds)))}, dirty={self.dirty}>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __add__(self, other):
 | 
				
			||||||
 | 
					        self.leds.extend(other)
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __bool__(self):
 | 
				
			||||||
 | 
					        return self.dirty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __getitem__(self, index):
 | 
				
			||||||
 | 
					        return self.leds[index]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def __setitem__(self, index, value):
 | 
				
			||||||
 | 
					        ivalue = self.leds[index]
 | 
				
			||||||
 | 
					        if ivalue != value:
 | 
				
			||||||
 | 
					            self.dirty = True
 | 
				
			||||||
 | 
					            self.leds[index] = value
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def __getattr__(self, name):
 | 
				
			||||||
 | 
					        import word2num
 | 
				
			||||||
 | 
					        name = int(word2num.word2num(name))
 | 
				
			||||||
 | 
					        print(name)
 | 
				
			||||||
 | 
					        if 0 <= name < len(self.leds):
 | 
				
			||||||
 | 
					            return self.leds[name]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a = Ring()
 | 
				
			||||||
 | 
					print(a)
 | 
				
			||||||
 | 
					b = Ring()
 | 
				
			||||||
 | 
					b.leds[2] = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(a + b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					b.active = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if b:
 | 
				
			||||||
 | 
					    print("Bexist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					c = [a, b, b, a, a]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					d = list(filter(lambda x: bool(x), c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(d)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					for i, ring in enumerate(c):
 | 
				
			||||||
 | 
					    ring[0] = i
 | 
				
			||||||
 | 
					    print(ring)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(a, b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(f"\u001b[32m{a}")
 | 
				
			||||||
 | 
					print(f"\u001b[37ma")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(getattr(a, "twenty two"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# eval(f"getattr(a,\"{input()}\")")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# a = r"wow this string is cursed; for example \n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SEARCHDATA=r"""{ "q": "{QUERY}", "sortCriteria": "relevancy", "numberOfResults": "250", "sortCriteria": "@catalogitemwebdisplaypriority ascending", "searchHub": "products-only-search", "pipeline": "Site Search", "maximumAge": "900000", "tab": "products-search", "locale": "en", "aq": "(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((@syssource==\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\" @catalogitemprimarycategorypublished==true)) ((@catalogitemregionavailable=Global) (@z95xlanguage==en))", "cq": "((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\"Coveo_web_index - rg-nc-prod-sitecore-prod\")) OR (@source==(\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\",\"website_001002_Category_index-rg-nc-prod-sitecore-prod\"))", "firstResult": "0" }, "categoryFacets": "[{\"field\":\"@catalogitemcategories\",\"path\":[],\"injectionDepth\":1000,\"maximumNumberOfValues\":6,\"delimitingCharacter\":\"|\"}]", "facetOptions": "{}", "groupBy": "    [{\"field\":\"@contenttype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[\"Products\"],\"queryOverride\":\"{QUERY}\",\"advancedQueryOverride\":\"(NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B)) ((((((((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) ((@z95xpath=C292F3A37B3A4E6BAB345DF87ADDE516 @z95xid<>C292F3A37B3A4E6BAB345DF87ADDE516) @z95xtemplate==E4EFEB787BDC4B1A908EFC64D56CB2A4)) OR ((@z95xpath=723501A864754FEEB8AE377E4C710271 @z95xid<>723501A864754FEEB8AE377E4C710271) ((@z95xpath=600114EAB0E5407A84AAA9F0985B6575 @z95xid<>600114EAB0E5407A84AAA9F0985B6575) @z95xtemplate==2BE4FD6B3B2C49EBBD9E1F6C92238B05))) OR (@syssource==\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\" @catalogitemprimarycategorypublished==true)) OR ((@z95xpath=3324AF2D58F64C0FB725521052F679D2 @z95xid<>3324AF2D58F64C0FB725521052F679D2) @z95xpath<>C292F3A37B3A4E6BAB345DF87ADDE516)) OR @syssource==\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\") NOT @z95xtemplate==(ADB6CA4F03EF4F47B9AC9CE2BA53FF97,FE5DD82648C6436DB87A7C4210C7413B))) ((@catalogitemregionavailable=Global) (@z95xlanguage==en) OR (@contenttype=(Blogs,Resources,Other)) (NOT @ez120xcludefromcoveo==1))\",\"constantQueryOverride\":\"((@z95xlanguage==en) (@z95xlatestversion==1) (@source==\\"Coveo_web_index - rg-nc-prod-sitecore-prod\\")) OR (@source==(\\"website_001002_catalog_index-rg-nc-prod-sitecore-prod\\",\\"website_001002_Category_index-rg-nc-prod-sitecore-prod\\"))\"},{\"field\":\"@catalogitembrand\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemenvironment\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@catalogitemregionalavailability\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@prez45xtez120xt\",\"maximumNumberOfValues\":5,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@tags\",\"maximumNumberOfValues\":4,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetassettype\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetbrand\",\"maximumNumberOfValues\":3,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetmarket\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsolution\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]},{\"field\":\"@facetsearchcontentpagetype\",\"maximumNumberOfValues\":6,\"sortCriteria\":\"occurrences\",\"injectionDepth\":1000,\"completeFacetWithStandardValues\":true,\"allowedValues\":[]}]" }"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# QUERY = "AAAAAAAAAAAA"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# b = SEARCHDATA.replace(r"{QUERY}", QUERY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					q = [i * 2 for i in range(10)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					d = {a : b for a,b in enumerate(q)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(q)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(d)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def stalin_sort(a):
 | 
				
			||||||
 | 
					    b = sum(a)
 | 
				
			||||||
 | 
					    b /= len(a)
 | 
				
			||||||
 | 
					    return [b for _ in range(len(a))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def mao_sort(a):
 | 
				
			||||||
 | 
					    i = 0
 | 
				
			||||||
 | 
					    while i < len(a) - 1:
 | 
				
			||||||
 | 
					        if a[i+1] < a[i]:
 | 
				
			||||||
 | 
					            del a[i]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            i += 1
 | 
				
			||||||
 | 
					    return a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					print(stalin_sort(list(range(10))))
 | 
				
			||||||
 | 
					print(mao_sort([1, 3, 2, 4, 5, 8, 7, 6, 9]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# i l
 | 
				
			||||||
							
								
								
									
										58
									
								
								udp_send_test.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										58
									
								
								udp_send_test.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import sacn
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ipaddr = "192.168.68.130"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					sender = None
 | 
				
			||||||
 | 
					data = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					start = time.time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def init():
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    sender = sacn.sACNsender(universeDiscovery=False)
 | 
				
			||||||
 | 
					    sender.start()  # start the sending thread
 | 
				
			||||||
 | 
					    sender.activate_output(2)  # start sending out data
 | 
				
			||||||
 | 
					    sender[2].destination = ipaddr
 | 
				
			||||||
 | 
					    sender.manual_flush = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # initialize global pixel data list
 | 
				
			||||||
 | 
					    data = list()
 | 
				
			||||||
 | 
					    for x in range(170):
 | 
				
			||||||
 | 
					        data.append((1,2,3)) # some random data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def fastsendall(datain):
 | 
				
			||||||
 | 
					    # send all LED data to all controllers
 | 
				
			||||||
 | 
					    # data must have all LED data in it as [(R,G,B,)] tuples in an array, 1 tuple per pixel
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    sender[2].dmx_data = list(sum(datain[0:170] , ())) # flatten the subsection of the data array
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    sender.flush()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def close():
 | 
				
			||||||
 | 
					    global sender
 | 
				
			||||||
 | 
					    time.sleep(0.5)
 | 
				
			||||||
 | 
					    sender.stop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def send_fps(fps=140):
 | 
				
			||||||
 | 
					    global start
 | 
				
			||||||
 | 
					    while time.time() - start < 1/fps:
 | 
				
			||||||
 | 
					        time.sleep(0.00001)
 | 
				
			||||||
 | 
					    print("FPS:", 1 / (time.time() - start))
 | 
				
			||||||
 | 
					    start = time.time()
 | 
				
			||||||
 | 
					    global data
 | 
				
			||||||
 | 
					    fastsendall(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    init()
 | 
				
			||||||
 | 
					    while True:
 | 
				
			||||||
 | 
					        send_fps()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    time.sleep(1)
 | 
				
			||||||
 | 
					    close()
 | 
				
			||||||
 | 
					    #sys.exit(0)
 | 
				
			||||||
							
								
								
									
										326
									
								
								ur5_control.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										326
									
								
								ur5_control.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,326 @@
 | 
				
			|||||||
 | 
					import urx
 | 
				
			||||||
 | 
					import math3d as m3d
 | 
				
			||||||
 | 
					from scipy.optimize import fsolve
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					from urx.robotiq_two_finger_gripper import Robotiq_Two_Finger_Gripper
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					from util import fprint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rob = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def init(ip):
 | 
				
			||||||
 | 
					    global rob
 | 
				
			||||||
 | 
					    #sys.stdout = Logger()
 | 
				
			||||||
 | 
					    fprint("Starting UR5 power up...")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # power up robot here
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wait for power up (this function runs async)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # trigger auto-initialize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # wait for auto-initialize
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # init urx
 | 
				
			||||||
 | 
					    fprint("Connecting to arm at " + ip)
 | 
				
			||||||
 | 
					    trying = True
 | 
				
			||||||
 | 
					    while trying:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            rob = urx.Robot(ip)
 | 
				
			||||||
 | 
					            trying = False
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            time.sleep(1)
 | 
				
			||||||
 | 
					    robotiqgrip = Robotiq_Two_Finger_Gripper(rob)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Sets robot arm endpoint offset (x,y,z,rx,ry,rz)
 | 
				
			||||||
 | 
					    rob.set_tcp((0, 0, 0.15, 0, 0, 0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Set weight
 | 
				
			||||||
 | 
					    rob.set_payload(2, (0, 0, 0.1))
 | 
				
			||||||
 | 
					    #rob.set_payload(2, (0, 0, 0.1))
 | 
				
			||||||
 | 
					    time.sleep(0.2)
 | 
				
			||||||
 | 
					    fprint("UR5 ready.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_pos_abs(x, y, z, xb, yb, zb, threshold=None):
 | 
				
			||||||
 | 
					    global rob
 | 
				
			||||||
 | 
					    new_orientation = m3d.Transform()
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_xb(xb)  # Replace rx with the desired rotation around X-axis
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_yb(yb)  # Replace ry with the desired rotation around Y-axis
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_zb(zb)  # Replace rz with the desired rotation around Z-axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get the current pose
 | 
				
			||||||
 | 
					    trans = rob.getl()
 | 
				
			||||||
 | 
					    # Apply the new orientation while keeping the current position
 | 
				
			||||||
 | 
					    new_trans = m3d.Transform(new_orientation.orient, m3d.Vector(trans[0:3]))
 | 
				
			||||||
 | 
					    new_trans.pos.x = x
 | 
				
			||||||
 | 
					    new_trans.pos.y = y
 | 
				
			||||||
 | 
					    new_trans.pos.z = z
 | 
				
			||||||
 | 
					    #rob.speedj(0.2, 0.5, 99999)
 | 
				
			||||||
 | 
					    rob.set_pose(new_trans, acc=2, vel=2, command="movej", threshold=threshold)  # apply the new pose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_pos_rel_rot_abs(x, y, z, xb, yb, zb):
 | 
				
			||||||
 | 
					    global rob
 | 
				
			||||||
 | 
					    new_orientation = m3d.Transform()
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_xb(xb)  # Replace rx with the desired rotation around X-axis
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_yb(yb)  # Replace ry with the desired rotation around Y-axis
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_zb(zb)  # Replace rz with the desired rotation around Z-axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get the current pose
 | 
				
			||||||
 | 
					    trans = rob.getl()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Apply the new orientation while keeping the current position
 | 
				
			||||||
 | 
					    new_trans = m3d.Transform(new_orientation.orient, m3d.Vector(trans[0:3]))
 | 
				
			||||||
 | 
					    new_trans.pos.x += x
 | 
				
			||||||
 | 
					    new_trans.pos.y += y
 | 
				
			||||||
 | 
					    new_trans.pos.z += z
 | 
				
			||||||
 | 
					    #rob.speedj(0.2, 0.5, 99999)
 | 
				
			||||||
 | 
					    rob.set_pose(new_trans, acc=0.1, vel=0.4, command="movej")  # apply the new pose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def set_pos_abs_rot_rel(x, y, z, xb, yb, zb):
 | 
				
			||||||
 | 
					    global rob
 | 
				
			||||||
 | 
					    new_orientation = m3d.Transform()
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_xb(xb)  # Replace rx with the desired rotation around X-axis
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_yb(yb)  # Replace ry with the desired rotation around Y-axis
 | 
				
			||||||
 | 
					    new_orientation.orient.rotate_zb(zb)  # Replace rz with the desired rotation around Z-axis
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Get the current pose
 | 
				
			||||||
 | 
					    trans = rob.getl()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Apply the new orientation while keeping the current position
 | 
				
			||||||
 | 
					    new_trans = m3d.Transform(new_orientation.orient, m3d.Vector(trans[0:3]))
 | 
				
			||||||
 | 
					    new_trans.pos.x = x
 | 
				
			||||||
 | 
					    new_trans.pos.y = y
 | 
				
			||||||
 | 
					    new_trans.pos.z = z
 | 
				
			||||||
 | 
					    #rob.speedj(0.2, 0.5, 99999)
 | 
				
			||||||
 | 
					    rob.set_pose(new_trans, acc=0.1, vel=0.4, command="movej")  # apply the new pose
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_safe_move(start_pose, end_pose, r=0.25):
 | 
				
			||||||
 | 
					    start_x, start_y = (start_pose[0], start_pose[1])
 | 
				
			||||||
 | 
					    end_x, end_y = (end_pose[0], end_pose[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        m = (end_y-start_y)/(end_x-start_x)
 | 
				
			||||||
 | 
					        b = start_y - m*start_x
 | 
				
			||||||
 | 
					        # print('m = y/x =', m)
 | 
				
			||||||
 | 
					        # print('b =', b)
 | 
				
			||||||
 | 
					    except:
 | 
				
			||||||
 | 
					        m = (end_x-start_x)/(end_y-start_y)
 | 
				
			||||||
 | 
					        b = start_x - m*start_y
 | 
				
			||||||
 | 
					        # print('m = x/y =', m)
 | 
				
			||||||
 | 
					        # print('b =', b)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return r**2 - b**2 + m**2 * r**2 < 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def cartesian_to_polar(x, y):
 | 
				
			||||||
 | 
					    r = np.sqrt(x**2 + y**2)
 | 
				
			||||||
 | 
					    theta = np.arctan2(y, x)
 | 
				
			||||||
 | 
					    return r, theta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def polar_to_cartesian(r, theta):
 | 
				
			||||||
 | 
					    x = r * np.cos(theta)
 | 
				
			||||||
 | 
					    y = r * np.sin(theta)
 | 
				
			||||||
 | 
					    return x, y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def move_to_polar(start_pos, end_pos):
 | 
				
			||||||
 | 
					    global rob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Convert to polar coordinates
 | 
				
			||||||
 | 
					    start_r, start_theta = cartesian_to_polar(start_pos[0], start_pos[1])
 | 
				
			||||||
 | 
					    end_r, end_theta = cartesian_to_polar(end_pos[0], end_pos[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Interpolate for xy (spiral arc)
 | 
				
			||||||
 | 
					    n_points = 30
 | 
				
			||||||
 | 
					    r_intermediate = np.linspace(start_r, end_r, n_points)
 | 
				
			||||||
 | 
					    theta_intermediate = np.linspace(start_theta, end_theta, n_points)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Interpolate for z (height)
 | 
				
			||||||
 | 
					    start_z = start_pos[2]
 | 
				
			||||||
 | 
					    end_z = end_pos[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    z_intermediate = np.linspace(start_z, end_z, n_points)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Interpolate for rz (keep tool rotation fixed relative to robot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    curr_rot = rob.getl()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    theta_delta = theta_intermediate[1]-theta_intermediate[0]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    rx_intermediate = [curr_rot[5] + theta_delta*i for i in range(n_points)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # curr_rot = rob.getj()
 | 
				
			||||||
 | 
					    # start_rz = curr_rot[5]
 | 
				
			||||||
 | 
					    # rot = end_theta - start_theta
 | 
				
			||||||
 | 
					    # end_base_joint = curr_rot[0]-start_theta + rot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # end_rz = curr_rot[0] + rot
 | 
				
			||||||
 | 
					    # # rob.movel([*polar_to_cartesian(end_r, end_theta), *rob.getl()[2:]], acc=2, vel=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # print('start_theta = ', math.degrees(start_theta))
 | 
				
			||||||
 | 
					    # print('end_theta = ', math.degrees(curr_rot[0]-start_theta+rot))
 | 
				
			||||||
 | 
					    # print('start_rz =', math.degrees(start_rz))
 | 
				
			||||||
 | 
					    # print('rot =', math.degrees(rot))
 | 
				
			||||||
 | 
					    # print('end_rz =', math.degrees(end_rz))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # rz_intermediate = np.linspace(start_rz, end_rz, n_points)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Convert back to cartesian coordinates
 | 
				
			||||||
 | 
					    curr_pos = rob.getl()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    intermediate_points = [[*polar_to_cartesian(r, theta), z, *curr_pos[3:]]
 | 
				
			||||||
 | 
					                            for r, theta, z, rx in zip(r_intermediate, 
 | 
				
			||||||
 | 
					                                                       theta_intermediate, 
 | 
				
			||||||
 | 
					                                                       z_intermediate, 
 | 
				
			||||||
 | 
					                                                       rx_intermediate)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Move robot
 | 
				
			||||||
 | 
					    rob.movels(intermediate_points, acc=2, vel=2, radius=0.1)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return rx_intermediate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def move_to_home():
 | 
				
			||||||
 | 
					    global rob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Home position in degrees
 | 
				
			||||||
 | 
					    home_pos = [0.10421807948612624, 
 | 
				
			||||||
 | 
					                -2.206111555015423, 
 | 
				
			||||||
 | 
					                1.710679229503537, 
 | 
				
			||||||
 | 
					                -1.075834511928354, 
 | 
				
			||||||
 | 
					                -1.569301366430687, 
 | 
				
			||||||
 | 
					                1.675098295930943]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Move robot
 | 
				
			||||||
 | 
					    rob.movej(home_pos, acc=2, vel=2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def normalize_degree(theta):
 | 
				
			||||||
 | 
					    # Normalizes degree theta from -1.5pi to 1.5pi
 | 
				
			||||||
 | 
					    multiplier = 1
 | 
				
			||||||
 | 
					    normalized_theta = theta % (math.pi * multiplier)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Maintain the negative sign if the original angle is negative
 | 
				
			||||||
 | 
					    if theta < 0:
 | 
				
			||||||
 | 
					        normalized_theta -= math.pi * multiplier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Return angle
 | 
				
			||||||
 | 
					    return normalized_theta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_joints_from_xyz_rel(x, y, z, initial_guess = (math.pi/2, math.pi/2, 0), limbs=(.422864, .359041, .092124)):
 | 
				
			||||||
 | 
					    # Get polar coordinates of x,y pair
 | 
				
			||||||
 | 
					    r, theta = cartesian_to_polar(x, y)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Get length of each limb
 | 
				
			||||||
 | 
					    l1, l2, l3 = limbs
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Formulas to find out joint positions for (r, z)
 | 
				
			||||||
 | 
					    def inv_kin_r_z(p):
 | 
				
			||||||
 | 
					        a, b, c = p
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (l1*math.cos(a) + l2*math.cos(a-b) + l3*math.cos(a-b-c) - r, # r
 | 
				
			||||||
 | 
					                l1*math.sin(a) + l2*math.sin(a-b) - l3*math.sin(a-b-c) - z,  # z
 | 
				
			||||||
 | 
					                a-b-c) # wrist angle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Normalize angles
 | 
				
			||||||
 | 
					    base, shoulder, elbow, wrist = [normalize_degree(deg) for deg in [theta, *fsolve(inv_kin_r_z, initial_guess)]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Return result
 | 
				
			||||||
 | 
					    return base, shoulder, elbow, wrist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_joints_from_xyz_abs(x, y, z):
 | 
				
			||||||
 | 
					    joints = get_joints_from_xyz_rel(x, y, z)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Joint offsets
 | 
				
			||||||
 | 
					    # Base, Shoulder, Elbow, Wrist
 | 
				
			||||||
 | 
					    inverse = [1, -1, 1, 1]
 | 
				
			||||||
 | 
					    offsets = [0, 0, 0, -math.pi/2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Return adjusted joint positions
 | 
				
			||||||
 | 
					    return [o+j*i for j, o, i in zip(joints, offsets, inverse)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #rob.movej((0, 0, 0, 0, 0, 0), 0.1, 0.2)
 | 
				
			||||||
 | 
					    #rob.movel((x, y, z, rx, ry, rz), a, v)
 | 
				
			||||||
 | 
					    init("192.168.1.145")
 | 
				
			||||||
 | 
					    print("Current tool pose is: ",  rob.getl())
 | 
				
			||||||
 | 
					    move_to_home()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    home_pose = [-0.4999999077032916, 
 | 
				
			||||||
 | 
					               -0.2000072960336574, 
 | 
				
			||||||
 | 
					               0.40002172976662786, 
 | 
				
			||||||
 | 
					               0, 
 | 
				
			||||||
 | 
					               -3.14152741295329, 
 | 
				
			||||||
 | 
					               0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # time.sleep(.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    p1 = [0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        0.6,
 | 
				
			||||||
 | 
					        .4,
 | 
				
			||||||
 | 
					        0.2226,
 | 
				
			||||||
 | 
					        3.1126,
 | 
				
			||||||
 | 
					        0.0510]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    p2 = [0.171,
 | 
				
			||||||
 | 
					        -0.115,
 | 
				
			||||||
 | 
					        0.2,
 | 
				
			||||||
 | 
					        0.2226,
 | 
				
			||||||
 | 
					        3.1126,
 | 
				
			||||||
 | 
					        0.0510]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    curr_pos = rob.getl()
 | 
				
			||||||
 | 
					    # up/down, 
 | 
				
			||||||
 | 
					    # tool rotation
 | 
				
			||||||
 | 
					    # tool angle (shouldn't need)
 | 
				
			||||||
 | 
					    # rob.set_pos(p1[0:3], acc=0.5, vel=0.5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # set_pos_abs(*home_pose)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    angles = get_joints_from_xyz_abs(0.3, 0.3, 0.3)
 | 
				
			||||||
 | 
					    rob.movej([*angles, *rob.getj()[4:]], acc=1, vel=1)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    angles = get_joints_from_xyz_abs(-0.3, -0.3, 0.7)
 | 
				
			||||||
 | 
					    rob.movej([*angles, *rob.getj()[4:]], acc=1, vel=1)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    angles = get_joints_from_xyz_abs(-0.3, 0.4, 0.2)
 | 
				
			||||||
 | 
					    rob.movej([*angles, *rob.getj()[4:]], acc=1, vel=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # set_pos_abs(*p1)
 | 
				
			||||||
 | 
					    # move = move_to_polar(p1, p2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # for p in move:
 | 
				
			||||||
 | 
					    #     print(math.degrees(p))
 | 
				
			||||||
 | 
					    # print("Safe? :", is_safe_move(p1, p2))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # #set_pos_rel_rot_abs(0, 0, -0.2, math.pi, 0, -math.pi)
 | 
				
			||||||
 | 
					    # set_pos_abs(0.3, -0.2, 0.5, math.pi, 0, -math.pi)
 | 
				
			||||||
 | 
					    # set_pos_abs(0, 0.2, 0.6, math.pi, 0, -math.pi)
 | 
				
			||||||
 | 
					    # set_pos_abs(-0.5, -0.2, 0.4, math.pi, 0, -math.pi)
 | 
				
			||||||
 | 
					    # #set_pos_rel_rot_abs(0, 0, 0, math.pi, 0, -math.pi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # print("Current tool pose is: ",  rob.getl())
 | 
				
			||||||
 | 
					    # print("getj(): ",  rob.getj())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # move_to_home()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rob.stop()
 | 
				
			||||||
 | 
					    os.kill(os.getpid(), 9) # dirty kill of self
 | 
				
			||||||
 | 
					    sys.exit(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										165
									
								
								util.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										165
									
								
								util.py
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from sys import platform
 | 
				
			||||||
 | 
					import time as t
 | 
				
			||||||
 | 
					from time import sleep
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					import csv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					win32 = platform == "win32"
 | 
				
			||||||
 | 
					linux = platform == "linux" or platform == "linux2"
 | 
				
			||||||
 | 
					macos = platform == "darwin"
 | 
				
			||||||
 | 
					datafile = ""
 | 
				
			||||||
 | 
					logMsg = ""
 | 
				
			||||||
 | 
					logCont = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					settings = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if win32:
 | 
				
			||||||
 | 
					    sysid = hex(uuid.getnode())
 | 
				
			||||||
 | 
					    # Python is running as Administrator (so netstat can get filename, to block, etc), 
 | 
				
			||||||
 | 
					    # so we use this to see who is actually logged in
 | 
				
			||||||
 | 
					    # it's very hacky
 | 
				
			||||||
 | 
					    startupinfo = subprocess.STARTUPINFO()
 | 
				
			||||||
 | 
					    #if not getattr(sys, "frozen", False):
 | 
				
			||||||
 | 
					    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # hide powershell window
 | 
				
			||||||
 | 
					    res = subprocess.check_output(["WMIC", "ComputerSystem", "GET", "UserName"], universal_newlines=True, startupinfo=startupinfo)
 | 
				
			||||||
 | 
					    _, username = res.strip().rsplit("\n", 1)
 | 
				
			||||||
 | 
					    userid, sysdom = username.rsplit("\\", 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if linux or macos:
 | 
				
			||||||
 | 
					    sysid = hex(uuid.getnode())
 | 
				
			||||||
 | 
					    #fprint(sysid)
 | 
				
			||||||
 | 
					    res = subprocess.check_output(["who",], universal_newlines=True)
 | 
				
			||||||
 | 
					    userid = res.strip().split(" ")[0]
 | 
				
			||||||
 | 
					    #sysdom = subprocess.check_output(["hostname",], universal_newlines=True).strip()
 | 
				
			||||||
 | 
					    #fprint(sysdom)
 | 
				
			||||||
 | 
					    #fprint("d")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def time():
 | 
				
			||||||
 | 
					    return int(t.time())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def kill(pid):
 | 
				
			||||||
 | 
					    setup_child()
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if pid > 4:
 | 
				
			||||||
 | 
					            fprint("Killing PID " + str(pid), settings)
 | 
				
			||||||
 | 
					            os.kill(int(pid), 9)
 | 
				
			||||||
 | 
					            fprint("Signal 9 sent to PID " + str(pid), settings)
 | 
				
			||||||
 | 
					    except:
 | 
				
			||||||
 | 
					        fprint("Unable to kill " + str(pid), settings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def fprint(msg, settings = None, sendqueue = None):
 | 
				
			||||||
 | 
					    #if not getattr(sys, "frozen", False):
 | 
				
			||||||
 | 
					    setup_child()
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        frm = inspect.stack()[1]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        mod = inspect.getmodule(frm[0])
 | 
				
			||||||
 | 
					        logMsg = '[' + mod.__name__ + ":" + frm.function + ']:' + str(msg)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        print(logMsg)
 | 
				
			||||||
 | 
					        if (sendqueue is not None):
 | 
				
			||||||
 | 
					            sendqueue.put(("*", "{ \"type\": \"log\", \"call\":\"send\", \"data\": \"" + logMsg + "\" }"))
 | 
				
			||||||
 | 
					        if (settings is not None):
 | 
				
			||||||
 | 
					            tmpList = settings["logMsg"]
 | 
				
			||||||
 | 
					            tmpList.append(logMsg)
 | 
				
			||||||
 | 
					            settings["logMsg"] = tmpList
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            print('[????:' + frm.function + ']:', str(msg))
 | 
				
			||||||
 | 
					            print('[util:fprint]: ' + str(e))
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            print('[????]:', str(msg))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					   # else:
 | 
				
			||||||
 | 
					        #print(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def find_data_file(filename):
 | 
				
			||||||
 | 
					    if getattr(sys, "frozen", False):
 | 
				
			||||||
 | 
					        # The application is frozen
 | 
				
			||||||
 | 
					        datadir = os.path.dirname(sys.executable)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        # The application is not frozen
 | 
				
			||||||
 | 
					        # Change this bit to match where you store your data files:
 | 
				
			||||||
 | 
					        datadir = os.path.dirname(__file__)
 | 
				
			||||||
 | 
					    return os.path.join(datadir, filename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def run_cmd(cmd):
 | 
				
			||||||
 | 
					    if win32:
 | 
				
			||||||
 | 
					        startupinfo = subprocess.STARTUPINFO()
 | 
				
			||||||
 | 
					        #print("DICKS")
 | 
				
			||||||
 | 
					        #if not getattr(sys, "frozen", False):
 | 
				
			||||||
 | 
					        #    print("test")
 | 
				
			||||||
 | 
					        #    
 | 
				
			||||||
 | 
					        #completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True, startupinfo=startupinfo)
 | 
				
			||||||
 | 
					        #else:
 | 
				
			||||||
 | 
					        #    print("alt")
 | 
				
			||||||
 | 
					        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW    # , "-WindowStyle", "hidden"
 | 
				
			||||||
 | 
					        fprint("running PS command: " + cmd, settings)
 | 
				
			||||||
 | 
					        completed = subprocess.run(["powershell", "-Command", cmd], capture_output=True, startupinfo=startupinfo)
 | 
				
			||||||
 | 
					        fprint("ran PS command successfully", settings)
 | 
				
			||||||
 | 
					        #completed = subprocess.run(["powershell", "-WindowStyle", "hidden", "-Command", cmd], capture_output=True, startupinfo=startupinfo)
 | 
				
			||||||
 | 
					        return completed
 | 
				
			||||||
 | 
					    if linux or macos:
 | 
				
			||||||
 | 
					        fprint("running sh command: " + cmd, settings)
 | 
				
			||||||
 | 
					        completed = subprocess.run(["sh", "-c", cmd], capture_output=True)
 | 
				
			||||||
 | 
					        fprint("ran sh command successfully", settings)
 | 
				
			||||||
 | 
					        return completed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_child(sets=None):
 | 
				
			||||||
 | 
					    if not getattr(sys, "frozen", False):
 | 
				
			||||||
 | 
					        sys.stdout = Logger(filename=find_data_file("output.log"))
 | 
				
			||||||
 | 
					        sys.stderr = Logger(filename=find_data_file("output.log"))
 | 
				
			||||||
 | 
					    if sets is not None:
 | 
				
			||||||
 | 
					        settings = sets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Logger(object):
 | 
				
			||||||
 | 
					    def __init__(self, filename="output.log"):
 | 
				
			||||||
 | 
					        self.log = open(filename, "a")
 | 
				
			||||||
 | 
					        self.terminal = sys.stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def write(self, message):
 | 
				
			||||||
 | 
					        #self.log.write(message)
 | 
				
			||||||
 | 
					        #close(filename)
 | 
				
			||||||
 | 
					        #self.log = open(filename, "a")
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.terminal.write(message)
 | 
				
			||||||
 | 
					        except:
 | 
				
			||||||
 | 
					            sleep(0)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def flush(self):
 | 
				
			||||||
 | 
					        print("", end="")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def write_stats(stats):
 | 
				
			||||||
 | 
					    fprint("Writing stats", settings)
 | 
				
			||||||
 | 
					    tmp = list()
 | 
				
			||||||
 | 
					    tmp.append(["connections blocked", "connections allowed", "data uploaded", "data recieved", "block ratio"])
 | 
				
			||||||
 | 
					    tmp.append(stats)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    with open(find_data_file("stats.csv"), "w", newline="") as f:
 | 
				
			||||||
 | 
					        writer = csv.writer(f)
 | 
				
			||||||
 | 
					        writer.writerows(tmp)
 | 
				
			||||||
 | 
					    fprint("Done writing stats", settings)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_stats():
 | 
				
			||||||
 | 
					    with open(find_data_file("stats.csv"), newline='') as csvfile:
 | 
				
			||||||
 | 
					        csvreader = csv.reader(csvfile, delimiter=',', quotechar='|')
 | 
				
			||||||
 | 
					        header = True
 | 
				
			||||||
 | 
					        fprint(csvreader, settings)
 | 
				
			||||||
 | 
					        data = list()
 | 
				
			||||||
 | 
					        for line in csvreader:
 | 
				
			||||||
 | 
					            fprint(line, settings)
 | 
				
			||||||
 | 
					            if header:
 | 
				
			||||||
 | 
					                header = False
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					            data = line
 | 
				
			||||||
 | 
					        for idx in range(len(data) - 1):
 | 
				
			||||||
 | 
					            data[idx] = int(data[idx])
 | 
				
			||||||
 | 
					        data[len(data) - 1] = float(data[len(data) - 1])
 | 
				
			||||||
 | 
					        return data
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
#!/bin/sh
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					# change this to #!/bin/bash for windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
python -m venv ./venv
 | 
					python3 -m venv ./venv
 | 
				
			||||||
source ./venv/bin/activate
 | 
					source ./venv/bin/activate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pip install --upgrade pip
 | 
					pip install --upgrade pip
 | 
				
			||||||
							
								
								
									
										156
									
								
								websocket_test.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								websocket_test.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,156 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <title>WebSocket Test</title>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        footer {
 | 
				
			||||||
 | 
					            background-color: #333;
 | 
				
			||||||
 | 
					            color: #fff;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            padding: 10px 0;
 | 
				
			||||||
 | 
					            position: fixed;
 | 
				
			||||||
 | 
					            bottom: 0;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            justify-content: space-around;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .service-box {
 | 
				
			||||||
 | 
					            width: 150px;
 | 
				
			||||||
 | 
					            padding: 10px;
 | 
				
			||||||
 | 
					            border-radius: 5px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .service-up {
 | 
				
			||||||
 | 
					            background-color: green;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .service-down {
 | 
				
			||||||
 | 
					            background-color: red;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // class Service {
 | 
				
			||||||
 | 
					        //     constructor(name, status) {
 | 
				
			||||||
 | 
					        //         this.name = name
 | 
				
			||||||
 | 
					        //         this.status = status
 | 
				
			||||||
 | 
					        //     }
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var updatedTime = new Date();
 | 
				
			||||||
 | 
					        // Initial status of services
 | 
				
			||||||
 | 
					        // var serviceA = new Service("234234", 'down');
 | 
				
			||||||
 | 
					        // var serviceBStatus = 'down';
 | 
				
			||||||
 | 
					        // var serviceCStatus = 'down';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        document.addEventListener("DOMContentLoaded", function () {
 | 
				
			||||||
 | 
					            // Create WebSocket connection.
 | 
				
			||||||
 | 
					            const socket = new WebSocket('ws://localhost:9000');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Connection opened
 | 
				
			||||||
 | 
					            socket.addEventListener('open', function (event) {
 | 
				
			||||||
 | 
					                console.log("Connected to WebSocket server");
 | 
				
			||||||
 | 
					                updatedTime = new Date();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Listen for messages
 | 
				
			||||||
 | 
					            socket.addEventListener('message', function (event) {
 | 
				
			||||||
 | 
					                console.log('Message from server', event.data);
 | 
				
			||||||
 | 
					                let messages = document.getElementById('messages');
 | 
				
			||||||
 | 
					                let message = document.createElement('li');
 | 
				
			||||||
 | 
					                message.textContent = "Received: " + event.data;
 | 
				
			||||||
 | 
					                messages.appendChild(message);
 | 
				
			||||||
 | 
					                updatedTime = new Date();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Send a message to the server
 | 
				
			||||||
 | 
					            function sendMessage() {
 | 
				
			||||||
 | 
					                let message = document.getElementById('messageInput').value;
 | 
				
			||||||
 | 
					                socket.send(message);
 | 
				
			||||||
 | 
					                console.log('Message sent', message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // This function just sends a ping to make sure the server is there and it is able to responds
 | 
				
			||||||
 | 
					            function ping() {
 | 
				
			||||||
 | 
					                let message = `{ "call": "send", "type": "log", "data": "This is a ping!!" }`;
 | 
				
			||||||
 | 
					                socket.send(message);
 | 
				
			||||||
 | 
					                console.log('Message sent', message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            setInterval(ping, 1500);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // setInterval(() => {
 | 
				
			||||||
 | 
					            //     updateServiceStatus('serviceA', 'down');
 | 
				
			||||||
 | 
					            // }, 2000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Bind send message function to button click
 | 
				
			||||||
 | 
					            document.getElementById('sendMessage').addEventListener('click', sendMessage);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <h2>WebSocket Test</h2>
 | 
				
			||||||
 | 
					    <textarea rows="4" cols="50" id="messageInput" placeholder="Type a message..."> </textarea>
 | 
				
			||||||
 | 
					    <button id="sendMessage">Send Message</button>
 | 
				
			||||||
 | 
					    <p>Example JSON</p>
 | 
				
			||||||
 | 
					    <p>{ "type": "cable_map", "call": "request", "data": { } }</p>
 | 
				
			||||||
 | 
					    <p>{ "type": "log", "call": "send", "data": "123123" }</p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p>Messages/Logs</p>
 | 
				
			||||||
 | 
					    <ul id="messages"></ul>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <footer>
 | 
				
			||||||
 | 
					        <!-- <div id="serviceA" class="service-box"></div>
 | 
				
			||||||
 | 
					        <div id="serviceB" class="service-box"></div>
 | 
				
			||||||
 | 
					        <div id="serviceC" class="service-box"></div> -->
 | 
				
			||||||
 | 
					        <div id="clock"></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </footer>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        // // Function to update service status
 | 
				
			||||||
 | 
					        // function updateServiceStatus(service) {
 | 
				
			||||||
 | 
					        //     // serviceId, status
 | 
				
			||||||
 | 
					        //     var serviceElement = document.getElementById(service.serviceId);
 | 
				
			||||||
 | 
					        //     // updateClock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //     if (service.status === 'up') {
 | 
				
			||||||
 | 
					        //         serviceElement.innerHTML = '<h3>' + service.serviceId + '</h3><p>Running</p>';
 | 
				
			||||||
 | 
					        //         serviceElement.classList.remove('service-down');
 | 
				
			||||||
 | 
					        //         serviceElement.classList.add('service-up');
 | 
				
			||||||
 | 
					        //     } else {
 | 
				
			||||||
 | 
					        //         serviceElement.innerHTML = '<h3>' + service.serviceId + '</h3><p>Down</p>';
 | 
				
			||||||
 | 
					        //         serviceElement.classList.remove('service-up');
 | 
				
			||||||
 | 
					        //         serviceElement.classList.add('service-down');
 | 
				
			||||||
 | 
					        //     }
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // // Update service statuses
 | 
				
			||||||
 | 
					        // updateServiceStatus('node.js (for this page)', serviceAStatus);
 | 
				
			||||||
 | 
					        // updateServiceStatus('Python WebSocket', serviceBStatus);
 | 
				
			||||||
 | 
					        // updateServiceStatus('serviceC', serviceCStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Function to update clock
 | 
				
			||||||
 | 
					        function updateClock() {
 | 
				
			||||||
 | 
					            var now = new Date();
 | 
				
			||||||
 | 
					            now = now.getTime() - updatedTime.getTime();
 | 
				
			||||||
 | 
					            // console.log(now)
 | 
				
			||||||
 | 
					            document.getElementById('clock').textContent = 'Milliseconds Since Update: ' + now.toString().padStart(6, '0');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update clock every second
 | 
				
			||||||
 | 
					        setInterval(updateClock, 100);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
		Reference in New Issue
	
	Block a user