From f262b5c24bf8406dafffe9440cebaf94fb2305f4 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 23 Sep 2020 01:27:53 +0200 Subject: [PATCH] configure script --- README.md | 36 +- ethertools/dac3.py | 235 ++++++++++ ethertools/sitter.app/Contents/Info.plist | 35 ++ ethertools/sitter.app/Contents/MacOS/Python | Bin 0 -> 34224 bytes ethertools/sitter.app/Contents/MacOS/sitter | 23 + ethertools/sitter.app/Contents/PkgInfo | 1 + .../Contents/Resources/PythonApplet.icns | Bin 0 -> 63136 bytes .../Resources/__argvemulator_sitter.py | 4 + .../sitter.app/Contents/Resources/sitter.py | 414 ++++++++++++++++++ ethertools/sitter.py | 414 ++++++++++++++++++ ethertools/talk3.py | 95 ++++ ethertools/watch.py | 21 + updateUI.py | 20 +- 13 files changed, 1273 insertions(+), 25 deletions(-) create mode 100755 ethertools/dac3.py create mode 100755 ethertools/sitter.app/Contents/Info.plist create mode 100755 ethertools/sitter.app/Contents/MacOS/Python create mode 100755 ethertools/sitter.app/Contents/MacOS/sitter create mode 100755 ethertools/sitter.app/Contents/PkgInfo create mode 100755 ethertools/sitter.app/Contents/Resources/PythonApplet.icns create mode 100755 ethertools/sitter.app/Contents/Resources/__argvemulator_sitter.py create mode 100755 ethertools/sitter.app/Contents/Resources/sitter.py create mode 100755 ethertools/sitter.py create mode 100755 ethertools/talk3.py create mode 100755 ethertools/watch.py diff --git a/README.md b/README.md index 4c0b6e2..8d7a061 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ LJ is in dev : versions in this repository will always be core functionnal : acc # Install # -With Linux Buster, in LJ directory, type in a terminal window : + +- Linux Buster : in LJ directory, type in a terminal window : cd server ./install.sh @@ -82,22 +83,23 @@ cd server Server directory also contains config files for optionnal nginx, supervisorctl and syncthing. -For OS X, you need brew already installed, then : +- OS X : you need brew already installed, then : brew update brew upgrade brew install redis +cd server type all install.sh commands beginning line 4. An OS X install script soon !! -For Linux and OS X : + +- KVM : +an ISO is available here : https://www.tmplab.org/wp-content/lazer-iso.zip + +- Postinstall for all : You probably want redis bound to all network interfaces : comment the bind line in /etc/redis/redis.conf and restart it. -WebUI pages needs to know the LJ IP address. So you need to change the line wwwIP = "192.168.2.43" in updateUI.py then run python updateUI.py - -Using the same idea check all ip address in LJ.conf. - -There is a nice websocket debug tool : websocat. +You probably also want to run the configure script to enter your etherdreams IPs,... python3 configure.py @@ -342,20 +344,18 @@ See links section for great etherdream managing tools. About hardware setup, especially if you have several lasers : ILDA cables are insanely expensive. For each DAC, buy a very small ILDA cable and RJ 45 cable, all DAC goes to a local switch and only one long cable to your You may also consider the Power Over Ethernet 'POE' option. Buy a POE splitter and connect everything to the ether dream fixed near your laser. You can have then a simple and very long network cable and use a Power Over Ethernet injector or switch close to the driving computer. Beware some vendors use 24V POE Injector : POE injectors and splitters must match. +# +# Ethertools directory +# + +2 useful and always working tools from j4cdac github repository : sitter and talk +- Sitter will display all real etherdreams available on the network and their state (playing, idle,...). python sitter.py or use the compiled version (for macOS). +- Talk : will draw a 4 colors square. python3 talk3.py # # Links # -Tools : - -Display all connected etherdreams on the network : ![sitter](https://github.com/j4cbo/j4cDAC/tree/master/tools/sitter) -python sitter.py - -Draw simple square : ![talk](https://github.com/j4cbo/j4cDAC/tree/master/tools/tester) - -python talk.py - Generic : @@ -366,6 +366,8 @@ Generic : ![Laser Faq](https://www.repairfaq.org/sam/lasersam.htm) +There is a nice websocket debug tool : websocat. + # # LJ commands reference # diff --git a/ethertools/dac3.py b/ethertools/dac3.py new file mode 100755 index 0000000..325672b --- /dev/null +++ b/ethertools/dac3.py @@ -0,0 +1,235 @@ +# j4cDAC test code +# +# Copyright 2011 Jacob Potter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import socket +import time +import struct + +def pack_point(x, y, r, g, b, i = -1, u1 = 0, u2 = 0, flags = 0): + """Pack some color values into a struct dac_point. + + Values must be specified for x, y, r, g, and b. If a value is not + passed in for the other fields, i will default to max(r, g, b); the + rest default to zero. + """ + #print(r,g,b) + if i < 0: + i = max(r, g, b) + + return struct.pack(" len(self.buf): + + buffy = self.conn.recv(4096) + #print(buffy) + + self.buf += buffy + + obuf = self.buf + self.buf = obuf[l:] + return obuf[:l] + + def readresp(self, cmd): + """Read a response from the DAC.""" + data = self.read(22) + response = data[0] + cmdR = chr(data[1]) + status = Status(data[2:]) + + status.dump() + + if cmdR != cmd: + raise ProtocolError("expected resp for %r, got %r" + % (cmd, cmdR)) + if response != ord('a'): + raise ProtocolError("expected ACK, got %r" + % (response, )) + + self.last_status = status + return status + + def __init__(self, host, port = 7765): + """Connect to the DAC over TCP.""" + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + conn.connect((host, port)) + conn.settimeout(1) + self.conn = conn + self.buf = b'' + + # Read the "hello" message + first_status = self.readresp("?") + first_status.dump() + + ''' + self.conn.sendall('v') + self.firmware_string = self.read(32).replace("\x00", " ").strip() + print "Firmware: %s" % (self.firmware_string, ) + ''' + + def begin(self, lwm, rate): + cmd = struct.pack(" + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeOSTypes + + **** + fold + disk + + CFBundleTypeRole + Viewer + + + CFBundleExecutable + sitter + CFBundleIconFile + PythonApplet.icns + CFBundleIdentifier + sitter + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + sitter + CFBundlePackageType + APPL + CFBundleSignature + ???? + + diff --git a/ethertools/sitter.app/Contents/MacOS/Python b/ethertools/sitter.app/Contents/MacOS/Python new file mode 100755 index 0000000000000000000000000000000000000000..1cfc9df9be554cdea6a5fa5da84ea58a0e57a875 GIT binary patch literal 34224 zcmeHP2V4``)}Is*q=Y7j0tpx_2+2@H1W`Z`5L5)giZO%$k-A9`a6y_4!G;w<0dW<@ zf+D(hP*(*N6?<7%7aJm06jT=8xhX6ny6^YxdvD*qZ_Y0#x6HX`&i&8KoqOlZnd-Jf z$1n^d1NQ)?V3-CZwm$Hu3aka(6T?t(NU5w25`qW>2m}ZO2m}ZO2m}ZO2m}ZO2m}ZO z2m}ZO2n7C42-JLRY?CegB@Y4A!1z`whRuhR1Z)S);rKYayG?cT9S;RLp~nPYA=EAl z{y3b8Zj&c=5<+d{?I1<_Rgf`~;&8L>*r9#CHjI^d9X zOXyg5NREZ8^F-%PA`HmOkB977z{8&oJseIHU?_z$Ml6JVf7xCMse`tiZASPZJ%~7* z2=2FZC^4iDnUuQdke?S)9Wlb_P$4&xGn*?EA)TMwp#wT}Kv1WP4%Ajo98NG-%$1kE z)G?4Dg<{}0bRZn1*Mhg_I4?I>Px*O`!v<)N*hg1-P9s@4nZW6aL^_CpVZA#FkcYAj zl)WKEr#`|GZLfeb!okKE!@?j{0rmk#g!Ic9$)juNYor71CC90Y09-V z*%5*uc5p&OFqHcOBO7$*>|np?S!LoJS*g=7&h7x(53&L6TLbu?C3M|(M`+)ZfziHD zKd1`u56t#U5Q+JbY;Qr3kSk1JkA}m~kBb(Di`YI1;;`r_R*1ZY9l#fg1kq6WQSfvbaCN=#esj`ktI+rn>sgy}cKS`Zm5!(y} z)u+l}Mh2DnG!D`xe`x%CU`Ak5Mr=z)QtMM>;GpaP#%^96!#@m*X>KqR04g99adH zz=zch_T_dTU$@a9ZEeMIL2C}wB{XkG)Hy}-XGCb;i}(Yw00`yLdQ25sYDik6+Aql) z){+6hGl9`W7_EuSfpK6PV1ft)2m}ZO2m}ZO2m}ZO2m}ZO2m}ZO2m}ZO2n7DV2xvJW zr=P@I&ItlvGdR2_2(jguP)XB?MSL+oYBtkARjmdJ-mP)^8LZH67;wP zGnM2?k~~YAm)L}!jgT;sB=084OI_J=KREJt0y!b>QU`wMFMA4jjpgzq;NroHMwuLHbxZa{x$-PP%b!e6$9r7E7(?Ke&B z#R4!MS?vmpQgjS;pbipA2T&b)rvP5! zP#{jQ)Wvm>97S7AD(4#=EoO{z#*OsU@S(VsrHz%PvL}yT)C^nKZ^$r@eB+VO5)vhzhwFZ34np0MV~#fKINN__`p-$DM z>MRv(IThzJj`~A@OWp+fg4?x>Wy&}mZSF_yjq9f948A0AUo~5~ttos7%V|rq``Bcq z8MrMHpi|kn1wOc7P{Du&reR`n3=Ao8ad9jzI$K%1=t#CC$K$evaPqNu!idfX7EX9L zuq3RIcYKw%Q2+84FU`pt=3dY|UvlnE(WN5;@OK*=>Gx0X3dWgK|UdiDfAL)}@A1%AH(`)4EiV0p`_MY5vE!R@! z)SNmtEqzDLq<(1zk9y-#l=X^Vt%D}pk^}p2w%o{Q4RnM!ZRCW$9 zxu7sft5QqXezyCXy?OZX-NQt>LFWt4E4ZJ_Hy->`bj#aGwrd(g>XziV-nL2W{rUr? z%I(t>b;jfYgXlwxc4h@{x?NYIS3asO_bxqTP_WjF-GeQfMz*;m$7Gizdo8RpDceC0 z!yagnmtWYN-V_ak!eKm(QY0G`$Xzh{j|~d8xQ*2i7#DDBTNo6qVNk$_$%_9G8~*6J zf4E|sGJVAzBX^$$g~k~P^DG){H%;>Pf1atIaqojy#Je6PCOG+N?-4CN%2$$|i`RX) zTpg0f4A;C=bogOkvy=4I8|SEQcF&x4Rd=ZMxl^>UH9CUJRrHMqQ+BaC|%1 zb6}sdZ8hZw)o;x(xPH#D+hO&!Zt*<-BB5?>pxb|*-;r0BbBcR&Bi-E8NwmQQst z7akXRyUnG0hx@E?&Nw9wQqf3vUTD1-uI9_^G zj9XP@A-(R?NONE$oZaQhSNzr$-t7hahgWzS3Eg}Q8PqhA66}!*jLGg+ykG?qp85ti zlaDZDlCBZD{zYFM0Pxh49l~Thb$_4KGGBg#fE&f*GvKx*4iktNypB5=pAnS6;6^1d zVz@#v$qn79_9fJw`s=ew-1T*$Cux- zO|D$%e`{;bK`WO}H?MuSac-gSfxbsqx-hlO&d?0^kIkFGJL{FA=d*qBwc8I{FDdsJ zS{fT`jHf9W!4Re>8@@=>ZZ!mrZ7|`ng!zxOhzI-=6CORh7hDW5 z=~9@Mr7IrQ^sY z@0#668mz(-2ORdNiGE3Toq2J`Dd+2r$CeKJ^_=C$u^Vpv_|S3x%8$-83VjJrY5ls& z$2oKMF6>`h&ZhP^_KZ41o$afWVL}_Eb4@|RBJo+`osi9eQ3-lI8-KNO2rjZWEB3nJ z=4@@681A?&$V)Gu^Yn_2Q4!hU9OH5Cn=e+je4n^fp)vDBQqn%Zq5B`_yfQcVkRKU! z_0uy``jdRy#XVn(9Iyd)1 zqQOTpVMY zpN(;^@j)HK7D?Cen{%B1ks;0z0Yaz3Yyh_^zzbLl%)i-d9!%*)(i#2NCU`l6r*nqv z&|k$Anmu7TR3+z?40>=Vy{+8*{l~G1vz(^ZMwDio6(*NVNp*WtHK*`IwI}oEeX*s_ zUu1>W8vcBGsNa#bqa8Lic!f{0X?gkJ?ppSqF>%_gaixh2#yVVtn3J>qgEm zlW0CEgBtEWUaaEqrdciTu=0UZ?w+yPqfE|L_|UFbBpbWDXkJ>Cvrv^!KkZav^vj@I zPcoN(cXY+vb3J0c&u-@EWF`DWd-i(RimJOBn(u3er4QcI=c=Lcvp3sU2j#!{RIeX1 zfz-6F;QkNoi6wpJJWF13qn~hh;st$w$LsXSkeiQ1MP(I=3|;Pdhq+>!(HTB=eL_G& zRjOqgX$PEK#jrrcQ|JBV10ph`(<-&o5)_tNaJR>__1|g-I(Q}AZK)LfkBv)f$P zAv)K3!Jzvg*C!|adSmLt1b-{XgLRzQxot{COUu@ahNv~9`X=n(87Fcp9)H}-KGvcq ziQ}7lXXva4*$-l;-Rg7r)Xv?XjwG4+6?0b)tk~)#Z1!S4tRK}7p0K61VWN6Qe70?n zc*M5f7A#jwaXoe<3BP}QZ%Vk<`kXSKn&3WTPVk>JWBO~4+Q&ONyx3z(rVd!J@yy2Q z20tm9H>NXQj(Ib8^@`x~mzUq~duKpPZ#>@Db#Fv!KYiEtKRFdn_FYS}85*xTe!uF~ zMcV_mEgj<<%Mzb29KabJf59izsIQSwXG2z&FYVrCmAfN%`w7r8pCAGO0s#U60s#U6 z0s#U60s#U60s#U60)an)KwIO-8fY&2&Y#513cgqBOsJa=jNGqw-NyeTC;Qh4+L5ST zOol9yrddAvG?F+dFUUCxp)Z<(JS*((de z1_?x6{xs|W*&451X&%>fzr-8 zpx8mO^QTM9yu`E503pP^O5qRf7s<*A>4R4|7^n>Hf5uEmCjx&f3?98Y@D*Zszy|ah zL09SsgYOyx4}y{~c#HknFSw@x(1Z0rDooKYW46J&*Dro} zGQ5MH5BBh|EW3GFF|wtj4%$N9dDylC>YeS3F`wk3hWO-pb@3^n>oDj=cEUEQ<4jm| zXEK<`PPA`X*`Q0AAuoURTLOSdx8EA*@+|@M$$@3}H4rOF*OO>X#{ftfI55oZjV}Gp zp!74+_^d<}2m}ZO2m}ZO2m}ZO2m}ZO2m}ZO2m}ZO2m}ZO{;Lsi8xH0vaNS%Wp@Q=l z7WR_0eQ$_ql^bpfd3k|F!a4%YZ93(V-x&E|k$+U4%6!RxGFHC((0GY$viNv6C`k4N zDH9m^kLB_4fqxtyr6#m755I=G_h;})d`=hF{P+EYeb6{tMzEzUAWV!hTlSjgyO`g4gSBXka#&>}j^j{LZ7qK5vz|cwDH?P{(}x zSF-r{L|LcMK|oG>1h98Eaq|Sfu!Jpc53XaehzArnO zsNdUum;g Y9#ba<%zSVZJq{p>eQ(^r^2Z|=kI3I>+fx) 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 literal 0 HcmV?d00001 diff --git a/ethertools/sitter.app/Contents/Resources/__argvemulator_sitter.py b/ethertools/sitter.app/Contents/Resources/__argvemulator_sitter.py new file mode 100755 index 0000000..4f0c146 --- /dev/null +++ b/ethertools/sitter.app/Contents/Resources/__argvemulator_sitter.py @@ -0,0 +1,4 @@ +import argvemulator, os + +argvemulator.ArgvCollector().mainloop() +execfile(os.path.join(os.path.split(__file__)[0], "sitter.py")) diff --git a/ethertools/sitter.app/Contents/Resources/sitter.py b/ethertools/sitter.app/Contents/Resources/sitter.py new file mode 100755 index 0000000..a1aad5e --- /dev/null +++ b/ethertools/sitter.app/Contents/Resources/sitter.py @@ -0,0 +1,414 @@ +#!/usr/bin/python +# j4cDAC "sitter" +# +# Copyright 2012 Jacob Potter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import socket +import time +import struct + +def pack_point(x, y, r, g, b, i = -1, u1 = 0, u2 = 0, flags = 0): + """Pack some color values into a struct dac_point. + + Values must be specified for x, y, r, g, and b. If a value is not + passed in for the other fields, i will default to max(r, g, b); the + rest default to zero. + """ + + if i < 0: + i = max(r, g, b) + + return struct.pack(" len(self.buf): + self.buf += self.conn.recv(4096) + + obuf = self.buf + self.buf = obuf[l:] + return obuf[:l] + + def readresp(self, cmd): + """Read a response from the DAC.""" + data = self.read(22) + response = data[0] + cmdR = data[1] + status = Status(data[2:]) + +# status.dump() + + if cmdR != cmd: + raise ProtocolError("expected resp for %r, got %r" + % (cmd, cmdR)) + + if response != "a": + raise ProtocolError("expected ACK, got %r" + % (response, )) + + self.last_status = status + return status + + def __init__(self, macstr, bp): + self.macstr = macstr + self.firmware_string = "-" + self.got_broadcast(bp) + + try: + t1 = time.time() + self.connect(self.last_broadcast.ip[0]) + t = time.time() - t1 + self.conn_status = "ok (%d ms)" % (t * 500) + + if self.last_broadcast.sw_rev < 2: + self.firmware_string = "(old)" + else: + self.conn.sendall('v') + self.firmware_string = self.read(32).replace("\x00", " ").strip() + except e: + self.conn_status = str(e) + + def connect(self, host, port = 7765): + """Connect to the DAC over TCP.""" + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + conn.settimeout(0.2) + conn.connect((host, port)) + self.conn = conn + self.buf = "" + + # Read the "hello" message + first_status = self.readresp("?") + first_status.dump() + + + + def begin(self, lwm, rate): + cmd = struct.pack(">", self.update_selection) + + def update_selection(self, lb=None): + if self.dac_display: + try: + dac_obj = self.dac_list[self.index(ACTIVE)] + except: + return + self.dac_display.display_dac(dac_obj) + + def got_packet(self, bp): + macstr = bp.macstr() + if macstr not in self.dac_macstr_map: + new_dac = DAC(macstr, bp) + self.insert(END, macstr[6:]) + self.dac_list.append(new_dac) + self.dac_macstr_map[macstr] = new_dac + dac_obj = new_dac + else: + dac_obj = self.dac_macstr_map[macstr] + dac_obj.got_broadcast(bp) + + if len(self.dac_list) == 1: + self.selection_set(0) + self.update_selection() + + def check_on_dac(): + if time.time() - dac_obj.last_broadcast_time < 2: + return + + idx = self.dac_list.index(dac_obj) + self.dac_list.remove(dac_obj) + del self.dac_macstr_map[macstr] + self.delete(idx) + self.dac_display.display_none() + + + self.after(2000, check_on_dac) + + + +# Set up the basic window +root = Tk() +root.title("Ether Dream") +root.resizable(FALSE, FALSE) +frame = Frame(root) +frame.grid() + +disp = DacDisplay(root) +disp.grid(row=0, column=1, padx=5, pady=5) +tracker = DacTracker(root, height=22) +tracker.grid(row=0, column=0, padx=5, pady=5) +tracker.dac_display = disp + +# Set up queue checker +packet_queue = Queue.Queue() +def queue_check(): + try: + while True: + data, addr = packet_queue.get_nowait() + tracker.got_packet(BroadcastPacket(data, addr)) + except Queue.Empty: + root.after(100, queue_check) + +root.after(100, queue_check) + +# Set up listening socket and thread +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(("0.0.0.0", 7654)) +def socket_thread(): + while True: + packet_queue.put(s.recvfrom(1024)) +thread.start_new(socket_thread, ()) + +root.mainloop() diff --git a/ethertools/sitter.py b/ethertools/sitter.py new file mode 100755 index 0000000..677971b --- /dev/null +++ b/ethertools/sitter.py @@ -0,0 +1,414 @@ +#!/usr/bin/python +# j4cDAC "sitter" +# +# Copyright 2012 Jacob Potter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import socket +import time +import struct + +def pack_point(x, y, r, g, b, i = -1, u1 = 0, u2 = 0, flags = 0): + """Pack some color values into a struct dac_point. + + Values must be specified for x, y, r, g, and b. If a value is not + passed in for the other fields, i will default to max(r, g, b); the + rest default to zero. + """ + + if i < 0: + i = max(r, g, b) + + return struct.pack(" len(self.buf): + self.buf += self.conn.recv(4096) + + obuf = self.buf + self.buf = obuf[l:] + return obuf[:l] + + def readresp(self, cmd): + """Read a response from the DAC.""" + data = self.read(22) + response = data[0] + cmdR = data[1] + status = Status(data[2:]) + +# status.dump() + + if cmdR != cmd: + raise ProtocolError("expected resp for %r, got %r" + % (cmd, cmdR)) + + if response != "a": + raise ProtocolError("expected ACK, got %r" + % (response, )) + + self.last_status = status + return status + + def __init__(self, macstr, bp): + self.macstr = macstr + self.firmware_string = "-" + self.got_broadcast(bp) + + try: + t1 = time.time() + self.connect(self.last_broadcast.ip[0]) + t = time.time() - t1 + self.conn_status = "ok (%d ms)" % (t * 500) + + if self.last_broadcast.sw_rev < 2: + self.firmware_string = "(old)" + else: + self.conn.sendall('v') + self.firmware_string = self.read(32).replace("\x00", " ").strip() + except Exception, e: + self.conn_status = str(e) + + def connect(self, host, port = 7765): + """Connect to the DAC over TCP.""" + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + conn.settimeout(0.2) + conn.connect((host, port)) + self.conn = conn + self.buf = "" + + # Read the "hello" message + first_status = self.readresp("?") + first_status.dump() + + + + def begin(self, lwm, rate): + cmd = struct.pack(">", self.update_selection) + + def update_selection(self, lb=None): + if self.dac_display: + try: + dac_obj = self.dac_list[self.index(ACTIVE)] + except: + return + self.dac_display.display_dac(dac_obj) + + def got_packet(self, bp): + macstr = bp.macstr() + if macstr not in self.dac_macstr_map: + new_dac = DAC(macstr, bp) + self.insert(END, macstr[6:]) + self.dac_list.append(new_dac) + self.dac_macstr_map[macstr] = new_dac + dac_obj = new_dac + else: + dac_obj = self.dac_macstr_map[macstr] + dac_obj.got_broadcast(bp) + + if len(self.dac_list) == 1: + self.selection_set(0) + self.update_selection() + + def check_on_dac(): + if time.time() - dac_obj.last_broadcast_time < 2: + return + + idx = self.dac_list.index(dac_obj) + self.dac_list.remove(dac_obj) + del self.dac_macstr_map[macstr] + self.delete(idx) + self.dac_display.display_none() + + + self.after(2000, check_on_dac) + + + +# Set up the basic window +root = Tk() +root.title("Ether Dream") +root.resizable(FALSE, FALSE) +frame = Frame(root) +frame.grid() + +disp = DacDisplay(root) +disp.grid(row=0, column=1, padx=5, pady=5) +tracker = DacTracker(root, height=22) +tracker.grid(row=0, column=0, padx=5, pady=5) +tracker.dac_display = disp + +# Set up queue checker +packet_queue = Queue.Queue() +def queue_check(): + try: + while True: + data, addr = packet_queue.get_nowait() + tracker.got_packet(BroadcastPacket(data, addr)) + except Queue.Empty: + root.after(100, queue_check) + +root.after(100, queue_check) + +# Set up listening socket and thread +s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +s.bind(("0.0.0.0", 7654)) +def socket_thread(): + while True: + packet_queue.put(s.recvfrom(1024)) +thread.start_new(socket_thread, ()) + +root.mainloop() diff --git a/ethertools/talk3.py b/ethertools/talk3.py new file mode 100755 index 0000000..183e613 --- /dev/null +++ b/ethertools/talk3.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# +# j4cDAC test code +# +# Copyright 2011 Jacob Potter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import dac3 as dac + +class SquarePointStream(object): + ''' + def produce(self): + pmax = 15600 + pstep = 100 + cmax = 65535 + while True: + for x in xrange(-pmax, pmax, pstep): + yield (x, pmax, cmax, 0, 0, cmax) + for y in xrange(pmax, -pmax, -pstep): + yield (pmax, y, 0, cmax, 0, cmax) + for x in xrange(pmax, -pmax, -pstep): + yield (x, -pmax, 0, 0, cmax, cmax) + for y in xrange(-pmax, pmax, pstep): + yield (-pmax, y, cmax, cmax, cmax, cmax) + ''' + + def produce(self): + + pmax = 15600 + pstep = 100 + Cmax = 65535 + + + while True: + + print("Cmax:",Cmax) + + for x in range(-pmax, pmax, pstep): + + yield (x, pmax, Cmax, 0, 0) # pure Red + #yield (x, pmax, 0, Cmax, 0) # pure Green + #yield (x, pmax, 0, 0, Cmax) # pure Blue + #yield (x, pmax, Cmax, Cmax, Cmax) # pure White + + + for y in range(pmax, -pmax, -pstep): + + #yield (pmax, y, Cmax, 0, 0) # pure Red + yield (pmax, y, 0, Cmax, 0) # pure Green + #yield (pmax, y, 0, 0, Cmax) # pure Blue + #yield (pmax, y, Cmax, Cmax, Cmax) # pure White + + for x in range(pmax, -pmax, -pstep): + #yield (x, -pmax, Cmax, 0, 0) # pure Red + #yield (x, -pmax, 0, Cmax, 0) # pure Green + yield (x, -pmax, 0, 0, Cmax) # pure Blue + #yield (x, -pmax, Cmax, Cmax, Cmax) # pure White + + + for y in range(-pmax, pmax, pstep): + #yield (-pmax, y, Cmax, 0, 0) # pure Red + #yield (-pmax, y,0, Cmax, 0) # pure Green + #yield (-pmax, y, 0, 0, Cmax) # pure Blue + yield (-pmax, y, Cmax, Cmax, Cmax) # pure White + + + + + def __init__(self): + self.stream = self.produce() + + def read(self, n): + return [next(self.stream) for i in range(n)] + +class NullPointStream(object): + def read(self, n): + return [(0, 0, 0, 0, 0)] * n + +#dac.find_dac() + +d = dac.DAC(dac.find_first_dac()) +#d = dac.DAC("192.168.1.43") + +d.play_stream(SquarePointStream()) diff --git a/ethertools/watch.py b/ethertools/watch.py new file mode 100755 index 0000000..9fff745 --- /dev/null +++ b/ethertools/watch.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# +# j4cDAC test code +# +# Copyright 2011 Jacob Potter +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import dac + +dac.find_dac() diff --git a/updateUI.py b/updateUI.py index 969378e..e2f2a90 100644 --- a/updateUI.py +++ b/updateUI.py @@ -14,7 +14,7 @@ ljpath = r'%s' % os.getcwd().replace('\\','/') python2 = (2, 6) <= sys.version_info < (3, 0) -def Updatewww(file_name): +def Updatepage(file_name): print("updating", file_name) f=open(file_name,"r+") @@ -53,10 +53,14 @@ def Updatewww(file_name): o.close() #now the modification is done in the file -print("Updating www files...") -Updatewww(ljpath+"/www/LJ.js") -Updatewww(ljpath+"/www/trckr/trckrcam1.html") -Updatewww(ljpath+"/www/simu.html") -Updatewww(ljpath+"/www/align.html") -Updatewww(ljpath+"/www/auralls.html") -Updatewww(ljpath+"/www/index.html") +def www(wwwip): + global wwwIP + + wwwIP = wwwip + print("Updating www files to use", wwwIP) + Updatepage(ljpath+"/www/LJ.js") + Updatepage(ljpath+"/www/trckr/trckrcam1.html") + Updatepage(ljpath+"/www/simu.html") + Updatepage(ljpath+"/www/align.html") + Updatepage(ljpath+"/www/auralls.html") + Updatepage(ljpath+"/www/index.html")