91 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
42 changed files with 5315 additions and 1293 deletions

4
.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,8 +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

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

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

@ -30,6 +30,7 @@ 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"]
@ -47,7 +48,6 @@ 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)
@ -70,6 +70,8 @@ def query_search(partnum, source):
if idx < 0:
fprint("Could not find part in API: " + partnum)
return False
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"]
@ -79,15 +81,20 @@ 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)
@ -157,8 +164,9 @@ def touch(path):
def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
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:
failed = list()
actualpartnums = list()
def _try_download_datasheet(partnum, output_dir, dstype): # Guess datasheet URL
@ -186,7 +194,7 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
# 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"
@ -215,7 +223,7 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
# 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"
@ -242,7 +250,7 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
# 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]
@ -251,34 +259,35 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
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..."
out = 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..."
out = 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):
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
@ -286,8 +295,9 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
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
# 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:
@ -297,53 +307,80 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
if search_result is not False:
# Download high resolution part image if available and needed
#oldpartnum = partnum
partnum = search_result["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") 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 and cache:
out = __use_cached_datasheet(partnum, path, output_dir, dstype)
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:
out = __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 and cache:
out = __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, 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, dstype) is not False:
out = __downloaded_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 = __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
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)
out = __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"
@ -370,11 +407,11 @@ def get_multi(partnums, delay=0.25, dir="cables/", cache=True):
time.sleep(delay)
if not success:
fprint("Failed to download datasheet for part " + partnum)
bar.text = "Failed to download datasheet for part " + partnum
# bar.text = "Failed to download datasheet for part " + partnum
failed.append((partnum, dstype))
bar(skipped=True)
bar(skipped=True)
time.sleep(delay)
# bar(skipped=True)
# bar(skipped=True)
if len(failed) > 0:
fprint("Failed to download:")
@ -405,6 +442,7 @@ if __name__ == "__main__":
# ]
partnums = [
# Actual cables in Jukebox
"BL3092A",
"AW86104CY",
"AW3050",
"AW6714",
@ -434,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",
@ -448,13 +487,18 @@ if __name__ == "__main__":
"BLFISD012R9",
"BLFDSD012A9",
"BLFSSL024NG",
"BLFISX006W0",
"BLFISX00103",
"BLC6D1100007"
"BLFISX006W0", # datasheet only
"BLFISX00103", # invalid
"BLC6D1100007" # invalid
]
print(query_search("74002", "Belden"))
#get_multi(partnums, 0.25)
#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")

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")

View File

@ -9,6 +9,7 @@ import os
import signal
from PIL import Image
from label_image import generate_code
from label_document import generate_pdf
def input_cable():
@ -22,7 +23,7 @@ def input_cable():
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()) + "/", cache=False)
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:
@ -61,9 +62,17 @@ def input_cable():
imgstr = "BL"
elif output[1] == "Alphawire":
imgstr = "AW"
img = generate_code(imgstr + output[0])
os.makedirs("labels", exist_ok=True)
img.save("labels/" + imgstr + output[0] + ".png")
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

View File

@ -3,8 +3,13 @@ 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
@ -134,7 +139,8 @@ for charset in (CODE128A, CODE128B):
def generate_code(data, show=False, check=False):
img = code128_image(data)
#img = code128_image(data)
img = qr_image(data)
if show:
img.show()
#img.show()
@ -198,6 +204,7 @@ def code128_format(data):
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)
@ -227,12 +234,87 @@ def code128_image(data, height=100, thickness=3, quiet_zone=False):
#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("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,6 +14,9 @@ import cv2
import numpy as np
from uptime import uptime
class LEDSystem():
sender = None
debug = True
config = None
@ -30,8 +33,14 @@ firstrun = True
changecount = 0
animation_time = 0
start = uptime()
solid_controllers = None
def ping(host):
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

@ -16,6 +16,7 @@ from util import win32
import os
import glob
import sys
from PIL import Image
def touch(path):
with open(path, 'a'):
@ -37,7 +38,7 @@ def extract_table_name(table_start, searchpage, reader, dstype, fallbackname):
ymax = table_start + 10
elif dstype == "Alphawire":
ymin = table_start - 5
ymax = table_start + 10
ymax = table_start + 20
page = reader.pages[searchpage - 1]
parts = []
def visitor_body(text, cm, tm, fontDict, fontSize):
@ -52,14 +53,52 @@ def extract_table_name(table_start, searchpage, reader, dstype, fallbackname):
return text_body
#fprint(text_body)
def parse(filename, output_dir, partnum, dstype):
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
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', 't'])
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:
@ -95,11 +134,26 @@ def parse(filename, output_dir, partnum, dstype):
#fprint(table_start)
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":
table_list_raw[text_body] = table
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)
@ -134,6 +188,15 @@ 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()
@ -141,6 +204,7 @@ def parse(filename, output_dir, partnum, dstype):
#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]
@ -177,11 +241,11 @@ def parse(filename, output_dir, partnum, dstype):
if dstype == "Alphawire" and table_name_2.find("\n") >= 0:
torename[table_name_2] = table_name_2[0:table_name_2.find("\n")]
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(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()
@ -199,9 +263,9 @@ 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():
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]
@ -251,9 +315,6 @@ def parse(filename, output_dir, partnum, dstype):
#fprint(table_name)
#fprint(previous_table)
main_key = previous_table
cont_key = table_name
#fprint(tables)
@ -267,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 & 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):
@ -291,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
@ -308,16 +401,17 @@ def parse(filename, output_dir, partnum, dstype):
#print(output_table)
#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)
# 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_" + output_table["searchspecs"]["id"] + ".json", 'w') as json_file:
with open(output_dir + "/search.json", 'w') as json_file:
json.dump(output_table["searchspecs"], json_file)
with open(output_dir + "/specs_" + output_table["partnum"] + ".json", 'w') as json_file:
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
@ -346,12 +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(",")))
@ -369,4 +471,4 @@ def flatten(tables):
if __name__ == "__main__":
print(parse("cables/3050/datasheet.pdf", "cables/3050", "3050", "Alphawire"))
print(parse("cables/3050/datasheet-new.pdf", "cables/3050", "3050", "Alphawire"))

View File

@ -1,10 +1,11 @@
# Runtime
camelot-py
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
@ -16,10 +17,11 @@ numpy
scipy
ipywidgets
pandas
pyarrow
#pyarrow
ghostscript
pyzbar
segno
pyModbusTCP
# Development
matplotlib

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

View File

@ -10,7 +10,7 @@ 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"], "optimize": 0, "silent": True, "include_files": ["gs10030w64.exe"], "excludes": ["scipy", "torch"]}
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"

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,13 +294,14 @@ def normalize_degree(theta):
# Return angle
return normalized_theta
def get_joints_from_xyz_rel(x, y, z, rx=0, ry=-math.pi/2, rz=0, initial_guess = (math.pi/2, math.pi/2, 0)):
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
offset_x, offset_y, offset_z = (0, 0, 0.14) # Tool offset
l_bs, l1, l2, l3, l_wt = (0.1333, .425, .39225, .1267, .0997) # Limb lengths
#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
@ -238,19 +317,16 @@ def get_joints_from_xyz_rel(x, y, z, rx=0, ry=-math.pi/2, rz=0, initial_guess =
# 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(l_bs)
r = math.sqrt((x-cx)**2 + (y-cy)**2)
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)
# Formulas to find out joint positions for (r, z)
@ -264,98 +340,451 @@ def get_joints_from_xyz_rel(x, y, z, rx=0, ry=-math.pi/2, rz=0, initial_guess =
# Normalize angles
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, wrist1, ry, rz
def get_joints_from_xyz_abs(x, y, z, rx=0, ry=-math.pi/2, rz=math.pi/2):
joints = get_joints_from_xyz_rel(x, y, z, rx, ry, rz)
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, 1, 1]
offsets = [-math.pi/2, 0, 0, -math.pi/2, 0, 0]
# Return adjusted joint positions
return [o+j*i for j, o, i in zip(joints, offsets, inverse)]
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)
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,
math.radians(62)]
# 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.7, 0, 0)
rob.movej(angles, acc=2, vel=2)
# joints = []
# for i in np.linspace(-0.3, -0.7, 50):
# joints.append(get_joints_from_xyz_abs(i, 0, 0))
# rob.movejs(joints, acc=2, vel=2)
# time.sleep(5)
# angles = get_joints_from_xyz_abs(0, -0.6, 0)
# rob.movej(angles, acc=2, vel=2)
# 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

@ -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>