100 Commits

Author SHA1 Message Date
ba6c697f00 Add return from tray in web 2024-05-14 17:34:02 -05:00
24d959da71 Update label generation, websocket listen mode 2024-05-14 17:33:47 -05:00
33af359a86 Flip vertical move 2024-05-14 16:26:32 -05:00
43f8c3b615 Other way 2024-05-14 16:24:01 -05:00
a96f34baac Readjust 2024-05-14 16:22:50 -05:00
635c2708a0 Redo arm startup motion 2024-05-14 16:16:05 -05:00
62ec1e7443 Add tray return API call 2024-05-14 16:01:10 -05:00
951ae8cdda Remove unnecessary deps that are pre-installed 2024-05-14 14:59:12 -05:00
5e0f1e93d2 prerelease 2024-05-13 02:21:07 -05:00
fab8324dea Update web 2024-05-12 18:36:07 -05:00
dbaf018c7b Remove hover effects for touchscreens 2024-05-12 18:30:04 -05:00
f47270a600 Update jukebox-web 2024-05-12 18:19:03 -05:00
e4861e0590 Add datasheet URL to cable details 2024-05-12 18:14:39 -05:00
c45c59aa62 Fix cable_search 2024-05-12 17:56:47 -05:00
1e01141f41 Make cable_search match cable_map 2024-05-12 16:59:59 -05:00
2296aea463 Add useful print statements 2024-05-10 12:58:17 -05:00
341d2232d7 Don't delete old JSON files 2024-05-10 12:51:45 -05:00
25dc18669b Downgrade camelot for better parsing 2024-05-10 12:28:59 -05:00
6c8d0bf666 Update web ui 2024-05-09 18:19:24 -05:00
be51423c89 Add real-time UR5 position updates (hopefully) 2024-05-09 18:18:55 -05:00
f416e25c1c Update LED system 2024-05-08 20:28:48 -05:00
dd7bc12fe5 Update jukebox-web 2024-05-08 16:52:28 -05:00
f06540f568 Add category field, update datasheet extracted fields 2024-05-08 16:51:01 -05:00
4a78586c34 Update jukebox-web 2024-05-08 16:44:46 -05:00
21cb2beb67 Pull description from Belden API for more consistent values 2024-05-08 16:44:22 -05:00
a905858e3b Add more descriptions 2024-05-08 15:48:26 -05:00
faf33aede7 add descriptions 2024-05-08 15:18:09 -05:00
dc4cf5c222 Center images 2024-05-08 13:27:45 -05:00
ee78037f17 Remove debug statement 2024-05-08 13:07:47 -05:00
f1f2e3191b Square up images 2024-05-08 13:04:43 -05:00
1f69dbdb82 fix clean install bug 2024-05-08 12:34:07 -05:00
36249058ac Add brand and images, fileserver for images, start to add arm queue 2024-05-08 12:29:43 -05:00
eca65dc0e0 fix typo 2024-04-29 17:49:56 -05:00
fabd1fe10a Simulation for sensors 2024-04-29 17:48:42 -05:00
b4ad57d52d Separate scanning bypass 2024-04-29 17:25:02 -05:00
72ab357dbf Improve simulation mode 2024-04-29 17:19:52 -05:00
d0083ed33f Simulation mode 2024-04-29 13:30:04 -05:00
d92ceafa57 Fix non-matching positions 2024-04-29 12:42:11 -05:00
62bcb07956 Update runtime LED modes 2024-04-29 12:33:55 -05:00
c52fe167bf Merge branch 'main' of https://git.myitr.org/Jukebox/jukebox-software 2024-04-26 17:49:14 -05:00
f7b4f264c4 Fixed positions for holder 47 and 45 in config.yaml 2024-04-26 17:49:11 -05:00
fb31ab0d73 Add LED animations to runtime 2024-04-24 14:42:47 -05:00
302d275c64 Update config 2024-04-24 14:42:47 -05:00
43392fa3ca Freeze math3d version 2024-04-24 14:42:47 -05:00
660fe29236 Update camera image parser 2024-04-24 14:42:47 -05:00
275cbd027e Added open gripper to init routine 2024-04-24 14:42:07 -05:00
f4e43f33d2 Created functions for routines 2024-04-24 14:40:38 -05:00
ae97ed1a14 Made pickup and dropoff routines for tray and holder. Adjusted home position 2024-04-23 18:56:11 -05:00
237319db14 Fixed drop off routine to not hit walls 2024-04-22 21:56:07 -05:00
a698c4a753 Add more routines 2024-04-18 20:33:55 -05:00
939474378a Add open and close gripper functions 2024-04-12 22:05:34 -05:00
145f51d08c Update IPs for LED system 2024-04-12 20:28:34 -05:00
4973fc79be Merge branch 'main' of https://git.myitr.org/Jukebox/jukebox-software 2024-04-12 20:28:33 -05:00
ff2269193b small dimensions fixes 2024-04-12 20:28:31 -05:00
5edd7f4592 Remove extraneous pass 2024-03-27 20:41:07 -05:00
4dd6f7649a Implement cable_search 2024-03-27 20:40:20 -05:00
dc1e568a96 Add basic cable_map implementation 2024-03-27 20:33:21 -05:00
1ec6d92cfa Deploy docker containers 2024-03-27 19:50:01 -05:00
e21ded46f1 Update juekbox-web 2024-03-27 19:47:17 -05:00
672507f498 Add jukebox web 2024-03-27 19:46:45 -05:00
64bb50f055 Add setup-alpine-vm.sh 2024-03-28 00:44:57 +00:00
5016b4e99f Update compose.yml 2024-03-28 00:44:35 +00:00
efbda23c38 Fix docker runtime support 2024-03-27 18:53:50 -05:00
19ce328596 Correctly parse results from get_specs 2024-03-27 17:49:02 -05:00
ad216f21fa Implement cable_details call 2024-03-26 18:42:01 -05:00
6d6c2030a9 Merge branch 'main' of https://git.myitr.org/Jukebox/jukebox-software 2024-03-26 15:24:20 -05:00
9893222335 Pick-up routine 2024-03-26 15:24:18 -05:00
82a52dea5a Add cables to meilisearch db 2024-03-26 15:09:26 -05:00
77fdc43fce Add missing robot pass-ins 2024-03-24 15:35:46 -05:00
3de59f5985 Convert ur5_control to class based (untested) 2024-03-24 15:31:58 -05:00
6887fa943b Merge branch 'main' of https://git.myitr.org/Jukebox/jukebox-software 2024-03-24 13:50:17 -05:00
2ec7906ee4 Basic move restrictions and flip routine 2024-03-23 15:47:10 -05:00
069d2175d9 Convert led code to class based for multithreading 2024-03-23 15:33:51 -05:00
9b1b92e21d Re-add https:// format, needed for most devices 2024-03-21 19:48:46 -05:00
ee0f8f4250 Remove cairosvg VERSION patch 2024-03-21 19:41:46 -05:00
9c9435570b Remove cairosvg as it doesn't have pip-only lib. Use png instead. 2024-03-21 19:39:43 -05:00
1bf10f7349 Update label generator to include belden logo, use QR code, URL matching 2024-03-21 18:57:59 -05:00
3c8d6c7ad3 Remove debug print 2024-03-20 16:31:29 -05:00
cbe7225fc9 Fix bug with part name in query_search 2024-03-20 16:27:43 -05:00
44efc4006e Correct flip rz orientation 2024-03-19 17:48:32 -05:00
218303e92b Try different moves 2024-03-17 22:01:34 -05:00
e5d3f87b5c Go to home position first 2024-03-17 20:49:39 -05:00
2ab1d0dbb3 Add flip around mode to fit edge slots 2024-03-17 20:48:26 -05:00
1338c3f440 Added tool z rotation for angled gripper 2024-03-17 20:07:25 -05:00
83b077b4df Make limb lengths and offsets global 2024-03-17 19:59:17 -05:00
f16242f5be Re-merge calculate_theta into get_joints_from_xyz_rel 2024-03-17 19:53:49 -05:00
2f28a01b7c Add basic kinematics for gripper angle 2024-03-17 19:50:04 -05:00
fb85a56d47 Use movejs to go to all cable positions 2024-03-17 16:21:57 -05:00
bec0c63763 Fix ur5_control bugs, fully working IK!! Thanks Nadeem 2024-03-17 16:13:53 -05:00
4ae30b82a0 Cleaned up notebook 2024-03-17 15:43:06 -05:00
4bc3e30116 Fixed edge cases when calculating base angle 2024-03-17 01:01:39 -05:00
6053d1b4ec Update Dockerfile with runtime dependencies, copy only necessary files 2024-03-15 21:02:59 -05:00
f43f9cda2f Catch RuntimeError when GS not installed 2024-03-15 20:41:01 -05:00
992040e812 Add basic label generator app, add return values to parsing 2024-03-15 20:31:37 -05:00
5502a5069d All real samples ordered have working spec lookup 2024-03-14 22:09:12 -05:00
fc2af34450 Add Alphawire datasheet fallback 2024-03-14 22:06:13 -05:00
39723ec442 Update Alphawire table parsing 2024-03-14 21:35:28 -05:00
25ceb6c133 Merge branch 'main' of https://git.myitr.org/Jukebox/jukebox-software 2024-03-14 01:49:19 -05:00
56451d3e5c Inverse kinematic update to account for base rotation 2024-03-14 01:49:15 -05:00
53638f72e1 Merge branch 'dthomas_meilisearch' 2024-03-12 16:15:13 -05:00
44 changed files with 5945 additions and 1264 deletions

8
.gitignore vendored
View File

@ -3,6 +3,7 @@ venv
__pycache__
# cable data folder(s)
cables
cables_old
cables-sample.zip
# meilisearch (mainly where I've put the data volume for the container)
meili_data
@ -15,4 +16,9 @@ output.mp4
# log files
output.log
# images
*.png
map*.png
# Built app
build
# Generated label images
labels
temp

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "jukebox-web"]
path = jukebox-web
url = https://git.myitr.org/Jukebox/jukebox-web

View File

@ -1,11 +1,13 @@
FROM python:latest
FROM python:3.11-slim
RUN apt-get update && apt-get install -y libgl1-mesa-glx ghostscript && apt-get clean && rm -rf /var/lib/apt/lists
COPY . .
# Get runtime dependencies
# glx for OpenCV, ghostscript for datasheet PDF rendering, zbar for barcode scanning, git for cloning repos
RUN apt-get update && apt-get install -y libgl1-mesa-glx ghostscript libzbar0 git && apt-get clean && rm -rf /var/lib/apt/lists
COPY requirements.txt ./
#COPY config-server.yml config.yml
RUN pip3 install -r requirements.txt
CMD ["python3", "run.py"]
COPY *.py *.yml *.sh *.txt *.html static templates ./
CMD ["sh", "-c", "python3 run.py"]
EXPOSE 5000
EXPOSE 8000
EXPOSE 9000

BIN
GothamCond-Medium.otf Normal file

Binary file not shown.

View File

@ -40,13 +40,16 @@ class DriveImg():
self.onLine = False
fprint("Offline")
def close(self):
self.trans.close()
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)
#sleep(2)
try:
self.trans = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.trans.connect((self.ip,self.PORT))
@ -55,7 +58,7 @@ class DriveImg():
except:
self.onLine = False
self.trans.close()
return resposta
return "Error", None
ret = self.trans.recv(64)
try:
valida = str(ret[0:15].decode('UTF-8'))
@ -63,15 +66,15 @@ class DriveImg():
if valida.find("TC IMAGE")<0:
self.onLine = False
self.trans.close()
sleep(2)
#sleep(2)
gravaLog(ip=self.ip,tipo="Falha",msg=f'Unable to find TC IMAGE bookmark')
return "Error"
return "Error", None
except Exception as ex:
self.onLine = False
self.trans.close()
sleep(2)
#sleep(2)
gravaLog(ip=self.ip,tipo="Falha",msg=f'Error - {str(ex)}')
return "Error"
return "Error", None
if ret:
frame = int.from_bytes(ret[24:27],"little")
isJpeg = int.from_bytes(ret[32:33],"little")
@ -113,8 +116,8 @@ class DriveImg():
#print(f'erro {str(ex)}')
self.onLine = False
self.trans.close()
sleep(2)
return resposta
#sleep(2)
return "Error", None
class DriveData():
HEADERSIZE = 100
@ -139,7 +142,7 @@ class DriveData():
if not self.onLine:
#print(f'tentando Conectar...\n')
gravaLog(ip=self.ip,msg=f'tentando Conectar...',file="log_data.txt")
sleep(2)
#sleep(2)
try:
self.trans = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.trans.connect((self.ip,self.PORT))
@ -154,7 +157,7 @@ class DriveData():
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)
#sleep(2)
return resposta

BIN
belden-logo-superhires.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

41
belden-logo.svg Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 446.6 151.4" style="enable-background:new 0 0 446.6 151.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#004990;}
</style>
<g>
<g>
<path class="st0" d="M21.2,32.1h-1.4v87.2H55c20.3,0,32-9.1,32-24.9c0-12.3-5.6-19.7-15.8-22.1c5.1-3.6,7.8-9.1,7.8-16.9
c0-15.5-8.9-23.3-26.5-23.3H21.2z M44.7,51.6c7.4,0,11.4,1.1,11.4,6.8c0,4.8-3.1,6.8-10.5,6.8c0,0-0.6,0-1.1,0
c0-2.1,0-11.5,0-13.6C44.5,51.6,44.7,51.6,44.7,51.6z M45.4,84.4l1.8,0c4.6,0,10.3-0.1,13,2.6c1.2,1.2,1.8,2.9,1.8,5.2
c0,2-0.6,3.5-1.7,4.7c-3,3-9.2,2.9-13.8,2.9c0,0-1.4,0-2.1,0c0-2.1,0-13.3,0-15.4C44.9,84.4,45.4,84.4,45.4,84.4z"/>
<g>
<path class="st0" d="M139.8,32.1H90.4v87.2h50.8V98c0,0-23.7,0-26.1,0c0-2,0-9.8,0-11.8c2.4,0,24.8,0,24.8,0V64.8
c0,0-22.3,0-24.8,0c0-2,0-9.4,0-11.4c2.5,0,26.1,0,26.1,0V32.1H139.8z"/>
</g>
<g>
<path class="st0" d="M169.3,32.1H146v87.2h51V98c0,0-23.9,0-26.3,0c0-2.6,0-65.9,0-65.9H169.3z"/>
</g>
<g>
<path class="st0" d="M332.4,32.1H283v87.2h50.8V98c0,0-23.7,0-26.1,0c0-2,0-9.8,0-11.8c2.4,0,24.8,0,24.8,0V64.8
c0,0-22.3,0-24.8,0c0-2,0-9.4,0-11.4c2.5,0,26.1,0,26.1,0V32.1H332.4z"/>
</g>
<g>
<path class="st0" d="M424.6,32.1h-23.3c0,0,0,43,0,49.3c-4-5.1-38.4-49.3-38.4-49.3h-24v87.2h24.7c0,0,0-43.1,0-49.5
c4,5.1,38.4,49.5,38.4,49.5h24V32.1H424.6z"/>
</g>
<g>
<g>
<path class="st0" d="M233.8,32.1h-32.5v87.2h32.5c24.4,0,44.3-19.6,44.3-43.6C278.1,51.7,258.2,32.1,233.8,32.1z M226,53.5
c13.6,0.3,22,8.7,22,22.2c0,13.6-8.2,21.9-22,22.2V53.5z M231.5,101.3c12.5-3,20.7-10.7,20.8-25.7c-0.2-15-8.3-22.7-20.8-25.7
c14,2.1,25,9.5,25,25.6v0.1C256.5,91.8,245.5,99.2,231.5,101.3z M260.2,75.6c-0.2-18-10-29.8-24.9-33.3
c16.7,2.5,29.6,14,29.6,33.3c0,0,0,0,0,0.1h0c0,19.3-13,30.7-29.7,33.2C250.1,105.3,260,93.6,260.2,75.6z M240.3,115.7
c16.7-4.7,28.3-19.2,28.5-39.9v-0.3c-0.2-20.7-11.9-35.1-28.5-39.8c19,3.9,33.5,17.7,33.6,39.7v0.4
C273.9,97.9,259.4,111.8,240.3,115.7z"/>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

26
cables.txt Normal file
View File

@ -0,0 +1,26 @@
1172C
86104CY
FIT-221-1_4
10GXW13
10GXW53
29501F
29512
3092A
3105A
3106A
6300FE
6300UE
7922A
7958A
8760
9841
FI4X012W0
FISX012W0
IOP6U
RA500P
SPE101
SPE102
TF-1LF-006-RS5
TF-SD9-006-RI5
TT-SLG-024-HTN
3050

13
compose-search-only.yml Normal file
View 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:

View File

@ -9,5 +9,22 @@ services:
volumes:
- "meili_data:/meili_data"
jukebox-software:
build: .
init: true
ports:
- "5000:5000"
- "8000:8000"
- "9000:9000"
environment:
- PYTHONUNBUFFERED=1
depends_on:
- meilisearch
jukebox-web:
build: jukebox-web
ports:
- "3000:3000"
volumes:
meili_data:

View File

@ -3,70 +3,98 @@ core:
serverip: 172.26.178.114
clientip: 172.26.176.1
server: Hyper-Vd
loopspeed: 100 # fps
arm:
ip: 192.168.1.145
tool:
offset_x: 0
offset_y: 0
offset_z: 0.14
limbs:
limb_base: 0.11
limb1: 0.425
limb2: 0.39225
limb3: 0.1
limb_wrist: 0.0997
cables:
port: 7900
directory: ./cables/ # must include trailing slash
#cable_map:
cameras:
banner:
ip: 192.168.1.125
port: 32200
animation_time: 100
led:
fps: 90
timeout: 0
fps: 100
timeout: 1
controllers:
- universe: 9
ip: 192.168.68.131
- universe: 1
ip: 192.168.1.200
ledstart: 0
ledend: 143
mode: rgb
- universe: 3
ip: 192.168.68.131
- universe: 2
ip: 192.168.1.201
ledstart: 144
ledend: 287
mode: rgb
- universe: 2
ip: 192.168.68.131
- universe: 3
ip: 192.168.1.202
ledstart: 288
ledend: 431
mode: rgb
- universe: 4
ip: 192.168.5.40
ip: 192.168.1.203
ledstart: 432
ledend: 575
mode: rgb
- universe: 1
ip: 192.168.5.4
- universe: 5
ip: 192.168.1.204
ledstart: 576
ledend: 719
mode: rgb
- universe: 5
ip: 192.168.68.131
- universe: 6
ip: 192.168.1.205
ledstart: 720
ledend: 863
mode: rgb
- universe: 6
ip: 192.168.68.131
- universe: 7
ip: 192.168.1.206
ledstart: 864
ledend: 1007
mode: rgb
- universe: 7
ip: 192.168.68.131
- universe: 8
ip: 192.168.1.207
ledstart: 1008
ledend: 1151
mode: rgb
- universe: 8
ip: 192.168.68.131
- universe: 9
ip: 192.168.1.208
ledstart: 1152
ledend: 1295
mode: rgb
- universe: 0
ip: 192.168.68.130
ledstart: 1296
ledend: 1365
mode: rgbw
- universe: 10
ip: 192.168.1.209
mode: solid
color: [0, 50, 150]
- universe: 11
ip: 192.168.1.210
mode: solid
color: [0, 50, 150]
- universe: 12
ip: 192.168.1.211
mode: solid
color: [0, 50, 150]
# - universe: 0
# ip: 192.168.1.209
# ledstart: 1296
# ledend: 1365
# mode: rgbw
map:
@ -76,37 +104,37 @@ led:
start: 0
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [0, 304.8]
- type: circle
start: 24
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-65.991, 266.7]
- type: circle
start: 48
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-131.982, 228.6]
- type: circle
start: 72
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-197.973, 190.5]
- type: circle
start: 96
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-263.965, 152.4]
- type: circle
start: 120
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-263.965, 76.2]
# controller 2
@ -114,37 +142,37 @@ led:
start: 144
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [0, 228.6]
- type: circle
start: 168
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-65.991, 190.5]
- type: circle
start: 192
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-131.982, 152.4]
- type: circle
start: 216
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-197.973, 114.3]
- type: circle
start: 240
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-197.973, 38.1]
- type: circle
start: 264
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-263.965, 0]
# controller 3
@ -152,37 +180,37 @@ led:
start: 288
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [0, 152.4]
- type: circle
start: 312
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-65.991, 114.3]
- type: circle
start: 336
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-131.982, 76.2]
- type: circle
start: 360
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-131.982, 0]
- type: circle
start: 384
size: 24
diameter: 63.5
angle: 0
pos: [-197.973, -38.1]
angle: 180
pos: [-199.0, -35.0]
- type: circle
start: 408
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-263.965, -76.2]
# controller 4
@ -190,37 +218,37 @@ led:
start: 432
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, 76.2]
- type: circle
start: 456
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, 152.4]
- type: circle
start: 480
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, 228.6]
- type: circle
start: 504
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [65.991, 266.7]
- type: circle
start: 528
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [65.991, 190.5]
- type: circle
start: 552
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [65.991, 114.3]
@ -229,37 +257,37 @@ led:
start: 576
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, 0]
- type: circle
start: 600
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [197.973, 38.1]
- type: circle
start: 624
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [197.973, 114.3]
- type: circle
start: 648
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [197.973, 190.5]
- type: circle
start: 672
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [263.965, 152.4]
- type: circle
start: 696
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [263.965, 76.2]
# controller 6
@ -267,37 +295,37 @@ led:
start: 720
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, -76.2]
- type: circle
start: 744
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [197.973, -38.1]
- type: circle
start: 768
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [263.965, 0]
- type: circle
start: 792
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [263.965, -76.2]
- type: circle
start: 816
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [263.965, -152.4]
- type: circle
start: 840
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [197.973, -114.3]
# controller 7
@ -305,75 +333,75 @@ led:
start: 864
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [65.991, -114.3]
- type: circle
start: 888
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [0, -152.4]
- type: circle
start: 912
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-65.991, -114.3]
- type: circle
start: 936
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-131.982, -76.2]
- type: circle
start: 960
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-197.973, -114.3]
- type: circle
start: 984
size: 24
diameter: 63.5
angle: 0
pos: [-131.982, -152.4]
angle: 180
pos: [-133.0, -151.0]
# controller 8
- type: circle
start: 1008
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [0, -228.6]
- type: circle
start: 1032
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-65.991, -190.5]
- type: circle
start: 1056
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-65.991, -266.7]
- type: circle
start: 1080
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-131.982, -228.6]
- type: circle
start: 1104
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-197.973, -190.5]
- type: circle
start: 1128
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [-263.965, -152.4]
# controller 9
@ -381,49 +409,49 @@ led:
start: 1152
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [0, -304.8]
- type: circle
start: 1176
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [65.991, -266.7]
- type: circle
start: 1200
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, -228.6]
- type: circle
start: 1224
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [197.973, -190.5]
- type: circle
start: 1248
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [131.982, -152.4]
- type: circle
start: 1272
size: 24
diameter: 63.5
angle: 0
angle: 180
pos: [65.991, -190.5]
# Strips
- type: strip
start: 1296
size: 70
length: 600
angle: 270 # down
pos: [375, 300]
# - 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]
@ -500,7 +528,7 @@ position_map:
- index: 36
pos: [-228.6, -131.982]
- index: 37
pos: [-152.4, -131.982]
pos: [-151.0, -133.0]
- index: 38
pos: [-76.2, -131.982]
- index: 39
@ -516,7 +544,7 @@ position_map:
- index: 44
pos: [-114.3, -197.973]
- index: 45
pos: [-38.1, -197.973]
pos: [-35.0, -199.0]
- index: 46
pos: [38.1, -197.973]
- index: 47

17
fileserver.py Normal file
View File

@ -0,0 +1,17 @@
import http.server
import socketserver
import os
def run_server(port, directory):
"""
Run a simple HTTP server serving files from the specified directory.
"""
# Change the working directory to the specified directory
os.makedirs(directory, exist_ok=True)
os.chdir(directory)
# Create the HTTP server
handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(("", port), handler) as httpd:
print(f"Serving cable images & files at port {port}")
httpd.serve_forever()

View File

@ -27,8 +27,10 @@ def check_internet(url='https://belden.com', timeout=5):
def query_search(partnum, source):
fprint("Searching for " + partnum)
if source == "Belden":
token_url = "https://www.belden.com/coveo/rest/token?t=" + str(int(time.time()))
try:
with requests.get(token_url) as r:
out = json.loads(r.content)
token = out["token"]
@ -46,21 +48,20 @@ def query_search(partnum, source):
'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"])):
for partid in range(len(a["results"])-1, -1, -1):
name = a["results"][partid]["title"]
if name != partnum:
if name.find(partnum) >= 0:
idx = partid
break
#break
elif partnum.find(name) >= 0:
idx = partid
break
#break
else:
idx = partid
@ -69,7 +70,9 @@ def query_search(partnum, source):
if idx < 0:
fprint("Could not find part in API: " + partnum)
return False
fprint("Search result found: result " + str(idx) + ", for ID " + name)
name = a["results"][idx]["title"]
#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("?")]
@ -78,21 +81,26 @@ def query_search(partnum, source):
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)
app = a["results"][idx]["raw"]["catalogitemapplication"]
category = a["results"][idx]["raw"]["catalogitemfilterproductcategory"]
#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["short_description"] = shortdesc
out["description"] = desc
out["application"] = app
out["category"] = category
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.")
print("Failed to search with API. Falling back to datasheet lookup.")
return False
@ -112,12 +120,14 @@ def query_search(partnum, source):
r = requests.get(url=alphaurl)
data = r.json()
output = dict()
#print(data)
#print(data["Results"])
try:
if data["Count"] > 0:
#print(data["Results"][0]["Url"])
for result in data["Results"]:
if result["Url"].split("/")[-1] == partnum:
#print(result["Url"])
if result["Url"].split("/")[-1] == partnum.replace("-", "").replace("/", "_"):
#print(partnum)
#print(result["Html"])
try:
@ -133,14 +143,18 @@ def query_search(partnum, source):
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()
output["partnum"] = partnum.replace("/", "_") #.replace("-", "").replace("/", "_")
#
# "test".index()
#print(output)
return output
except:
print("Failed to search with API. Falling back to datasheet lookup.")
return False
print("Failed to search with API. Falling back to datasheet lookup.")
return False
@ -150,14 +164,20 @@ def touch(path):
def get_multi(partnums, delay=0.25):
with alive_bar(len(partnums) * 2, dual_line=True, calibrate=30, bar="classic2", spinner="classic") as bar:
def get_multi(partnums, delay, dir, webport, cache=True, bar=None):
#with alive_bar(len(partnums) * 2, dual_line=True, calibrate=30, bar="classic2", spinner="classic", disable=True, file=sys.stdout) as bar:
def _try_download_datasheet(partnum, output_dir): # Guess datasheet URL
failed = list()
actualpartnums = list()
def _try_download_datasheet(partnum, output_dir, dstype): # Guess datasheet URL
global bartext
if dstype == "Belden":
sanitized_name = partnum.replace(" ", "")
url = "https://catalog.belden.com/techdata/EN/" + sanitized_name + "_techdata.pdf"
elif dstype == "Alphawire":
# Alphawire Datasheet URLs do not use a sanitized part number (but product pages do)
url = "https://www.alphawire.com/disteAPI/SpecPDF/DownloadProductSpecPdf?productPartNumber=" + partnum
#fprint(url)
try:
with requests.get(url, stream=True) as r:
@ -167,13 +187,14 @@ def get_multi(partnums, delay=0.25):
if r.status_code == 404:
return False
os.makedirs(output_dir, exist_ok=True)
bartext = ""
with open(output_dir + "/datasheet.pdf", 'wb') as f:
for chunk in r.iter_content(chunk_size=131072):
for chunk in r.iter_content(chunk_size=65536):
# If you have chunk encoded response uncomment if
# and set chunk_size parameter to None.
#if chunk:
bartext = bartext + "."
bar.text = bartext
# bar.text = bartext
f.write(chunk)
#fprint("")
return output_dir + "/datasheet.pdf"
@ -195,13 +216,14 @@ def get_multi(partnums, delay=0.25):
if r.status_code == 404:
return False
os.makedirs(output_dir, exist_ok=True)
bartext = ""
with open(output_dir + "/datasheet.pdf", 'wb') as f:
for chunk in r.iter_content(chunk_size=131072):
for chunk in r.iter_content(chunk_size=65536):
# If you have chunk encoded response uncomment if
# and set chunk_size parameter to None.
#if chunk:
bartext = bartext + "."
bar.text = bartext
# bar.text = bartext
f.write(chunk)
#fprint("")
return output_dir + "/datasheet.pdf"
@ -221,13 +243,14 @@ def get_multi(partnums, delay=0.25):
if r.status_code == 404:
return False
os.makedirs(output_dir, exist_ok=True)
bartext = ""
with open(output_dir + "/part-hires." + url.split(".")[-1], 'wb') as f:
for chunk in r.iter_content(chunk_size=131072):
for chunk in r.iter_content(chunk_size=65536):
# If you have chunk encoded response uncomment if
# and set chunk_size parameter to None.
#if chunk:
bartext = bartext + "."
bar.text = bartext
# bar.text = bartext
f.write(chunk)
#fprint("")
return output_dir + "/part-hires." + url.split(".")[-1]
@ -236,82 +259,128 @@ def get_multi(partnums, delay=0.25):
os.remove(partnum + "/datasheet.pdf")
sys.exit()
def __use_cached_datasheet(partnum, path, output_dir, dstype):
def __use_cached_datasheet(partnum, path, output_dir, dstype, weburl, extra):
fprint("Using cached datasheet for " + partnum)
bar.text = "Using cached datasheet for " + partnum
bar(skipped=True)
# bar.text = "Using cached datasheet for " + partnum
# bar(skipped=True)
if not os.path.exists(output_dir + "/parsed"):
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, partnum, dstype)
bar(skipped=False)
out = read_datasheet.parse(path, output_dir, partnum, dstype, weburl, extra)
# bar(skipped=False)
return out
else:
fprint("Datasheet already parsed for " + partnum)
bar.text = "Datasheet already parsed for " + partnum + ".pdf"
bar(skipped=True)
# bar.text = "Datasheet already parsed for " + partnum + ".pdf"
# bar(skipped=True)
def __downloaded_datasheet(partnum, path, output_dir, dstype):
def __downloaded_datasheet(partnum, path, output_dir, dstype, weburl, extra):
fprint("Downloaded " + path)
bar.text = "Downloaded " + path
bar(skipped=False)
# bar.text = "Downloaded " + path
# bar(skipped=False)
fprint("Parsing Datasheet contents of " + partnum)
bar.text = "Parsing Datasheet contents of " + partnum + ".pdf..."
read_datasheet.parse(path, output_dir, partnum, dstype)
bar(skipped=False)
# bar.text = "Parsing Datasheet contents of " + partnum + ".pdf..."
out = read_datasheet.parse(path, output_dir, partnum, dstype, weburl, extra)
# bar(skipped=False)
return out
def run_search(partnum):
output_dir = "cables/" + partnum
partnum = partnum.replace("%20", " ") # undo URL encoding
oldpartnum = partnum
if dstype == "Alphawire":
# For alphawire, sanitize the part number for only the final result check, because their API is very wierd
# For the actual search, it must be un-sanitized
partnum = partnum.replace("/","_")
output_dir = dir + partnum
path = output_dir + "/datasheet.pdf"
weburl = ":" + str(webport) + "/" + partnum + "/"
bartext = "Downloading files for part " + partnum
bar.text = bartext
#
if (not os.path.exists(output_dir + "/found_part_hires")) or not (os.path.exists(path) and os.path.getsize(path) > 1):
# bar.text = bartext
partnum = oldpartnum.replace("_","/")
returnval = [partnum, dstype, False, False]
if (not os.path.exists(output_dir + "/found_part_hires")) or not (os.path.exists(path) and os.path.getsize(path) > 1) or not cache:
# Use query
search_result = query_search(partnum, dstype)
# Try to use belden.com search
if search_result is not False:
# Download high resolution part image if available and needed
#oldpartnum = partnum
partnum = search_result["partnum"]
output_dir = "cables/" + partnum
returnval = [partnum, dstype, False, False]
output_dir = dir + partnum
path = output_dir + "/datasheet.pdf"
bartext = "Downloading files for part " + partnum
bar.text = bartext
os.makedirs(output_dir, exist_ok=True)
with open(output_dir + "/search-result.json", 'w') as json_file:
fprint("Saving search result of " + partnum)
json.dump(search_result, json_file)
# bar.text = bartext
if not os.path.exists(output_dir + "/found_part_hires"):
if not os.path.exists(output_dir + "/found_part_hires") or not cache:
if _download_image(search_result["image"], output_dir):
fprint("Downloaded hi-res part image for " + partnum)
returnval = [partnum, dstype, True, False]
if os.path.exists(output_dir + "/parsed"):
os.remove(output_dir + "/parsed")
touch(output_dir + "/found_part_hires")
else:
fprint("Using cached hi-res part image for " + partnum)
# Download datasheet from provided URL if needed
if os.path.exists(path) and os.path.getsize(path) > 1:
__use_cached_datasheet(partnum, path, output_dir, dstype)
if os.path.exists(path) and os.path.getsize(path) > 1 and cache:
out = __use_cached_datasheet(partnum, path, output_dir, dstype, weburl, search_result)
returnval = [partnum, dstype, True, out]
elif _download_datasheet(search_result["datasheet"], output_dir) is not False:
__downloaded_datasheet(partnum, path, output_dir, dstype)
out = __downloaded_datasheet(partnum, path, output_dir, dstype, weburl, search_result)
returnval = [partnum, dstype, True, out]
elif os.path.exists(path) and os.path.getsize(path) > 1:
__use_cached_datasheet(partnum, path, output_dir, dstype)
elif os.path.exists(path) and os.path.getsize(path) > 1 and cache:
search_result = {}
if os.path.exists(output_dir + "/search-result.json"):
with open(output_dir + "/search-result.json", 'r', encoding='utf-8') as file:
search_result = json.load(file)
out = __use_cached_datasheet(partnum, path, output_dir, dstype, weburl, search_result)
returnval = [partnum, dstype, True, out]
# 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:
__downloaded_datasheet(partnum, path, output_dir, dstype)
elif _try_download_datasheet(partnum, output_dir, dstype) is not False:
search_result = {}
if os.path.exists(output_dir + "/search-result.json"):
with open(output_dir + "/search-result.json", 'r', encoding='utf-8') as file:
search_result = json.load(file)
out = __downloaded_datasheet(partnum, path, output_dir, dstype, weburl, search_result)
returnval = [partnum, dstype, False, out]
# Failed to download with search or guess :(
else:
return False
return True
time.sleep(delay)
actualpartnums.append(returnval)
return returnval
# 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)
search_result = {}
if os.path.exists(output_dir + "/search-result.json"):
with open(output_dir + "/search-result.json", 'r', encoding='utf-8') as file:
search_result = json.load(file)
out = __use_cached_datasheet(partnum, path, output_dir, dstype, weburl, search_result)
returnval = [partnum, dstype, False, out]
actualpartnums.append(returnval)
return True
for fullpartnum in partnums:
if fullpartnum is False:
actualpartnums.append(False)
continue
if fullpartnum[0:2] == "BL": # catalog.belden.com entry
partnum = fullpartnum[2:]
dstype = "Belden"
@ -338,19 +407,19 @@ def get_multi(partnums, delay=0.25):
time.sleep(delay)
if not success:
fprint("Failed to download datasheet for part " + partnum)
bar.text = "Failed to download datasheet for part " + partnum
failed.append(partnum)
bar(skipped=True)
bar(skipped=True)
time.sleep(delay)
# bar.text = "Failed to download datasheet for part " + partnum
failed.append((partnum, dstype))
# bar(skipped=True)
# bar(skipped=True)
if len(failed) > 0:
fprint("Failed to download:")
for partnum in failed:
fprint(partnum)
return False # Go to manual review upload page
fprint(partnum[1] + " " + partnum[0])
return False, actualpartnums # Go to manual review upload page
else:
return True # All cables downloaded; we are good to go
return True, actualpartnums # All cables downloaded; we are good to go
@ -373,11 +442,12 @@ if __name__ == "__main__":
# ]
partnums = [
# Actual cables in Jukebox
"BL3092A",
"AW86104CY",
"AW3050",
"AW6714",
"AW1172C",
"AW2211/4",
"AWFIT-221-1/4",
"BLTF-1LF-006-RS5N",
"BLTF-SD9-006-RI5N",
@ -402,10 +472,11 @@ if __name__ == "__main__":
"BL6300FE 009Q",
"BLRA500P 006Q",
]
# Some ones I picked, including some invalid ones
a = [
"BL10GXS12",
"BLRST 5L-RKT 5L-949",
"BLRST%205L-RKT%205L-949",
"BL10GXS13",
"BL10GXW12",
"BL10GXW13",
@ -416,12 +487,17 @@ if __name__ == "__main__":
"BLFISD012R9",
"BLFDSD012A9",
"BLFSSL024NG",
"BLFISX006W0",
"BLFISX00103",
"BLC6D1100007"
"BLFISX006W0", # datasheet only
"BLFISX00103", # invalid
"BLC6D1100007" # invalid
]
#query_search("86104CY", "Alphawire")
#print(query_search("TT-SLG-024-HTNN", "Belden"))
from label_generator import gen_label
gen_label("BLTF-SD9-006-RI5")
gen_label("BLRA500P")
gen_label("AWFIT-221-1_4")
gen_label("BLRST 5L-RKT 5L-949")
get_multi(partnums, 0.25)
#query_search("10GXS13", "Belden")

BIN
gs10030w64.exe Normal file

Binary file not shown.

View File

@ -1 +0,0 @@
<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>

File diff suppressed because one or more lines are too long

1
jukebox-web Submodule

Submodule jukebox-web added at 3781e9adcf

51
label_document.py Executable file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env python3
import fitz # PyMuPDF
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import os
def generate_pdf(path="labels"):
# Open the existing PDF
image_folder_path = path
doc = fitz.open('label_template.pdf')
page = doc[0] # Assuming you want to read from the first page
placeholders = []
for shape in page.get_drawings():
if shape['type'] == 's': # Checking for rectangle types
placeholders.append({
'x': shape['rect'].x0,
'y': shape['rect'].y0,
'width': shape['rect'].width,
'height': shape['rect'].height
})
# List all PNG images in the folder
image_files = [f for f in os.listdir(image_folder_path) if f.endswith('.png')]
image_paths = [os.path.join(image_folder_path, file) for file in image_files]
# Create a new PDF with ReportLab
c = canvas.Canvas(path + "/print.pdf", pagesize=letter)
current_placeholder = 0 # Track the current placeholder index
for image_path in image_paths:
if current_placeholder >= len(placeholders): # Check if a new page is needed
c.showPage()
current_placeholder = 0 # Reset placeholder index for new page
# Get current placeholder
placeholder = placeholders[current_placeholder]
# Place image at the placeholder position
c.drawImage(image_path, placeholder['x'], page.rect.height - placeholder['y'] - placeholder['height'], width=placeholder['width'], height=placeholder['height'])
current_placeholder += 1
# Save the final PDF
c.save()
# Close the original PDF
doc.close()
if __name__ == "__main__":
generate_pdf("labels")

109
label_generator.py Executable file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
from get_specs import get_multi
import sys
import uuid
import os
import signal
from PIL import Image
from label_image import generate_code
from label_document import generate_pdf
def input_cable():
print("")
print("Use the full part number. Spaces, special characters are allowed. Do not specify the brand.")
print("")
print("Please enter a part number and press enter:")
inputnum = input("").strip()
if len(inputnum) < 2:
killall_signal(0, 0)
print("Input part number:", inputnum)
print("Searching databases for cables...")
# Search both AW and BL sites
status, output = get_multi(["BL"+inputnum, "AW"+inputnum], delay=0.1, dir="temp/" + str(uuid.uuid4()) + "/", webport=":9000", cache=False)
print("")
if len(output) > 1:
for i in output:
print(i[1], i[0])
print("Multiple brands with the same part number! Please type \"b\" for the Belden part number or \"a\" for the Alphawire cable")
inputbrand = input()
if inputbrand == "b":
output = [output[0]]
elif inputbrand == "a":
output = [output[1]]
elif len(output) == 0:
print("No results found for part number", inputnum + ". Please try again with a different part number.")
return
output = output[0]
print("")
if output[2] and output[3]:
print("Cable result found -",output[1], output[0], "with high-quality image and full specs")
elif output[2]:
print("Cable result found -",output[1], output[0], "with high-quality image and no specs")
elif output[3]:
print("Cable result found -",output[1], output[0], "with no/low quality image and full specs")
else:
print("Cable result found -",output[1], output[0], "with no/low quality image and no specs")
print("")
if not output[3]:
print("Unable to decode cable specs. Please try again with a different part number.")
return False
else:
print("")
print("*** Cable details confirmed. Creating label...")
print("")
img = None
imgstr = ""
if output[1] == "Belden":
imgstr = "BL"
elif output[1] == "Alphawire":
imgstr = "AW"
gen_label(imgstr + output[0])
#img = generate_code(imgstr + output[0])
#os.makedirs("labels", exist_ok=True)
#img.save("labels/" + imgstr + output[0] + ".png")
def gen_label(partnum, path="labels"):
img = generate_code(partnum)
os.makedirs(path, exist_ok=True)
img.save(path + "/" + partnum + ".png")
generate_pdf(path)
def delete_folder(path):
# Check if the path is a directory
if not os.path.isdir(path):
return
# List all files and directories in the path
for filename in os.listdir(path):
file_path = os.path.join(path, filename)
# If it's a directory, recursively call this function
if os.path.isdir(file_path):
delete_folder(file_path)
else:
# If it's a file, remove it
os.remove(file_path)
# After removing all contents, remove the directory itself
os.rmdir(path)
def killall_signal(a,b):
delete_folder("temp")
os.kill(os.getpid(), 9) # dirty kill of self
if __name__ == "__main__":
signal.signal(signal.SIGINT, killall_signal)
signal.signal(signal.SIGTERM, killall_signal)
print("Welcome to the Jukebox cable utility. This tool will allow you to verify Belden & Alphawire cable part numbers and create labels for samples in the Jukebox.")
print("This tool requires internet access to download cable specifications and verify part numbers.")
#print("Use Ctrl+C to exit.")
while True:
delete_folder("temp")
input_cable()

320
label_image.py Executable file
View File

@ -0,0 +1,320 @@
#!/usr/bin/env python3
from util import fprint
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
#import cv2
import numpy as np
from util import find_data_file
import segno
import io
#import cairosvg
#import math
# Copied from http://en.wikipedia.org/wiki/Code_128
# Value Weights 128A 128B 128C
CODE128_CHART = """
0 212222 space space 00
1 222122 ! ! 01
2 222221 " " 02
3 121223 # # 03
4 121322 $ $ 04
5 131222 % % 05
6 122213 & & 06
7 122312 ' ' 07
8 132212 ( ( 08
9 221213 ) ) 09
10 221312 * * 10
11 231212 + + 11
12 112232 , , 12
13 122132 - - 13
14 122231 . . 14
15 113222 / / 15
16 123122 0 0 16
17 123221 1 1 17
18 223211 2 2 18
19 221132 3 3 19
20 221231 4 4 20
21 213212 5 5 21
22 223112 6 6 22
23 312131 7 7 23
24 311222 8 8 24
25 321122 9 9 25
26 321221 : : 26
27 312212 ; ; 27
28 322112 < < 28
29 322211 = = 29
30 212123 > > 30
31 212321 ? ? 31
32 232121 @ @ 32
33 111323 A A 33
34 131123 B B 34
35 131321 C C 35
36 112313 D D 36
37 132113 E E 37
38 132311 F F 38
39 211313 G G 39
40 231113 H H 40
41 231311 I I 41
42 112133 J J 42
43 112331 K K 43
44 132131 L L 44
45 113123 M M 45
46 113321 N N 46
47 133121 O O 47
48 313121 P P 48
49 211331 Q Q 49
50 231131 R R 50
51 213113 S S 51
52 213311 T T 52
53 213131 U U 53
54 311123 V V 54
55 311321 W W 55
56 331121 X X 56
57 312113 Y Y 57
58 312311 Z Z 58
59 332111 [ [ 59
60 314111 \ \ 60
61 221411 ] ] 61
62 431111 ^ ^ 62
63 111224 _ _ 63
64 111422 NUL ` 64
65 121124 SOH a 65
66 121421 STX b 66
67 141122 ETX c 67
68 141221 EOT d 68
69 112214 ENQ e 69
70 112412 ACK f 70
71 122114 BEL g 71
72 122411 BS h 72
73 142112 HT i 73
74 142211 LF j 74
75 241211 VT k 75
76 221114 FF l 76
77 413111 CR m 77
78 241112 SO n 78
79 134111 SI o 79
80 111242 DLE p 80
81 121142 DC1 q 81
82 121241 DC2 r 82
83 114212 DC3 s 83
84 124112 DC4 t 84
85 124211 NAK u 85
86 411212 SYN v 86
87 421112 ETB w 87
88 421211 CAN x 88
89 212141 EM y 89
90 214121 SUB z 90
91 412121 ESC { 91
92 111143 FS | 92
93 111341 GS } 93
94 131141 RS ~ 94
95 114113 US DEL 95
96 114311 FNC3 FNC3 96
97 411113 FNC2 FNC2 97
98 411311 ShiftB ShiftA 98
99 113141 CodeC CodeC 99
100 114131 CodeB FNC4 CodeB
101 311141 FNC4 CodeA CodeA
102 411131 FNC1 FNC1 FNC1
103 211412 StartA StartA StartA
104 211214 StartB StartB StartB
105 211232 StartC StartC StartC
106 2331112 Stop Stop Stop
""".split()
VALUES = [int(value) for value in CODE128_CHART[0::5]]
WEIGHTS = dict(zip(VALUES, CODE128_CHART[1::5]))
CODE128A = dict(zip(CODE128_CHART[2::5], VALUES))
CODE128B = dict(zip(CODE128_CHART[3::5], VALUES))
CODE128C = dict(zip(CODE128_CHART[4::5], VALUES))
for charset in (CODE128A, CODE128B):
charset[' '] = charset.pop('space')
def generate_code(data, show=False, check=False):
#img = code128_image(data)
img = qr_image(data)
if show:
img.show()
#img.show()
#print(data)
if(check):
from pyzbar.pyzbar import decode
from pyzbar.pyzbar import ZBarSymbol
print(decode(img, symbols=[ZBarSymbol.CODE128])[0].data.decode('ascii'))
#if(decode(img, symbols=[ZBarSymbol.CODE128])[0].data.decode('ascii') == data):
# return True
#else:
# return False
return img
def code128_format(data):
"""
Generate an optimal barcode from ASCII text
"""
text = str(data)
pos = 0
length = len(text)
# Start Code
if text[:2].isdigit():
charset = CODE128C
codes = [charset['StartC']]
else:
charset = CODE128B
codes = [charset['StartB']]
# Data
while pos < length:
if charset is CODE128C:
if text[pos:pos+2].isdigit() and length - pos > 1:
# Encode Code C two characters at a time
codes.append(int(text[pos:pos+2]))
pos += 2
else:
# Switch to Code B
codes.append(charset['CodeB'])
charset = CODE128B
elif text[pos:pos+4].isdigit() and length - pos >= 4:
# Switch to Code C
codes.append(charset['CodeC'])
charset = CODE128C
else:
# Encode Code B one character at a time
codes.append(charset[text[pos]])
pos += 1
# Checksum
checksum = 0
for weight, code in enumerate(codes):
checksum += max(weight, 1) * code
codes.append(checksum % 103)
# Stop Code
codes.append(charset['Stop'])
return codes
def code128_image(data, height=100, thickness=3, quiet_zone=False):
partnum = data
if not data[-1] == CODE128B['Stop']:
data = code128_format(data)
barcode_widths = []
for code in data:
for weight in WEIGHTS[code]:
barcode_widths.append(int(weight) * thickness)
width = sum(barcode_widths)
x = 0
if quiet_zone:
width += 20 * thickness
x = 10 * thickness
# Monochrome Image
img = Image.new('RGB', (int(width * 10), int(width * 10)), 'white')
draw = ImageDraw.Draw(img)
draw_bar = True
for bwidth in barcode_widths:
bwidth *= 4
if draw_bar:
draw.rectangle(((x + int(width * 3), width*6.25), (x + int(width * 3) + bwidth - 1, width*7)), fill='black')
draw_bar = not draw_bar
x += bwidth
#draw.arc(((width - width/5, width - width/5), (width*9 + width/5, width*9 + width/5)),0,360,fill='blue', width = int(width/8))
draw.arc(((width+int(width / 1.4), width+int(width / 1.4)), (width*9-int(width / 1.4), width*9-int(width / 1.4))),0,360,fill='blue', width = int(width/8))
font_path = find_data_file("OCRAEXT.TTF")
font_size = width/2
font = ImageFont.truetype(font_path, font_size)
text_width = font.getlength(partnum)
while text_width > width*4:
font_size -= 1
font = ImageFont.truetype(font_path, font_size)
text_width = font.getlength(partnum)
txtx = (int(width * 10) - text_width) / 2
txty = (int(width * 10)) / 2 + width / 2
draw.text((txtx,txty),partnum, "black", font)
return img
def qr_image(data, width=600):
partnum = data
# Monochrome Image
img = Image.new('RGB', (int(width * 10), int(width * 10)), 'white')
draw = ImageDraw.Draw(img)
#svg_path = find_data_file("belden-logo.svg")
#with open(svg_path, 'rb') as svg_file:
# png_image = cairosvg.svg2png(file_obj=svg_file,dpi=width*30, scale=30, background_color="white")
#with open("output.png", 'wb') as file:
# file.write(png_image)
png_image_io = "belden-logo-superhires.png"
png_image_pillow = Image.open(png_image_io)
png_width, png_height = png_image_pillow.size
png_image_pillow = png_image_pillow.resize((int(width*4), int(width*4/png_width*png_height)))
png_width, png_height = png_image_pillow.size
# paste belden logo first because it has a big border that would cover stuff up
img.paste(png_image_pillow, (int(width*5-png_width/2), int(width*3.25 - png_height/2)))
# draw circle border
#draw.arc(((width - width/5, width - width/5), (width*9 + width/5, width*9 + width/5)),0,360,fill='blue', width = int(width/8))
draw.arc(((width+int(width / 1.4), width+int(width / 1.4)), (width*9-int(width / 1.4), width*9-int(width / 1.4))),0,360,fill=(0, 73,144), width = int(width/8))
font_path = find_data_file("GothamCond-Medium.otf")
font_size = width/2
font = ImageFont.truetype(font_path, font_size)
text_width = font.getlength(partnum[2:])
# shrink font dynamically if it's too long of a name
while text_width > width*4:
font_size -= 1
font = ImageFont.truetype(font_path, font_size)
text_width = font.getlength(partnum[2:])
txtx = (int(width * 10) - text_width) / 2
txty = (int(width * 7.5)) / 2
# draw part number text
draw.text((txtx,txty),partnum[2:], "black", font)
# Draw QR code
partnum = partnum.replace(" ", "%20")
qrcode = segno.make('HTTPS://BLDN.APP/' + partnum,micro=False,boost_error=False,error="L",mask=3)
out = io.BytesIO()
qrx, _ = qrcode.symbol_size(1,0)
qrcode.save(out, scale=width*3/qrx, kind="PNG", border=0)
qrimg = Image.open(out)
img.paste(qrimg, box=(int(width*3.5),int(width*4.5)))
img = img.crop((width+int(width / 1.4)-int(width/8),width+int(width / 1.4)-int(width/8),img.size[0] - (width+int(width / 1.4)-int(width/8)), img.size[1] - (width+int(width / 1.4)-int(width/8)) ))
img = img.resize((1200, 1200), Image.LANCZOS) # 1200 dpi
return img
if __name__ == "__main__":
#print(generate_code("BL10GXS13"))
#print(generate_code("BL10GXgd35j35S13"))
#print(generate_code("BL10GX54hS13"))
#print(generate_code("BL10Gj34qXS13", False, False))
#print(generate_code("BL104w5545dp7bfwp43643534/4563G-XS13"))
#adjust_image(cv2.imread('test_skew.jpg'))
path = "labels"
img = generate_code("BL10GXS13")
import os
os.makedirs(path, exist_ok=True)
img.save(path + "/" + "BL10GXS13" + ".png")

2064
label_template.pdf Normal file

File diff suppressed because one or more lines are too long

View File

@ -14,24 +14,33 @@ 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):
class LEDSystem():
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()
solid_controllers = None
def __init__(self):
self.start = uptime()
#self.init()
#return self
def ping(self, host):
#Returns True if host (str) responds to a ping request.
# Option for the number of packets as a function of
@ -49,32 +58,25 @@ def ping(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
def map(self):
with open('config.yml', 'r') as fileread:
#global config
config = yaml.safe_load(fileread)
self.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"])))
self.animation_time = self.config["animation_time"]
self.leds = list()
self.leds_size = list()
self.controllers = list()
self.solid_controllers = list()
self.rings = list(range(len(self.config["position_map"])))
print("Setting ring status")
self.ringstatus = list(range(len(self.config["position_map"])))
#print(rings)
#fprint(config["led"]["map"])
generate_map = False
map = list()
for shape in config["led"]["map"]:
for shape in self.config["led"]["map"]:
if shape["type"] == "circle":
if generate_map:
@ -84,24 +86,24 @@ def map():
angle = 0
radius = shape["diameter"] / 2
lednum = shape["start"]
for item in config['position_map']:
for item in self.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]
self.rings[item["index"]] = (shape["pos"][1],shape["pos"][0],lednum,lednum+shape["size"]) # rings[index] = x, y, startpos, endpos
self.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:
if len(self.leds) < lednum + shape["size"]:
for x in range(lednum + shape["size"] - len(self.leds)):
self.leds.append(None)
self.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)
self.leds[lednum] = (x,y)
lednum = lednum + 1
angle = angle + anglediv
angle = angle - anglediv
elif shape["type"] == "strip":
angle = shape["angle"]
@ -112,13 +114,13 @@ def map():
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)
if len(self.leds) < lednum + shape["size"]:
for x in range(lednum + shape["size"] - len(self.leds)):
self.leds.append(None)
self.leds_size.append(None)
while dist < length:
leds[lednum] = (pos[0], pos[1])
self.leds[lednum] = (pos[0], pos[1])
pos[0] += xmov
pos[1] += ymov
dist += distdiv
@ -140,276 +142,356 @@ def map():
yaml_str = yaml.dump(data, default_flow_style=False)
print(yaml_str)
print(rings)
print(self.rings)
flag = 0
for x in leds:
for x in self.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)
for x in range(len(self.leds)):
if self.leds[x] is None:
self.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)
for ctrl in self.config["led"]["controllers"]:
if ctrl["mode"] == "solid":
self.solid_controllers.append((ctrl["universe"]-1, ctrl["color"], ctrl["ip"]))
else:
if len(self.controllers) < ctrl["universe"]:
print(ctrl["universe"])
for x in range(ctrl["universe"] - len(self.controllers)):
self.controllers.append(None)
controllers[ctrl["universe"]] = (ctrl["ledstart"],ctrl["ledend"]+1,ctrl["ip"])
self.controllers[ctrl["universe"]-1] = (ctrl["ledstart"],ctrl["ledend"]+1,ctrl["ip"])
for x in range(ctrl["ledstart"],ctrl["ledend"]+1):
leds_size[x] = len(ctrl["mode"])
self.leds_size[x] = len(ctrl["mode"])
#fprint(controllers)
if(debug):
if(self.debug):
import matplotlib.pyplot as plt
plt.axis('equal')
for ctrl in controllers:
plt.scatter(*zip(*leds[ctrl[0]:ctrl[1]]), s=2)
for ctrl in self.controllers:
plt.scatter(*zip(*self.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_adj = [(x-min([led[0] for led in self.leds]), # push to zero start
y-min([led[1] for led in self.leds]) )
for x, y in self.leds]
leds_normalized = [(x / max([led[0] for led in leds_adj]),
self.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="")
def init(self):
self.map()
self.sender = sacn.sACNsender(fps=self.config["led"]["fps"], universeDiscovery=False)
self.sender.start() # start the sending thread
for x in range(len(self.controllers)):
print("Waiting for the controller at", self.controllers[x][2], "to be online...", end="", flush=True)
count = 0
while not ping(controllers[x][2]):
while not self.ping(self.controllers[x][2]):
count = count + 1
if count >= config["led"]["timeout"]:
fprint(" ERROR: controller still offline after " + str(count) + " seconds, continuing...")
if count >= self.config["led"]["timeout"]:
print(" 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
else:
print(" done")
#if count < self.config["led"]["timeout"]:
for x in range(len(self.solid_controllers)):
print("Waiting for the controller at", self.solid_controllers[x][2], "to be online...", end="", flush=True)
count = 0
while not self.ping(self.solid_controllers[x][2]):
count = count + 1
if count >= self.config["led"]["timeout"]:
print(" ERROR: controller still offline after " + str(count) + " seconds, continuing...")
break
else:
print(" done")
time.sleep(1)
for x in range(len(self.controllers)):
print("Activating controller", x+1, "at", self.controllers[x][2], "with", self.controllers[x][1]-self.controllers[x][0], "LEDs.")
self.sender.activate_output(x+1) # start sending out data
self.sender[x+1].destination = self.controllers[x][2]
for x in range(len(self.solid_controllers)):
ctrl = self.solid_controllers[x]
print("Activating controller", ctrl[0], "at", ctrl[2], "with solid color ", ctrl[1])
self.sender.activate_output(ctrl[0]+1) # start sending out data
self.sender[ctrl[0]+1].destination = ctrl[2]
self.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))
self.data = list()
self.exactdata = list()
for x in range(len(self.leds)):
if self.leds_size[x] == 3:
self.exactdata.append(None)
self.data.append((0,0,127))
elif self.leds_size[x] == 4:
self.exactdata.append(None)
self.data.append((50,50,255,0))
else:
exactdata.append(None)
data.append((0,0,0))
sendall(data)
self.exactdata.append(None)
self.data.append((0,0,0))
self.sendall(self.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()
# fprint("Running start-up test sequence...")
# for y in range(1):
# for x in range(len(self.leds)):
# self.setpixel(0,60,144,x)
# self.sendall(self.data)
# #time.sleep(2)
# self.alloffsmooth()
self.startup_animation(show=False)
def sendall(datain):
def sendall(self, 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
self.sender.manual_flush = True
for x in range(len(self.controllers)):
self.sender[x+1].dmx_data = list(sum(datain[self.controllers[x][0]:self.controllers[x][1]] , ())) # flatten the subsection of the data array
sender.flush()
offset = len(self.controllers)
for x in range(len(self.solid_controllers)):
ctrl = self.solid_controllers[x]
self.sender[ctrl[0]+1].dmx_data = list(ctrl[1]) * 170
self.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):
def fastsendall(self, 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
self.sender.manual_flush = True
#print(datain[self.controllers[0][0]:self.controllers[0][1]])
for x in range(len(self.controllers)):
self.sender[x+1].dmx_data = list(sum(datain[self.controllers[x][0]:self.controllers[x][1]] , ())) # flatten the subsection of the data array
sender.flush()
self.sender.flush()
def senduniverse(datain, lednum):
def senduniverse(self, 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
for x in range(len(self.controllers)):
if lednum >= self.controllers[x][0] and lednum < self.controllers[x][1]:
self.sender[x+1].dmx_data = list(sum(datain[self.controllers[x][0]:self.controllers[x][1]] , ())) # flatten the subsection of the data array
sender.flush()
self.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():
def alloff(self):
tmpdata = list()
for x in range(len(leds)):
if leds_size[x] == 3:
for x in range(len(self.leds)):
if self.leds_size[x] == 3:
tmpdata.append((0,0,0))
elif leds_size[x] == 4:
elif self.leds_size[x] == 4:
tmpdata.append((0,0,0,0))
else:
tmpdata.append((0,0,0))
sendall(tmpdata)
self.sendall(tmpdata)
#sendall(tmpdata)
#sendall(tmpdata) #definitely make sure it's off
return self
def allon():
global data
sendall(data)
def allon(self):
self.sendall(self.data)
return self
def alloffsmooth():
tmpdata = data
def alloffsmooth(self):
tmpdata = self.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)
for x in range(len(self.data)):
self.setpixel(tmpdata[x][0]-1,tmpdata[x][1]-1,tmpdata[x][2]-1, x)
self.sendall(tmpdata)
alloff()
self.alloff()
return self
def setpixelnow(r, g, b, num):
def setpixelnow(self, 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)
self.setpixel(r,g,b,num)
self.senduniverse(self.data, num)
return self
def setmode(stmode, r=0,g=0,b=0):
global mode
global firstrun
def setmode(self, stmode, r=0,g=0,b=0):
if stmode is not None:
if mode != stmode:
firstrun = True
if self.mode != stmode:
self.firstrun = True
mode = stmode
self.mode = stmode
return self
def setrange(self, start, end, r,g,b):
val = (r,g,b)
for x in range(start,end):
self.data[x] = val
def setallringsexcept(self, r,g,b, exclude):
startidx1 = 0
endidx2 = len(self.leds)
endidx1 = self.rings[exclude][2]
startidx2 = self.rings[exclude][3]+1
self.setrange(startidx1, endidx1, r,g,b)
self.setrange(startidx2, endidx2, r,g,b)
def setring(r,g,b,idx):
ring = rings[idx]
def setring(self, r,g,b,idx):
ring = self.rings[idx]
for pixel in range(ring[2],ring[3]):
setpixel(r,g,b,pixel)
self.setpixel(r,g,b,pixel)
#global data
#senduniverse(data, ring[2])
return self
def runmodes(ring = -1, speed = 1):
global mode
global firstrun
global changecount
fprint("Mode: " + str(mode))
if mode == "Startup":
def runmodes(self, ring = -1, arm_position = None):
#fprint("Mode: " + str(self.mode))
if self.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 self.firstrun:
self.changecount = self.animation_time * 2
self.firstrun = False
for x in range(len(self.ringstatus)):
self.ringstatus[x] = [True, self.animation_time]
if changecount > 0:
fprint(changecount)
changecount = fadeorder(0,len(leds), changecount, 0,50,100)
if self.changecount > 0:
#fprint(self.changecount)
self.changecount = self.fadeorder(0,len(self.leds), self.changecount, 0,50,100)
else:
setmode("Startup2")
self.setmode("Startup2")
elif mode == "Startup2":
if firstrun:
firstrun = False
elif self.mode == "Startup2":
if self.firstrun:
self.firstrun = False
else:
for x in range(len(ringstatus)):
if ringstatus[x][0]:
setring(0, 50, 100, x)
for x in range(len(self.ringstatus)):
if self.ringstatus[x][0]:
self.setring(0, 50, 100, x)
else:
ringstatus[x][1] = fadeall(rings[x][2],rings[x][3], ringstatus[x][1], 100,0,0) # not ready
#self.ringstatus[x][1] = self.fadeall(self.rings[x][2],self.rings[x][3], self.ringstatus[x][1], 100,0,0) # not ready
self.ringstatus[x][1] = self.fadeall(self.rings[x][2],self.rings[x][3], self.ringstatus[x][1], 0,50,100)
elif mode == "StartupCheck":
if firstrun:
firstrun = False
for x in range(len(ringstatus)):
ringstatus[x] = [False, animation_time]
elif self.mode == "StartupCheck":
if self.firstrun:
self.firstrun = False
for x in range(len(self.ringstatus)):
self.ringstatus[x] = [False, self.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
for x in range(len(self.ringstatus)):
if self.ringstatus[x][0]:
self.ringstatus[x][1] = self.fadeall(self.rings[x][2],self.rings[x][3], self.ringstatus[x][1], 0,50,100) # ready
else:
setring(100, 0, 0, x)
self.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)
elif self.mode == "GrabA":
if self.firstrun:
self.firstrun = False
self.changecount = self.animation_time # 100hz
if self.changecount > 0:
self.changecount = self.fadeall(self.rings[ring][2],self.rings[ring][3], self.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)
self.setring(100,0,0,ring)
self.setmode("GrabB")
elif self.mode == "GrabB":
if self.firstrun:
self.firstrun = False
self.changecount = self.animation_time # 100hz
if self.changecount > 0:
#self.changecount = self.fadeorder(self.rings[ring][2],self.rings[ring][3], self.changecount, 0,100,0)
self.changecount = self.fadeorder(self.rings[ring][2],self.rings[ring][2]+24, self.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)
self.setring(0,100,0,ring)
self.setmode("Idle")
elif self.mode == "GrabC":
if self.firstrun:
self.firstrun = False
self.changecount = self.animation_time # 100hz
if self.changecount > 0:
self.changecount = self.fadeall(self.rings[ring][2],self.rings[ring][3], self.changecount, 0,50,100)
else:
setring(0,50,100,ring)
setmode("idle")
elif mode == "idle":
time.sleep(0)
self.setring(0,50,100,ring)
self.setmode("Idle")
sendall(data)
elif self.mode == "GrabAA":
print("GrabAA")
if self.firstrun:
self.firstrun = False
self.changecount = self.animation_time # 100hz
if self.changecount > 0:
self.changecount = self.fadeall(self.rings[ring][2],self.rings[ring][3], self.changecount, 100,0,0)
else:
self.setring(100,0,0,ring)
self.setmode("Idle")
elif self.mode == "GrabAB":
if self.firstrun:
self.firstrun = False
self.changecount = self.animation_time # 100hz
if self.changecount > 0:
#self.changecount = self.fadeorder(self.rings[ring][2],self.rings[ring][3], self.changecount, 0,100,0)
self.changecount = self.fadeorder(self.rings[ring][2],self.rings[ring][2]+24, self.changecount, 0,100,0)
else:
self.setring(0,100,0,ring)
self.setmode("Idle")
def fadeall(idxa,idxb,sizerem,r,g,b):
elif self.mode == "Idle":
if self.firstrun:
self.firstrun = False
if arm_position is not None:
posxy = list()
posxy.append(int(arm_position[0] * 1000)) # x convert m to mm
posxy.append(int(arm_position[1] * 1000)) # y
radius = int(arm_position[2] * 1000) / 3
base = (0,50,100)
target = (100,100,100)
deltar = target[0] - base[0]
deltag = target[0] - base[0]
#deltab = target[0] - base[0]
# reset!
self.setallringsexcept(0,50,100, ring)
exclude = self.rings[ring][2],self.rings[ring][3]
# fade outwards
for idx,led in enumerate(self.leds):
if posxy[0] - led[0] < radius and posxy[1] - led[1] < radius and idx < exclude[0] or idx > exclude[1]:
dist = math.isqrt(int(math.pow(int(posxy[0] - led[0]), 2) + math.pow(int(posxy[1] - led[1]), 2)))
#dist = max(posxy[0] - led[0], posxy[1] - led[1])
if dist < radius:
ratio = 1 - dist/radius
#if ratio < 0:
# ratio = 0
self.data[idx] = (int(base[0] + ratio * deltar), int(base[1] + ratio * deltag), 100) #base[2] + ratio * deltab)
else:
#print("No arm position!")
pass
self.sendall(self.data)
return self
def fadeall(self, 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]
if self.exactdata[x] is None:
self.exactdata[x] = self.data[x]
old = self.exactdata[x]
dr = (r - old[0])/sizerem
sum += abs(dr)
dr += old[0]
@ -419,27 +501,26 @@ def fadeall(idxa,idxb,sizerem,r,g,b):
db = (b - old[2])/sizerem
db += old[2]
sum += abs(db)
exactdata[x] = (dr, dg, db)
self.exactdata[x] = (dr, dg, db)
#print(new)
setpixel(dr, dg, db, x)
self.setpixel(dr, dg, db, x)
if sizerem == 1:
exactdata[x] = None
self.exactdata[x] = None
if sum == 0 and sizerem > 2:
sizerem = 2
return sizerem - 1
def fadeorder(idxa,idxb,sizerem,r,g,b):
def fadeorder(self, 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]
if self.exactdata[x] is None:
self.exactdata[x] = self.data[x]
old = self.exactdata[x]
dr = (r - old[0])
dg = (g - old[1])
db = (b - old[2])
@ -451,9 +532,9 @@ def fadeorder(idxa,idxb,sizerem,r,g,b):
dgs /= sizerem
dbs /= sizerem
sum += abs(drs) + abs(dgs) + abs(dbs)
print(drs,dgs,dbs)
#print(drs,dgs,dbs)
for x in range(idxa,idxb):
old = exactdata[x]
old = self.exactdata[x]
new = list(old)
if drs > 0:
if old[0] + drs > r:
@ -500,20 +581,18 @@ def fadeorder(idxa,idxb,sizerem,r,g,b):
dbs = 0
if drs != 0 or dgs != 0 or dbs != 0:
exactdata[x] = new
setpixel(new[0],new[1],new[2],x)
self.exactdata[x] = new
self.setpixel(new[0],new[1],new[2],x)
if sizerem == 1:
exactdata[x] = None
self.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
def setpixel(self, r, g, b, num):
# constrain values
if r < 0:
r = 0
@ -528,29 +607,37 @@ def setpixel(r, g, b, num):
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))) )
if self.leds_size[num] == 3:
self.data[num] = (int(r), int(g), int(b))
elif self.leds_size[num] == 4: # cut out matching white and turn on white pixel instead
self.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))
self.data[num] = (int(r), int(g), int(b))
return self
def setpixelfast3(self, rgb, num):
self.data[num] = rgb
def close():
global sender
def close(self):
time.sleep(0.5)
sender.stop()
self.sender.stop()
return self
def mapimage(image, fps=90):
global start
while uptime() - start < 1/fps:
time.sleep(0.00001)
fprint(1 / (uptime() - start))
start = uptime()
def mapimage(self, image, fps=30):
#fprint(1 / (uptime() - self.start))
self.start = uptime()
minsize = min(image.shape[0:2])
leds_normalized2 = [(x * minsize,
if image.shape[1] > image.shape[0]:
offset = (image.shape[1] - image.shape[0])/2
leds_normalized2 = [(x * minsize + offset,
y * minsize)
for x, y in leds_normalized]
for x, y in self.leds_normalized]
else:
offset = (image.shape[0] - image.shape[1])/2
leds_normalized2 = [(x * minsize,
y * minsize + offset)
for x, y in self.leds_normalized]
cv2.imshow("video", image)
cv2.waitKey(1)
@ -562,44 +649,49 @@ def mapimage(image, fps=90):
for xx in range(len(leds_normalized2)):
led = leds_normalized2[xx]
x, y = int(round(led[0])), int(round(led[1]))
yinv = image.shape[0]-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
color = tuple(image[yinv-y, x])
self.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)
self.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:
self.fastsendall(self.data)
while self.start + 1.0/fps > uptime():
time.sleep(0.00001)
fprint(1 / (uptime() - start))
start = uptime()
if mode is not None:
setmode(stmode)
runmodes(ring)
if preview:
drawdata()
return self
def drawdata():
def mainloop(self, stmode, ring = -1, fps = 100, preview = False, arm_position = None):
while uptime() - self.start < 1/fps:
time.sleep(0.00001)
#fprint("Running LED loop with ring " + str(ring) + " and set mode " + str(stmode))
#fprint(1 / (uptime() - self.start))
self.start = uptime()
if self.mode is not None:
self.setmode(stmode)
#if self.
self.runmodes(ring, arm_position)
if preview:
self.drawdata()
return self
def drawdata(self):
#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
x = [led[0] for led in self.leds]
y = [led[1] for led in self.leds]
colors = self.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)
@ -612,67 +704,77 @@ def drawdata():
plt.show()
plt.savefig("map3.png", dpi=50, bbox_inches="tight")
plt.clf()
return self
def startup_animation(show):
def startup_animation(self, show):
stmode = "Startup"
mainloop(stmode, preview=show)
while mode == "Startup":
mainloop(None, preview=show)
self.mainloop(stmode, preview=show)
while self.mode == "Startup":
self.mainloop(None, preview=show)
for x in range(54):
ringstatus[x][0] = False
mainloop(None, preview=show)
self.ringstatus[x][0] = False
self.mainloop(None, preview=show)
for x in range(animation_time):
mainloop(None, preview=show)
clear_animations()
stmode = "StartupCheck"
mainloop(stmode, preview=show)
clear_animations()
for x in range(self.animation_time):
self.mainloop(None, preview=show)
self.clear_animations()
stmode = "Idle"
self.mainloop(stmode, preview=show)
self.clear_animations()
return self
def clear_animations():
for x in range(len(leds)):
exactdata[x] = None
def clear_animations(self):
for x in range(len(self.leds)):
self.exactdata[x] = None
return self
def do_animation(stmode, ring=-1):
mainloop(stmode, ring, preview=show)
wait_for_animation(ring)
def do_animation(self, stmode, ring=-1):
self.mainloop(stmode, ring, preview=show)
self.wait_for_animation(ring)
return self
def start_animation(stmode, ring=-1):
mainloop(stmode, ring, preview=show)
def start_animation(self, stmode, ring=-1):
self.mainloop(stmode, ring, preview=show)
return self
def wait_for_animation(ring=-1):
while mode != "idle":
mainloop(None, ring, preview=show)
def wait_for_animation(self, ring=-1):
while self.mode != "Idle":
self.mainloop(None, ring, preview=show)
return self
if __name__ == "__main__":
init()
import matplotlib.pyplot as plt
"""cap = cv2.VideoCapture('badapple.mp4')
ledsys = LEDSystem()
ledsys.init()
cap = cv2.VideoCapture('output.mp4')
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
mapimage(frame, fps=30)"""
show = True
ledsys.mapimage(frame, fps=60)
show = False
ring = 1
startup_animation(show)
ledsys.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)
ledsys.ringstatus[x][0] = True
ledsys.mainloop(None, preview=show)
for x in range(ledsys.animation_time):
ledsys.mainloop(None, preview=show)
do_animation("GrabA", 1)
ledsys.do_animation("GrabA", 1)
do_animation("GrabA", 5)
start_animation("GrabC", 1)
ledsys.do_animation("GrabA", 5)
ledsys.start_animation("GrabC", 1)
wait_for_animation(1)
do_animation("GrabC", 5)
ledsys.wait_for_animation(1)
ledsys.do_animation("GrabC", 5)
close()
ledsys.close()
#sys.exit(0)

BIN
map.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 340 KiB

BIN
map3.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -8,13 +8,18 @@ from util import fprint
class qr_reader():
camera = None
def __init__(self, ip, port):
self.camera = banner_ivu_export.DriveImg(ip, port)
self.ip = ip
self.port = port
#self.camera = banner_ivu_export.DriveImg(ip, port)
def read_qr(self, tries=1):
print("Trying " + str(tries) + " frames.")
self.camera = banner_ivu_export.DriveImg(self.ip, self.port)
for x in range(tries):
try:
print(str(x) + " ", end="", flush=True)
imgtype, img = self.camera.read_img()
if img is not None:
#fprint(imgtype)
image_array = np.frombuffer(img, np.uint8)
img = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
@ -22,9 +27,13 @@ class qr_reader():
#cv2.waitKey(1)
detect = cv2.QRCodeDetector()
value, points, straight_qrcode = detect.detectAndDecode(img)
if value != "":
self.camera.close()
return value
except:
continue
self.camera.close()
return False
@ -41,5 +50,7 @@ class video_streamer():
if __name__ == "__main__":
test = qr_reader("192.168.1.125", 32200)
import time
while True:
fprint(test.read_qr(5))
fprint(test.read_qr(300))
time.sleep(1)

View File

@ -1,9 +1,8 @@
#!/usr/bin/env python3
# Parse Belden catalog techdata datasheets
# Parse Belden (100%) & Alphawire (75%) catalog techdata datasheets
import pandas as pd
pd.set_option('future.no_silent_downcasting', True)
from PyPDF2 import PdfReader
import camelot
import numpy as np
@ -13,55 +12,148 @@ import json
from util import fprint
import uuid
from util import run_cmd
from util import win32
import os
import glob
import sys
from PIL import Image
def touch(path):
with open(path, 'a'):
os.utime(path, None)
def parse(filename, output_dir, partnum, dstype):
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 extract_table_name(table_start, searchpage, reader, dstype, fallbackname):
if dstype == "Belden":
ymin = table_start
ymax = table_start + 10
elif dstype == "Alphawire":
ymin = table_start - 5
ymax = table_start + 20
page = reader.pages[searchpage - 1]
parts = []
def visitor_body(text, cm, tm, fontDict, fontSize):
y = tm[5]
if y > ymin and y < ymax:
parts.append(text)
page.extract_text(visitor_text=visitor_body)
text_body = "".join(parts).strip('\n')
if len(text_body) == 0:
text_body = str(fallbackname)
return text_body
#fprint(text_body)
def find_file_noext(directory, prefix="part-hires"):
"""
Find files in the specified directory that start with the given prefix and have any extension.
:param directory: The directory to search in.
:param prefix: The prefix to search for.
:return: A list of matching file names.
"""
# Get all files and directories in the specified directory
entries = os.listdir(directory)
# Filter files that match 'filename.EXTENSION'
matching_files = [file for file in entries if os.path.isfile(os.path.join(directory, file)) and file.split('.')[0] == prefix and len(file.split('.')) == 2]
#print(directory, matching_files)
return matching_files
def rotate_and_crop_image(path, image_name, force_rotate=False, partnum=""):
# Open the image file
fprint("Generating thumbnail image for part " + partnum)
image_path = path + "/" + image_name
with Image.open(image_path) as img:
# Check if the image is wider than it is tall
if force_rotate or img.width > img.height * 1.2:
# Rotate the image by 90 degrees counter-clockwise
img = img.rotate(90, expand=True)
# Determine the size of the square (the length of the shorter side of the image)
square_size = min(img.width, img.height)
if img.height < img.width:
offset = (img.width - img.height)/2
img_cropped = img.crop((offset, 0, square_size+offset, square_size))
else:
# Crop the image to a square from the top
img_cropped = img.crop((0, 0, square_size, square_size))
# Save or display the image
img_cropped.save(path + "/" + "thumbnail-" + image_name) # Save the cropped image
def parse(filename, output_dir, partnum, dstype, weburl, extra):
tables = []
# 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'])
try:
if dstype == "Belden":
tables = camelot.read_pdf(filename, pages="1-end", flavor='lattice', backend="ghostscript", 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'])
elif dstype == "Alphawire":
tables = camelot.read_pdf(filename, pages="1-end", flavor='lattice', backend="ghostscript", split_text=False, line_scale=50, process_background=True, resolution=600, interations=1, layout_kwargs={'detect_vertical': True, 'char_margin': 0.5}, shift_text=['l', 'b'])
except (OSError, RuntimeError) as e:
print(e)
if win32:
print("Ghostscript is not installed! Launching installer...")
#subprocess.run([r".\\gs10030w64.exe"])
os.system(r'''Powershell -Command "& { Start-Process \"''' + find_data_file("gs10030w64.exe") + r'''\" -Verb RunAs } " ''')
# Will return once file launched...
print("Once the install is completed, try again.")
return False
else:
print("Ghostscript is not installed. You can install it with e.g. apt install ghostscript for Debian-based systems.")
return False
#fprint("Total tables extracted:", tables.n)
n = 0
pagenum = 0
#pagenum = 0
reader = PdfReader(filename)
page = reader.pages[0]
table_list = {}
table_list_raw = {}
pd.set_option('future.no_silent_downcasting', True)
for table in tables:
#with pd.options.context("future.no_silent_downcasting", True):
table.df.infer_objects(copy=False)
table.df.replace('', np.nan, inplace=True)
table.df = table.df.replace('', np.nan).infer_objects(copy=False)
table.df.dropna(inplace=True, how="all")
table.df.dropna(inplace=True, axis="columns", how="all")
table.df.replace(np.nan, '', inplace=True)
table.df = table.df.replace(np.nan, '').infer_objects(copy=False)
if not table.df.empty:
#fprint("\nTable " + str(n))
# Extract table names
table_start = table.cells[0][0].lt[1] # Read top-left cell's top-left coordinate
#fprint(table_start)
ymin = table_start
ymax = table_start + 10
if pagenum != table.page - 1:
pagenum = table.page - 1
page = reader.pages[table.page - 1]
parts = []
def visitor_body(text, cm, tm, fontDict, fontSize):
y = tm[5]
if y > ymin and y < ymax:
parts.append(text)
page.extract_text(visitor_text=visitor_body)
text_body = "".join(parts).strip('\n')
if len(text_body) == 0:
text_body = str(n)
#fprint(text_body)
text_body = extract_table_name(table_start, table.page, reader, dstype, n)
#print(text_body)
table_list[text_body] = table.df
#print(table_list[text_body])
if dstype == "Alphawire":
def reorder_row(row):
# Filter out NaNs and compute the original non-NaN values
non_nans = row[~row.isnull()]
# Create a new row with NaNs filled at the end
new_row = pd.Series(index=row.index)
new_row[:len(non_nans)] = non_nans
return new_row
# Apply the function to each row and return a new DataFrame
#table_list[text_body] = table.df.apply(reorder_row, axis=1)
#print(table_list[text_body])
table_list_raw[text_body] = table
#print(tbl)
#table.to_html("table" + str(n) + ".html")
#fprint(table.df)
@ -71,7 +163,7 @@ def parse(filename, output_dir, partnum, dstype):
#tables.export(output_dir + '/techdata.json', f='json')
# fprint(table_list)
#fprint(table_list)
# Extract Basic details - part name & description, image, etc
reader = PdfReader(filename)
@ -96,28 +188,46 @@ def parse(filename, output_dir, partnum, dstype):
fp.write(image_file_object.data)
count += 1
if os.path.exists(output_dir + "/found_part_hires"):
rotate_and_crop_image(output_dir, find_file_noext(output_dir, prefix="part-hires")[0], force_rotate=(dstype == "Alphawire"), partnum=partnum)
img = weburl + find_file_noext(output_dir, prefix="thumbnail-part-hires")[0]
elif len(find_file_noext(output_dir, prefix="part")) > 0:
rotate_and_crop_image(output_dir, find_file_noext(output_dir, prefix="part")[0], force_rotate=(dstype == "Alphawire"), partnum=partnum)
img = weburl + find_file_noext(output_dir, prefix="thumbnail-part")[0]
else:
img = None
# Table parsing and reordring
tables = dict()
torename = dict()
previous_table = ""
#print(table_list.keys())
for table_name in table_list.keys():
# determine shape: horizontal or vertical
#print(table_name)
table = table_list[table_name]
rows = table.shape[0]
cols = table.shape[1]
vertical = None
#print(rows, cols, table_name)
if rows > 2 and cols == 2:
vertical = True
elif cols == 1:
elif cols == 1 and rows > 1:
vertical = False
elif rows == 1:
vertical = True
elif cols == 2: # and rows <= 2
# inconsistent
if dstype == "Belden":
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 dstype == "Alphawire":
if table.iloc[0, 0].find(")") == 1 or table.iloc[0, 0].find(")") == 2 or 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
@ -125,19 +235,26 @@ def parse(filename, output_dir, partnum, dstype):
vertical = False
else: # 1 column, <= 2 rows
vertical = False
#print(vertical)
# 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 dstype == "Alphawire" and table_name_2.find("\n") >= 0:
torename[table_name_2] = table_name_2[0:table_name_2.find("\n")]
# if dstype == "Alphawire" and 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()
if rows > 1:
for row in table.itertuples(index=False, name=None):
out[row[0].replace("\n", " ").replace(":", "")] = row[1]
else:
for row in table.itertuples(index=False, name=None):
out[row[0].replace("\n", " ").replace(":", "")] = ""
else: # horizontal
out = dict()
@ -146,18 +263,58 @@ def parse(filename, output_dir, partnum, dstype):
out[col_data[0].replace("\n", " ")] = col_data[1:]
tables[table_name] = out
#print(out)
# multi-page table check, Alphawire
if dstype == "Alphawire" and table_name.isdigit() and previous_table != "":
# table continues from previous page or has name on previous page
thistbl = table_list_raw[table_name]
prevtbl = table_list_raw[previous_table]
if prevtbl.cells[-1][0].lb[1] < 50 and thistbl.cells[0][0].lt[1] > 600:
# wraparound
#print("WRAP")
#print("PREV TABLE", prevtbl.df)
#print("THIS TABLE", thistbl.df)
#print("PREV TABLE CORNER", prevtbl.cells[-1][0].lb[1])
#print("THIS TABLE CORNER", thistbl.cells[0][0].lt[1])
main_key = previous_table
cont_key = table_name
#print(vertical)
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):
#print(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:
#print(tables[cont_key].keys())
for key in tables[cont_key].keys():
#print(main_key, key, cont_key, key)
tables[main_key][key] = tables[cont_key][key]
del tables[table_name]
elif thistbl.cells[0][0].lt[1] > 600:
# name on previous page (grrrr)
#print("NAMEABOVE")
#print("PREV TABLE", prevtbl.df)
#print("THIS TABLE", thistbl.df)
#print("PREV TABLE CORNER", prevtbl.cells[-1][0].lb[1])
#print("THIS TABLE CORNER", thistbl.cells[0][0].lt[1])
name = extract_table_name(50, prevtbl.page,reader,dstype,table_name).strip("\n").strip()
#print("FOUND NAME:", name)
torename[table_name] = name
# multi-page table check
# multi-page table check, Belden
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)
@ -171,15 +328,21 @@ def parse(filename, output_dir, partnum, dstype):
del tables[table_name]
else:
#print(tables)
#print(main_key)
#print(cont_key)
for key in tables[cont_key].keys():
tables[main_key][key] = tables[cont_key][key]
del tables[table_name]
else:
previous_table = table_name
else:
previous_table = table_name
# remove renamed tables
# remove & rename tables
#print(torename)
for table_name in torename.keys():
tables[torename[table_name]] = tables[table_name]
tables[torename[str(table_name)]] = tables[str(table_name)]
del tables[table_name]
# remove multi-line values that occasionally squeak through
def replace_newlines_in_dict(d):
@ -195,15 +358,41 @@ def parse(filename, output_dir, partnum, dstype):
tables = replace_newlines_in_dict(tables)
# summary
#print(tables)
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)}
if "brand" in extra:
output_table["brand"] = extra["brand"]
else:
output_table["brand"] = dstype
if img is not None:
output_table["image"] = img
output_table["fullspecs"] = {"partnum": partnum, "id": id, "brand": output_table["brand"], "image": img, "datasheet": output_dir + "/datasheet.pdf", **tables}
output_table["searchspecs"] = {"partnum": partnum, "brand": output_table["brand"], "image": img, "datasheet": output_dir + "/datasheet.pdf", **flatten(tables)}
else:
output_table["fullspecs"] = {"partnum": partnum, "id": id, "brand": output_table["brand"], "datasheet": output_dir + "/datasheet.pdf", **tables}
output_table["searchspecs"] = {"partnum": partnum, "brand": output_table["brand"], "datasheet": output_dir + "/datasheet.pdf", **flatten(tables)}
if "short_description" in extra:
output_table["short_description"] = extra["short_description"]
output_table["fullspecs"]["short_description"] = extra["short_description"]
output_table["searchspecs"]["short_description"] = extra["short_description"]
if "description" in extra:
output_table["description"] = extra["description"]
output_table["fullspecs"]["description"] = extra["description"]
output_table["searchspecs"]["description"] = extra["description"]
if "application" in extra:
output_table["application"] = extra["application"]
output_table["fullspecs"]["application"] = extra["application"]
output_table["searchspecs"]["application"] = extra["application"]
if "category" in extra:
output_table["category"] = extra["category"]
output_table["fullspecs"]["category"] = extra["category"]
output_table["searchspecs"]["category"] = extra["category"]
output_table["searchspecs"]["id"] = id
@ -211,11 +400,21 @@ def parse(filename, output_dir, partnum, dstype):
#print(output_table)
run_cmd("rm \"" + output_dir + "\"/*.json") # not reliable!
with open(output_dir + "/" + output_table["searchspecs"]["id"] + ".json", 'w') as json_file:
#run_cmd("rm \"" + output_dir + "\"/*.json") # not reliable!
# pattern = os.path.join(output_dir, '*.json')
# json_files = glob.glob(pattern)
# for file_path in json_files:
# os.remove(file_path)
#print(f"Deleted {file_path}")
with open(output_dir + "/search.json", 'w') as json_file:
json.dump(output_table["searchspecs"], json_file)
touch(output_dir + "/parsed")
return output_table
with open(output_dir + "/specs.json", 'w') as json_file:
json.dump(output_table["fullspecs"], json_file)
fprint("Datasheet values parsed and saved for " + partnum)
#print(json.dumps(output_table, indent=2))
touch(output_dir + "/parsed") # mark as parsed
return True
def flatten(tables):
@ -241,13 +440,20 @@ def flatten(tables):
fullkeyname = (table + ": " + keyname).replace(".","")
if type(tables[table][key]) is not tuple:
if len(tables[table][key]) > 0:
out[fullkeyname] = convert_to_number(tables[table][key])
#print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
elif len(tables[table][key]) == 1:
if len(tables[table][key][0]) > 0:
out[fullkeyname] = convert_to_number(tables[table][key][0])
#print("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
else:
tmp = []
for x in range(len(tables[table][key])):
if len(tables[table][key][x]) > 0:
tmp.append(tables[table][key][x].strip())
#out[fullkeyname + " " + str(x+1)] = convert_to_number(tables[table][key][x])
out[fullkeyname] = tmp
# 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(",")))
@ -256,7 +462,7 @@ def flatten(tables):
# 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("\"" + keyname + "\":", "\"" + str(out[fullkeyname]) + "\",")
#print("}")
@ -265,4 +471,4 @@ def flatten(tables):
if __name__ == "__main__":
parse("test2.pdf", "cables/10GXS13", "10GXS13")
print(parse("cables/3050/datasheet-new.pdf", "cables/3050", "3050", "Alphawire"))

View File

@ -1,10 +1,11 @@
# Runtime
camelot-py[base]
opencv-python
camelot-py[base]==0.9.0
#opencv-python
pypdf2==2.12.1
alive-progress
requests
git+https://github.com/Byeongdulee/python-urx.git
math3d==4.0.0
git+https://git.myitr.org/adeck/python-urx-getl-rt.git
meilisearch
pyyaml
Flask
@ -15,6 +16,14 @@ websockets
numpy
scipy
ipywidgets
pandas
#pyarrow
ghostscript
pyzbar
segno
pyModbusTCP
# Development
matplotlib
#cx_Freeze # uncomment if building label generator app
# requires windows 10 SDK, visual C++, etc

784
run.py
View File

@ -1,5 +1,6 @@
#!/usr/bin/env python3
from alive_progress import alive_bar
import get_specs
import traceback
#import logging
@ -12,19 +13,29 @@ from util import fprint
from util import run_cmd
import sys
import ur5_control
from ur5_control import Rob
import os
import signal
import socket
from flask import Flask, render_template, request
import requests
import led_control
from led_control import LEDSystem
import server
import asyncio
import json
import process_video
import search
from search import JukeboxSearch
#multiprocessing.set_start_method('spawn', True)
from pyModbusTCP.client import ModbusClient
from uptime import uptime
import fileserver
# set to false to run without real hardware for development
real = True
skip_scanning = True
mbconn = None
config = None
keeprunning = True
arm_ready = False
@ -32,25 +43,54 @@ led_ready = False
camera_ready = False
sensor_ready = False
vm_ready = False
cable_search_ready = False
killme = None
#pool = None
serverproc = None
camera = None
ledsys = None
arm = None
to_server_queue = Queue()
from_server_queue = Queue()
mainloop_get = Queue()
mode = "Startup"
oldmode = "Startup"
counter = 0
jbs = None
scan_value = None
arm_state = None
cable_list = list()
parse_res = None
cable_list_state = list()
just_placed = -1
ring_animation = None
led_set_mode = None
sensors = [0,0,0,0]
websocket_process = None
arm_updates = None
animation_wait = False
arm_position = (0,0,0,0,0,0)
arm_position_process = None
start_animation = False
failcount = 0
def arm_start_callback(res):
fprint("Arm action complete.")
global arm_ready
arm_ready = True
def led_start_callback(res):
global led_ready
led_ready = True
global ledsys
ledsys = res
def camera_start_callback(res):
global camera_ready
camera_ready = True
global scan_value
scan_value = res
def sensor_start_callback(res):
global sensor_ready
@ -60,6 +100,12 @@ def vm_start_callback(res):
global vm_ready
vm_ready = True
def cable_search_callback(res):
global cable_search_ready
cable_search_ready = True
global parse_res
parse_res = res
def wait_for(val, name):
#global val
if val is False:
@ -67,29 +113,20 @@ def wait_for(val, name):
while val is False:
sleep(0.1)
def start_server_socket():
"""app = Flask(__name__)
def send_data(type, call, data, client_id="*"):
out = dict()
out["type"] = type
out["call"] = call
out["data"] = data
to_server_queue.put((client_id, json.dumps(out)))
@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)"""
def check_server():
#print("HI")
global cable_list
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")
global jbs
if True:
# Handeling Server Requests Loop, will run forever
if not from_server_queue.empty():
@ -99,44 +136,89 @@ def start_server_socket():
# Message handler
try:
decoded = json.loads(message)
except:
fprint("Non-JSON message recieved")
return
if "type" not in decoded:
fprint("Missing \"type\" field.")
continue
return
if "call" not in decoded:
fprint("Missing \"call\" field.")
continue
return
if "data" not in decoded:
fprint("Missing \"data\" field.")
continue
return
# if we get here, we have a "valid" data packet
data = decoded["data"]
call = decoded["call"]
try:
match decoded["type"]:
case "log":
fprint("log message")
if call == "send":
fprint("webapp: " + str(data), sendqueue=to_server_queue)
elif call == "request":
fprint("")
pass
case "cable_map":
fprint("cable_map message")
if call == "send":
fprint("")
pass
elif call == "request":
fprint("")
tmp = list()
for idx in range(len(cable_list)):
if cable_list[idx] is not False:
cabledata = jbs.get_position(str(idx))
fs = cabledata["fullspecs"]
tmp1 = {"part_number": cable_list[idx], "position": idx, "name": cable_list[idx], "brand": cabledata["brand"] }
if "Product Overview" in fs and "Product Category" in fs["Product Overview"]:
tmp1["category"] = fs["Product Overview"]["Product Category"]
if "Product Overview" in fs and "Suitable Applications" in fs["Product Overview"]:
if len(fs["Product Overview"]["Suitable Applications"]) == 0 or not isinstance(fs["Product Overview"]["Suitable Applications"], str):
for key, value in fs["Product Overview"].items():
#print(key,value)
if len(value) > 5 and isinstance(value, str):
tmp1["application"] = value
elif len(key) > 15 and not isinstance(value, str):
tmp1["application"] = key
else:
tmp1["application"] = fs["Product Overview"]["Suitable Applications"]
elif "Product Overview" in fs and "Suitable Applications:" in fs["Product Overview"]:
if len(fs["Product Overview"]["Suitable Applications:"]) == 0 or not isinstance(fs["Product Overview"]["Suitable Applications:"], str):
for key, value in fs["Product Overview"].items():
#print(key,value)
if len(value) > 5 and isinstance(value, str):
tmp1["application"] = value
elif len(key) > 15 and not isinstance(value, str):
tmp1["application"] = key
else:
tmp1["application"] = fs["Product Overview"]["Suitable Applications:"]
if "image" in cabledata:
tmp1["image"] = cabledata["image"]
if "datasheet" in cabledata:
tmp1["datasheet"] = cabledata["datasheet"]
if "description" in cabledata:
tmp1["description"] = cabledata["description"]
if "short_description" in cabledata:
tmp1["short_description"] = cabledata["short_description"]
if "application" in cabledata:
tmp1["application"] = cabledata["application"]
if "category" in cabledata:
tmp1["category"] = cabledata["category"]
tmp.append(tmp1)
out = {"map": tmp}
fprint(out)
send_data(decoded["type"], "send", out, client_id)
case "ping":
fprint("Pong!!!")
# Lucas' notes
# Add a ping pong :) response/handler
# Add a get cable response/handler
# this will tell the robot arm to move
# Call for turning off everything
# TODO Helper for converting Python Dictionaries to JSON
# make function: pythonData --> { { "type": "...", "call": "...", "data": pythonData } }
@ -145,23 +227,80 @@ def start_server_socket():
case "cable_details":
fprint("cable_details message")
if call == "send":
fprint("")
pass
elif call == "request":
fprint("")
dataout = dict()
dataout["cables"] = list()
print(data)
if "part_number" in data:
for part in data["part_number"]:
#print(part)
#print(jbs.get_partnum(part))
dataout["cables"].append(jbs.get_partnum(part)["fullspecs"])
if "position" in data:
for pos in data["position"]:
#print(pos)
#print(jbs.get_position(str(pos)))
dataout["cables"].append(jbs.get_position(str(pos))["fullspecs"])
send_data(decoded["type"], "send", dataout, client_id)
case "cable_search":
fprint("cable_search message")
if call == "send":
fprint("")
pass
elif call == "request":
fprint("")
results = jbs.search(data["string"])["hits"]
dataout = dict()
dataout["map"] = list()
for cabledata in results:
fs = cabledata["fullspecs"]
tmp1 = {"part_number": cabledata["partnum"], "position": cabledata["position"], "name": cabledata["partnum"], "brand": cabledata["brand"] }
if "Product Overview" in fs and "Product Category" in fs["Product Overview"]:
tmp1["category"] = fs["Product Overview"]["Product Category"]
if "Product Overview" in fs and "Suitable Applications" in fs["Product Overview"]:
if len(fs["Product Overview"]["Suitable Applications"]) == 0 or not isinstance(fs["Product Overview"]["Suitable Applications"], str):
for key, value in fs["Product Overview"].items():
#print(key,value)
if len(value) > 5 and isinstance(value, str):
tmp1["application"] = value
elif len(key) > 15 and not isinstance(value, str):
tmp1["application"] = key
else:
tmp1["application"] = fs["Product Overview"]["Suitable Applications"]
elif "Product Overview" in fs and "Suitable Applications:" in fs["Product Overview"]:
if len(fs["Product Overview"]["Suitable Applications:"]) == 0 or not isinstance(fs["Product Overview"]["Suitable Applications:"], str):
for key, value in fs["Product Overview"].items():
#print(key,value)
if len(value) > 5 and isinstance(value, str):
tmp1["application"] = value
elif len(key) > 15 and not isinstance(value, str):
tmp1["application"] = key
else:
tmp1["application"] = fs["Product Overview"]["Suitable Applications:"]
if "image" in cabledata:
tmp1["image"] = cabledata["image"]
if "datasheet" in cabledata:
tmp1["datasheet"] = cabledata["datasheet"]
if "description" in cabledata:
tmp1["description"] = cabledata["description"]
if "short_description" in cabledata:
tmp1["short_description"] = cabledata["short_description"]
if "application" in cabledata:
tmp1["application"] = cabledata["application"]
if "category" in cabledata:
tmp1["category"] = cabledata["category"]
dataout["map"].append(tmp1)
# after loop
send_data(decoded["type"], "send", dataout, client_id)
case "keyboard":
fprint("keyboard message")
if call == "send":
fprint("")
pass
elif call == "request":
fprint("")
if data["enabled"] == True:
# todo : send this to client
p = Process(target=run_cmd, args=("./keyboard-up.ps1",))
@ -169,24 +308,43 @@ def start_server_socket():
elif data["enabled"] == False:
p = Process(target=run_cmd, args=("./keyboard-down.ps1",))
p.start()
case "machine_settings":
fprint("machine_settings message")
if call == "send":
fprint("")
pass
elif call == "request":
fprint("")
pass
case "cable_get":
fprint("cable_get message")
if call == "send":
global mainloop_get
if "part_number" in data:
for cableidx in range(len(cable_list)):
cable = cable_list[cableidx]
if cable == data["part_number"]:
fprint("Adding cable to dispense queue")
mainloop_get.put(("pickup", cableidx))
elif "position" in data:
fprint("Adding cable to dispense queue")
mainloop_get.put(("pickup", data["position"]))
elif "tray" in data:
fprint("Adding tray return to dispense queue")
mainloop_get.put(("return", data["tray"]))
else:
fprint("Invalid data.")
case _:
fprint("Unknown/unimplemented data type: " + decoded["type"])
except Exception as e:
fprint(traceback.format_exc())
fprint(e)
except:
fprint("Non-JSON message recieved")
continue
sleep(0.001) # Sleep to prevent tight loop
#sleep(0.001) # Sleep to prevent tight loop
@ -221,7 +379,7 @@ def check_server_online(serverip, clientip):
return True
def setup_server(pool):
def setup_server(pool, manager):
# linux server setup
global config
global counter
@ -231,57 +389,530 @@ def setup_server(pool):
global arm_ready
global serverproc
global camera
global arm
global jbs
global to_server_queue
global from_server_queue
global websocket_process
global arm_updates
global arm_position_process
global ledsys
arm_updates = manager.Queue()
arm = Rob(config)
if real:
pool.apply_async(ur5_control.powerup_arm, (arm,), callback=arm_start_callback, error_callback=handle_error)
else:
arm_ready = True
pool.apply_async(ur5_control.init, (config["arm"]["ip"],), callback=arm_start_callback)
pool.apply_async(led_control.init, callback=led_start_callback)
if real:
ledsys = LEDSystem()
#pool.apply_async(ledsys.init, callback=led_start_callback)
#pool.apply_async(sensor_control.init, callback=sensor_start_callback)
serverproc = Process(target=start_server_socket)
serverproc.start()
jbs = JukeboxSearch()
if led_ready is False:
fprint("waiting for " + "LED controller initialization" + " to complete...", sendqueue=to_server_queue)
while led_ready is False:
sleep(0.1)
if real:
ledsys.init()
led_ready = True
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)
global mbconn
mbconn = ModbusClient(host="192.168.1.20", port=502, auto_open=True, auto_close=False)
get_sensors()
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"])
camera = process_video.qr_reader(config["cameras"]["banner"]["ip"], int(config["cameras"]["banner"]["port"]))
fprint("Camera initialized.", sendqueue=to_server_queue)
arm_ready = True
#arm_ready = True
if arm_ready is False:
fprint("waiting for " + "UR5 initilization" + " to complete...", sendqueue=to_server_queue)
fprint("waiting for " + "UR5 powerup" + " to complete...", sendqueue=to_server_queue)
while arm_ready is False:
sleep(0.1)
if real:
ur5_control.init_arm(arm)
fprint("Arm initialized.", sendqueue=to_server_queue)
fprint("Creating arm position watcher...")
arm_position_process = pool.apply_async(ur5_control.get_position_thread, (arm,arm_updates))
fprint("Starting websocket server...", sendqueue=to_server_queue)
websocket_process = server.start_websocket_server(to_server_queue, from_server_queue)
fprint("Starting image file server...", sendqueue=to_server_queue)
image_server_process = Process(target=fileserver.run_server, args=(config["cables"]["port"], config["cables"]["directory"]))
image_server_process.start()
return True
def handle_error(error):
print(error, flush=True)
def get_sensors():
global mbconn
global sensors
oldsens = sensors
#print("Reading sensors")
# mbconn.open()
"""
port 1: 256
port 2: 272
port 3: 288
port 4: 304
port 5: 320
port 6: 336
port 7: 352
port 8: 368
"""
out = list()
if real:
for reg in [352, 288, 304, 368]:
val = mbconn.read_holding_registers(reg)
if val is not None:
val = val[0]
if val == 1:
out.append(1)
else:
out.append(0)
else:
out = [0, 0, 0, 0]
if len(out) != 4:
return -1
sensors = out
#fprint("Values: " + str(sensors))
mbconn.close()
for x in range(len(oldsens)):
if oldsens[x] == 0 and out[x] == 1:
# cable newly detected on tray
fprint("Precense detected: slot " + str(x))
return x
return -1
def get_open_spot(sensordata):
for x in range(len(sensordata)):
sens = sensordata[x]
if not sens:
return x
# if we get here, every spot is full
return False
def mainloop_server(pool):
def mainloop_server(pool, manager):
# NON-blocking loop
global real
global ring_animation
global led_set_mode
global just_placed
global config
global counter
global killme
global mode
global jbs
global arm
global ledsys
global camera
global arm_ready
global arm_state
global camera_ready
global cable_search_ready
global cable_list
global mainloop_get
global cable_list_state
global scan_value
global oldmode
global arm_updates
global animation_wait
global arm_position
global arm_position_process
global start_animation
global failcount
if mode != oldmode:
print(" ***** Running mode:", mode, "***** ")
oldmode = mode
if mode == "Startup": # very first loop
pass
if killme.value > 0:
killall()
counter = counter + 1
# fprint("Looking for QR code...")
# print(camera.read_qr(30))
# check for messages
check_server()
# do every loop!
checkpoint = None
if not arm_updates.empty():
val = arm_updates.get()
if isinstance(val, tuple):
arm_position = val
else:
print("Arm queue message " + str(val))
checkpoint = val
print(ring_animation, animation_wait, ledsys.mode, arm_position)
if start_animation:
# animation start requested
# may not be immediate
if ring_animation is not None:
if animation_wait:
# wait for checkpoint
if checkpoint is not None:
fprint("Starting checkpointed animation " + str(led_set_mode) + " for ring " + str(ring_animation))
ledsys.mainloop(led_set_mode, ring_animation, arm_position=arm_position)
led_set_mode = None
animation_wait = False
start_animation = False
else:
# still waiting
ledsys.mainloop(None, ring_animation, arm_position=arm_position)
else:
# no waiting, just start
fprint("Starting immediate animation " + str(led_set_mode) + " for ring " + str(ring_animation))
ledsys.mainloop(led_set_mode, ring_animation, arm_position=arm_position)
led_set_mode = None
animation_wait = False
start_animation = False
else:
# no ring animation specified
pass
else:
# no new animation
if ring_animation is not None:
ledsys.mainloop(None, ring_animation, arm_position=arm_position)
else:
pass
# if start_animation is False and ring_animation is not None and ledsys.mode != "Idle" and real:
# ledsys.mainloop(None, ring_animation, arm_position=arm_position)
# elif start_animation is True and ring_animation is not None and real:
# if animation_wait:
# if checkpoint is not None: # got to checkpoint from UR5
# fprint("Starting checkpointed animation " + str(led_set_mode) + " for ring " + str(ring_animation))
# ledsys.mainloop(led_set_mode, ring_animation, arm_position=arm_position)
# led_set_mode = None
# animation_wait = False
# start_animation = False
# else:
# fprint("Starting immediate animation " + str(led_set_mode) + " for ring " + str(ring_animation))
# ledsys.mainloop(led_set_mode, ring_animation, arm_position=arm_position)
# led_set_mode = None
# start_animation = False
# else:
# ledsys.mainloop(None, 49, arm_position=arm_position)
# pass
# #fprint("Not triggering LED loop: no ring animation")
if mode == "Startup":
if not real or skip_scanning:
counter = 54
if counter < 54:
# scanning cables
if arm_state is None:
#pool.apply_async(arm_start_callback, ("",))
arm_ready = False
pool.apply_async(ur5_control.holder_to_camera, (arm,arm_updates,counter), callback=arm_start_callback, error_callback=handle_error)
fprint("Getting cable index " + str(counter) + " and scanning...")
arm_state = "GET"
ring_animation = counter
animation_wait = True
start_animation = True
led_set_mode = "GrabA"
#ur5_control.to_camera(arm, counter)
#arm_ready = True
elif arm_ready and arm_state == "GET":
fprint("Looking for QR code...")
pool.apply_async(camera.read_qr, (10,), callback=camera_start_callback, error_callback=handle_error)
arm_ready = False
elif camera_ready:
ring_animation = counter
animation_wait = True
start_animation = True
led_set_mode = "GrabC"
fprint("Adding cable to list...")
global scan_value
if scan_value is False:
cable_list.append(scan_value)
elif scan_value.find("bldn.app/") > -1:
scan_value = scan_value[scan_value.find("bldn.app/")+9:]
else:
cable_list.append(scan_value)
fprint(scan_value)
pool.apply_async(ur5_control.camera_to_holder, (arm,arm_updates,counter), callback=arm_start_callback, error_callback=handle_error)
#ur5_control.return_camera(arm, counter)
#arm_ready = True
arm_state = "RETURN"
camera_ready = False
elif arm_ready and arm_state == "RETURN":
counter += 1
arm_state = None
else:
# just wait til arm/camera is ready
pass
else:
# scanned everything
ring_animation = None
led_set_mode == "Idle"
start_animation = True
tmp = [
# Actual cables in Jukebox
"BLTF-1LF-006-RS5",
"BLTF-SD9-006-RI5",
"BLTT-SLG-024-HTN",
"BLFISX012W0",
"BLFI4X012W0",
"BLSPE101",
"BLSPE102",
"BL7922A",
"BL7958A",
"BLIOP6U",
"BL10GXW13",
"BL10GXW53",
"BL29501F",
"BL29512",
"BL3106A",
"BL9841",
"BL3105A",
"BL3092A",
"BL8760",
"BL6300UE",
"BL6300FE",
"BLRA500P",
"AW86104CY",
"AW3050",
"AW6714",
"AW1172C",
"AWFIT-221-1_4"
]
while len(tmp) < 54:
tmp.append(False) # must have 54 entries
if not real or skip_scanning:
cable_list = tmp # comment out for real demo
for idx in range(len(cable_list)):
cable_list_state.append(True)
pool.apply_async(get_specs.get_multi, (cable_list, 0.3, config["cables"]["directory"], config["cables"]["port"]), callback=cable_search_callback, error_callback=handle_error)
mode = "Parsing"
fprint("All cables scanned. Finding & parsing datasheets...")
if mode == "Parsing":
# waiting for search & parse to complete
#cable_search_ready = True
if cable_search_ready is False:
pass
else:
# done
global parse_res
success, partnums = parse_res
for idx in range(len(partnums)):
if partnums[idx] is not False:
cable_list[idx] = partnums[idx][0].replace("/", "_")
else:
cable_list[idx] = False
print(partnums)
if success:
# easy mode
fprint("All cables inventoried and parsed.")
fprint("Adding to database...")
for idx in range(len(cable_list)):
partnum = cable_list[idx]
if partnum is not False:
with open("cables/" + partnum + "/search.json", "rb") as f:
searchdata = json.load(f)
searchdata["position"] = idx
with open("cables/" + partnum + "/specs.json", "rb") as f:
specs = json.load(f)
searchdata["fullspecs"] = specs
searchdata["fullspecs"]["position"] = idx
jbs.add_document(searchdata)
#sleep(0.5)
#print(jbs.get_position("1"))
fprint("All cables added to database.")
mode = "Idle"
else:
# TODO: manual input
pass
if mode == "Idle":
# do nothing
if arm_ready is False:
pass
else:
global mainloop_get
#print("Checking sensors..")
newtube = get_sensors()
if newtube >= 0 and newtube != just_placed:
# need to return a cable
mainloop_get.put(("return", newtube))
just_placed = -1
if not mainloop_get.empty():
fprint("Movement requested. Keep clear of the machine!")
action, get_cable = mainloop_get.get()
if get_cable > -1:
global sensors
if action == "pickup":
spot = get_open_spot(sensors)
if spot is not False:
arm_ready = False
if real:
animation_wait = False
ring_animation = get_cable
start_animation = True
led_set_mode = "GrabAA"
pool.apply_async(ur5_control.holder_to_tray, (arm, arm_updates, get_cable, spot), callback=arm_start_callback, error_callback=handle_error)
else:
arm_ready = True
fprint("Getting cable at position " + str(get_cable))
mode = "Pickup"
cable_list_state[get_cable] = False # mark as removed
get_sensors()
if action == "return":
arm_ready = False
fprint("Returning cable from tray position " + str(get_cable))
if real:
failcount = 0
pool.apply_async(ur5_control.tray_to_camera, (arm, arm_updates, get_cable), callback=arm_start_callback, error_callback=handle_error)
else:
arm_ready = True
mode = "ReturnC"
else:
# LED idle anim
pass
if mode == "Pickup":
# complete
if arm_ready == True:
mode = "Idle"
else:
# getting cable and bringing to tray
# led animation
if ledsys.mode == "Idle" and led_set_mode != "GrabAA":
animation_wait = True
start_animation = True
led_set_mode = "GrabAB"
pass
if mode == "ReturnC":
# complete
if arm_ready == True:
mode = "Scan"
arm_ready = False
camera_ready = False
if real:
animation_wait = False
start_animation = True
ring_animation = 49
led_set_mode = "Camera"
pool.apply_async(camera.read_qr, (50,), callback=camera_start_callback, error_callback=handle_error)
else:
camera_ready = True
scan_value = "10GXS13"
else:
# getting cable from and bringing to camera
# led animation
pass
if mode == "Scan":
if camera_ready == True:
if scan_value is False:
# unable to scan ???? not good
if failcount > 15:
mode = "Idle"
fprint("Giving up scanning cable.")
failcount = 0
else:
fprint("Unable to scan cable. Gonna retry.")
camera_ready = False
#mode = "Idle"
failcount += 1
pool.apply_async(camera.read_qr, (10,), callback=camera_start_callback, error_callback=handle_error)
elif scan_value.find("bldn.app/") > -1:
scan_value = scan_value[scan_value.find("bldn.app/")+9:]
fprint("Got cable: " + str(scan_value))
if scan_value[0:2] == "BL" or scan_value[0:2] == "AW":
scan_value = scan_value[2:]
for idx in range(len(cable_list)):
cable = cable_list[idx]
if cable == scan_value and cable_list_state[idx] == False:
cable_list_state[idx] = True # mark cable as returned
arm_ready = False
if real:
animation_wait = True
ring_animation = idx
led_set_mode = "GrabC"
start_animation = True
pool.apply_async(ur5_control.camera_to_holder, (arm, arm_updates, idx), callback=arm_start_callback, error_callback=handle_error)
else:
arm_ready = True
mode = "Return"
break
elif cable == scan_value and cable_list_state[idx] == True:
fprint("WARNING: Holder still marked as occupied!")
arm_ready = False
if real:
animation_wait = True
ring_animation = idx
led_set_mode = "GrabC"
start_animation = True
pool.apply_async(ur5_control.camera_to_holder, (arm, arm_updates, idx), callback=arm_start_callback, error_callback=handle_error)
else:
arm_ready = True
mode = "Return"
break
if mode == "Scan":
mode = "Idle"
if mode == "Return":
if arm_ready == True:
mode = "Idle"
#arm_ready = False
# movement finished
else:
# cable going from camera to holder
# led animation
pass
def run_loading_app():
@ -389,19 +1020,20 @@ class LogExceptions(object):
return result
class LoggingPool(Pool):
def apply_async(self, func, args=(), kwds={}, callback=None):
return Pool.apply_async(self, LogExceptions(func), args, kwds, callback)
def apply_async(self, func, args=(), kwds={}, callback=None, error_callback=None):
return Pool.apply_async(self, LogExceptions(func), args, kwds, callback, error_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)
fprint("Config loaded.")
with Manager() as manager:
fprint("Spawning threads...")
pool = LoggingPool(processes=10)
@ -418,9 +1050,21 @@ if __name__ == "__main__":
elif config["core"]["mode"] == "linuxserver":
fprint("Starting in server mode.")
if setup_server(pool):
if setup_server(pool, manager):
fprint("Entering main loop...")
start = 0
speed = config["core"]["loopspeed"]
while(keeprunning):
mainloop_server(pool)
start = uptime()
mainloop_server(pool, manager)
#sleep(0.01)
# limit to certain "framerate"
#print(start, start + 1.0/speed, uptime())
while start + 1.0/speed > uptime():
sleep(0.001)
else:
fprint("Mode unspecified - quitting")

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python3
"""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
import time
DEFAULT_URL = "http://localhost:7700"
DEFAULT_URL = "http://127.0.0.1:7700"
DEFAULT_APIKEY = "fluffybunnyrabbit" # I WOULD RECOMMEND SOMETHING MORE SECURE
DEFAULT_INDEX = "cables"
DEFAULT_FILTERABLE_ATTRS = ["partnum", "uuid", "position"] # default filterable attributes
@ -34,12 +36,15 @@ class JukeboxSearch:
# create the index if it does not exist already
try:
self.client.get_index(self.index)
self.client.delete_index(self.index)
self.client.create_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)
time.sleep(0.05)
# update filterable attributes if needed
self.idxref.update_distinct_attribute('partnum')
self.update_filterables(filterable_attrs)
def add_document(self, document: dict) -> TaskInfo:
@ -65,11 +70,10 @@ class JukeboxSearch:
:param filterables: List of all filterable attributes"""
existing_filterables = self.idxref.get_filterable_attributes()
if len(set(existing_filterables).difference(set(filterables))) > 0:
#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)
#self.client.wait_for_task(taskref.index_uid)
def search(self, query: str, filters: str = None):
"""Execute a search query on the Meilisearch index.
@ -90,7 +94,7 @@ class JukeboxSearch:
:returns: A dict containing the results; If no results found, an empty dict."""
q = self.search("", filter)
if q["estimatedTotalHits"] != 0:
return ["hits"][0]
return q["hits"][0]
else:
return dict()

View File

@ -83,7 +83,7 @@ async def send_messages(to_server_queue):
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)
start_server = websockets.serve(lambda ws, path: handler(ws, path, to_server_queue, from_server_queue), "0.0.0.0", 9000)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().create_task(send_messages(to_server_queue))

20
setup-alpine-vm.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/sh
# This script must run as root!
echo "https://dl-cdn.alpinelinux.org/alpine/latest-stable/main
https://dl-cdn.alpinelinux.org/alpine/latest-stable/community" > /etc/apk/repositories
apk upgrade
apk add git docker docker-cli-compose
rc-update add docker
service docker start
git clone https://git.myitr.org/Jukebox/jukebox-software
cd jukebox-software
git submodule init
git submodule update
docker compose build
docker compose up -d

31
setup-label-generator.py Normal file
View File

@ -0,0 +1,31 @@
import sys
from cx_Freeze import setup, Executable
debug = True
debug = not debug
# Dependencies are automatically detected, but it might need fine tuning.
# "packages": ["os"] is used as example only
import opcode
import os
import distutils
#distutils_path = os.path.join(os.path.dirname(opcode.__file__), 'distutils')
build_exe_options = {"include_msvcr": True, "packages": ["camelot", "setuptools", "segno"], "optimize": 0, "silent": True, "include_files": ["gs10030w64.exe", "GothamCond-Medium.otf", "belden-logo-superhires.png"], "excludes": ["scipy", "torch"]}
# base="Win32GUI" should be used only for Windows GUI app
base = "console"
#if sys.platform == "win32" and not debug:
# base = "Win32GUI"
if sys.platform == "linux" or sys.platform == "linux2" or sys.platform == "darwin":
name = "jukebox-labelgen"
else:
name = "jukebox-labelgen.exe"
setup(
name="IP Pigeon",
version="0.2.4",
description="IP Pigeon client application",
options={"build_exe": build_exe_options},
executables=[Executable("label_generator.py", base=base, uac_admin=False, target_name=name)],
)

76
table0.html Normal file
View File

@ -0,0 +1,76 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
</tr>
</thead>
<tbody>
<tr>
<th>2</th>
<td></td>
<td></td>
<td></td>
<td>Diameters\n(In)</td>
</tr>
<tr>
<th>4</th>
<td>1) Component\n1\n1 X 1 HOOKUP</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<th>6</th>
<td>a) Conductor\n24\n(7/32) AWG Tinned Copper</td>
<td></td>
<td></td>
<td>0.024</td>
</tr>
<tr>
<th>8</th>
<td>b)\nInsulation\n0.016" Wall, Nom. PVC</td>
<td></td>
<td></td>
<td>0.056+/-\n0.002</td>
</tr>
<tr>
<th>10</th>
<td></td>
<td>(1) Print</td>
<td>ALPHA WIRE\nE163869-* RU AWM STYLES\n1569\n105C\nOR 1007\n80C 300V VW-1\nIEC 60332-2\n24 AWG\nOR CRU TR-64\n90C FT1 CE ROHS\n{0}\n* = Factory Code</td>
<td></td>
</tr>
<tr>
<th>12</th>
<td></td>
<td>(2) Color(s)</td>
<td>WHITE, BLACK, GREEN,\nYELLOW, BLUE, BROWN, ORANGE\nSLATE, VIOLET, WHITE/BLACK, WHITE/RED</td>
<td></td>
</tr>
<tr>
<th>13</th>
<td></td>
<td></td>
<td>WHITE/GREEN, WHITE/YELLOW, WHITE/BLUE\nWHITE/BROWN, WHITE/ORANGE, WHITE/SLATE\nWHITE/VIOLET, GREEN/YELLOW,\nPINK, DK. BLUE</td>
<td></td>
</tr>
<tr>
<th>14</th>
<td></td>
<td></td>
<td>BROWN/BLACK, WHITE/PINK, BROWN/GREEN, BROWN/BLUE</td>
<td></td>
</tr>
<tr>
<th>15</th>
<td></td>
<td></td>
<td>RED</td>
<td></td>
</tr>
</tbody>
</table>

46
table1.html Normal file
View File

@ -0,0 +1,46 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>1</th>
<th>2</th>
</tr>
</thead>
<tbody>
<tr>
<th>2</th>
<td>1) UL</td>
<td>AWM/STYLE\n1007\n80°C /\n300 VRMS</td>
</tr>
<tr>
<th>4</th>
<td></td>
<td>AWM/STYLE\n1569\n105°C /\n300 VRMS</td>
</tr>
<tr>
<th>6</th>
<td></td>
<td>VW-1</td>
</tr>
<tr>
<th>8</th>
<td>2) CSA International</td>
<td>TR-64\n90°C</td>
</tr>
<tr>
<th>10</th>
<td></td>
<td>FT1</td>
</tr>
<tr>
<th>12</th>
<td>3)\nIEC</td>
<td>EN 60332-2\nFlame Behavior</td>
</tr>
<tr>
<th>14</th>
<td>4) CE:</td>
<td>EU Low Voltage Directive\n2014/35/EU</td>
</tr>
</tbody>
</table>

60
table2.html Normal file
View File

@ -0,0 +1,60 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>1</th>
<th>2</th>
<th>4</th>
</tr>
</thead>
<tbody>
<tr>
<th>2</th>
<td>1)\nEU Directive\n2011/65/EU(RoHS2),\nEU Directive\n2015/863/EU (RoHS3):</td>
<td></td>
<td></td>
</tr>
<tr>
<th>4</th>
<td></td>
<td></td>
<td>All materials used in the manufacture of\nthis part\nare\nin compliance with European Directive\n2011/65/EU</td>
</tr>
<tr>
<th>5</th>
<td></td>
<td></td>
<td>and the\namending Directive\n2015/863/EU of\n4\nJune\n2015\nregarding the\nrestriction of use of\ncertain</td>
</tr>
<tr>
<th>6</th>
<td></td>
<td></td>
<td>hazardous\nsubstances\nin electrical\nand electronic\nequipment.</td>
</tr>
<tr>
<th>7</th>
<td>2) REACH Regulation (EC 1907/2006):</td>
<td></td>
<td></td>
</tr>
<tr>
<th>8</th>
<td></td>
<td></td>
<td>This product does not\ncontain Substances of Very High Concern (SVHC)\nlisted on the\nEuropean Union's</td>
</tr>
<tr>
<th>9</th>
<td></td>
<td></td>
<td>REACH candidate\nlist\nin excess of\n0.1% mass of\nthe\nitem.</td>
</tr>
<tr>
<th>11</th>
<td></td>
<td>3) California Proposition 65:</td>
<td>This product may\ncontain substances\nknown to the\nState of California\nto cause Cancer or Reproductive\nHarm, but\nis\nexempt\nfrom labeling based on the Consent\nJudgement.\nSee\nthe Alpha Wire website\nfor more\ninformation.</td>
</tr>
</tbody>
</table>

60
table3.html Normal file
View File

@ -0,0 +1,60 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>1</th>
<th>2</th>
<th>3</th>
</tr>
</thead>
<tbody>
<tr>
<th>2</th>
<td></td>
<td>Physical &amp; Mechanical Properties</td>
<td></td>
</tr>
<tr>
<th>4</th>
<td>1)\nTemperature Range</td>
<td></td>
<td>-40\nto 105°C</td>
</tr>
<tr>
<th>6</th>
<td>2) Bend Radius</td>
<td></td>
<td>10X Cable Diameter</td>
</tr>
<tr>
<th>8</th>
<td>3) Pull\nTension</td>
<td></td>
<td>3.5\nLbs, Maximum</td>
</tr>
<tr>
<th>9</th>
<td></td>
<td>Electrical Properties\n(For\nEngineering purposes only)</td>
<td></td>
</tr>
<tr>
<th>11</th>
<td>1) Voltage Rating</td>
<td></td>
<td>300 VRMS</td>
</tr>
<tr>
<th>13</th>
<td>2)\nInductance</td>
<td></td>
<td>0.07 μH/ft, Nominal</td>
</tr>
<tr>
<th>15</th>
<td>3) Conductor DCR</td>
<td></td>
<td>25 Ω/1000ft @20°C, Nominal</td>
</tr>
</tbody>
</table>

18
table4.html Normal file
View File

@ -0,0 +1,18 @@
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>1</th>
</tr>
</thead>
<tbody>
<tr>
<th>2</th>
<td>Packaging\nFlange\nx\nTraverse\nx Barrel\n(inches)</td>
</tr>
<tr>
<th>3</th>
<td>a)\n32800\nFT\n23\nx\n15\nx\n0 Max.\n4\nseparate pieces; Min length/piece\n10000\nFT.\nb)\n10000\nFT\n12\nx\n5.94\nx\n5 Max.\n3\nseparate pieces; Min length/piece\n1000\nFT.\nc)\n5000\nFT\n10.5\nx\n5\nx\n3.5 Continuous\nlength\nd)\n1000\nFT\n6.5\nx\n2\nx\n1.9 Continuous\nlength\ne)\n100\nFT\n2.75\nx\n2\nx\n1.125 Continuous\nlength\n \n[Spool\ndimensions may\nvary\nslightly]\nNotes:\na) Certain color\nand put-up combinations may only be\navailable by\nspecial order. Minimums may\napply.\nb)\nTolerance on 32800\nft. Put-Up is +/-\n10%</td>
</tr>
</tbody>
</table>

2
tempCodeRunnerFile.py Normal file
View File

@ -0,0 +1,2 @@
drop_off_tray(robot, 0)

BIN
test.pdf

Binary file not shown.

44
test.py
View File

@ -1,4 +1,46 @@
print("\u001b[37m")
from pyModbusTCP.client import ModbusClient
def get_sensors():
mbconn = ModbusClient(host="192.168.1.20", port=502, auto_open=True, auto_close=True)
"""
port 1: 256
port 2: 272
port 3: 288
port 4: 304
port 5: 320
port 6: 336
port 7: 352
port 8: 368
"""
out = list()
for reg in [352, 288, 304, 368]:
val = mbconn.read_holding_registers(reg)[0] # read only one register
print(val)
if val == 1:
out.append(True)
else:
out.append(False)
return out
def get_open_spot(sensordata):
for x in range(len(sensordata)):
sens = sensordata[x]
if not sens:
return x
# if we get here, every spot is full
return False
testmb = get_sensors()
print(testmb)
print("Spot open", get_open_spot(testmb))
exit()
class Ring:
def __init__(self) -> None:

BIN
test2.pdf

Binary file not shown.

View File

@ -5,53 +5,117 @@ import math
import numpy as np
import time
import os
import logging
from urx.robotiq_two_finger_gripper import Robotiq_Two_Finger_Gripper
#import logging
import yaml
import sys
from util import fprint
from pyModbusTCP.client import ModbusClient
from multiprocessing import Queue
import subprocess
from util import win32
class Rob():
robot = None
#offset_x, offset_y, offset_z = (0, 0, 0.14) # Tool offset
#
rob = None
def __init__(self, config):
self.config = config
armc = config["arm"]
self.ip = armc["ip"]
tool = armc["tool"]
limbs = armc["limbs"]
self.offset_x, self.offset_y, self.offset_z = (tool["offset_x"], tool["offset_y"], tool["offset_z"])
self.limb_base = limbs["limb_base"]
self.limb1 = limbs["limb1"]
self.limb2 = limbs["limb2"]
self.limb3 = limbs["limb3"]
self.limb_wrist = limbs["limb_wrist"]
#self.init_arm()
def init(ip):
global rob
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 powerup_arm(robot):
#sys.stdout = Logger()
fprint("Starting UR5 power up...")
# power up robot here via PCB
# power up robot here
#
# wait for power up (this function runs async)
count = 0
while not ping(robot.ip) and count == 10:
time.sleep(0.5)
count += 1
# trigger auto-initialize
fprint("Arm online. Waiting for calibration.")
# wait for auto-initialize
# init urx
def connect(robot):
if robot.robot is None:
newrobot = Rob(robot.config)
robot = newrobot
ip = robot.ip
fprint("Connecting to arm at " + ip)
trying = True
while trying:
count = 0
while trying and count < 10:
count += 1
try:
rob = urx.Robot(ip)
robot.robot = urx.Robot(ip, use_rt=False)
robot.robot.set_tcp((robot.offset_x, robot.offset_y, robot.offset_z, 0, 0, 0))
# Set weight
robot.robot.set_payload(2, (0, 0, 0.1))
trying = False
except:
time.sleep(1)
robotiqgrip = Robotiq_Two_Finger_Gripper(rob)
time.sleep(0.5)
# 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))
return robot
def init_arm(robot):
robot = connect(robot)
# init urx
#rob.set_payload(2, (0, 0, 0.1))
time.sleep(0.2)
fprint("UR5 ready.")
#return robot.robot
def set_pos_abs(x, y, z, xb, yb, zb, threshold=None):
global rob
# setup - in case of fail. open gripper, move up, then go home.
rob = robot.robot
open_gripper()
curr_pos = rob.getl()
new_pos = curr_pos
new_pos[2] += 0.025
rob.movel(new_pos, vel=0.05, acc=1)
curr_j = rob.getj()
curr_j[3] -= 0.2 # radians
rob.movej(curr_j, vel=0.2, acc=1)
move_to_home(robot, speed=0.5)
return True
def set_pos_abs(robot, x, y, z, xb, yb, zb, threshold=None):
rob = robot.robot
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
@ -67,8 +131,8 @@ def set_pos_abs(x, y, z, xb, yb, zb, threshold=None):
#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
def set_pos_rel_rot_abs(robot, x, y, z, xb, yb, zb):
rob = robot.robot
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
@ -85,8 +149,8 @@ def set_pos_rel_rot_abs(x, y, z, xb, yb, zb):
#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
def set_pos_abs_rot_rel(robot, x, y, z, xb, yb, zb):
rob = robot.robot
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
@ -131,9 +195,8 @@ def polar_to_cartesian(r, theta):
y = r * np.sin(theta)
return x, y
def move_to_polar(start_pos, end_pos):
global rob
def move_to_polar(robot, start_pos, end_pos):
rob = robot.robot
# Convert to polar coordinates
start_r, start_theta = cartesian_to_polar(start_pos[0], start_pos[1])
@ -190,19 +253,34 @@ def move_to_polar(start_pos, end_pos):
return rx_intermediate
def move_to_home():
global rob
def move_to_home(robot, keep_flip=False, speed=2):
rob = robot.robot
if is_flipped(robot) and not keep_flip:
flip(robot)
# Move robot to home position
rob.movej(offset_gripper_angle(robot, *(-0.18, -0.108, 0.35), flip=is_flipped(robot)), vel=2, acc=2) # Move to safe position
return True
def move_to_packup(robot, speed=0.25):
rob = robot.robot
# known good starting point to reach store position
goto_holder_index(robot, 12, 0.3, flip=False, use_closest_path=False)
# Home position in degrees
home_pos = [0.10421807948612624,
-2.206111555015423,
1.710679229503537,
-1.075834511928354,
-1.569301366430687,
1.675098295930943]
store_pos = [-1.5708,
-1.3,
2.362,
0.7056,
-1.425,
1.5708]
# Move robot
rob.movej(home_pos, acc=2, vel=2)
rob.movej(store_pos, acc=0.1, vel=speed)
return True
def normalize_degree(theta):
# Normalizes degree theta from -1.5pi to 1.5pi
@ -216,109 +294,497 @@ def normalize_degree(theta):
# 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)
def get_joints_from_xyz_rel(robot, x, y, z, rx=0, ry=-math.pi/2, rz=0, initial_guess = (math.pi/2, math.pi/2, 0), l3offset=0):
# Get limbs and offsets
#l3=0.15
l_bs, l1, l2, l3, l_wt = (robot.limb_base, robot.limb1, robot.limb2, robot.limb3, robot.limb_wrist) # Limb lengths
l3 += l3offset # add wrist offset, used for gripper angle calculations
offset_x = robot.offset_x
offset_y = robot.offset_y
offset_z = robot.offset_z
# Calculate base angle and r relative to shoulder joint
def calculate_theta(x, y, a):
# Calculate if we need the + or - in our equations
if (x>-a and y>=0) or (x>a and y<0):
flip = 1
elif (x<-a and y>=0) or (x<a and y<0):
flip = -1
else:
# Critical section (x=a, or x=-a). Infinite slope
# Return 0 or 180 depending on sign
return math.atan2(y, 0)
# Calculate tangent line y = mx + b
m = (x*y - math.sqrt(x*x*y*y-(x*x-a*a)*(y*y-a*a)))/(x*x-a*a)
b = flip * a * math.sqrt(1+m*m)
# Calculate equivalent tangent point on circle
cx = (-flip*m*b)/(1+m*m)
cy = m*cx + flip*b
# Calculate base angle, make angle negative if flip=1
theta = math.atan2(cy, cx) + (-math.pi if flip==1 else 0)
return theta
base_theta = calculate_theta(x, y, l_bs)
cx, cy = l_bs*math.cos(base_theta), l_bs*math.sin(base_theta)
r = math.sqrt((x+offset_x+cx)**2 + (y+offset_y+cy)**2)
# 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
l1*math.sin(a) + l2*math.sin(a-b) - l3*math.sin(a-b-c) - (l3*math.sin(a-b-c)) - (z + offset_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)]]
base, shoulder, elbow, wrist1 = [normalize_degree(deg) for deg in [base_theta, *fsolve(inv_kin_r_z, initial_guess)]]
wrist1 += rx
# Return result
return base, shoulder, elbow, wrist
return base, shoulder, elbow, wrist1, ry, rz
def get_joints_from_xyz_abs(x, y, z):
joints = get_joints_from_xyz_rel(x, y, z)
def get_joints_from_xyz_abs(robot, x, y, z, rx=0, ry=-math.pi/2, rz=math.pi/2, l3offset=0, use_closest_path=True):
rob = robot.robot
joints = get_joints_from_xyz_rel(robot, x, y, z, rx, ry, rz, l3offset=l3offset)
# Return current positions if coordinates don't make sense
if z<0:
return rob.getj()
# Joint offsets
# Base, Shoulder, Elbow, Wrist
inverse = [1, -1, 1, 1]
offsets = [0, 0, 0, -math.pi/2]
inverse = [1, -1, 1, 1, 1, 1]
offsets = [-math.pi/2, 0, 0, -math.pi/2, 0, 0]
if math.degrees(joints[1]) > 137:
print("CRASH! Shoulder at", joints[1] * 180/math.pi)
#else:
#print("Shoulder at", joints[1] * 180/math.pi)
# Get adjusted joint positions
adjusted_joints = [o+j*i for j, o, i in zip(joints, offsets, inverse)]
curr_joints = rob.getj()
def get_complimentary_angle(joint_angle):
if joint_angle<0:
new_angle = joint_angle + 2*math.pi
else:
new_angle = joint_angle - 2*math.pi
if abs(new_angle) > math.radians(350):
return joint_angle
else:
return new_angle
# Use closest path (potentially going beyond 180 degrees)
if use_closest_path:
if abs(get_complimentary_angle(adjusted_joints[0])-curr_joints[0]) < abs(adjusted_joints[0]-curr_joints[0]):
adjusted_joints[0] = get_complimentary_angle(adjusted_joints[0])
# final_joint_positions = []
# for curr_joint, adjusted_joint in zip(curr_joints, adjusted_joints):
# if abs(curr_joint - adjusted_joint) < abs(curr_joint - get_complimentary_angle(adjusted_joint)):
# final_joint_positions.append(adjusted_joint)
# else:
# final_joint_positions.append(get_complimentary_angle(adjusted_joint))
# return final_joint_positions
return adjusted_joints
def move_arc(robot, x, y, z, rx=0, ry=-math.pi/2, rz=math.pi/2):
rob = robot.robot
start_joints = rob.getj()
end_joint = get_joints_from_xyz_abs(robot, x, y, z, rx, ry, rz)
n_points = 50
intermediate_joints = []
for i in range(0, 6):
intermediate_joints.append(np.linspace(start_joints[i], end_joint[i], n_points))
joints = [joint_position for joint_position in zip(*intermediate_joints)]
rob.movejs(joints, acc=2, vel=2, radius=0.1)
def offset_gripper_angle(robot, x, y, z, gripperangle=30, gripperlength=0.20+0.018, flip=False, use_closest_path=True, rzoffset=0):
# gripper angle: from vertical
# gripper length: from joint to start of grip
# to flip, you can use flip=True or make gripper angle negative
limb3 = robot.limb3
# Determine tool rotation depending on gripper angle
if gripperangle < 0:
rz = - math.pi / 2
else:
rz = math.pi / 2
if flip:
gripperangle = -math.radians(gripperangle)
grippery = gripperlength - math.cos(gripperangle) * gripperlength
grippery += math.sin(gripperangle) * limb3
gripperx = math.sin(gripperangle) * gripperlength + limb3 * 2
gripperx -= (1-math.cos(gripperangle)) * limb3
rz = math.pi / 2
# flip the whole wrist
return get_joints_from_xyz_abs(robot, x, y, z-grippery, rx=gripperangle + math.radians(180), l3offset=-gripperx, ry=-3*math.pi/2, rz=rz + rzoffset, use_closest_path=use_closest_path)
else:
gripperangle = math.radians(gripperangle)
grippery = gripperlength - math.cos(gripperangle) * gripperlength
grippery -= math.sin(gripperangle) * limb3
gripperx = math.sin(gripperangle) * gripperlength
gripperx += (1-math.cos(gripperangle)) * limb3
return get_joints_from_xyz_abs(robot, x, y, z-grippery, rx=gripperangle, l3offset=-gripperx, rz=rz, use_closest_path=use_closest_path)
def goto_holder_index(robot, idx, z=0.05, gripperangle=30, flip=False, use_closest_path=True, verbose=False):
joint = robot.config["position_map"][idx]
if verbose:
print("Going to cable holder index", joint["index"], "at position", joint["pos"])
safe_move(robot, joint["pos"][0]/1000, joint["pos"][1]/1000, z, use_closest_path=use_closest_path)
#angles = offset_gripper_angle(joint["pos"][1]/1000, joint["pos"][0]/1000, z, gripperangle=gripperangle, flip=flip)
#rob.movej(angles, acc=2, vel=2)
#return angles
#angles = get_joints_from_xyz_abs(joint["pos"][1]/1000, joint["pos"][0]/1000, 0.05, )
return True
def is_flipped(robot):
rob = robot.robot
wrist1 = rob.getj()[3]
if wrist1 > 0:
return True
else:
return False
def flip(robot):
rob = robot.robot
# A list of safe positions to flip
safe_positions = [(-0.18, -0.108, 0.35),
(0.18, -0.108, 0.35)]
# Find the closest safe position
curr_pos = rob.getl()[:3]
def dist_from_robot(pos):
x, y, z = pos
rx, ry, rz = curr_pos
return math.sqrt((rx-x)**2+(ry-y)**2+(rz-z)**2)
pos_dist_pairs = zip(safe_positions, [dist_from_robot(pos) for pos in safe_positions])
safe_pos = min(pos_dist_pairs, key=lambda x:x[1])[0]
# Flip at safe position
rob.movej(offset_gripper_angle(robot, *safe_pos, flip=is_flipped(robot)), vel=2, acc=2) # Move to safe position
rob.movej(offset_gripper_angle(robot, *safe_pos, flip=(not is_flipped(robot))), vel=2, acc=2) # Flip gripper
# print('flip?: ', is_flipped(robot))
return True
def safe_move(robot, x, y, z, use_closest_path=True):
rob = robot.robot
flip_radius = 0.22 # Min radius on which to flip
r = math.sqrt(x**2 + y**2) # Get position radius
# Flip gripper if needed
if (r <= flip_radius and is_flipped(robot)) or (r > flip_radius and not is_flipped(robot)):
flip(robot)
rob.movej(offset_gripper_angle(robot, x, y, z, flip=is_flipped(robot), use_closest_path=use_closest_path), vel=2, acc=2)
return True
def holder_routine(robot, pos_updates, holder_index, pick_up, verbose=False):
robot = connect(robot)
rob = robot.robot
# Don't attempt to place a tube in the camera slot
if holder_index == 49:
return
if verbose:
fprint('Pickup routine for index' + str(holder_index))
# Go to the correct holder
if pick_up:
goto_holder_index(robot, holder_index, 0.05, use_closest_path=False)
else:
goto_holder_index(robot, holder_index, 0.2, use_closest_path=False)
if pick_up:
open_gripper()
# Move down
curr_pos = rob.getl()
new_pos = curr_pos
new_pos[2] = 0.005
rob.movel(new_pos, vel=0.1, acc=1)
if pos_updates is not None:
pos_updates.put(1)
fprint("Triggering LED interface")
# Pick up or drop off
if pick_up:
close_gripper()
else:
open_gripper()
# Move up
new_pos[2] = 0.2
rob.movel(new_pos, vel=2, acc=1)
was_flipped = is_flipped(robot)
if pos_updates is not None:
pos_updates.put(2)
fprint("Triggering LED interface")
# goto_holder_index(robot, 25, z=0.2)
def pick_up_holder(robot, pos_updates, holder_index, verbose=False):
holder_routine(robot, pos_updates, holder_index, True, verbose=verbose)
def drop_off_holder(robot, pos_updates, holder_index, verbose=False):
holder_routine(robot, pos_updates, holder_index, False, verbose=verbose)
def tray_routine(robot, slot=0, pick_up=True):
robot = connect(robot)
rob = robot.robot
# Default to 0 if invalid value
if slot not in [0, 1, 2, 3]:
slot = 0
slot_prepositions = [(-9.93, -112.67, 144.02, -116.69, -54.13, -10.29),
(-12.35, -124.95, 148.61, -107.27, -54.36, -13.26),
(-16.45, -96.97, 137.85, 58.39, -305.08, 161.75),
(-16.66, -97.28, 138.16, 58.54, -305.05, 161.50)]
# Initial position depending on slot and robot orientation
if slot in [0, 1]:
if is_flipped(robot):
flip(robot)
else:
move_to_home(robot, keep_flip=True)
else:
goto_holder_index(robot, 25, z=0.3)
# Align robot to the slot
if slot in [2,3]:
angles = [(-2.77, -99.64, 131.02, 67.67, 70.04-360, 153.03),
slot_prepositions[slot]]
else:
angles = [(-58, -114.45, 100.52, -45.24, -96.95, 120),
(-39.98, -124.92, 132.28, -61.56, -55.60, -50.77),
slot_prepositions[slot]]
angles = [[x*math.pi/180 for x in move] for move in angles]
rob.movejs(angles,vel=2,acc=1)
# Positions for each slot
slot_distance = .052
slot_height = -.015-.0095
first_slot = -0.3084+0.02
slot_position = [
[first_slot, -0.3426, slot_height, 1.5899, 1.5526, -0.9411],
[first_slot+slot_distance, -0.3426, slot_height, 1.5899, 1.5526, -0.9411],
[first_slot+2*slot_distance, -0.3426, slot_height, 1.5899, 1.5526, -0.9411],
[first_slot+3*slot_distance, -0.3426, slot_height, 1.5899, 1.5526, -0.9411],
]
rob.movel(slot_position[slot],vel=0.2, acc=1)
# Place/Grab the tube
if pick_up:
close_gripper()
else:
open_gripper()
# Move back
tilt = 0.3
curr_pos = rob.getl()
new_pos = curr_pos
if slot==3:
new_pos[0] -= 0.05 #x
new_pos[1] += 0.15 #y
new_pos[2] = 0.09 #z
new_pos[3] += tilt
new_pos[4] += tilt
new_pos[5] += tilt
rob.movel(new_pos, vel=0.2, acc=1)
# Go home to safe position
move_to_home(robot, speed=1, keep_flip=True)
def pick_up_tray(robot, slot=0):
tray_routine(robot, slot, True)
def drop_off_tray(robot, slot=0):
tray_routine(robot, slot, False)
def return_routine(robot, slot, holder_index=None, verbose=False):
# OLD UNUSED
robot = connect(robot)
rob = robot.robot
open_gripper()
was_flipped = is_flipped(robot)
if slot is None:
rob.movej(offset_gripper_angle(robot, -0.15, -0.15, 0.3, flip=was_flipped, use_closest_path=False), vel=4, acc=3)
rob.movej(get_joints_from_xyz_abs(robot, -0.35, -0.15, 0.0, math.pi/2, 0.1), vel=4, acc=3)
close_gripper()
else:
xoffset = 0.051 * slot
rob.movej(offset_gripper_angle(robot, -0.15, -0.15, 0.3, flip=was_flipped, use_closest_path=False), vel=4, acc=3)
rob.movej(get_joints_from_xyz_abs(robot, -0.35+xoffset, -0.15, 0.0, math.pi/2, 0.1), vel=4, acc=3)
close_gripper()
if holder_index is not None:
goto_holder_index(robot, holder_index, 0.2, use_closest_path=False)
curr_pos = rob.getl()
new_pos = curr_pos
new_pos[2] = 0.015
rob.movel(new_pos, vel=0.1, acc=1)
open_gripper()
new_pos[2] = 0.1
rob.movel(new_pos, vel=2, acc=1)
return True
else:
# go to camera
rob.movej(offset_gripper_angle(robot, 0.35, -0.35, 0.3, flip=was_flipped, use_closest_path=False), vel=2, acc=2)
return True
def goto_camera(robot, pos_updates):
robot = connect(robot)
goto_holder_index(robot, 49, 0.2)
def tray_to_camera(robot, pos_updates, slot):
pick_up_tray(robot, slot)
goto_camera(robot, pos_updates)
def holder_to_tray(robot, pos_updates, holder_index, slot):
pick_up_holder(robot, pos_updates, holder_index)
drop_off_tray(robot, slot)
def holder_to_camera(robot, pos_updates, holder_index, verbose=False):
robot = connect(robot)
fprint("Bringing tube at " + str(holder_index) + " to camera")
rob = robot.robot
pick_up_holder(robot, pos_updates, holder_index)
goto_camera(robot, pos_updates)
def camera_to_holder(robot, pos_updates, holder_index, verbose=False):
robot = connect(robot)
rob = robot.robot
drop_off_holder(robot, pos_updates, holder_index)
def open_gripper():
fprint("Opening gripper")
c = ModbusClient(host="192.168.1.21", port=502, auto_open=True, auto_close=False)
c.write_single_register(112, 0b0)
c.write_single_register(435, 0b10000000)
time.sleep(0.5)
c.write_single_register(112, 0b0)
c.write_single_register(435, 0b10000000)
time.sleep(0.5)
c.close()
#c.close()
def close_gripper():
fprint("Closing gripper")
c = ModbusClient(host="192.168.1.21", port=502, auto_open=True, auto_close=False)
c.write_single_register(435, 0b00000000)
c.write_single_register(112, 0b1)
time.sleep(0.5)
c.write_single_register(435, 0b00000000)
c.write_single_register(112, 0b1)
time.sleep(0.5)
c.close()
#
def get_position_thread(robot, pos_updates):
robot = connect(robot)
rob = robot.robot
oldvals = rob.getl()
deltavals = [0,0,0]
import uptime
t = 0.01
count = 0
while True:
start = uptime.uptime()
if pos_updates.qsize() < 2:
vals = rob.getl()
if vals != oldvals:
if pos_updates is not None:
pos_updates.put(tuple(oldvals))
#time.sleep(0.01)
# deltavals = list()
# deltavals.append(vals[0]-oldvals[0])
# deltavals.append(vals[1]-oldvals[1])
# deltavals.append(vals[2]-oldvals[2])
# count = 0
oldvals = vals
# else:
# count += 0.2
# if count < 1:
# tmpvals = vals
# tmpvals[0] = oldvals[0] + deltavals[0]*count
# tmpvals[1] = oldvals[1] + deltavals[1]*count
# tmpvals[2] = oldvals[2] + deltavals[2]*count
# pos_updates.put(tuple(tmpvals))
while start + t > uptime.uptime():
time.sleep(0.0001)
# 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")
with open('config.yml', 'r') as fileread:
#global config
config = yaml.safe_load(fileread)
robot = Rob(config) # robot of type Rob is the custom class above
#powerup_arm(robot)
robot = connect(robot)
init_arm(robot)
rob = robot.robot # rob is robot.robot is the urx robot class, what we've been using previously
move_to_packup(robot)
# pick_up_holder(robot, 2)
#drop_off_tray(robot, 0)
# drop_off_tray(robot, 1)
# drop_off_tray(robot, 2)
# drop_off_tray(robot, 3)
# pick_up_tray(robot, 1)
# drop_off_holder(robot, 5)
# pick_up_holder(robot, 26)
# drop_off_tray(robot, 3)
# for i in range(0,54):
# pick_up_holder(robot, None, i)
# #print('Drop off', i+1)
# drop_off_tray(robot, 0)
# #input()
# # holder_to_camera(robot, 0)
# # camera_to_holder(robot, 0)
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)
config = None
# 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

View File

@ -70,7 +70,7 @@ def fprint(msg, settings = None, sendqueue = None):
except Exception as e:
try:
print('[????:' + frm.function + ']:', str(msg))
print('[util:fprint]: ' + str(e))
#print('[util:fprint]: ' + str(e))
except:
print('[????]:', str(msg))

View File

@ -81,7 +81,7 @@
socket.send(message);
console.log('Message sent', message);
}
setInterval(ping, 1500);
//setInterval(ping, 1500);
// setInterval(() => {
// updateServiceStatus('serviceA', 'down');
@ -150,7 +150,7 @@
setInterval(updateClock, 100);
</script>
<iframe src="http://192.168.1.12:3000/d-solo/cdiqwmlr8c9ogf/sensors?orgId=1&refresh=5s&from=1714431825720&to=1714433625721&theme=light&panelId=7" width="450" height="200" frameborder="0"></iframe>
</body>
</html>