From 819c67d4428ab1d1b08ec08ebe57fcce30626c12 Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Wed, 6 Nov 2019 22:12:54 +0000 Subject: [PATCH] add other apps --- apps.json | 44 ++++++++++++ apps/bootloader.js | 74 ++++++++++++++++++++ apps/bootloader.png | Bin 0 -> 741 bytes apps/heartrate-icon.js | 1 + apps/heartrate.js | 69 +++++++++++++++++++ apps/heartrate.json | 5 ++ apps/heartrate.png | Bin 0 -> 1777 bytes apps/settings-default.json | 8 +++ apps/settings-icon.js | 1 + apps/settings-init.js | 55 +++++++++++++++ apps/settings.js | 135 +++++++++++++++++++++++++++++++++++++ apps/settings.json | 5 ++ apps/settings.png | Bin 0 -> 1733 bytes apps/spiritlevel.js | 7 ++ apps/stopwatch-icon.js | 1 + apps/stopwatch.js | 84 +++++++++++++++++++++++ apps/stopwatch.json | 5 ++ apps/stopwatch.png | Bin 0 -> 1566 bytes 18 files changed, 494 insertions(+) create mode 100644 apps/bootloader.js create mode 100644 apps/bootloader.png create mode 100644 apps/heartrate-icon.js create mode 100644 apps/heartrate.js create mode 100644 apps/heartrate.json create mode 100644 apps/heartrate.png create mode 100644 apps/settings-default.json create mode 100644 apps/settings-icon.js create mode 100644 apps/settings-init.js create mode 100644 apps/settings.js create mode 100644 apps/settings.json create mode 100644 apps/settings.png create mode 100644 apps/stopwatch-icon.js create mode 100644 apps/stopwatch.js create mode 100644 apps/stopwatch.json create mode 100644 apps/stopwatch.png diff --git a/apps.json b/apps.json index 802451c46..c66a90fe0 100644 --- a/apps.json +++ b/apps.json @@ -1,4 +1,13 @@ [ + { "id": "boot", + "name": "Bootloader", + "icon": "bootloader.png", + "description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings", + "tags": "tool,system", + "storage": [ + {"name":".bootcde","url":"bootloader.js"} + ] + }, { "id": "trex", "name": "T-Rex", "icon": "trex.png", @@ -75,6 +84,19 @@ {"name":"*slevel","url":"spiritlevel-icon.js","evaluate":true} ] }, + { "id": "settings", + "name": "Settings", + "icon": "settings.png", + "description": "Show the current angle of the watch, so you can use it to make sure something is absolutely flat", + "tags": "tool,system", + "storage": [ + {"name":"+settings","url":"settings.json"}, + {"name":"-settings","url":"settings.js"}, + {"name":"=settings","url":"settings-init.js"}, + {"name":"@settings","url":"settings-default.json","evaluate":true}, + {"name":"*settings","url":"settings-icon.js","evaluate":true} + ] + }, { "id": "sbat", "name": "Battery Level Widget", "icon": "widget-battery.png", @@ -84,6 +106,28 @@ {"name":"=sbat","url":"widget-battery.js"} ] }, + { "id": "hrm", + "name": "Heart Rate Monitor", + "icon": "heartrate.png", + "description": "Measure your current heart rate", + "tags": "health", + "storage": [ + {"name":"+hrm","url":"heartrate.json"}, + {"name":"-hrm","url":"heartrate.js"}, + {"name":"*hrm","url":"heartrate-icon.js","evaluate":true} + ] + }, + { "id": "swatch", + "name": "Stopwatch", + "icon": "stopwatch.png", + "description": "Simple stopwatch with Lap Time recording", + "tags": "health", + "storage": [ + {"name":"+swatch","url":"stopwatch.json"}, + {"name":"-swatch","url":"stopwatch.js"}, + {"name":"*swatch","url":"stopwatch-icon.js","evaluate":true} + ] + }, { "id": "qrcode", "name": "Custom QR Code", "icon": "qrcode.png", diff --git a/apps/bootloader.js b/apps/bootloader.js new file mode 100644 index 000000000..a4aed10ef --- /dev/null +++ b/apps/bootloader.js @@ -0,0 +1,74 @@ +E.setTimeZone(1); +E.setFlags({pretokenise:1}); +setWatch(function() { + Bangle.setLCDMode("direct"); + g.clear(); + clearInterval(); + clearWatch(); + Bangle.removeAllListeners(); + + var s = require("Storage"); + var apps = s.list().filter(a=>a[0]=='+').map(app=>s.readJSON(app)); + var selected = 0; + var menuScroll = 0; + var menuShowing = false; + + function drawMenu() { + g.setFont("6x8",2); + g.setFontAlign(-1,0); + var n = 3; + if (selected>=n+menuScroll) menuScroll = 1+selected-n; + if (selectedn+menuScroll) g.fillPoly([120,239,100,219,140,219]); + else g.clearRect(100,219,140,239); + for (var i=0;i0) { + selected--; + drawMenu(); + } + }, BTN1, {repeat:true}); + setWatch(function() { + if (selected+1WIDGETS[k].draw()); +} +eval(require("Storage").read("-clock")); +require("Storage").list().filter(a=>a[0]=='=').forEach(widget=>eval(require("Storage").read(widget))); +setTimeout(drawWidgets,100); diff --git a/apps/bootloader.png b/apps/bootloader.png new file mode 100644 index 0000000000000000000000000000000000000000..abbc8bf90713f1ea1bd8df4d0f3455b6eec4534b GIT binary patch literal 741 zcmV#n=&BI2LQ{y>=oiIs$F)D zP6-{WOGrVtK)O`WC9qC~p3h#pya3oPc~C<;xMSIE^gA1X2924i?g&4S9vk{ zb-QKB8Y0Djqa=v`$bJw@TSuUy9@Ud8L)s8227;X}C{72FCohP<`-JNA;BTvwA#KRI z4R{ai!d)Ys@F~6SQ7wR{|F+UVE)SwqN8si zM_u}$EymPFwyF0*()M3dABer4{(Bu69K*A(0{6~x^7ial)&}6(QbO^?Q%P4VJrsq{ z#<8t?3W)_Bt^et=dyBCI;ejy#3XV6TIRAN13Iscwr7g9lGBDG1blI zhOecD$m0nB@|&u0Z7pSCHpJJ`I+xad&JO7soIuLApjNyTHi1z zAaq@-bb1T`@#z^$bhEx9GnD}|wP~iv`V80xGL0qGclY()6@N?ioa>T4Ue;$I>kXuo z((W6#I}qX)Agkz|MN!W5oIO3T)(2C|fDkv47d$}GAI&wB>J%zV@ws$s+Su6G*d*sS X1VXZEdEUi300000NkvXXu0mjfQsr6S literal 0 HcmV?d00001 diff --git a/apps/heartrate-icon.js b/apps/heartrate-icon.js new file mode 100644 index 000000000..cadbc7dfa --- /dev/null +++ b/apps/heartrate-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AH4AThnMAAXABJoMHBwgJJAAYMFAAIJLFxImCBJIuLABYuI4gXNNZFCC6AIFkZIQA4szC6vEmdMC60sC6nDmc8C6RDBC4irLC4gTBocymgGBoYXO4UyUwNEAYKrMC4ZEBUwNMVAR7LC4dDCoYBBSYJ7DoZQCC4kCmczkc0JIVM4UzmgaBAAQWD4AXBggJBJAIkBocs4c0BAQXJJARBD4c8oc8HAKZCI4gWCVAYXEJIJoCOovNC4cMUIQPB4RFBTAYAFIwapEC4JyCZAalHGAvCJYZYCVAYuIMIhjE5heGCwxhDMYTtIFw4wFoYsGFxIwF4YuRGAh7DFxxhGFyIYKCxqrGIpwwKFx4YGCyJJFCyQYDCygA/AH4AFA=")) diff --git a/apps/heartrate.js b/apps/heartrate.js new file mode 100644 index 000000000..c31314af1 --- /dev/null +++ b/apps/heartrate.js @@ -0,0 +1,69 @@ +Bangle.setLCDPower(1); +Bangle.setLCDTimeout(0); +Bangle.ioWr(0x80,0) +x=0; +var min=0,max=0; +var wasHigh = 0, wasLow = 0; +var lastHigh = getTime(); +var hrmList = []; +var hrm; + +function readHRM() { + var a = analogRead(D29); + var h = getTime(); + min=Math.min(min*0.97+a*0.03,a); + max=Math.max(max*0.97+a*0.03,a); + y = E.clip(170 - (a*960*4),100,230); + if (x==0) { + g.clearRect(0,100,239,239); + g.moveTo(-100,0); + } + /*g.setColor(0,1,0); + var z = 170 - (min*960*4); g.fillRect(x,z,x,z); + var z = 170 - (max*960*4); g.fillRect(x,z,x,z);*/ + g.setColor(1,1,1); + g.lineTo(x,y); + if ((max-min)>0.005) { + if (4*a > (min+3*max)) { // high + g.setColor(1,0,0); + g.fillRect(x,230,x,239); + g.setColor(1,1,1); + if (!wasHigh && wasLow) { + var currentHrm = 60/(h-lastHigh); + lastHigh = h; + if (currentHrm<250) { + while (hrmList.length>12) hrmList.shift(); + hrmList.push(currentHrm); + // median filter + var t = hrmList.slice(); // copy + t.sort(); + // average the middle 3 + var mid = t.length>>1; + hrm = (t[mid]+t[mid+1]+t[mid+2])/3; + g.setFontVector(40); + g.setFontAlign(0,0); + g.clearRect(0,0,239,100); + var str = Math.round(hrm); + var px = 120; + g.drawString(str,px,40); + px += g.stringWidth(str)/2; + g.setFont("6x8"); + g.drawString("BPM",px+20,60); + } + } + wasLow = 0; + wasHigh = 1; + } else if (4*a < (max+3*min)) { // low + wasLow = 1; + } else { // middle + g.setColor(0.5,0,0); + g.fillRect(x,230,x,239); + g.setColor(1,1,1); + wasHigh = 0; + } + } + x++; + if (x>239)x=0; +} + +setInterval(readHRM,50); diff --git a/apps/heartrate.json b/apps/heartrate.json new file mode 100644 index 000000000..ef69a8d5f --- /dev/null +++ b/apps/heartrate.json @@ -0,0 +1,5 @@ +{ + name:"Heart Rate", + icon:"*hrm", + src:"-hrm" +} diff --git a/apps/heartrate.png b/apps/heartrate.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc07d6474a68433d2610723b1dcf85b80fde104 GIT binary patch literal 1777 zcmVgEM_r_S=>n|FG`Wd%7r%OY9)LN zxECk`OpxoK`hX**HvT%Nz2l-Me@(&rw9Q%+j{@bu=Ku{{1}=)Q&$MJu@uBX4+afSp zS6$|qvIThFO*ktw!kdOAKh6|5(pb4rWBw9httWr5(-8Bg#o?1zawag+5L^TNS)gQ^ zKvOX!$c8{$_nV2r;rd{!QtX(CKjj$D`j2$HovzbJCme35-Yl?t2EhOV3iZZtef19~ z{RLk^v1_LO#BlyL)KK+uy3Ujl7-rFnPDksi%fytkUcQtH%)_jz!44nDrZyCP269d}gFj{XzUn`Gd@v?R`pS;w{M_p> zo69OOmn@r3EkOQ~2M9RM#Fj)soIs?pav`Fg^Xkbguf$wZp2yt0LBaaby6Upndz^qR z#u}zy+M-?*W_bmwbUwy{vg!5n3sak{j=jVQDAX#i9*i&Dk1Cz-win-lQBd;DcrKGd zzU0O9^v(?}5B?bY5aPatq->foMOJ5E5rNvG`l&Qr<8bLL<^un4q9 zhCa(&2g<;BU{T(ZPK3DI3?O+Ky*yRXC|gKD>kAlPymwNzWI0$q^!X3bXWnyVY3jgS zyp)2qo)@)T5zjTT20oaoN z^vBwhQ%Gb_l9Im!>yZWke6=f4CGP*oEPoIH-PeQeKjq4%)`4ka25Vf~rk1=DNX#`( zU|c!-6b#4Stw)>Py$(P@Ay(~5jJxl_xN8A2bQ9;od7LW)$fws+>cCw3AZl)@>!-Fc zPCgyXiXg?m*+ucT*D#k?dC7m&9xMDuJkMBTeyI1RePCa|`SqG$qP87AfK|T=Uvmp+ zx6XCndmz#F+0$4xwHS-baR%L~X{~r9YHY{JPwan-g71DGU(1up=?L*1S>cA48QrvQ zn*7!F(qqRyjlCu9h3vsg&k-4wxbJIM8T->V#wQZ_Ag@6J9^$ZDX zt&9v~9}PtVrUubxqswVU5UZjJM6f%LxN7u#w4@g6JSw7x+q9>y zlD5uI3~`Ruf;M?x>IsLCR1l(!ld#n`-i_jnSBmcSPCY?auem8_fBDp3%?kM$S zri+4%$HwAtPnQx|3)le8O7W7A?vx4oP?;i#ti#~`JvvNjc@C=Y`DI99ZI$X{u#O$ z1NEblw$AK(h%C6>>pq4XS1d*J8^DqrP0uMYp7Dn}QvZxOAg3Bghmz3A)BYQ}LKIs_ zIICuX2(rU(-m1LaW3VWoxoSp22-5ZsP) { + settings.timeout = 0 | v; + updateSettings(); + Bangle.setLCDTimeout(settings.timeout); + } + }, + 'Beep': { + value: settings.beep, + format: boolFormat, + onchange: () => { + settings.beep = !settings.beep; + updateSettings(); + if (settings.beep) { + Bangle.beep(1); + } + } + }, + 'Vibration': { + value: settings.vibrate, + format: boolFormat, + onchange: () => { + settings.vibrate = !settings.vibrate; + updateSettings(); + if (settings.vibrate) { + VIBRATE.write(1); + setTimeout(()=>VIBRATE.write(0), 10); + } + } + }, + 'Time Zone': { + value: settings.timezone, + min: -11, + max: 12, + step: 1, + onchange: v => { + settings.timezone = 0 | v; + updateSettings(); + } + }, + 'HID': { + value: settings.HID, + format: boolFormat, + onchange: () => { + settings.HID = !settings.HID; + updateSettings(); + } + }, + 'Debug': { + value: settings.debug, + format: boolFormat, + onchange: () => { + settings.debug = !settings.debug; + updateSettings(); + } + }, + 'Reset': showResetMenu, + 'Turn Off': Bangle.off, + '< Back': load + }; + return Bangle.menu(mainmenu); +} + +function showResetMenu() { + const resetmenu = { + '': { 'title': 'Reset' }, + '< Back': showMainMenu, + 'Reset Settings': () => { + E.showPrompt('Reset Settings?').then((v) => { + if (v) { + E.showMessage('Resetting'); + resetSettings(); + } + setTimeout(showMainMenu, 50); + }); + }, + // this is include for debugging. remove for production + /*'Erase': () => { + storage.erase('=settings'); + storage.erase('-settings'); + storage.erase('@settings'); + storage.erase('*settings'); + storage.erase('+settings'); + E.reboot(); + }*/ + }; + return Bangle.menu(resetmenu); +} + +showMainMenu(); diff --git a/apps/settings.json b/apps/settings.json new file mode 100644 index 000000000..b0dee3cc6 --- /dev/null +++ b/apps/settings.json @@ -0,0 +1,5 @@ +{ + name: 'Settings', + icon: '*settings', + src: '-settings' +} diff --git a/apps/settings.png b/apps/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..71e7535238fc79aafe33e060f25ed6ab9b729379 GIT binary patch literal 1733 zcmV;$20HnPP)5?UwC3AlaHEvFmg*cgngh7Sc z*L!Ju&pFTj0Ox%<=bqDYlaY+?f9HAmKF{-eea<;>12^#h4XG4ShkYQWp7Xk>-dxsD zWg7Is^g!=FD-kzV2@(Ktb8R5s4IL8!QS&|EKqcauN{|2`+!WPRjkZ=QY{d%jRH7&4 zMK3lC6)Qkk#Hb3_-wd#b84xO0KtfzGFg+f{v&2Uw?~>xzZ0 z<1m+h82rbo2a8ES%(!X2%Au}zsyK3%emej;4wd?uiuY*_tksV z<`hu=R=o<(b(x+RCp$fXYPfx(zLCb}7K|179`yvibELoL?P7)RDdg3d-9$`&E}P-P z*-yw!MN1waJ2S^#?&{1=!Q`!}xRL@`|+k6YXF1qB5(OeQ`?^kf*z%*6PT zmQ4^m&tqm{Jc{YKTJnc{K|ukB4sD;m&1BahJOlV<=sFJBnUHm>ykZRN#hR}ZYram< z4Zy4AT(UEhxNg~5_@n1pZAa~%5Aub9$8Trf^BWX73arYPXQn5ayf{`Sj_f$`(&1nF zX*O=&)`jSBkZ0raR_Yp3#o~EY**!9_b3aJQnhk8`j`a8b2`knHyp}J&oS6;IU3n!a zd4LnYIlLcbG)O;RvYGW?So-aef$m|yo9V5H|DZF3H-L?yJ^`Y%~ zZPip@OfBA$8h%cP7NBAZc5G22R)DJb{5Qh*a#9;;W5uk3XMu931rSoeqxMh>s+;m< zv3PLXugFhB1A4)2N-S2$t#J24Pwl(2EMJhKTep49p#c2f!0;4gO{jsER(SwJAR<+a$o^lcMsj_8hwkR%c~fZ7stp%?II&0 z*_a~L5_0H>yi-3>wf@kd?em|D3YL&#ybXjT574lz8DlEwYF&vH;q6S&-yN~L|51=j{zKZf=TPa660|0fq;dHQ zuB`|mwY-&@L^6u;ys=_Ci-jfYAU)9iTZK2HFxEAsXkK~Cl9rP(7R@VfAz9kI`9t`{ zi+0z?B|Lt%o_gDXTom4#M3NP&Zl^xA?8;FRmWM#1A;pR>-4=D^0UgVk>Sz5lmY?v0 zx8A&T*M@sr@c$-M0%Hu-i6qH}Myz<%l|?0T<aok*IH2+^mWD z$+2gic-L1I?47eB9*7`D$4sJ{0vA$&%_t!MP*P$?*~f0H34s@`@8R1 zbUXdhbGs%4>B|#cR^eBcWvvPuXq{e??qx8^WoPpZC02j;Lb)$U6H5|Eh3TBxB`pdZ5R*Raq=5cAaX3*7v`* zqLFRGP{AJ@(F-uhby$GmLR8c1at6d(#HecYV#83e|HO*-1}g1{Hk7xNb6D{YD;4(! b{@3^qyIVZM$=d%600000NkvXXu0mjfeqCj+ literal 0 HcmV?d00001 diff --git a/apps/spiritlevel.js b/apps/spiritlevel.js index ec02321f9..492fc60e1 100644 --- a/apps/spiritlevel.js +++ b/apps/spiritlevel.js @@ -1,6 +1,13 @@ g.clear(); var old = {x:0,y:0}; Bangle.on('accel',function(v) { + var max = Math.max(Math.abs(v.x),Math.abs(v.y),Math.abs(v.z)); + if (Math.abs(v.y)==max) { + v = {x:v.x,y:v.z,z:v.y}; + } else if (Math.abs(v.x)==max) { + v = {x:v.z,y:v.y,z:v.x}; + } + var d = Math.sqrt(v.x*v.x+v.y*v.y); var ang = Math.atan2(d,Math.abs(v.z))*180/Math.PI; diff --git a/apps/stopwatch-icon.js b/apps/stopwatch-icon.js new file mode 100644 index 000000000..9c3f21c86 --- /dev/null +++ b/apps/stopwatch-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwghC/AFECkQACkAX/C/4AKgMRiMQVCYXFGYIWOxAAEgJLPCwuIwRkCCyQABC5sICIUzn/zmYYEFxs//4AC+ZJCL5QuCCwXzDAuAFxeDCYYyDnALBC5YSD+UvGApGKFwYXFGARIIC4OPCIfxj4FD/AXJRgwXFJBQJBCAYXBiYGEC5ReE/8xC4pgBC50hiQXPOwn/iMRMwgXP+QXBVAiQBC8pHCO6rvFC6IAGC5TXFAAzvLUAgAF+YXJhB4GAAiOBwAXJMBReBC5BILIxQXDGBAuBC5RIBGA4uCIxIwDDAoWCFxQwExEzn/zmYGCFxYwEAAwWMDBIWODA4WQAH4AXA==")) diff --git a/apps/stopwatch.js b/apps/stopwatch.js new file mode 100644 index 000000000..98d6cba83 --- /dev/null +++ b/apps/stopwatch.js @@ -0,0 +1,84 @@ +var tStart = Date.now(); +var tCurrent = Date.now(); +var started = false; +var timeY = 60; +var hsXPos = 0; +var lapTimes = []; +var displayInterval; + +function timeToText(t) { + var secs = Math.floor(t/1000)%60; + var mins = Math.floor(t/60000); + var hs = Math.floor(t/10)%100; + return mins+":"+("0"+secs).substr(-2)+"."+("0"+hs).substr(-2); +} +function updateLabels() { + g.clear(); + g.setFont("6x8",2); + g.setFontAlign(0,0,3); + g.drawString(started?"STOP":"GO",230,120); + if (!started) g.drawString("RESET",230,50); + g.drawString("LAP",230,190); + g.setFont("6x8",1); + g.setFontAlign(-1,-1); + for (var i in lapTimes) { + g.drawString(i+": "+timeToText(lapTimes[i]),10,timeY + 30 + i*8); + } + drawsecs(); +} +function drawsecs() { + var t = tCurrent-tStart; + g.setFont("Vector",48); + g.setFontAlign(0,0); + var secs = Math.floor(t/1000)%60; + var mins = Math.floor(t/60000); + var txt = mins+":"+("0"+secs).substr(-2); + var x = 100; + g.clearRect(0,timeY-26,200,timeY+26); + g.drawString(txt,x,timeY); + hsXPos = 5+x+g.stringWidth(txt)/2; + drawms(); +} +function drawms() { + var t = tCurrent-tStart; + var hs = Math.floor(t/10)%100; + g.setFontAlign(-1,0); + g.setFont("6x8",2); + g.clearRect(hsXPos,timeY,220,timeY+20); + g.drawString("."+("0"+hs).substr(-2),hsXPos,timeY+10); +} + +setWatch(function() { // Start/stop + started = !started; + if (started) + tStart = Date.now()+tStart-tCurrent; + tCurrent = Date.now(); + if (displayInterval) { + clearInterval(displayInterval); + displayInterval = undefined; + } + updateLabels(); + if (started) + displayInterval = setInterval(function() { + var last = tCurrent; + if (started) tCurrent = Date.now(); + if (Math.floor(last/1000)!=Math.floor(tCurrent/1000)) + drawsecs(); + else + drawms(); + }, 20); +}, BTN2, {repeat:true}); +setWatch(function() { // Reset + if (!started) { + tStart = tCurrent = Date.now(); + } + updateLabels(); +}, BTN1, {repeat:true}); +setWatch(function() { // Lap + if (started) tCurrent = Date.now(); + lapTimes.unshift(tCurrent-tStart); + tStart = tCurrent; + updateLabels(); +}, BTN3, {repeat:true}); + +updateLabels(); diff --git a/apps/stopwatch.json b/apps/stopwatch.json new file mode 100644 index 000000000..2b57a1cbc --- /dev/null +++ b/apps/stopwatch.json @@ -0,0 +1,5 @@ +{ + name:"Stopwatch", + icon:"*swatch", + src:"-swatch" +} diff --git a/apps/stopwatch.png b/apps/stopwatch.png new file mode 100644 index 0000000000000000000000000000000000000000..92ffe73b7f172f7019486231d5bc0ba327c13a67 GIT binary patch literal 1566 zcmV+(2I2XMP)rZGih zXw&#$)jrm=iAGIK8xtz&C7L#UsP>_)mll=wMNO+ffoiD_T1#yUim(D(ys%vMfQPei zrVqOZR|L*1ZtO$(Kh4bfZvQzm-^@2KjcNS9p~TbJ{=+BFgR;y*?EJRZOY-C8(-tp7 zVORza@KQWI#+kt5$25We8u2D@-cmyFb37f!H4B+o`PybN-uT(0hvM!pG2RAaMai~Z zb8H5+fL|F7l^*}>gRWTg<2NIR8$39)H7FE$0)fri3@0JG9e^ZV$1ylSfBMXW2&%E$ zPO&yWxOMB+q<~{)6;Ked;unf2IeFmyUmqWV%KAJEV+T+Q%#hR?1dbK`*5J(O?<*bi zO;Z%!IDVk)sEnbh6R5b5f)d(`!dpynDcXcVZikO&eb-4ajOeKPP8=vpld&G6#BUZr!wEAw+((0R3 zrXbzLH_tucuqlj0hL$qu+9ey79D&OEyhyeIpghm3SOpJZ0ylh<6M!&@vIX9#5%D$^ zHQ6$u$q@+W`9uhB*iAW^QhKevc3RtRO1aoFFEin3XYxR#>%x|>M@G*&ki!ig!iN+9 z<#}G0?8a$r^U>1QCMBx?`|`Zb`y*l_6*ZQ>*_wbuTSMf8&2iX+vZ=DKzn?&FFTtQn zcOW38Q<&~zjy+M0be$yF)>!FS6Hq8Jo3G6BI3^Q~Myv1s(rIb4-UlX1veWFN9dY0H zM6f0xl*MMVIK!PJ8_3nan@8J?SO1b#qd3!TkLoU-P%r|SKQP~H&P}mTCOL|}{(ipr z>d0vS%cB+Cx!WzJJ2&|OAM+=YGa>N@@R-ePyWG|3Y)vhL_wLbMu#!7PtICQ=hz{kT!_!z`=nUlJl03sr&j0}&?P-3E-D?`>v1Tssg-)!E}hoy|JDk@}lRu&l- zYRIfV%j~QyRj3EpMT1a)jt8Ss%SYLw_Cy$ zv}^iU-;=_cfKaZ0SOQSht?Gs8E~%aDal2W+_GKB-2XL;tKLmucox&9>d6`XIZ8j@! z_e$Lm9|hmt>bvj55SBaNntrAr?+kNN?GIAf(_O<-+) z5IAObcQpIe7!!pQ3<2AlI-_PsIm(6=ma`>W50nETfTn>V*P-_A5exa4aj@g=$hAS; zoP8Zd*(akXM__&7H4yvF?#}Ce>f^YS>UvamU;ih1=wsFuSY{xIr6LQ8JwOA1AxJ1~ z^l9-RQP-pLYNJmx>tf&nGX~z3)sL;HKUp^iAua-QAzWIPE%35s-uOlRWH1CA?7VC2 zZtlWBm=oEKVJu<83hC#?DLkvxEdp_h2nm2Zr(G@2&2bfG$kht9Ju2IqI@PE(573Um z8a5O*#uCodUgj!lva#8|1C~Y)XWJ9ib=hh5;!L+aCpigk)WcS%7NR;3)K7Nx>QSlE zjN%tYjFMPp{?P~koIaf#emxNH08UH!@u4HcM>q%HlYK@Ri${Olt*FTk7vN$e@QR9l#uqzNePq-K{A831lR^T#FgJGPOIi#jBjcHipUslrouSl-W Q%m4rY07*qoM6N<$g48qYF8}}l literal 0 HcmV?d00001