countdowntest.xml0000644000175000017500000000225111675237203013152 0ustar danodanoI0:0/0boolFalseI0:0/1boolFalseI0:0/2boolFalseI0:0/3boolFalseI0:0/4boolFalseI0:0/5boolFalseI0:0/6boolTrueI0:0/7boolFalseI0:0/8boolFalseI0:0/9boolFalsetimeint100AndI0:0/0CountDowntimeI0:0/1I0:0/2countuptest.xml0000644000175000017500000000221611675237203012630 0ustar danodanoI0:0/0boolFalseI0:0/1boolTrueI0:0/2boolTrueI0:0/3boolFalseI0:0/4boolFalseI0:0/5boolFalseI0:0/6boolTrueI0:0/7boolFalseI0:0/8boolFalseI0:0/9boolFalsecntint0AndI0:0/0CountUpcntI0:0/1faults1.png0000644000175000017500000005730311703163603011577 0ustar danodanoPNG  IHDR~[sRGBgAMA a pHYsod^XIDATx^} Uŕ}(4NDi D5q! K\ZmM2KF%DAlDZEeHc@ d4FGAю~?$٪n~uWS_U}Su&QT\ џ"5J%{ꪫ~x孫ss8Ygbud%l2=#R>' *݋ EhQ$A(2s~Rv P@~ن '%F(_Shr7rXď)0Ypx\iŧawǧ?[76{,fؖƌ>c㿜76~ش&] ~ /wI՟j LЀCQ |#8L |%·\ڈn+}Cl0te]T"+x- q_[|ww[ Kx"q3P1\ޅ^=MTGfZNsn-w<~'쳿={־{Y4nj AJvVzL9J(-)++-~ /)+-)).-J_򵥕&QRY(Da6 q $e H?C꧴]D<$0@QCL?x2455a&qD* /m|!=cMB˪nS.s+ mma\,$RIUs I ꄁ0 cJ24g ƦB2h^5w`>o\i޷۷mƋo] 9L$ sg^,ʅotyx_M_ $̸Y -ى܅@ppH9S\".n䓿u_Íywݺf d5=lCtig/Uk: ]!JX&sv)\&mPO ټJVd𺦴c]M]2jY2c?=ܵ|j-eLU?Ŋ crAf8JH\5a<ŏXs.^#W$u76z}e;e˖ǓԎy|鿷l<1p| |a>Wl_^Bb@dӘ9'?9o|هGC1~ F'Ȧ1]8;j@g}d0A /''Bik'9cn'>C^1}t~8?k=?. /Dpq|w?&[#(KsߞOq`?"S=y"G3  @z<)BԇR ĂK!'!k̄FE h\'JԃفgHӌg @k(2꤬Y H >&҈m) $&?$D8 7Chla@+G\dnF*ǶN">;:R 9{z*zID2[ [Qi;~ [4\nZ,)^z=>By{سg cCDܻ-3:8c}>P TmoG&=k*D!#{7}.rA#˛'$ܻ_2gKұuX; >A Q+N&sO01 D4u쬢ce^[֓@Ix@ ag?l.95pX@'~_7qdYR [4~gõvɅ!2R*>pd)/jvMv^E=tЅ¢oP ZI^s~nU`6 Sp0dA ܳ#-_PEoUS}8G ĐZ`HifNgo=qpwAt)O$(f wu]w䝷 q,y%Ӯd#T/s>xK'~7~>oB~?`BpMpAQtestri40>i& e3~rL; cϷ.,$ ĵNyPX &MCm }I(*+xGo9/–ַQ}/3 N"%/7t\+.; X V=FnIWM1'_i2k/%J{dC;z7<ӧO+eHH)wl늉7ПqN^N~cȐ9|u' I8#AN %p\{Ӄ_}8F'Fq!E+~: ?kE ?g WEE)Ql$wFܽX wV,\مаB\4 ~IL!4'(tm( [85 BV8fTF#(W t<2'8FtΪ ̋9I(bDr?qh+R)!_X L1}>Ī>t}r& cpyG=Lc.iɰLqZ#7dߢi)h[f3:Z3lls`VvRP-U:bN@ TCh4JQY8\$z{ s:W,F܃ #ڙб=/o)JҾ}pZȠA14_h][ >?bghb1!*1?T@Ykn@>7~ 5/+b 0 nWuMg0sϻLsXwu˛kΫn^!So/m?#>`h8cdfEpߧZrg[D< .C2&A~ZzlnHLc`O}B^鉙Ba>DƁgW)%Re"@/iᚼ< ;1!h)+D/>G7V"<)9`50"^,+Qu/M(+٭G7GD^ܣף;3 r-GwMLH{p,z ݊zv+QN,-*+(9~tȒ;Gْ}^uӯGg0[M'x 9]pm_]QT{Ӽ:){Ww4qP3(sPi?wXO ^QL/^bG{ ?~ )a^0a_Eg!1%{ɪfPY7À P$2+uŻ|0pwlat"kBSH`(+GWq:yB>D`o1!N'X Y0.=Xemy8xt1PMYq'M⋧^[ɧH^V)v,KL$/W {N{hJ ;x%6p|&A3G]3mʟi UX; +r!An?TZC9wwCRa-p h.j:j#f}'N.aLd: AMI@&4T=eE-&㜌'*" 4~EfsQIe$)8sI\6&nGf s[;:hR}T($ĤP-Z%t8bY U@~…pl}CL.n1 obp[$.?J}}5"P,*Igq313~7y~DtCFhQ<F_nV#SKb ;:Xz@}&HXuy0OX,-gn=JˊK˚,Fgwnݽ(ݺ7X֣[˺RYҝmnx{iكNHOΒ28Jz\r.)Wq^%Y^Ƴ">g>C&ؐ AE1^E{ApBYTnY>E,.B6zX-0C"mg9v|5LS:l}:(9߰^.\h#=i e91@*t p, ?mi@wxxOS_<pX4G.8e'Q 0f@ q 4Bk&|`'õtm|aKѱ4 o\py6j2̔mAn؁p/\, N1skapHDMƆб~=* xW ٽGO GOA?VOH'E#yFyb4 O㔕v.β>%p./lջw>p!.w ,S '$T2ȹ!O$R(r{|sz.ՓAO$ ~1Fb=-e;h j^b8iS̿I}"Cfqb˖6:oU$" eBTB9h 5lrCj?~|>s6g>Z#/ҘAcnTuBR\xԏÄt & Hj3ǃ(,V# 뀆k>$g@K1)0FS RUld7߽EpyDdpYH6 ;;)gJ7b3e}4 eiȮ bH]gPͰ55k~/~A=q' =0x 5%'>A,8̊b]x[i.ջ:^e{uݫ Cz l|p)?=!σ(iK2ğwٽOOխߠAGEDq%oy]i :exۼdn΍WͿ){G;iU|_Iߝ{oί}?az1."KHB^A!#?dJL@B:V 2Egh.LZQѿc ;\; 6f+$IbHZ# 7QOc81M#E@qY;mq6A\HҤ?{LіH*q ~lqqH(>gcKVP&?TđB1\y}ܵqZgO 38/ž+F(2A*d]-\K/D - 5bk.aa_o bY_*~6P>2` zz%em;7o۶4elS.ܢ~r\\X;c+msn1{@yӧN ONF iU$zÄ' ך >3|)gt;{я+~3G/[$J͹H롱fbrT,t'o&J."LҲx.6#Ռ橼R|0ݰ9J&SǥAη1-O R ?ȃkXReI74egǻQFE%\¶2q,7٢[mĵv j0h\-ZQnrCRa:<~HX1Ѣ-!#ЖheCQX(<1ehGPa1Y'K}Ih'!jK1Xѡab2fimKV$& kQ=%/ @]NO3O\,Yqa8>.iAPZl2m0Ӻ3ԙBIcyjC.̋;Md~t"@HHD|"P lKGiq-΅N4~`u$;3/N|A-ȃ쐦^1I-gƤ ߵ$ˀ<&+"" o# ȃ$Ȱ3zrQíe]$]B<# Q4#V!M}d E2Ў@dSdlhlH=ŨpY6s4 S 5"BGfY CZLl,":HѩLNqIJKK@L[pˊ H`6N nr,? I+V^d~! |_GG"N%ElժĘ`*HMKf^*'Hv.RO38#b̐>,)cP%Y} CߔptL2%d,&+eM+e;CNOfÀ=`!!jd-XRƎz쿅b{"0LcU1N; 20 A8\Mq⥨.d[\dl#萡:*;vls=PYTy& H{41~b0(<`f$ bb! ~lʃ7@딽yn;نEnzP+ie Mș;fgaҿ.Oݐv*0>{rl;P&LB<-ME?fup5Ǡ~fY U_aq /̕ƴ6w{tj߹*MJ[jVɐ"J}v0'Y_ xhX޼ YTAJ4&!Pƒj]%S*Ux} i+lfmAwC3Ā6f(y`øqKe97/m#?\vBbYwmI 0e29s<,<K·2!#Z}YؑGL$B.BBGNƤKoi+hS:`IH>^쏲@7'FNb?D$t]}:01#CYZ9O~b~`jS iQA|/,aiԉSq.hB􏑊y $$u L2.~HVH,23~&PG\8܎O3s#ϝQhS'̶G})4./ E*EZM{K!iٰve*> ?c7Q-jCTD<5k~럸N4 M*x+n8HkmzǷ2JVխysƧ~7hޅN;>s1뽪Q AkVA[UhIߵ!=}Ł7zȹވ~Z7l]0S m" $" F{fc+O9;]\eWCH拝T2wHEv=WƜ~S#FNN9DzFyv猧@;1nڪ3j7XV˧ {ϳRyG&KUQŲE2|ʯY3 He*YSe ~3U ^+rē+E#F%V=&GxjGo6hm@?ÄҼar̀9эOǻ^I512oɘ̽%&+ȵKtesظ歗ex\DqO!M(ewV<*kw?O1MZ"¤[ v?Z5hٜyXWYS|빧_pŪQj?,[+1H 'f~.r#NrSxƶ-ͽ4<ҩ0:zׯn}ք v";N^7Z+qm+͑Y50v1cnZS `e_e(-eP^+)rKu 3FEY+sRE8G?P2%2e4)Mukpֽ -1NȈ5i7~ۗ/Vne:pyc/۹{Mǥvӓƽ>kiˌ FV=E@+DIa#3]>NaQӿlޖ'z9wXΪB@ȬM3MKl !O[ _ ߤ)ӟ^⸹O`Ӡ [c|O/vM㖣ӿqܱ"AY"O#IRL^qK?m7s)-V.HqQ+^N~mڝWKιEH[H~tݥ<$#$T^DBjP8v 4=1m8\[FiurU\u~8 Z2<0ifR (q!FffDÊ/Mt=E@H|\ Gn>s>krƮWAE@+ydc5O^;_R\Ʈ]Ny[y;nB"(9@G=bc΋Lru~kE=w޹g3« "dU+_ud~'i=;xYCPE3D^dvoEh5zٕ>xmuZ"(@`ɐ_汕b/]|͏tF">U욇"  [k~VirR*"(!@Ѝ~3b GF4?[P @sBwxA^` @:ԉ.Ӫ*"d.V1Q|}б3U*@Ai9|A$F3QvqiSTP2C'ϟ'a U(] #qAVp;O?#GUT5mzF>;ck+wftTLk &,\]̡'pB6f̘)(Z TK7׬ͯ\^2o/ ;?{Nzʷ1"ynpuuC Ãy `n.xBaÆ_|x0wDd𷽋6mZ߾}GىyRhH䊀"Zig^]#DD}^1.tP!VX?@ ,ʣ><ƣޱM0Ak"s@w]H05Yx`^)v"e@wet.[D(Xf$@5nU͏o9  ]˛ڹDok3珎\ fJUO#Xԟ' $I:B.XDt_NbA*:F.CsbRKX]4. 8~UQs]3k{RjIccպؐdc/V5rv,r")4 І2c=A#:ZYfz`"ȥ ǚ-+6nF(Z~Xey2$!csK^DxS8{jp[G]/Q7FnPPI̓ cLtkYaݕ9 1#[I'!uiR7o ;1bCY«6A/dr=-<=AQWiG4nHÚFEyr#$!csKQDhUkD,Ǽ9-yGb4,%#[*P/>)hLю6;Xi?RbF}Kw}kvq WB]R2\\5/b{⸳[P;򍆤]kޚccDx1vTYAI*G_q=XR"ff6 J?i1[+d %.&-s\7"yR'Mpfvh)!÷T V !c}ܝį\hQxnH%TJPZw7~nE>oFB6V-X_Le} :ʢNAsQ[B6V_t 0zwF6XþUJEc1 |M J&DEszJ}2>dEDˤ6+bd>&EF}*?Xjsv'u`&BLÎ̮>mu[iD1LvΘQMڒ|ľTG,}X?ƿڬGi|ݺ-e9-Q}0^[[ uu}4e[ٴF}Z.[01'h8ıLfk)-}; a^kEXn_~?z~1;se4;E@h 0t=G$-3F.K</mۺu >tE+hyXawlL d 7rJl2A:Kunɝ W|yCÃ?EEsgxȃ[nZ;n1ʃŃzt:O^~}ٲhX^B(z^ݜnr% 81ʃZ'Z"Y8<Dxp~_c %(@"+}+l2u Cfꋫ/$v}{t6>O_v1 f<4L it DZEp`cP?n 9 lXl*{RD@X}An;p 7_s4\>@*K0f&P|GOhI{rmDA˃<{ˁ/<+^ӦMWav-(3߰aCjw2Cad xl? :qT/>)k9|_<'IAM)@LriA څ.E*". ;/Yrl"б7R 2ݏcFKSBxX.f 𡬣4RwPZ"(ۻ>RѾ>pd E)"С.Ȍ%fqaJՅ [?E;pTtE@Y. ~ 8[Z_E@H|u_;OB(w,އI T&M0^ X{<'ri/TE!ۗ_~n$wmwğ9I[8i81%j0r ~*7c7_ ݍ]? e4^Iư+!uch9R-CP@# dA;_lfSPn܀G/JqzsFWt.f [yKf2skȪm`Mt]7 L8_l͘3)\1 T e,Y,WzNBU ؅WL n7 ú:֎N튘BU(]iF d9kjrJe,Tfy^α*s"雯bz&%&&HGЖ3%Z:[W`= X@ LkVZװ3X8[/UޠxWYYov HBnMxY!$fo߻P1?lkq"!YPׇ-c~B$mVTݺ2#[L/yA]RNe4X9iPvZ5BNMkZ&n/z4}SQ3oVIFXF/fEVn ~V(YIVA4x0޼ Ub0Q[ј٪STԛw[1E ~1Fpv}vJkVD Vxpݸffo&'0Fz$9`tC?k]75BC_?{o9<ȓ2eӘ0DK9,ݺ2nԻfDQ(ae>qHٲ?cw(yc H=lEj E@0\1LbL/9{nLv)g N>_|q…advL'E8vإJ4'0yTC oݷo_^zikիWCcյ:M+'O՚om-Ẩ|쑟+_eXW狃kyfa%\ݺp".H"Q'onET2E@PZ:ςى&=m"hBE@P:4͌_,S]NU WEU11 A)bi"t fApYV xiSNCmo yx]PyZ!`o? ~̃||&Aw๴nk]0aBI4~,nF;"E̽'X#ƒ3+ojqFySyP[NP Pz:iM7>TXTT+>\YϸAX\ ͣTF`Æ @ţF|Qgҙ7Z}_((6ĬYA,[v8h'pBଛ;x}80OOgL2$U RdJ~05L+P7VW7W^k zFG`H&q 'k*2I 13V $bLO*'NV= ^US(@"Z$,CkIϨ8%Bz-w,NE:Z"MeA_HU 3uXuyRWWb,=/HѨӯX|'_;x%-V59Z=E7 - j,am#o x5 `kA 03n"5^6wlk4a5S]".A2QAӣ0L*a`?j mspڍu|FaMFƭT )UydMmRP5fe\  7_hq %'Š# fռ7 &FmAC6nN0,TI0@YaėLD8<UUVgV"P0 Xp{T5^d@CJI*|du`7YglheSE͓u8dMM$ith"2c?iP̀M'C;B-1E@B!aZaP@@uTX@+<j|PE IPE@P,?O>}­so1c !woݫW;fuؗ]hc]^K"P^^{CrCk]KcԜp /b]]]fP_喺Uw\<*0f̘`@ED<-J0aB yVIɴi<خ k)py!Ū-lذ-7Ϟ9dv1P i)sxB-&XzuN yV}@P.@t4 (@}U eVPr  9:*"tʃ(@{uZq.4ʠ(@@T*l#,Ѱ]SYjE@PRͥK曝M_Oxn7h]fn$$֡%N7{y~L[_K߫*b|xck_oI,++fܭ" jrE wHkVg! 1 7o4FpG;q2D;nEcv*5]`(Dwg֕J "z׬Twq͂͢ n߼MN`ƍ>&1;n%Ir6:~]f݌KÃh\]z)Cn]Yh=B`R.F>$D8M}o`x-Sh̖ko(2ف7V>_ـö\dHO:;#Lusf͘1s΄իWwbY74 .&@ j¯QDcǎ]Dy@54pA}] ;x0r/(//ġB]B۩W8Ey555'pke;ydWknn96oˢ>_ 6%tEKݺ:-NPD c*\")Y!";~0)@^ `^4VBPڀ6l$<{)拇چJiwxSHg貽Qʹ7š2,NP(e_7 `V@I'XmJdGzҥ^WZ?ohkE 3OMaWʩ(@躙!"g躙|nfάI,5+ڐ@I&m=ME I| aXLt, ZuTN9L cyvk\Sy04Nbn@n,H܌Y^W{9g.GM(@n"տuޭ7̾يW8TREx~paU(ߨi'n]_o{$lӥ4('PNkekMyVP5FsqIiyyW^yyIFB]E2GAef3z1N'N[eu5it6Af*8;~G`(JXɜ/*SlUm{4H0}ܪ5"(]M]Y?,kzTIENDB`faults2.png0000644000175000017500000003353611703165233011603 0ustar danodanoPNG  IHDR  x&sRGBgAMA a pHYsod6IDATx^} Օޫ}Q B eғ!j 7F%$jNhQ2ӃIfF%i!BN:Z\& C`HhT֢ovoy[QPEq9箩͛7{? 9*9&M|xNK:ncN~JfJ@?(U(H\U\4SdՋZf|ܶ']36tS,y>rpͿy{iV^GVYbu]pXyPnHj@,|BHR0gŰP_%$~D)qt)av \j*ܮ*[ sspa$<#CBg<gv|=")Y 'R'$(S$i%)U _/^wK|Z@Hzc_;\l ($T:_JS%@Hg? F*+凹SY2*ġ 6Y8~Ȑ@<5IJ"LyE.Uɟh%:[E;d* ȵxbV^Qf 2QTKY*b)(JB9_ BBPW b>P,"~&Op y"'| 7r"< C&p KȽC8l|{~1VàA`A` BxN3 \<ՒA x0 K|K'cPQaԺ 0tBQΎU&?+`(0qb5#8Ԋk&AŒTRJķX+|>+bPOL@ȁ/Ec |KҡXu(\ F\.ۦ 0PApEA_APiA"΁#(|+) k$ɟzz7Q84QZbR|%m}$K4`i*)O/DD MmQ˲+ߣSuYahMn޷?x~AX.PTJJR)8U/oW!zǤ~rɤV2PVpU0g|O!mf¤`' H ʹg-O!یPF!=b)4( TĂ':i=2bV&|I("l7q3 \bب*%V[,>U am4c4]!1*O1$&ڈس'rFH| \!-e |/-~Cp p'߿DI F9PI%E4k(_P4lEKc[qnuTKE‡ ˦,C@{)@c "Ƶb52"=eS>q (E0BCȟL,'I@maTXmF\ćKrbY)+䲹P8¡xH@؀,)\a@ȏOk,Ց@ʖhc{ J4|ME-ˇ(T -䋓;A>QVA?  ܒ7ocCm+DEn 1eW $Wy(؎ ߇ʇQ׏\jT*XvCHBOU[l`Ob>mSC |wO류&!Cΐ?|%B\D4n 0NN:mjkFY/R}V4^b+)+) ז-#J`eyX,g T/RPU":7[`)F"B \ozs'?N~#F!,*L@l4>2]B 5![K9HpX=5+>8[DZ¸/oUqg]'`rǵrK|=hCP!<b_y_|//&,!p,“t)HA7n)柀]G_{ۃݟ24㜮KZ#P%fɀ Sc!b=ʞ=r (zE n0a7rsϒ{S x#ݫe,Ք~"6jQ&4Z/Zԩe&C}MOa/W2ΡZNFL,N|P Hh&S"h-SOuwx*EK`_:qbOLZ(ƈ".B =XsYVzEՀ1 FdXp틅ިAȾ H:l(LPL#ueZ6/wS@*EU%VCI!,l|cF1W ~ۋm14O9$IM!_6O.&lwC VcL}⿝~O8z3Go:ٷgɏޤr[?yOĴDjpt}71PV"7-Z(髰A韴dc1%pԅ0\ mBٮ0nŵjD6MCc#%jKw:2m=ٙJwm]AGW++#pg_]>| WI0-6 !(ʂ B`%\U`\=.=_;~7tٝNODP?s&ݲwԥ7={S_zom7JoG~sׯ^셀$"Hq+)LPSE o,aRycE]+%A_XɴFXQ0kt:dgEhUmEt[Z;Zz2]=Iwtg:]]TOG#N ]fg,~;DP4Ls OЉ%rAK;0[, U<>hPa{={{6iuyZNJFIkXU43F 8EpoM#H)G[Yz TS(( }:wHBuȫ,G$t+&p̻xL"جIL56qbKOOeB/t_oKLτt߄ }Ao_K_O'(|gɼ 9@n'LE@YP" C&4IЏbVQ"E.!on㫿|fP |:|eqjVVp@DtU+`g7+DeH+i0,T v)eY2P4` `-֜&H d?A5XU`xLI24!db_w)m&B0Nh7m"-'f SQr;CK%BP:ᴾ+)'!xD$ɦζqV*lyD ?] V"xo b<{V;~`GK[;ibeJ$! lBJiʊFs. r"IR WIV*-Q%Z5꺙JrPUl#f\ް|!XJKf2hi !x}ŗvuNގ޶޾>mA5- S%D~[(̧[J&Nrtì+ީ {x=ұK3@` 4 4,%/kD8mm<06l\/a}4L4-6|Ղ&WLoMh54"aqR)?嫢ŭގL*wOs< 8L^!ZڑFU0Ҷ?qs1hV0qe>,ȅWPs@^3SRb*RZ5TRd\ y,10' b;kD3-EWyVX :>Qfʚ*ca"B"Nb^$VX_!sX˫iV5M4K2VBm`_\ 0!`# $6KCش5`+9%H2צב9/-L#uo0γ x4e /TFVCːS˓SG-|Lci%gQZ14qR"ܖd0h_Ŕ(V4tTQQ a\UuE c`!5b#[v]fUh:xj, 44$A$ N 3ه&Y'g0@_9%\F?gMbq7]"oOK!ID*iVEIEdm`XIXTh?K HHrPL$LDck ^@f!QxF+QPlXLc7V˴xj dZF(Q*(!(<%?FL*-l*`~?Z\@g7i 64ͧwTZxEJ"TOHd *1)[UlEj[xm1TU":)RԘKc#WARnMH0<尌ZJuĪCt!I(w,ek^JFMMW%jӒMHmPYel~(CXbȺD'ZdhR(Ͷ$WS]wQj 8K$VlLjJΦ1pI! # ި4IJ+H(~DBm3ezBpCyCǰĨDm~XҞoeȔKK$ya(* hAO,4BLd#p"TFj&CEXqDK@3AeLʦ1)d#*bSx<ʦv#/k͓q|L?jP`Y;2ms.m%(m;n3uU `7br!\asFɵ9#!c;O3h)\9θc8}'S4vz SN&~'Y,\STkxk~V;fo/&hd,[=X{5}2ƒ}'r's k=<E/>%sK>j]nxC/7IHX#z(ۛ7qKn6~r_~]7*D$SGv""&zxAV] /1Ղ2 K7U䌻:yT0U?xLoV*-|jǮ1qMTlX"'2y]s"i k۵੅M4 9(fP.Ƹ^Y08F"ٴ q4e<^~`a~DkTfnk`EAJ}hJOIzm-"n?&غdzZ޴aMwEZfJCjx.{Xq_JU|hk\O-DQ+ҤUmU'F VOc{zW+ZM@ǂ>,*D^# C j/]P5Tm1]@p]j"N8&GcW'zq[pհvOқ@W~u[DU~Jy4:\:?,#UQXs6+soDi7QaG ΞX*T<s%H4[Y'Y3LkICō<ӍAV~MՌQ~%|,\蘆lpH*0PP>uc$=*ֱ[J5VD {ȹ=#GDcU@3E{Tچh>]seqD?p9(Cc:l FY&yhX=qL~ "k)pŹ\uB [}=t^;ڬ]6cCypϕRWs+9@`xM˾oqڵڂ_q6ոr eA#{ߐB A& ZSC&"%[M: 4A7)ZF Dmk7jV cPo$s ]v*ihds`s6Ý`ެ1O@+s%<6gO18Pu+BoWihAvCiҢ<ݫg uDFCJ֥g,OkO͸Gh1mPkz$q iBw?8Ɂ]Ld'QD:vQ.y怵CcWax.9":r[8ecWB Q(+G%rEEEaQaemVe=:\ifC.FhDž2'h xxlxp\yx8΁xml1-^3ɓ.`%hl#  w%uʕ1\uXy.&o!D(6p's |ϕ ρPohY^_Nr & xl8)hဖ*Âث/1r !]\8Olh#^Q)K `IC i ў%e㮷:pΉ<q:"JxG:ICq?]uq~5s_ixe3V.uz]qrյGȸ]oa9/'2@~5gëW- K+/(r@9_jΕ Jd,+3n+sU<[VZ~`/w9`!]prh( aQ^.ôA۵G:!]:h\/t]@v͸W~R8鉓J8Qq!q|2;iQ( y|8M4w"J# z̿wivyCW ?bٔQ*B&O@/\.\ 0 2=<ר/ baph ;884x,;4Ȏ8/ހOj5yz]@>+G ?:a&O<0QN+pYVρMh jeԜ9Wȓ.<|@guHQorSr9 8r@C())Apd=#0?\sxy?̝~yP860pt`> !W AX[8&ܠX,@ :xz 8p^<phH884~H6;ij`Ԝ&zJE <ϼr5R|5Rx+@<<Rb! joΣ)WH8qe;ұ )"hS<\ϰ}BhT W&Τ- 7GK<<*)詒4B#Hf dZ[tyyiU*Gi϶ (d*x^\k TB+%㵇8IDd\!H`]OSGsF 4=Bx@@ K$N\Cˊ u[Ӂ=%pV"\ ;vsHh 䡣"OD+Hky`to'k/4.q]iW2d+Z&e.IU8@&s@x&l"vC zqs] Wڃ`?J8p l*ziQqxރN.'6ޡ :u |?㒧Uքv.+"윸!OHXt Á X?9tUr8["roF{Tyћt{,ރY#CZ ֯tYV]Ch[]CNñNhNr0n>z`@%xᰘI=lF[1Tʹ ,G&ʟxx٘2r' :`_M=ƕtfOgWOWwogw/Z~90mu^!ηpp3NX3aAqt>qY@> dZee#=h{ ,:w{Z+99W Yi6aΉ'`s'Ŷ&.3"=xIh]ު_q iFc2m/<<99<j}Txt iOc޽'bR{nAO]1f1eAxȞB7l0ew߽^},j++q|Le@hAI\@|\[Psy >C$@uX{QpR`vItC~9{<,í-|,WVOWOdx"/e9 =6Xa^~lY2k/^5w^ӈИGg GW8W߷vѺ͋2'.~tea[`@&Ɖz')ݫoY:c ԏ("=hx0:cׅ}w?~ۢy|Lsv-gIqo8NeJ%6$H41$bJ(d]A]; `mK]tR-N@ b)sWnmylhTٙtQ!'E tP|>y5 Yn#~W+*^#Z|XyRs- H 5_,RݫWCXe~.5uڌ d {ÞTB^Pݍ:U0^%Ps?vh"ݲsF-!Ԩ`Iq%Pǧ7pUh9TRxJQ]=`Bv ў>{˖^iSIC\+4= |c~X}3^cI=^s[٣c,_Á \k2V8 u<90 m?ٝ,,Z6@?>V8w{l֎nե߹l˿I{qgʸ]Z ~+S cي{ ebg&^tmR' j@j`BB*Qx-Ghe4"4V"bQqWV.*&26tz-_vz`}铦MM[~\K/޶ m &>>CR([._jiNTw.c%BaT=`^4jJTz*_ ;^ʆ y䑁#O?-=pO n def __ge__(self,n): return self.value >= n def __le__(self,n): return self.value <= n def __trunc__(self): return self.value def __int__(self): try: i = int(self.value) except: i = 0 return i def toggle(self): if self.latched == False: self.value = not self.value def properties(self): ds = self.name + "\n" + str(self.value) + "\n" ds += "Force: " if self.latched: ds += "on" else: ds += "off" ds += "\nDescription:" + self.description return ds class block(object): def __init__(self,function = block_functions.DEFAULT,parent=None,output=False,rung=None): self.faulted = False self.function = function() self.name = self.function.name self.arguments = [] #self.display = None #self.frm2 = None self.rung=rung #self.popupparent = None #if parent: # #self.display_new(parent) #if output: # #self.display_update_output() #else: # #self.display_update() def status(self): return self.function.on(*self.arguments) def run(self,on=True): try: if on: self.function.on(*self.arguments) else: self.function.off(*self.arguments) self.faulted = False except: self.faulted = True raise def __nonzero__(self):#evaluates the decision block return self.status() def properties(self): ds = self.function.properties() + "\n\n" return ds class rung(object): def __init__(self): self.inputs = [] self.outputs = [] def run(self): conds = map(lambda x:x.status(),self.inputs) if all(conds) or not self.inputs: map(lambda x:x.run(),self.outputs) else: map(lambda x:x.run(on=False),self.outputs) def closeblock(bit): b = block() b.arguments.append(bit) return b if __name__ == '__main__': I1 = create_binary_array(10,'I1:0/') b1 = block() b1.function = func_and b1.arguments.append(I1[0]) b1.arguments.append(I1[1]) I1[0].set(True) I1[1].set(True) a = value_word(0) out1 = block() out1.function = func_add out1.arguments.append(a) out1.arguments.append(1) out1.run() r = rung() r.inputs += [b1] r.outputs += [out1] r.run() I1[0].set(False) r.run() pyladderlogic.py0000755000175000017500000000004511677227637012722 0ustar danodano#!/usr/bin/python import tkintergui test.xml0000644000175000017500000000131711675237203011213 0ustar danodano I0:1 bool 0 I0:2 bool 0 O0:0 bool 0 And I0:1 I0:2 Close O0:0 test3.xml0000644000175000017500000000220011675237203011266 0ustar danodanoI0:0/0boolTrueI0:0/1boolFalseI0:0/2boolTrueI0:0/3boolTrueI0:0/4boolFalseI0:0/5boolFalseI0:0/6boolTrueI0:0/7boolFalseI0:0/8boolFalseI0:0/9boolFalseAndNotI0:0/1I0:0/0CloseI0:0/3tkintergui.py0000644000175000017500000002505711703165500012250 0ustar danodano#Created by dan december 2011. www.danomagnum.com #from Tkinter import Tk, Frame, BOTH, RIGHT, LEFT, RAISED, Label, IntVar, BooleanVar, X, RAISED, Menu, Scrollbar,Y, Scrollbar, TclError, N, S from Tkinter import * #from ttk import Button, Style, Scale #from ttk import Scale import tkinterladder as ladder #from .ladder import tkinterladder as ladder import time import pickle import copy import tkFileDialog import tkSimpleDialog import tkxmltoladder as xmltoladder import datetime FAULTDEBUG = FALSE FORMATS = [('Python Ladder Logic','*.pll')] FORMATSXML = [('Python Ladder Logic XML files','*.xml')] class AutoScrollbar(Scrollbar): # a scrollbar that hides itself if it's not needed. only # works if you use the grid geometry manager. def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: # grid_remove is currently missing from Tkinter! self.tk.call("grid", "remove", self) else: self.grid() Scrollbar.set(self, lo, hi) def pack(self, **kw): raise TclError, "cannot use pack with this widget" def place(self, **kw): raise TclError, "cannot use place with this widget" class IOWindow(tkSimpleDialog.Dialog): def body(self, master): self.vartype = StringVar() Label(master, text="Type:").grid(row=0) Radiobutton(master, text="Boolean", variable=self.vartype, value='bool').grid(row=0,column=1) Radiobutton(master, text="Integer", variable=self.vartype, value='int').grid(row=0,column=2) Radiobutton(master, text="String", variable=self.vartype, value='str').grid(row=0,column=3) self.vartype.set('bool') Label(master, text="Name:").grid(row=1) self.name = Entry(master) self.name.grid(row=1, column=1) Label(master, text="Value:").grid(row=2) self.value = Entry(master) self.value.insert(0,0) self.value.grid(row=2, column=1) self.result = None return self.name # initial focus def apply(self): val = False vartype = self.vartype.get() print vartype val2 = self.value.get() if vartype == 'bool': val = bool(val2) elif vartype == 'int': val = int(val2) elif vartype == 'str': val = str(val2) print val self.result = [val, vartype,self.name.get()] #print first, second class FaultWindow(tkSimpleDialog.Dialog): def __init__(self, parent,faults = [], title = 'Fault'): Toplevel.__init__(self, parent) self.transient(parent) self.faults = faults if title: self.title(title) self.parent = parent self.result = None body = Frame(self) self.initial_focus = self.body(body) body.pack(padx=5, pady=5,fill=BOTH,expand=1) self.buttonbox() self.grab_set() if not self.initial_focus: self.initial_focus = self self.protocol("WM_DELETE_WINDOW", self.cancel) self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50)) self.initial_focus.focus_set() self.wait_window(self) def body(self, master): Label(master, text="Faults:").pack(side=TOP) self.faultlist = Listbox(master) self.faultlist.pack(side=TOP,fill=BOTH,expand=1) for f in self.faults: self.faultlist.insert(END,f) self.result = None return self.faultlist def apply(self): pass #print first, second class MainWindow(Frame): def __init__(self, parent): Frame.__init__(self, parent, background="white") self.parent = parent self.rungs = [] self.exspeed = 100 self.paused = False self.pausedvar = BooleanVar() self.faulted = False self.faultvar = BooleanVar() self.faults = [] self.pausedvar.set(self.paused) self.faultvar.set(self.faulted) self.initUI() self.updatesize = False self.calculating = False def initUI(self): #this sets up the interface, and then goes into the program part #self.parent.title("PyLadderLogic") #self.style = Style() #self.style.theme_use("default") #self.pack(fill=BOTH,expand=1) self.grid(row=0,column=0,sticky='news') ladder.fix_scroll = self.scrollsizeupdate ladder.remove_row = self.removerows #sc = Scrollbar(self) #sc.pack(side=RIGHT,fill=Y) topperfrm = Frame(self) topperfrm.pack(fill=X,expand=1) spdlbl = Label(topperfrm,text="Speed^-1") spdlbl.pack(side=LEFT) scale = Scale(topperfrm, from_=50, to=1000, command=self.onScale,orient=HORIZONTAL) scale.set(100) scale.pack(side=LEFT) spdlbl = Checkbutton(topperfrm,text="Pause",command=self.playpause, variable=self.pausedvar,indicatoron=0) spdlbl.pack(side=RIGHT) self.faultbutton = Checkbutton(topperfrm,text="Fault",command=self.clearfault, variable=self.faultvar,indicatoron=0,fg='black') self.faultbutton.pack(side=RIGHT) button1 = Button(topperfrm,text="Run one loop",command=self.runone) button1.pack(side=RIGHT) button1 = Button(topperfrm,text="Update",command=self.toggleit) button1.pack(side=RIGHT) topfrm = Frame(self) topfrm.pack(fill=X,expand=1) self.popup = Menu(root,tearoff=0) self.popup.add_command(label='Add Rung',command=self.addrung) self.popup.add_command(label='Add IO',command=self.addIO) self.popup.add_command(label='Load XML',command=self.loadxml) self.popup.add_command(label='Save XML',command=self.savexml) root.config(menu=self.popup) #this section defines the "bits" in the "plc" and a few 10-bit words #IO = ladder.value_word(0,"I0:0") #IO.bits_display_new(topfrm) #self.IO = IO.bits() self.IO = [] #self.IO[0].set(True) #self.IO[1].set(True) #self.IO[2].set(True) #self.IO[6].set(True) #ladder.itemlist += self.IO self.dataforms = [topfrm] def addrung(self): r = ladder.rung() self.rungs.append(r) r.display_new(self) def addIO(self): w = IOWindow(self) if w.result is not None: [value, vartype, name] = w.result r = ladder.IO(value, name, vartype) self.IO.append(r) r.display_new(self.dataforms[0]) ladder.itemlist = self.IO def toggleit(self): #this function is named this because I just kept reusing it, so no reason. #self.bv.set(random.randint(0,1023)) #self.bv.display_update() #self.blk1.display_update() #for r in self.rungs: #r.display_update() #self.update_idletasks() #canvas.config(scrollregion=canvas.bbox("all")) self.removedata() def runone(self): #do one run through the ladder self.paused = True self.pausedvar.set(self.paused) for r in self.rungs: r.run() r.display_update() def onScale(self, val): #set the speed speed = int(float(val)) self.exspeed = int(float(val)) def playpause(self): #toggle the play/pause self.paused = not self.paused self.pausedvar.set(self.paused) def clearfault(self): #clears any faults w = FaultWindow(self,self.faults) self.faulted = False self.faultvar.set(self.faulted) self.faultbutton.configure(fg='black') def scrollsizeupdate(self): #canvas.config(scrollregion=canvas.bbox("all")) self.updatesize=True def removerows(self,row): pausestate = self.paused self.paused = True self.pausedvar.set(self.paused) while self.calculating: time.sleep(.1) self.rungs.remove(row) row.__del__() self.paused = pausestate self.pausedvar.set(self.paused) def removedata(self): pausestate = self.paused self.paused = True self.pausedvar.set(self.paused) while self.calculating: time.sleep(.1) self.IO = [] ladder.itemlist = [] dfs = self.dataforms[:] for f in dfs: f.destroy() self.dataforms = [] self.paused = pausestate self.pausedvar.set(self.paused) def savexml(self): filename = tkFileDialog.asksaveasfilename(parent=root,filetypes=FORMATSXML,title="Save") if not filename: return False pausestate = self.paused self.paused = True self.pausedvar.set(self.paused) while self.calculating: time.sleep(.1) xmltoladder.create_xml(self.rungs,self.IO,filename) self.paused = pausestate self.pausedvar.set(self.paused) def loadxml(self): filename = tkFileDialog.askopenfilename(parent=root,filetypes=FORMATSXML,title="Save") if not filename: return False rows = self.rungs[:] for r in rows: self.removerows(r) self.removedata() f = open(filename,'r') [self.rungs, self.IO] = xmltoladder.create_ladder(f) f.close() self.reinit() self.updatesize=True def reinit(self): ladder.itemlist = [] topfrm = Frame(self) topfrm.pack(fill=X,expand=1) for i in self.IO: i.display_new(topfrm) ladder.itemlist += self.IO self.dataforms.append(topfrm) for r in self.rungs: r.display_new(self) r.display_update() def calculatron(*args): #this is what automatically executes the logic at whatever interval is set app = args[0] if not any((app.paused, app.faulted)): app.calculating = True for r in app.rungs: try: r.run() r.display_update() except Exception as e: app.faulted = True app.faultvar.set(True) app.faultbutton.configure(fg='red') app.faults.append(e.message) if FAULTDEBUG: raise app.calculating = False else: pass root.after(app.exspeed,calculatron,app) def scrollbarupdate(*args): app = args[0] if app.updatesize: canvas.config(scrollregion=canvas.bbox("all")) app.updatesize = False root.after(100,scrollbarupdate,app) def OnMouseWheel(event): d = 0 if event.num == 5 or event.delta == -120: d = 1 elif event.num == 4 or event.delta == 120: d = -1 canvas.yview("scroll", d, "units") root = Tk() vscrollbar = AutoScrollbar(root) vscrollbar.grid(row=0,column=1, sticky=N+S) hscrollbar = AutoScrollbar(root, orient=HORIZONTAL) hscrollbar.grid(row=1, column=0, sticky=E+W) canvas = Canvas(root, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set,width=1000,height=300) canvas.grid(row=0, column=0, sticky=N+S+E+W) root.bind('', OnMouseWheel) root.bind('', OnMouseWheel) root.bind('', OnMouseWheel) vscrollbar.config(command=canvas.yview) hscrollbar.config(command=canvas.xview) root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0,weight=1) frame = Frame(canvas) frame.rowconfigure(1, weight=1) frame.columnconfigure(1,weight=1) #rows = 5 #for i in range(1,rows): #for j in range(1,10): #button = Button(frame, padx=7, pady=7, text="[%d,%d]" % (i,j)) #button.grid(row=i, column=j, sticky='news') app = MainWindow(frame) canvas.create_window(0, 0, anchor=NW, window=frame) frame.update_idletasks() canvas.config(scrollregion=canvas.bbox("all")) app.update_idletasks() root.after(100,calculatron,app) root.after(100,scrollbarupdate,app) root.mainloop() tkinterladder.py0000644000175000017500000002550311703163104012711 0ustar danodano#Created by dan december 2011. www.danomagnum.com #from ttk import Label, Button#, Checkbutton, Style from Tkinter import StringVar, LEFT, RIGHT, BOTH, Frame, X, RAISED, IntVar, Menu, Checkbutton, Y, BooleanVar, Label, Button import tkMessageBox import tkSimpleDialog import block_functions import ladder TRUECOLOR = "green" FALSECOLOR = "red" FAULTCOLOR = "yellow" LATCHCOLOR = "blue" DEFAULTCOLOR = "black" fix_scroll = lambda x:x remove_row = lambda x:x functionlist = block_functions.BLOCK_FUNCTION_LIST itemlist = [] #need to fill this in to add stuff I guess class IO(ladder.IO): def __init__(self,value,name,typedesc,input=False,description=""): super(IO,self).__init__(value,name,typedesc,input,description) self.displayvariable = StringVar() if self.typedesc == 'bool': self.displayvalue = BooleanVar() elif self.typedesc == 'int': self.displayvalue = IntVar() else: self.displayvalue = StringVar() self.display = [] def prompt_for_value(self): if self.typedesc == 'int': result = tkSimpleDialog.askinteger(self.name,self.name,initialvalue=self.value,minvalue=0,maxvalue=1023) if result is not None: self.set(result) self.display_update() def delifparent(self,parent): displays = self.display[:] for d in displays: if d.master == parent: #I am a child of you, so I wll delete myself self.display.remove(d) d.destroy() if not self.display: del(self) def latchon(self): super(IO,self).latchon() self.display_update() def latchoff(self): super(IO,self).latchoff() self.display_update() def unlatch(self): super(IO,self).unlatch() self.display_update() def clicked(self): #super(IO,self).clicked() self.toggle_and_display() def toggle_and_display(self): self.toggle() self.display_update() def display_new(self,parent): self.display_update() b = Checkbutton(parent,textvariable=self.displayvariable,command=self.clicked,variable=self.displayvalue,indicatoron=0) b.pack(side=LEFT) self.display.append(b) self.popup = Menu(parent,tearoff=0) self.popup.add_command(label="force on",command=self.latchon) self.popup.add_command(label="force off",command=self.latchoff) self.popup.add_command(label="remove force",command=self.unlatch) self.popup.add_separator() self.popup.add_command(label="properties",command=self.properties_display) self.popup.add_command(label="Set",command=self.prompt_for_value) self.popup.add_command(label="description",command=self.setdesc) b.bind("",self.popup_menu) return b def popup_menu(self,event): try: self.popup.tk_popup(event.x_root,event.y_root,0) finally: self.popup.grab_release() def properties_display(self): ds = self.properties() tkMessageBox.showinfo(self.name,ds) def display_update(self): if self.display: for d in self.display: if self.latched: d.configure(foreground=LATCHCOLOR) else: d.configure(foreground=DEFAULTCOLOR) ds = self.name + "\n" + str(self.value) if self.description: ds += "\n" + self.description if self.displayvariable: self.displayvariable.set(ds) dv = None if self.typedesc == 'bool': dv = bool(self.value) elif self.typedesc == 'int': dv = int(self.value) else: dv = str(self.value) self.displayvalue.set(dv) def setdesc(self): result = tkSimpleDialog.askstring(self.name,self.name,initialvalue=self.description) if result is not None: self.description = result self.display_update() class block(ladder.block): def __init__(self,function = block_functions.DEFAULT,parent=None,output=False,rung=None): super(block,self).__init__(function,parent,output,rung) self.display = None self.frm2 = None self.popupparent = None if parent: self.display_new(parent) if output: self.display_update_output() else: self.display_update() def run(self,on=True): try: super(block,self).run(on) except: self.display_update() raise def delifparent(self,parent): if self.frm.master == parent: #I am a child of you, so I wll delete myself if self.frm2: self.frm2.destroy() for a in self.arguments: a.delifparent(self.frm) def removeblock(self,child): if child in self.arguments: self.arguments.remove(child) def display_new(self,parent): frm = Frame(parent,border=2,relief=RAISED,pady=5) frm2 = Frame(frm,border=1,relief=RAISED,) frm2.pack(fill=X) l = Label(frm2,text=self.function.__name__()) l.pack() for bit in self.arguments: bit.display_new(frm) frm.pack(side=LEFT) self.display = frm self.popupparent = parent self.popup = Menu(self.popupparent,tearoff=0,postcommand=self.updatemenu) addmenu = Menu(self.popup,tearoff=0) for item in itemlist: addmenu.add_command(label=item.name,command=self.additem(item)) self.popup.add_cascade(label="add argument",menu=addmenu) self.popup.add_command(label="properties",command=self.properties_display) self.popup.add_separator() self.popup.add_command(label="remove block",command=self.delblock) self.popup.add_separator() self.updatemenu() self.frm = frm frm.bind("",self.popup_menu) fix_scroll() def testing(self): print self.arguments print self.status() print self.function.on def properties_display(self): ds = self.properties() tkMessageBox.showinfo(self.name,ds) def updatemenu(self): self.popup.delete(5) self.popup.delete(4) self.popup.delete(3) self.popup.delete(2) self.popup.delete(1) self.popup.delete(1) self.popup.delete(1) addmenu = Menu(self.popup,tearoff=0) for item in itemlist: addmenu.add_command(label=item.name,command=self.additem(item)) for fnct in functionlist: addmenu.add_command(label=fnct.__name__,command=self.addsubblock(fnct)) self.popup.add_cascade(label="add argument",menu=addmenu) self.popup.add_command(label="properties",command=self.properties) self.popup.add_command(label="test",command=self.testing) self.popup.add_separator() self.popup.add_command(label="remove block",command=self.delblock) self.popup.add_separator() self.removeargsmenu = Menu(self.popup,tearoff=0) for item in self.arguments: self.removeargsmenu.add_command(label=item.name,command=self.removearg(item)) self.popup.add_cascade(label="remove argument",menu=self.removeargsmenu,) def addsubblock(self,fnct): return lambda:self.arguments.append(block(fnct,parent=self.frm,rung=self)) def removearg(self,item): return lambda:self.removearg2(item) def removearg2(self,item): item.delifparent(self.frm) self.arguments.remove(item) def additem(self,item): return lambda:self.additem2(item) def additem2(self,item): self.arguments.append(item) item.display_new(self.frm) fix_scroll() def delblock(self): if self.frm2: self.frm2.destroy() self.frm.destroy() def popup_menu(self,event): try: self.popup.tk_popup(event.x_root,event.y_root,0) finally: self.popup.grab_release() def display_update(self): if self.display: if self.faulted: self.display.configure(bg=FAULTCOLOR) elif self.status(): self.display.configure(bg=TRUECOLOR) else: self.display.configure(bg=FALSECOLOR) for bit in self.arguments: bit.display_update() def display_update_output(self): for bit in self.arguments: bit.display_update() class rung(ladder.rung): def __init__(self): super(rung,self).__init__() self.frminput = None self.frmoutput = None def __del__(self): for i in self.inputs: i.delifparent(self.frminput) for o in self.outputs: o.delifparent(self.frmoutput) self.frminput.destroy() self.frmoutput.destroy() self.frm.destroy() def run(self): super(rung,self).run() conds = map(lambda x:x.status(),self.inputs) if all(conds) or not self.inputs: if self.frminput: self.frminput.configure(bg=TRUECOLOR) else: if self.frminput: self.frminput.configure(bg=FALSECOLOR) def removeblock(self,child): if child in self.inputs: self.inputs.remove(child) elif child in self.outputs: self.outputs.remove(child) def display_new(self,parent): frm = Frame(parent,border=2,relief=RAISED) frmminheight = Frame(frm,height=20,width=1) frmminheight.pack(side=LEFT) self.frminput = Frame(frm,border=1,relief=RAISED) self.frmoutput = Frame(frm,border=1,relief=RAISED) self.frminput.pack(side=LEFT,fill=Y) self.frmoutput.pack(side=RIGHT,fill=Y) for inputblock in self.inputs: inputblock.display_new(self.frminput) for outputblock in self.outputs: outputblock.display_new(self.frmoutput) frm.pack(fill=X,expand=1) self.display = frm self.popup = Menu(parent,tearoff=0) self.inputmenu = Menu(self.popup,tearoff=0) self.outputmenu = Menu(self.popup,tearoff=0) for fnct in functionlist: self.inputmenu.add_command(label=fnct.__name__,command=self.addinput(fnct)) self.outputmenu.add_command(label=fnct.__name__,command=self.addoutput(fnct)) self.popup.add_cascade(label="add input",menu=self.inputmenu) self.popup.add_cascade(label="add output",menu=self.outputmenu) self.popup.add_separator() self.popup.add_command(label="remove rung",command=self.removerowclick) self.popup.add_command(label="new rung") self.popup.add_command(label="test",command=self.test) frm.bind("",self.popup_menu) self.frminput.bind("",self.popup_menu_input) fix_scroll() self.frm = frm def test(self): for i in self. inputs: i.test(self.frminput) def removerowclick(self): remove_row(self) def addinput(self,fnct): return lambda:self.inputs.append(block(fnct,parent=self.frminput,rung=self)) def addoutput(self,fnct): return lambda:self.outputs.append(block(fnct,parent=self.frmoutput,output=True)) def popup_menu(self,event): try: self.popup.tk_popup(event.x_root,event.y_root,0) finally: self.popup.grab_release() def popup_menu_input(self,event): try: self.inputmenu.tk_popup(event.x_root,event.y_root,0) finally: self.popup.grab_release() def display_update(self): for inputblock in self.inputs: inputblock.display_update() for outputblock in self.outputs: outputblock.display_update_output() def closeblock(bit): b = block() b.arguments.append(bit) return b if __name__ == '__main__': I1 = create_binary_array(10,'I1:0/') b1 = block() b1.function = func_and b1.arguments.append(I1[0]) b1.arguments.append(I1[1]) I1[0].set(True) I1[1].set(True) a = value_word(0) out1 = block() out1.function = func_add out1.arguments.append(a) out1.arguments.append(1) out1.run() r = rung() r.inputs += [b1] r.outputs += [out1] r.run() I1[0].set(False) r.run() tkxmltoladder.py0000644000175000017500000001153411677227637012760 0ustar danodanofrom xml.etree import ElementTree import tkinterladder as ladder import block_functions def block_from_xml(IOlist,block): newblock = ladder.block() funcxml = block.find('function') #print funcxml.text if funcxml is None: raise Warning('bad xml in a block, no function name' + str(block)) functxt = funcxml.text if block_functions.BLOCK_FUNCTION_DICT.has_key(functxt): newblock.function = block_functions.BLOCK_FUNCTION_DICT[functxt]() argsxml = block.find('arguments') for arg in argsxml.getchildren(): newargument = None if arg.tag == 'item': if IOlist.has_key(arg.text): newargument = IOlist[arg.text] else: raise Warning('bad xml in a block\'s arguments. Cannot resolve IO name: ' + arg.text) elif arg.tag == 'block': newargument = block_from_xml(IOlist,arg) else: raise Warning('bad xml in block\'s arguments. Not an item or block') if newargument is not None: newblock.arguments.append(newargument) return newblock def create_ladder(xmlfile): data = ElementTree.parse(xmlfile) ladderxml = data.getroot() #Set up IO IOxml = ladderxml.find('IO') IOlist = {} for item in IOxml.getchildren(): value = None valuetxt=None newIO = None if item.tag != 'item': Warning('bad xml in the IO section: expected an : ' + str(item.tag)) continue namexml = item.find('name') typexml = item.find('type') descxml = item.find('desc') valuexml = item.find('value') #if (not namexml) and (not typexml): #raise Warning('bad xml in the IO section: missing name or type: ' + str(item.tag)) #continue nametxt = namexml.text typetxt = typexml.text if not descxml: desctxt = '' else: desctxt = descxml.text if valuexml is not None: valuetxt = valuexml.text if typetxt=='int': value = int(valuetxt) elif typetxt=='str': value = str(valuetxt) elif typetxt=='bool': value = bool(valuetxt) else: value = valuetxt #print nametxt, newIO = ladder.IO(value,nametxt,typetxt,description=desctxt) #print newIO if newIO is not None: IOlist[nametxt] = newIO #print "-----" #rint IOlist #rint "-----" #Set up Rungs #Rungsxml = ladderxml.findall('Rung') Rungslist = ladderxml.findall('Rung') Rungs = [] for rung in Rungslist: #for item in Rungsxml.getchildren(): #print "addrung" newrung = ladder.rung() for rungside in rung.getchildren(): for subitem in rungside.getchildren(): block = None if subitem.tag != 'block': Warning('bad xml in Rung input section, excpeted a block' + str(subitem.tag)) #print "error here", subitem.tag continue block = block_from_xml(IOlist,subitem) if block is not None: if rungside.tag == 'input': newrung.inputs.append(block) elif rungside.tag == 'output': newrung.outputs.append(block) else: Warning('bad xml in the Rung section, expected input or output block: ' + str(item.tag)) pass continue Rungs.append(newrung) IOlist = IOlist.values() ##print "---" #print Rungs #print "+++" return [Rungs, IOlist] def io_to_xml(IO): itemxml = ElementTree.Element('item') namexml = ElementTree.SubElement(itemxml,'name') namexml.text = str(IO.name) typexml = ElementTree.SubElement(itemxml,'type') typexml.text = str(IO.typedesc) descxml = ElementTree.SubElement(itemxml,'description') descxml.text = str(IO.description) valuexml = ElementTree.SubElement(itemxml,'value') valuexml.text = str(IO.value) return itemxml def block_to_xml(Block): itemxml = ElementTree.Element('block') functionxml = ElementTree.SubElement(itemxml,'function') functionxml.text = Block.function.name argumentsxml = ElementTree.SubElement(itemxml,'arguments') for arg in Block.arguments: if isinstance(arg,ladder.IO):#arg.__class__ == ladder.IO.__class__: argxml = ElementTree.SubElement(argumentsxml,'item') argxml.text = arg.name elif isinstance(arg,ladder.block):#arg.__class__ == ladder.block.__class__: argxml = block_to_xml(arg) argumentsxml.append(argxml) else: raise Exception('bad type' + str(ladder.IO.__class__) + " " + str(arg.__class__)) return itemxml def create_xml(Rungs,IO,filename): ladderxml = ElementTree.Element('ladder') #take care of IO IOxml = ElementTree.SubElement(ladderxml,'IO') for item in IO: itemxml = io_to_xml(item) IOxml.append(itemxml) #Set up Rungs for rung in Rungs: Rungxml = ElementTree.SubElement(ladderxml,'Rung') inputsxml = ElementTree.SubElement(Rungxml,'input') outputsxml = ElementTree.SubElement(Rungxml,'output') for block in rung.inputs: b = block_to_xml(block) inputsxml.append(b) for block in rung.outputs: b = block_to_xml(block) outputsxml.append(b) tree = ElementTree.ElementTree(ladderxml) tree.write(filename) return True xmltoladder.py0000644000175000017500000001151311675237203012402 0ustar danodanofrom xml.etree import ElementTree import ladder import block_functions def block_from_xml(IOlist,block): newblock = ladder.block() funcxml = block.find('function') #print funcxml.text if funcxml is None: raise Warning('bad xml in a block, no function name' + str(block)) functxt = funcxml.text if block_functions.BLOCK_FUNCTION_DICT.has_key(functxt): newblock.function = block_functions.BLOCK_FUNCTION_DICT[functxt]() argsxml = block.find('arguments') for arg in argsxml.getchildren(): newargument = None if arg.tag == 'item': if IOlist.has_key(arg.text): newargument = IOlist[arg.text] else: raise Warning('bad xml in a block\'s arguments. Cannot resolve IO name: ' + arg.text) elif arg.tag == 'block': newargument = block_from_xml(IOlist,arg) else: raise Warning('bad xml in block\'s arguments. Not an item or block') if newargument is not None: newblock.arguments.append(newargument) return newblock def create_ladder(xmlfile): data = ElementTree.parse(xmlfile) ladderxml = data.getroot() #Set up IO IOxml = ladderxml.find('IO') IOlist = {} for item in IOxml.getchildren(): value = None valuetxt=None newIO = None if item.tag != 'item': Warning('bad xml in the IO section: expected an : ' + str(item.tag)) continue namexml = item.find('name') typexml = item.find('type') descxml = item.find('desc') valuexml = item.find('value') #if (not namexml) and (not typexml): #raise Warning('bad xml in the IO section: missing name or type: ' + str(item.tag)) #continue nametxt = namexml.text typetxt = typexml.text if not descxml: desctxt = '' else: desctxt = descxml.text if valuexml is not None: valuetxt = valuexml.text if typetxt=='int': value = int(valuetxt) elif typetxt=='str': value = str(valuetxt) elif typetxt=='bool': value = bool(valuetxt) else: value = valuetxt #print nametxt, newIO = ladder.IO(value,nametxt,typetxt,description=desctxt) #print newIO if newIO is not None: IOlist[nametxt] = newIO #print "-----" #rint IOlist #rint "-----" #Set up Rungs #Rungsxml = ladderxml.findall('Rung') Rungslist = ladderxml.findall('Rung') Rungs = [] for rung in Rungslist: #for item in Rungsxml.getchildren(): #print "addrung" newrung = ladder.rung() for rungside in rung.getchildren(): for subitem in rungside.getchildren(): block = None if subitem.tag != 'block': Warning('bad xml in Rung input section, excpeted a block' + str(subitem.tag)) #print "error here", subitem.tag continue block = block_from_xml(IOlist,subitem) if block is not None: if rungside.tag == 'input': newrung.inputs.append(block) elif rungside.tag == 'output': newrung.outputs.append(block) else: Warning('bad xml in the Rung section, expected input or output block: ' + str(item.tag)) pass continue Rungs.append(newrung) IOlist = IOlist.values() ##print "---" #print Rungs #print "+++" return [Rungs, IOlist] def io_to_xml(IO): itemxml = ElementTree.Element('item') namexml = ElementTree.SubElement(itemxml,'name') namexml.text = str(IO.name) typexml = ElementTree.SubElement(itemxml,'type') typexml.text = str(IO.typedesc) descxml = ElementTree.SubElement(itemxml,'description') descxml.text = str(IO.description) valuexml = ElementTree.SubElement(itemxml,'value') valuexml.text = str(IO.value) return itemxml def block_to_xml(Block): itemxml = ElementTree.Element('block') functionxml = ElementTree.SubElement(itemxml,'function') functionxml.text = Block.function.name argumentsxml = ElementTree.SubElement(itemxml,'arguments') for arg in Block.arguments: if isinstance(arg,ladder.IO):#arg.__class__ == ladder.IO.__class__: argxml = ElementTree.SubElement(argumentsxml,'item') argxml.text = arg.name elif isinstance(arg,ladder.block):#arg.__class__ == ladder.block.__class__: argxml = block_to_xml(arg) argumentsxml.append(argxml) else: raise Exception('bad type' + str(ladder.IO.__class__) + " " + str(arg.__class__)) return itemxml def create_xml(Rungs,IO,filename): ladderxml = ElementTree.Element('ladder') #take care of IO IOxml = ElementTree.SubElement(ladderxml,'IO') for item in IO: itemxml = io_to_xml(item) IOxml.append(itemxml) #Set up Rungs for rung in Rungs: Rungxml = ElementTree.SubElement(ladderxml,'Rung') inputsxml = ElementTree.SubElement(Rungxml,'input') outputsxml = ElementTree.SubElement(Rungxml,'output') for block in rung.inputs: b = block_to_xml(block) inputsxml.append(b) for block in rung.outputs: b = block_to_xml(block) outputsxml.append(b) tree = ElementTree.ElementTree(ladderxml) tree.write(filename) return True