From 2f2b9a843ccea36292f2bb6aaf945c8b493db1d3 Mon Sep 17 00:00:00 2001 From: Christian Zufferey Date: Sat, 25 Jul 2020 11:44:54 +0200 Subject: [PATCH] =?UTF-8?q?Commenc=C3=A9=20une=20nouvelle=20version=20de?= =?UTF-8?q?=20pet=20tracker=20qui=20n'utilise=20pas=20le=20module=20rtc-me?= =?UTF-8?q?m=20J'utilisais=20la=20possibilit=C3=A9=20de=20sauvegarder=20le?= =?UTF-8?q?=20flag=20de=20dsleep=20dans=20la=20rtc-mem=20afin=20de=20pouvo?= =?UTF-8?q?ir=20diff=C3=A9rencier=20lors=20du=20boot=20si=20c'est=20un=20r?= =?UTF-8?q?eset=20ou=20une=20sortie=20de=20sommeil=20profond.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maintenant je vais partir du principe que quand il y a un *hardware RESET* c'est forcément une sortie de dsleep. Si on veut avoir la *seconde chance* lors de la procédure de boot, il faudra utiliser le *power on RESET* --- DeepSleep/Pet_tracker_2/README.md | 29 +- DeepSleep/Pet_tracker_3/0_dsleep2.lua | 116 +++++ DeepSleep/Pet_tracker_3/0_rtelnet1.lua | 148 +++++++ DeepSleep/Pet_tracker_3/0_tst5_socat.lua | 115 +++++ DeepSleep/Pet_tracker_3/0_wifi_scan.lua | 56 +++ DeepSleep/Pet_tracker_3/README.md | 191 ++++++++ DeepSleep/Pet_tracker_3/_secrets_project.lua_ | 35 ++ DeepSleep/Pet_tracker_3/_secrets_wifi.lua_ | 18 + DeepSleep/Pet_tracker_3/_zlocal_cmd.txt | 16 + DeepSleep/Pet_tracker_3/_zremote_cmd.txt | 301 +++++++++++++ DeepSleep/Pet_tracker_3/boot.lua | 77 ++++ DeepSleep/Pet_tracker_3/flash_led_xfois.lua | 38 ++ DeepSleep/Pet_tracker_3/goodies/0_cron.lua | 49 +++ .../Pet_tracker_3/goodies/0_http_post.lua | 45 ++ .../goodies/a_test_power_wifi.lua | 57 +++ DeepSleep/Pet_tracker_3/goodies/cat.lua | 18 + DeepSleep/Pet_tracker_3/goodies/dir.lua | 31 ++ DeepSleep/Pet_tracker_3/goodies/dir3.lua | 52 +++ DeepSleep/Pet_tracker_3/goodies/head.lua | 19 + DeepSleep/Pet_tracker_3/goodies/repair.lua | 16 + DeepSleep/Pet_tracker_3/goodies/restart.lua | 12 + DeepSleep/Pet_tracker_3/initz.lua | 91 ++++ DeepSleep/Pet_tracker_3/luatool.py | 408 ++++++++++++++++++ DeepSleep/Pet_tracker_3/oldies/0_dsleep.lua | 91 ++++ .../Pet_tracker_3/schemas/water-level.fzz | Bin 0 -> 77042 bytes DeepSleep/Pet_tracker_3/set_time.lua | 19 + DeepSleep/Pet_tracker_3/upload_s.sh | 48 +++ DeepSleep/Pet_tracker_3/wifi_info.lua | 45 ++ DeepSleep/Pet_tracker_3/wifi_init.lua | 92 ++++ 29 files changed, 2231 insertions(+), 2 deletions(-) create mode 100644 DeepSleep/Pet_tracker_3/0_dsleep2.lua create mode 100644 DeepSleep/Pet_tracker_3/0_rtelnet1.lua create mode 100644 DeepSleep/Pet_tracker_3/0_tst5_socat.lua create mode 100644 DeepSleep/Pet_tracker_3/0_wifi_scan.lua create mode 100644 DeepSleep/Pet_tracker_3/README.md create mode 100644 DeepSleep/Pet_tracker_3/_secrets_project.lua_ create mode 100644 DeepSleep/Pet_tracker_3/_secrets_wifi.lua_ create mode 100644 DeepSleep/Pet_tracker_3/_zlocal_cmd.txt create mode 100644 DeepSleep/Pet_tracker_3/_zremote_cmd.txt create mode 100644 DeepSleep/Pet_tracker_3/boot.lua create mode 100644 DeepSleep/Pet_tracker_3/flash_led_xfois.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/0_cron.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/0_http_post.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/a_test_power_wifi.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/cat.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/dir.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/dir3.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/head.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/repair.lua create mode 100644 DeepSleep/Pet_tracker_3/goodies/restart.lua create mode 100644 DeepSleep/Pet_tracker_3/initz.lua create mode 100755 DeepSleep/Pet_tracker_3/luatool.py create mode 100644 DeepSleep/Pet_tracker_3/oldies/0_dsleep.lua create mode 100644 DeepSleep/Pet_tracker_3/schemas/water-level.fzz create mode 100644 DeepSleep/Pet_tracker_3/set_time.lua create mode 100644 DeepSleep/Pet_tracker_3/upload_s.sh create mode 100644 DeepSleep/Pet_tracker_3/wifi_info.lua create mode 100644 DeepSleep/Pet_tracker_3/wifi_init.lua diff --git a/DeepSleep/Pet_tracker_2/README.md b/DeepSleep/Pet_tracker_2/README.md index ba6e000..3110d38 100644 --- a/DeepSleep/Pet_tracker_2/README.md +++ b/DeepSleep/Pet_tracker_2/README.md @@ -1,11 +1,14 @@ -# Mesure de hauteur d'eau dans un réservoir +# Enregistre sur la FLASH tous les AP WIFI vu lors du réveil du NodeMCU -zf200627.1330 +Version qui utilise la fonctionnalité d'enregistrer dans la mémoire rtc-mem un flag qui permet de détecter lors du boot si l'on sort du *sommeil profond*. Car quand le NodeMCU sort du *sommeil profond*, c'est une pin D0 qui fait un RESET une résistance de 1k ! +Nécessite donc d'avoir le module rtc-mem dans le firmware du NodeMCU +zf200725.1140 ## Table of Contents * [Buts](#buts) +* [Problématiques](#problématiques) * [Astuces de mesures de la distance au moyen du senseur ultrason](#astuces-de-mesures-de-la-distance-au-moyen-du-senseur-ultrason) * [Schéma](#schéma) * [Astuces](#astuces) @@ -19,6 +22,28 @@ zf200627.1330 ## Buts +Petit projet pour *géolocaliser* un chat lors de ses sorties à l'extérieur afin de *voir* où il va. + +Le but étant de fixer un NodeMCU avec un collier au coup d'un chat et d'enregistrer sur la FLASH tous les AP WIFI vu lors de la sortie. + +Après, en post traitement sur l'ordi, on arrive à faire la corrélation AP WIFI coordonnées GPS et pourvoir tracer (géolocaliser) la sortie du chat sans avoir besoin d'utiliser un module GPS qui est trop gros pour le mettre dans un collier. + + +## Problématiques +La problématique c'est que le NodeMCU doit fonctionner sur une batterie très petite, 400mA/h de capacité. + +Le NodeMCU doit donc *dormir* la plus part du temps si l'on veut pouvoir utiliser le *pet tracker* sur plusieurs jours sans devoir recharger la batterie à chaque sortie. + + + + + +ATTENTION, tout le reste provient d'un autre readme ! zf200725.1116 + + + + + Petit projet pour mesurer la hauteur d'eau dans un réservoir de 100l au moyen d'un senseur à ultrason utilisé pour de la robotique récréative. diff --git a/DeepSleep/Pet_tracker_3/0_dsleep2.lua b/DeepSleep/Pet_tracker_3/0_dsleep2.lua new file mode 100644 index 0000000..6d7710c --- /dev/null +++ b/DeepSleep/Pet_tracker_3/0_dsleep2.lua @@ -0,0 +1,116 @@ +-- Teste le deep sleep ! +-- s'endore pendant xx secondes après xx secondes + +-- ATTENTION: il faut connecter la pin 0 à la pin RESET avec une résistance de 1k ! + +print("\n dsleep.lua zf200725.1053 \n") + +zLED=4 +f= "flash_led_xfois.lua" if file.exists(f) then dofile(f) end + +function ztime() + tm = rtctime.epoch2cal(rtctime.get()+2*3600) + return (string.format("%04d/%02d/%02d %02d:%02d:%02d", tm["year"], tm["mon"], tm["day"], tm["hour"], tm["min"], tm["sec"])) +end + +function dsleep_on() + print("timer dsleep on...") + -- ztmr_SLEEP = tmr.create() + -- ztmr_SLEEP:alarm(2*1000, tmr.ALARM_SINGLE, function () + print("Il est "..ztime().." et je vais dormir...") + tmr.delay(100*1000) + -- node.dsleep(4*1000*1000) + -- print(node.bootreason()) + rtcmem.write32(10, 43690) --flag pour détecter le réveil dsleep au moment du boot + -- print("le flag est à "..rtcmem.read32(10)) + wifi.setmode(wifi.NULLMODE,true) + rtctime.dsleep(4*1000*1000) + -- end) +end + +--[[ +dsleep_on() +print(node.bootreason()) +print("le flag est à "..rtcmem.read32(10)) + +f= "wifi_info.lua" if file.exists(f) then dofile(f) end + +function ztime() + tm = rtctime.epoch2cal(rtctime.get()+2*3600) + print(string.format("%04d/%02d/%02d %02d:%02d:%02d", tm["year"], tm["mon"], tm["day"], tm["hour"], tm["min"], tm["sec"])) +end + +print(ztime()) + + +]] + +-- on se réveil, vérifie si on peut avoir du réseau autrement on va redormir +function dsleep_wake_up() + print("Coucou, je suis réveillé... et il est "..ztime()) + if wifi.sta.getip() == nil then + print("Unconnected...") + f = "0_wifi_scan.lua" if file.exists(f) then dofile(f) end + wifi.setmode(wifi.STATION) + scan_wifi() + else + print("Connected...") + end + -- f= "wifi_info.lua" if file.exists(f) then dofile(f) end +end + + + + +function zcat_logs_ap_wifi() + zfilei = file.open(z_logs_ap_wifi, "r") + zline=file.readline() + repeat + print(string.sub(zline,1,string.len(zline)-1)) + zline=file.readline() + until zline== nil + file.close(zfilei) +end + + +-- function dsleep_off() +-- print("timer dsleep off...") +-- ztmr_SLEEP:unregister() +-- end + +-- function watch_wifi_on() +-- dsleep_on() +-- ztmr_watch_wifi_on = tmr.create() +-- ztmr_watch_wifi_on:alarm(1*1000, tmr.ALARM_AUTO , function() +-- if wifi.sta.getip() == nil then +-- -- print("Unconnected... (on)") +-- else +-- ztmr_watch_wifi_on:stop() +-- print("Connected... (on)") +-- -- f= "wifi_info.lua" if file.exists(f) then dofile(f) end +-- watch_wifi_off() +-- end +-- end) +-- end + +-- function watch_wifi_off() +-- dsleep_off() +-- ztmr_watch_wifi_on:unregister() +-- ztmr_watch_wifi_off = tmr.create() +-- ztmr_watch_wifi_off:alarm(1*1000, tmr.ALARM_AUTO , function() +-- if wifi.sta.getip() == nil then +-- ztmr_watch_wifi_off:stop() +-- print("Unconnected... (off)") +-- watch_wifi_on() +-- ztmr_watch_wifi_off:unregister() +-- else +-- -- print("Connected... (off)") +-- xfois = 2 +-- blink_LED () +-- end +-- end) +-- end + +-- watch_wifi_on() + +dsleep_wake_up() diff --git a/DeepSleep/Pet_tracker_3/0_rtelnet1.lua b/DeepSleep/Pet_tracker_3/0_rtelnet1.lua new file mode 100644 index 0000000..f1efa9c --- /dev/null +++ b/DeepSleep/Pet_tracker_3/0_rtelnet1.lua @@ -0,0 +1,148 @@ +-- script telnet pour le socat + +function telnet_listener(socket) + print("\n 0_rtelnet1.lua zf200621.2309 \n") + + -- node, table, tmr, uwrite, tostring = + -- node, table, tmr, uart.write, tostring + + print("................telnet_listener") + -- insert, remove, concat, heap, gc = + -- table.insert, table.remove, table.concat, node.heap, collectgarbage + fifo1, fifo1l, fifo2, fifo2l = {}, 0, {}, 0 + -- local s -- s is a copy of the TCP socket if and only if sending is in progress + + function flushGarbage() + if node.heap() < 13440 then collectgarbage() end + end + + function sendLine() + if not s then return end + + if fifo2l + fifo1l == 0 then -- both FIFOs empty, so clear down s + s = nil + return + end + flushGarbage() + if #fifo2 < 4 then -- Flush FIFO1 into FIFO2 + table.insert(fifo2,table.concat(fifo1)) + fifo2l, fifo1, fifo1l = fifo2l + fifo1l, {}, 0 + end + rec = table.remove(fifo2,1)..(table.remove(fifo2,1) or '') ..(table.remove(fifo2,1) or '') .. (table.remove(fifo2,1) or '') + fifo2l = fifo2l - #rec + flushGarbage() + if srv_rt:getpeer()~=nil then + s:send(rec) + end + end + + local F1_SIZE = 256 + + function queueLine(str) + while #str > 0 do -- this is because str might be longer than the packet size! + k, l = F1_SIZE - fifo1l, #str + if #fifo1 >= 32 or (k < l and k < 16) then + table.insert(fifo2, table.concat(fifo1)) + fifo2l, fifo1, fifo1l, k = fifo2l + fifo1l, {}, 0, F1_SIZE + end + if l > k+16 then -- also tolerate a size overrun of 16 bytes to avoid a split + chunk, str = str:sub(1,k), str:sub(k+1) + else + chunk, str = str, '' + end + table.insert(fifo1, chunk) + fifo1l = fifo1l + #chunk + end + if not s and socket then + s = socket + sendLine() + else + flushGarbage() + end + end + + function receiveLine(s, line) + node.input(line) + end + + function disconnect(_,zerr) + node.output(nil) + gpio.write(zLED, gpio.HIGH) + print("................disconnect") + + if socket~=nil then + -- if http_post~=nil then http_post(influxdb_url,"energy,memory=srv_rt_no_nil_"..yellow_id.." ram="..node.heap()) end + print("................disconnect 2e", socket, socket:getpeer()) + socket:on("connection", nil) + socket:on("reconnection", nil) + socket:on("disconnection", nil) + socket:on("receive", nil) + socket:on("sent", nil) + + socket=nil + end + + -- fifo1, fifo1l, fifo2, fifo2l, s = nil, nil, nil, nil, nil + print("disconnected... "..zerr..", "..node.heap()) + -- if debug_rec~=nil then debug_rec("disconnect, disconnected, "..zerr..", "..node.heap()) end + + + -- node, table, tmr, uwrite, tostring = nil, nil, nil, nil, nil + -- insert, remove, concat, heap, gc = nil, nil, nil, nil, nil + -- fifo1, fifo1l, fifo2, fifo2l = nil, nil, nil, nil + -- rec = nil + -- s = nil + -- socket = nil + -- flushGarbage = nil + -- sendLine = nil + -- queueLine = nil + -- receiveLine = nil + -- zconnection = nil + -- disconnect = nil + -- -- telnet_listener=nil + + + + fifo1, fifo1l, fifo2, fifo2l = nil, nil, nil, nil + rec = nil + k = nil + l = nil + s = nil + chunk = nil + socket = nil + flushGarbage = nil + sendLine = nil + queueLine = nil + receiveLine = nil + zconnection = nil + disconnect = nil + telnet_listener=nil + srv_rt=nil + + + -- collectgarbage() + rt_connect() + end + + --zzz + function zconnection(s) + print("socket: ",socket) + if socket~=nil then + -- if http_post~=nil then http_post(influxdb_url,"energy,memory=srv_rt_no_nil_"..yellow_id.." ram="..node.heap()) end + print(socket:getpeer()) + end + + local zstr="zconnection, Oups, on ne devrait jamais passer par là to NodeMCU world." + print(zstr) if debug_rec~=nil then debug_rec(zstr) end + socket=nil + end + + socket:on("connection", zconnection) + socket:on("receive", receiveLine) + socket:on("disconnection", disconnect) + socket:on("sent", sendLine) + + node.output(queueLine, 0) + -- print(queueLine, 0) +end + diff --git a/DeepSleep/Pet_tracker_3/0_tst5_socat.lua b/DeepSleep/Pet_tracker_3/0_tst5_socat.lua new file mode 100644 index 0000000..0daa852 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/0_tst5_socat.lua @@ -0,0 +1,115 @@ +--[[ +tests connection reverse telnet commande à faire tourner sur le GATEWAY ! + +1ere console +pour une liaison directe: +socat TCP-LISTEN:23064,fork,reuseaddr STDIO +pour une console sur un port: +socat TCP-LISTEN:23043,reuseaddr,fork TCP-LISTEN:23000,reuseaddr,bind=127.0.0.1 + +2e console +telnet -r localhost 23000 +]] + +--[[ +tests connection reverse telnet commande à faire tourner sur le GATEWAY ET sur sa MACHINE ! + +1ere console sur le GATEWAY +socat TCP-LISTEN:23043,reuseaddr,fork TCP-LISTEN:23000,reuseaddr,bind=127.0.0.1 + +2e console sur sa MACHINE +ssh -L 23000:localhost:23000 user@GATEWAY + +3e console sur sa MACHINE (~.return pour sortir !) +telnet -r localhost 23000 +ou sur MAC +telnet -rN localhost 23000 +]] + +print("\n 0_tst5_socat.lua zf200628.1458 \n") + +function rt_connect() + -- print("................rt_connect") + collectgarbage() + local zlaps=tmr.now()/1000000-ztime_connect + -- print("time of retry connect... "..zlaps) + -- if debug_rec~=nil then debug_rec("time of retry connect... "..zlaps..", "..node.heap()) end + if zlaps>1 then + local zstr="trying connect to "..console_host..":"..console_port..", "..node.heap() + -- if debug_rec~=nil then debug_rec(zstr) end + if verbose==verbose then + gpio.write(zLED, gpio.LOW) tmr.delay(10000) gpio.write(zLED, gpio.HIGH) + -- print(zstr) + end + if http_post~=nil then http_post(influxdb_url,"energy,memory=socat_try_con_"..yellow_id.." ram="..node.heap()) end + ztime_connect=tmr.now()/1000000 + + srv_rt=nil + srv_rt = net.createConnection(net.TCP, 0) + + srv_rt:on("connection", function(sck) + print("................connection") + if debug_rec~=nil then debug_rec("rt_connect, srv_rt:on, connected on, "..node.heap()) end + collectgarbage() + -- if verbose then + gpio.write(zLED, gpio.LOW) + print("connected on "..console_host..":"..console_port..", "..node.heap()) + print(node.heap()) + -- end + if http_post~=nil then http_post(influxdb_url,"energy,memory=socat_connected_"..yellow_id.." ram="..node.heap()) end + dofile("0_rtelnet1.lua") + telnet_listener(sck) + print("Welcome to NodeMCU world.") + end) + + srv_rt:on("reconnection", function(sck) + -- print(";;;;;;;;;;;;;;;;reconnection") + srv_rt:on("connection", nil) + srv_rt:on("reconnection", nil) + end) + + -- srv_rt:on("disconnection", function(sck) + -- print(";;;;;;;;;;;;;;;;disconnection") + -- end) + -- + -- srv_rt:on("receive", function(sck) + -- print(";;;;;;;;;;;;;;;;receive") + -- end) + -- + -- srv_rt:on("sent", function(sck) + -- print(";;;;;;;;;;;;;;;;sent") + -- end) + + + srv_rt:connect(console_port,console_host) + else + print("on ne se reconnecte pas vite 1x...") + end + collectgarbage() +end + + + + +function rt_launch() + -- if http_post~=nil then http_post(influxdb_url,"energy,memory=tmr_socat1_"..yellow_id.." ram="..node.heap()) end + if srv_rt~=nil then + -- if http_post~=nil then http_post(influxdb_url,"energy,memory=srv_rt_no_nil_"..yellow_id.." ram="..node.heap()) end + if console_port ~= srv_rt:getpeer() then + rt_connect() + end + else + rt_connect() + end +end + + +tmr_socat1=tmr.create() +tmr_socat1:alarm(20*1000, tmr.ALARM_AUTO , rt_launch) + + +ztime_connect=tmr.now()/1000000-10 + +rt_launch() + +print("Revers telnet server running...\n") diff --git a/DeepSleep/Pet_tracker_3/0_wifi_scan.lua b/DeepSleep/Pet_tracker_3/0_wifi_scan.lua new file mode 100644 index 0000000..a54b9f6 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/0_wifi_scan.lua @@ -0,0 +1,56 @@ +-- Scripts pour tester l'écoute des AP WIFI + +print("\n wifi_scan.lua zf200725.1053 \n") + +f= "secrets_project.lua" if file.exists(f) then dofile(f) end + +-- https://www.epochconverter.com/ +ztime2020 = 1577836800 -- Unix time pour 1.1.2020 0:0:0 GMT + +-- sauvegarde les données dans la flash du NodeMCU +function save_flash(zstr_ap_wifi) + ztime1 = tostring(rtctime.get() + 2*3600 - ztime2020) + local zstr = ztime1..", "..zstr_ap_wifi + if verbose then print("saving to flash: "..zstr) end + file.open(z_logs_ap_wifi, "a+") file.writeline(zstr) file.close() +end + +-- print AP list in new format +function scan_wifi() + print(ztime()) + function listap(t) + print("start display liste ap wifi...") + for k,v in pairs(t) do + -- local ssid, rssi, authmode, channel = string.match(v, "([^,]+),([^,]+),([^,]+),([^,]*)") + -- print(ssid,rssi) + -- print(k.." : "..v) + -- local zstr = k..", "..v + local zstr = v + save_flash(zstr) + end + print("end display...") + dsleep_on() + end + print("wifi scan...") + wifi.sta.getap(1, listap) +end + +--[[ +scan_wifi() +]] + + + +--[[ +-- Print AP list that is easier to read +function listap(t) -- (SSID : Authmode, RSSI, BSSID, Channel) + print("\n\t\t\tSSID\t\t\t\t\tBSSID\t\t\t RSSI\t\tAUTHMODE\t\tCHANNEL") + for bssid,v in pairs(t) do + local ssid, rssi, authmode, channel = string.match(v, "([^,]+),([^,]+),([^,]+),([^,]*)") + print(string.format("%32s",ssid).."\t"..bssid.."\t "..rssi.."\t\t"..authmode.."\t\t\t"..channel) + end +end +wifi.sta.getap(1, listap) + + +]] diff --git a/DeepSleep/Pet_tracker_3/README.md b/DeepSleep/Pet_tracker_3/README.md new file mode 100644 index 0000000..88601e5 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/README.md @@ -0,0 +1,191 @@ +# Enregistre sur la FLASH tous les AP WIFI vu lors du réveil du NodeMCU + +Version qui part du principe que quand la *boot reason* est un RESET c'est par défaut une sortie de *sommeil profond*. Car quand le NodeMCU sort du *sommeil profond*, c'est une pin D0 qui fait un RESET une résistance de 1k ! + +Cela simplifie beaucoup la phase de réveil. + +Mais a pour corolaire de ne plus pouvoir faire de RESET et espérer arriver dans la partie seconde chance de la procédure de démarrage. + +zf200725.1144 + + +## Table of Contents +* [Buts](#buts) +* [Problématiques](#problématiques) +* [Astuces de mesures de la distance au moyen du senseur ultrason](#astuces-de-mesures-de-la-distance-au-moyen-du-senseur-ultrason) + * [Schéma](#schéma) + * [Astuces](#astuces) +* [Installation](#installation) +* [Utilisation](#utilisation) + * [Upload Lua code](#upload-lua-code) + * [Secrets pour le projet](#secrets-pour-le-projet) + * [Rename initz.lua pour le boot automatique](#rename-initzlua-pour-le-boot-automatique) + * [Utilisation de la console du NodeMCU en remote](#utilisation-de-la-console-du-nodemcu-en-remote) + * [Visualisation sur Grafana/InfluxDB](#visualisation-sur-grafanainfluxdb) + + +## Buts +Petit projet pour *géolocaliser* un chat lors de ses sorties à l'extérieur afin de *voir* où il va. + +Le but étant de fixer un NodeMCU avec un collier au coup d'un chat et d'enregistrer sur la FLASH tous les AP WIFI vu lors de la sortie. + +Après, en post traitement sur l'ordi, on arrive à faire la corrélation AP WIFI coordonnées GPS et pourvoir tracer (géolocaliser) la sortie du chat sans avoir besoin d'utiliser un module GPS qui est trop gros pour le mettre dans un collier. + + +## Problématiques +La problématique c'est que le NodeMCU doit fonctionner sur une batterie très petite, 400mA/h de capacité. + +Le NodeMCU doit donc *dormir* la plus part du temps si l'on veut pouvoir utiliser le *pet tracker* sur plusieurs jours sans devoir recharger la batterie à chaque sortie. + + + + + +ATTENTION, tout le reste provient d'un autre readme ! zf200725.1116 + + + + + + +Petit projet pour mesurer la hauteur d'eau dans un réservoir de 100l au moyen d'un senseur à ultrason utilisé pour de la robotique récréative. + +Le but est de mesurer la distance entre le haut du bidon et la surface de l'eau dans le bidon et ainsi pouvoir en déduire le pourcentage de remplissage du bidon. + +![Image](https://raw.githubusercontent.com/zuzu59/NodeMCU_Lua/master/Mesures/water-level/water-level_ruru_1/img/20200625_163032.jpg) +Senseur à ultrason, très bon marché, permettant de mesurer la distance + + +![Image](https://raw.githubusercontent.com/zuzu59/NodeMCU_Lua/master/Mesures/water-level/water-level_ruru_1/img/20200625_160818.jpg) +NodeMCU autonome, alimenté ici par une batterie, faisant la lecture de la hauteur d'eau et envoyant le résultat dans une DB InfluxDB via le WIFI + + +![Image](https://raw.githubusercontent.com/zuzu59/NodeMCU_Lua/master/Mesures/water-level/water-level_ruru_1/img/20200625_164022.jpg) +Banc test dans le jardin pour vérifier le bon fonctionnement du système + +![Image](https://raw.githubusercontent.com/zuzu59/NodeMCU_Lua/master/Mesures/water-level/water-level_ruru_1/img/grafana2020-06-25.16.54.32.png) +Graphique obtenu lors du banc test avec de l'eau dans le jardin + + +

+On peut voir ici, avec ce projet assez complet, toutes les possibilités offertes de la programmation des NodeMCU en LUA, en mode événementiel.
+Choses qui ne seraient pas possible si on l'avait fait en C++ (mode Arduino), comme par exemple: + +* crontab, horloge pour les mesures +* envoi des données sur la DB InfluxDB +* serveur reverse TELNET, traversant tous les routers sans devoir en modifier la configuration, permettant d'accéder à la console série (USB) du NodeMCU + +Toutes les fonctions sont bien séparées dans des scripts, cela *complexifie* le projet mais ce qui facilite la portabilité entre les projets et aussi sa mise au point. + + + +## Astuces de mesures de la distance au moyen du senseur ultrason + +Dans ce projet il y a 1x NodeMCU qui mesure la hauteur d'eau dans le bidon au moyen d'un senseur à ultrason utilisé pour de la robotique récréative très bon marché, 0.70FS + +https://www.aliexpress.com/item/32477198302.html + +https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf + +Il n'y a pas de *module* NodeMCU pour ce senseur, mais son utilisation en Lua est vraiment très simple, il suffit juste d'envoyer une *pulse* de 10uS sur la pin *trig* et de *connecter* une interruption du NodeMCU sur la pin *echo*.
+Après une simple règle de trois en relation avec la vitesse du son dans l'air et on a la distance en cm. + + +### Schéma + +![Image](https://raw.githubusercontent.com/zuzu59/NodeMCU_Lua/master/Mesures/water-level/water-level_ruru_1/schemas/schema.png) + +Le schéma est vraiment très simple ! + + +### Astuces + +* La seul problématique dans ce projet c'est que le senseur DOIT absolument être alimenté en 5V et que le NodeMCU lui est en 3.3V.
+Il faut donc lui ajouter une petite résistance, R1, d'adaptation du niveau pour le signal pour l'interruption du NodeMCU. + + +## Installation + +Il faut *flasher* le NodeMCU avec ce firmware: + +https://github.com/zuzu59/NodeMCU_Lua/blob/master/Firmware/nodemcu-master-19-modules-2019-12-31-16-40-12-float.bin + + +Avec ces modules: + +https://github.com/zuzu59/NodeMCU_Lua/blob/master/Firmware/nodemcu-master-19-modules-2019-12-31-16-40-12-float.pdf + + +## Utilisation + +### Upload Lua code +Après avoir *flashé* le NodeMCU avec le bon *firmware* il faut télécharger tous les fichiers \*.lua sur le NodeMCU. + + +### Secrets pour le projet +Mais il faut aussi bien *remplir* et charger sur le NodeMCU, le fichier des secrets du projet: +``` +secrets_project.lua +``` + +ainsi que le fichier des secrets pour le WIFI +``` +secrets_wifi.lua +``` +Tout en sachant que les variables utilisées pour les secrets sont utiles pour: + +* **znode_chipid == nnn then**
+C'est l'id du NodeMCU que chaque NodeMCU ont gravé dans leur mémoire, on peut le lire avec cette commande: +``` +=node.chipid() +``` + +* **node_id = "ttt"**
+C'est le nom de *fonction* du NodeMCU qui sera *visible* dans la DB InfluxDB + +* **yellow_id = nn**
+C'est le *numéro* du NodeMCU que l'on indique sur une *petite étiquette jaune collée* sur le NodeMCU. Ce *numéro* permet par la suite de connaitre très facilement le numéro du *port* utilisé pour le *reverse telnet* quand on veut accéder à la console série du NodeMCU + +* **-- thingspeak_url="http://api.thingspeak.com/update?api_key=kkk"**
+Pas utilisé dans ce projet + +* **influxdb_url="http://uuu:8086/write?db=ddd&u=admin&p=ppp"**
+Secrets utilisés pour envoyer des données sur le DB InfluxDB + +* **console_host = "uuu" console_port = 23000+yellow_id**
+Serveur utilisé pour le *tremplin* du reverse telnet utilisé pour accéder à la console série du NodeMCU au moyen d'un *socat*. L'information d'utilisation se trouve dans le fichier 0_tst5_socat.lua + +* **-- zdyndns_host = "hhh" zdyndns_port = nnn**
+Pas utilisé dans ce projet + + +### Rename initz.lua pour le boot automatique +Ne pas oublier après avoir vérifié que tout fonctionne bien de *renommer* le fichier **initz.lua** en **init.lua** afin que quand le NodeMCU puisse démarrer automatiquement le code et bien fonctionner de manière autonome. + + +### Utilisation de la console du NodeMCU en remote + +Très pratique pour le debug, on peut directement modifier le code source Lua du NodeMCU en remote via un *reverse telnet*. Plus d'info dans le fichier 0_tst5_socat.lua. +On peut aussi modifier le code Lua du NodeMCU en remote avec l'utilitaire *luatools.py* + + +### Visualisation sur Grafana/InfluxDB +![Image](https://raw.githubusercontent.com/zuzu59/NodeMCU_Lua/master/Mesures/water-level/water-level_ruru_1/img/grafana2020-06-25.16.54.32.png) +Graphique obtenu lors du banc test avec de l'eau dans le jardin + +La totale en détail + +https://github.com/zuzu59/docker-influxdb-grafana + + + + + +pense bête: + +``` +file.open("hello.lua","w+") +file.writeline([[print("hello nodemcu")]]) +file.writeline([[print(node.heap())]]) +file.close() +``` diff --git a/DeepSleep/Pet_tracker_3/_secrets_project.lua_ b/DeepSleep/Pet_tracker_3/_secrets_project.lua_ new file mode 100644 index 0000000..aa77c40 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/_secrets_project.lua_ @@ -0,0 +1,35 @@ +-- Petit script pour configurer les secrets dans ce projet +-- et que l'on n'aimerait pas être exportés sur Internet (github) +-- Il faut donc modifier le .gitignore avec secrets*.lua +-- il faut le renommer en 'secrets_project.lua' et sera exécuté +-- par 'wifi_init.lua' au moment du boot + +function secrets_project() + print("\n secrets_project.lua zf200625.1146 \n") + + zLED=4 zBTN=3 node_id = "generic" + znode_chipid=node.chipid() print("znode_chipid:",znode_chipid) + + if znode_chipid == iii then node_id = "sonoff_1" zLED=7 end + + if znode_chipid == iii then node_id = "sonoff_2" zLED=7 end + + if znode_chipid == iii then + node_id = "level1" + yellow_id = nn + -- thingspeak_url="http://api.thingspeak.com/update?api_key=kkk" + influxdb_url="http://uuu:8086/write?db=ddd&u=admin&p=ppp" + print("influxdb_url: "..influxdb_url) + console_host = "uuu" console_port = 23000+yellow_id + -- zdyndns_host = "hhh" zdyndns_port = nnn + end + + znode_chipid=nil + print("node_id: "..node_id..", console_port: "..console_port) +end +secrets_project() +secrets_project=nil + +--[[ +=node.chipid() +]] diff --git a/DeepSleep/Pet_tracker_3/_secrets_wifi.lua_ b/DeepSleep/Pet_tracker_3/_secrets_wifi.lua_ new file mode 100644 index 0000000..7b67d80 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/_secrets_wifi.lua_ @@ -0,0 +1,18 @@ +-- Petit script pour configurer les secrets utilisés pour le wifi +-- et que l'on n'aimerait pas être exportés sur Internet (github) +-- Il faut donc modifier le .gitignore avec eus_params* et secret* +-- il faut le renommer en 'secrets_wifi.lua' et sera exécuté +-- par 'wifi_init.lua' une fois pour la configuration du WIFI + +function secrets_wifi() + print("\n secrets_wifi.lua zf191222.2002 \n") + f= "eus_params.lua" if file.exists(f) then p = dofile(f) end + if p ~= nil then + cli_ssid = p.wifi_ssid cli_pwd = p.wifi_password p=nil + else + cli_ssid = "" cli_pwd = "" + end + ap_ssid="sss" ap_pwd="ppp" +end + +secrets_wifi() diff --git a/DeepSleep/Pet_tracker_3/_zlocal_cmd.txt b/DeepSleep/Pet_tracker_3/_zlocal_cmd.txt new file mode 100644 index 0000000..9f2c12e --- /dev/null +++ b/DeepSleep/Pet_tracker_3/_zlocal_cmd.txt @@ -0,0 +1,16 @@ +zf200724.1053 + +print(ztime()) +dsleep_on() + +zcat_logs_ap_wifi() + +print(node.bootreason()) +print("le flag est à "..rtcmem.read32(10)) + +f= "wifi_info.lua" if file.exists(f) then dofile(f) end + + + + + diff --git a/DeepSleep/Pet_tracker_3/_zremote_cmd.txt b/DeepSleep/Pet_tracker_3/_zremote_cmd.txt new file mode 100644 index 0000000..5ee21b5 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/_zremote_cmd.txt @@ -0,0 +1,301 @@ +# Quelques commandes remote (luatool) à envoyer avec le plugin Atom-IDE-terminal de l'éditeur Atom +# zf200720.2019 + + +Todo à faire pour ce projet ! + +??? + +# si luatool ne marche pas sur le MAC, il faut lire ceci: +# https://docs.google.com/document/d/1q64uK3IMOgEDdKaIAttbYuFt4GuLQ06k3FLeyfCsWLg/edit#heading=h.bmefcu67uwj0 + +# raccourcis clavier +# CTRL+ALT+ENTER envoie au terminal la ligne de l'éditeur +# SHIT+CTRL+` ouvre le terminal (attention, ne pas oublier de copier le *path* dans le *tree* et le changer) +# ALT+CMD+F bascule entre le terminal et l'éditeur + +# définitions à faire AVANT ! +#export luatool_tty="/dev/cu.wchusbserial1410" + +#export zIP="192.168.0.182" +#export zport="23" + +export zIP="localhost" +export zport="23000" + + +# ouvrir et fermer (ALT+N+.) une session telnet sur le NodeMCU avec l'adresse zIP) +telnet -rN $zIP $zport +~. +--node.restart() +collectgarbage() +=node.heap() +for k,v in pairs(_G) do print(k,v) end + + +################################ +# commandes lua pour ce projet # +################################ +# pour les tests en direct sur la gateway +ssh ubuntu@www.zuzu-test.ml +socat TCP-LISTEN:23069,fork,reuseaddr STDIO +################################ +# pour les tests en remote +killall -9 ssh +ssh ubuntu@www.zuzu-test.ml killall -9 socat + +ssh ubuntu@www.zuzu-test.ml socat TCP-LISTEN:23010,reuseaddr,fork TCP-LISTEN:23000,reuseaddr,bind=127.0.0.1 & +# SHIFT+CMD+K SHIFT+CMD+K ALT+CMD+F + +watch -n 1 'ssh ubuntu@www.zuzu-test.ml netstat -nat |grep 230' + +# ALT+CMD+F CTRL+C ALT+CMD+F + +export zIP="localhost" +export zport="23000" + +ssh -N -L 23000:localhost:23000 ubuntu@www.zuzu-test.ml & + +telnet -rN $zIP $zport + +verbose=false +~. + +=node.heap() +verbose=true +node.restart() + +dofile("head.lua") +zhead("0_ultra_son.lua") + +./luatool.py --ip $zIP:$zport -f boot.lua +./luatool.py --ip $zIP:$zport -f 0_get_power.lua +./luatool.py --ip $zIP:$zport -f 0_cron.lua +./luatool.py --ip $zIP:$zport -f 0_http_post.lua +./luatool.py --ip $zIP:$zport -f 0_tst5_socat.lua +./luatool.py --ip $zIP:$zport -f 0_ultra_son.lua + +./luatool.py --ip $zIP:$zport --zrestart + + += node.bootreason() +# https://nodemcu.readthedocs.io/en/master/modules/node/#nodebootreason + +for k,v in pairs(_G) do print(k,v) end +verbose=true +#zdyn + + + + +./luatool.py --ip $zIP:$zport -f api_sonoff.html +http://192.168.0.182 + +./luatool.py --ip $zIP:$zport -f dir.lua + +telnet -rN $zIP $zport +verbose=false +verbose=true +~. + +=node.heap() +collectgarbage() +=node.heap() + + +dofile("dir.lua") +dir() +for k,v in pairs(_G) do print(k,v) end +dofile("wifi_info.lua") + +node.restart() + +~. +./luatool.py --ip $zIP:$zport -f head.lua +telnet -rN $zIP $zport +dofile("head.lua") +zhead("boot.lua") + + + +verbose=true +verbose=false +zsort_rssi() zshow() + + +#commandes luatool pour ce projet le .137 est à jour avec la nouvelle version du wifi ! +~. +./luatool.py --ip $zIP:$zport -l +./luatool.py --ip $zIP:$zport -f wifi_init.lua + + +./luatool.py --ip $zIP:$zport -f secrets_wifi.lua +./luatool.py --ip $zIP:$zport -f initz.lua -t init.lua +./luatool.py --ip $zIP:$zport -f boot.lua +./luatool.py --ip $zIP:$zport -f boot2.lua +./luatool.py --ip $zIP:$zport -f wifi_init.lua +./luatool.py --ip $zIP:$zport -f set_time.lua +./luatool.py --ip $zIP:$zport -f wifi_info.lua +./luatool.py --ip $zIP:$zport -f c.lua +./luatool.py --ip $zIP:$zport -f cat.lua +./luatool.py --ip $zIP:$zport -f flash_led_xfois.lua +./luatool.py --ip $zIP:$zport -f head.lua + +./luatool.py --ip $zIP:$zport -f b.lua +./luatool.py --ip $zIP:$zport -f web_srv2.lua +./luatool.py --ip $zIP:$zport -f z_index.html + + +./luatool.py --ip $zIP:$zport -f wifi_get_conf.html +./luatool.py --ip $zIP:$zport -f wifi_set_conf.html + + +./luatool.py --ip $zIP:$zport -f z_page1.html +./luatool.py --ip $zIP:$zport -f z_page2.html +./luatool.py --ip $zIP:$zport -f z_page3.html +./luatool.py --ip $zIP:$zport -f z_page4.html + +./luatool.py --ip $zIP:$zport --delete wifi_ap_start.lua +./luatool.py --ip $zIP:$zport --delete wifi_cli_conf.lua +./luatool.py --ip $zIP:$zport --delete wifi_cli_start.lua +./luatool.py --ip $zIP:$zport --delete dir.lua +./luatool.py --ip $zIP:$zport --delete initz.lua +./luatool.py --ip $zIP:$zport --delete wifi_conf.lua + + + +dofile("wifi_info.lua") +--node.restart() +for k,v in pairs(_G) do print(k,v) end +t=12 +t=nil +print(pcall(function () print("2"..t) end)) + + + +# ici c'est maintenant ;-) +~. +./luatool.py --ip $zIP:$zport -f b.lua +#./luatool.py --ip $zIP:$zport --zrestart +telnet -rN $zIP $zport +zsort_rssi() zshow() +print(ztrig_rssi) +ztrig_rssi=-1000 +ztrig_rssi=-90 + + + +# test de la gestion des erreurs +~. +./luatool.py --ip $zIP:$zport -f c.lua +#./luatool.py --ip $zIP:$zport --zrestart +telnet -rN $zIP $zport +status, err = pcall(function () dofile("c.lua") end) if status==false then print("Error: ",err) end +zerr=nil +zerr=1 +ztmr_tst_err:unregister() + + + + +ztmr_tst_err:unregister() +for k,v in pairs(_G) do print(k,v) end + +./luatool.py --ip $zIP --zrestart + + +# faire un cat d'un fichier sur le NodeMCU +dofile("cat.lua") +cat("boot2.lua") + + +# commandes luatool +./luatool.py -h +./luatool.py --ip $zIP:$zport -i +./luatool.py --ip $zIP:$zport -l +./luatool.py --ip $zIP:$zport -f toto.lua -d +./luatool.py --ip $zIP:$zport --delete toto.lua + + + +**************************************************************************** + + +# création des tunnels ssh +ATTENTION: dans un premier terminal ! +Pour Bolo à Ruchonnet: +ssh -t -L 2323:localhost:2323 ubuntu@www.zuzu-test.ml ssh -N -L 2323:192.168.8.102:23 ubuntu@localhost -p 20223 + +Pour le Crêt +ssh -N -L 2323:192.168.0.137:23 admin@z.zufferey.com -p 1822 +ssh -N -L 2323:192.168.0.122:23 admin@z.zufferey.com -p 1822 +ssh -N -L 2323:192.168.0.118:23 admin@z.zufferey.com -p 1822 + +ATTENTION: dans un deuxième terminal ! +export zIP="localhost" +export zport="2323" +telnet -rN $zIP $zport +~. +=node.heap() + +verbose=false +verbose=true + +dofile("dir2.lua") +dir() +filec("head.lua") + +dofile("head.lua") +zhead("dir2.lua") + +dofile("cat.lua") +cat("head.lua") + +~. +--node.restart() +=node.heap() + + +status, err = pcall(function () fonction_a_tester() end) if status==false then print("Error: ",err) end +status, err = pcall(function () toto() end) if status==false then print("Error: ",err) end + + + +Gestion de la passerelle SSH reverse +Puis depuis une autre console on peut faire directement pour aller sur l'OpiZ (ici le 20223): +ssh -t ubuntu@www.zuzu-test.ml ssh ubuntu@localhost -p 20223 + +ou + +1ère console +On établit le tunnel local avec l'OpiZ +ssh -N -L 20223:localhost:20223 ubuntu@www.zuzu-test.ml + + +2ème console, on utilise le tunnel OpiZ en local sur sa machine +pour aller sur l'OpiZ +ssh ubuntu@localhost -p 20223 + +pour copier sa clef SSH depuis un MAC +./ssh-copy-id -i ~/.ssh/id_rsa.pub 'ubuntu@localhost -p 20223' +après on devrait pouvoir se connecter sans devoir entrer son password à chaque fois +ssh ubuntu@localhost -p 20223 + +ou, on établit un tunnel pour telnet sur un NodeMCU +ssh -L 2323:192.168.0.137:23 ubuntu@localhost -p 20223 +puis +telnet -rN localhost 2323 + +ou, on établit un tunnel pour le rpimonotor sur l'OpiZ +ssh -N -L 8888:192.168.0.113:8888 ubuntu@localhost -p 20223 +puis +http://localhost:8888 + +ou, on établit un tunnel proxy Socket 5 pour le modem 4G HUAWEI sur l'OpiZ +ssh -N -D 8080 ubuntu@localhost -p 20223 +puis depuis le browser FireFox avec le plugin FoxyProxy Socket 5 sur localhost port 8080 +http://192.168.8.1 + + + +. diff --git a/DeepSleep/Pet_tracker_3/boot.lua b/DeepSleep/Pet_tracker_3/boot.lua new file mode 100644 index 0000000..99e5c7c --- /dev/null +++ b/DeepSleep/Pet_tracker_3/boot.lua @@ -0,0 +1,77 @@ +-- Scripts à charger après le boot pour démarrer son projet + +function boot() + print("\n boot.lua zf200722.1944 \n") + print("On lance le boot...") + collectgarbage() print(node.heap()) + local f + -- f = "0_http_post.lua" if file.exists(f) then dofile(f) end + -- collectgarbage() print(node.heap()) + + -- local _, boot_reason = node.bootreason() + -- zarg_boot= "energy,memory=boot_"..yellow_id.." ram="..node.heap().."\n" + -- zarg_boot=zarg_boot.."energy,value=boot_reason_"..yellow_id.." val="..boot_reason + -- http_post(influxdb_url,zarg_boot) + + f = "set_time.lua" if file.exists(f) then dofile(f) end + print(node.heap()) collectgarbage() print(node.heap()) + + f = "0_dsleep2.lua" if file.exists(f) then dofile(f) end + print(node.heap()) collectgarbage() print(node.heap()) + + -- f = "0_wifi_scan.lua" if file.exists(f) then dofile(f) end + -- print(node.heap()) collectgarbage() print(node.heap()) + + -- f = "flash_led_xfois.lua" if file.exists(f) then dofile(f) end + -- print(node.heap()) collectgarbage() print(node.heap()) + + -- f = "0_zdyndns.lua" if file.exists(f) then dofile(f) end + -- print(node.heap()) collectgarbage() print(node.heap()) + + -- f="0_btn_flipflop.lua" if file.exists(f) then dofile(f) end + -- collectgarbage() print(node.heap()) + + -- f="0_ultra_son.lua" if file.exists(f) then dofile(f) end + -- collectgarbage() print(node.heap()) + + -- f="0_cron.lua" if file.exists(f) then dofile(f) end + -- collectgarbage() print(node.heap()) + + verbose = true + print("verbose: ",verbose,"\nle boot est lancé...") + gpio.write(zLED, gpio.HIGH) + f=nil boot=nil +end + +-- function debug_rec(zdebug) +-- local sec, usec = rtctime.get() local tm = rtctime.epoch2cal(sec + 2*3600) +-- local ztm = string.format("%04d/%02d/%02d %02d:%02d:%02d", tm["year"], tm["mon"], tm["day"], tm["hour"], tm["min"], tm["sec"]) +-- file.open("00_debug.txt", "a+") file.writeline(ztm.."."..usec..", "..zdebug) file.close() +-- end +-- +-- function rec_boot() +-- sntp.sync(nil, nil, nil, 1) +-- tmr_rec_boot1=tmr.create() +-- tmr_rec_boot1:alarm(1*1000, tmr.ALARM_AUTO, function() +-- print("beep...") +-- if rtctime.get() > 0 then +-- tmr_rec_boot1:unregister() +-- print("Voilà on à l'heure, on peut enregistrer la raison du boot...") +-- local _, zboot_reason, zboot_detail = node.bootreason() +-- debug_rec("boot reason: "..zboot_reason) +-- tmr_rec_boot1=nil rec_boot=nil +-- collectgarbage() print(node.heap()) +-- end +-- end) +-- end + +verbose=true +if rec_boot~=nil then rec_boot() end +boot() +collectgarbage() print(node.heap()) + + +--[[ +verbose = true +verbose = false +]] diff --git a/DeepSleep/Pet_tracker_3/flash_led_xfois.lua b/DeepSleep/Pet_tracker_3/flash_led_xfois.lua new file mode 100644 index 0000000..5b08aff --- /dev/null +++ b/DeepSleep/Pet_tracker_3/flash_led_xfois.lua @@ -0,0 +1,38 @@ +-- programme pour faire clignoter x fois une LED avec un rapport on/off + +function flash_led_xfois() + print("\n flash_led_xfois.lua zf200722.1139 \n") + + --zLED=0 --NodeMCU + --zLED=4 --EPS-M3 + zTm_On_LED = 50 --> en ms + zTm_Off_LED = 100 --> en ms + nbfois = 0 + gpio.write(zLED, gpio.HIGH) + gpio.mode(zLED, gpio.OUTPUT) + ztmr_Flash_LED = tmr.create() + + function blink_LED () + if nbfois >= xfois then + -- print(nbfois) + nbfois = 0 + else + if gpio.read(zLED)==gpio.HIGH then + gpio.write(zLED, gpio.LOW) +-- tmr.alarm(ztmr_Flash_LED, zTm_Off_LED, tmr.ALARM_SINGLE, blink_LED) + ztmr_Flash_LED:alarm(zTm_Off_LED, tmr.ALARM_SINGLE, blink_LED) + else + gpio.write(zLED, gpio.HIGH) + nbfois = nbfois+1 +-- tmr.alarm(ztmr_Flash_LED, zTm_On_LED, tmr.ALARM_SINGLE, blink_LED) + ztmr_Flash_LED:alarm(zTm_On_LED, tmr.ALARM_SINGLE, blink_LED) + end + end + end + + xfois =2 + blink_LED () +end + +flash_led_xfois() + diff --git a/DeepSleep/Pet_tracker_3/goodies/0_cron.lua b/DeepSleep/Pet_tracker_3/goodies/0_cron.lua new file mode 100644 index 0000000..172b16e --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/0_cron.lua @@ -0,0 +1,49 @@ +-- Petit script pour faire office de crontab pour les mesures +print("\n 0_cron.lua zf200705.2247 \n") + +cron1=tmr.create() +cron1:alarm(15*1000, tmr.ALARM_AUTO, function() + -- if verbose then print("cron1........................") end + -- if verbose then gpio.write(zLED, gpio.LOW) tmr.delay(10000) gpio.write(zLED, gpio.HIGH) end + + + -- rt_launch() + + + -- http_post(influxdb_url,"energy,value=test1_"..yellow_id.." val=1") + + -- http_post(influxdb_url,"energy,memory=cron1_"..yellow_id.." ram="..node.heap()) + + -- if yellow_id == 60 then http_post(influxdb_url,"energy,compteur=3 puissance="..zpower/1000) end + -- if yellow_id == 64 then http_post(influxdb_url,"energy,compteur=4 puissance="..zpower/1000) end + + + if yellow_id == 69 then + local zmes="bolo_ruru,capteur="..node_id.." level="..zlevel + zmes=zmes.."\n".."bolo_ruru,capteur="..node_id.." hauteur="..zlength + http_post(influxdb_url,zmes) + end + + + + + + + -- http_post(influxdb_url,"energy,value=test2_"..yellow_id.." val=2") + -- http_post(influxdb_url,"energy,value=test3_"..yellow_id.." val=3") + -- http_post(influxdb_url,"energy,value=test4_"..yellow_id.." val=4") + + + -- f = "0_zdyndns.lua" if file.exists(f) then dofile(f) end + -- f=nil + + -- if verbose then print("End cron:") end + collectgarbage() + -- if verbose then print(node.heap()) end +end) + + +--[[ +cron1:stop() +cron1:start() +]] diff --git a/DeepSleep/Pet_tracker_3/goodies/0_http_post.lua b/DeepSleep/Pet_tracker_3/goodies/0_http_post.lua new file mode 100644 index 0000000..7f847bc --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/0_http_post.lua @@ -0,0 +1,45 @@ +-- Petit script pour envoyer en // es valeurs sur un serveur WEB (InfluxDB) +-- via un http POST à travers un FIFO + +if verbose then print("\n 0_http_post.lua zf200625.1137 \n") end + +t_zurl={} t_zarg={} f_zpost=false + +-- function tprint(t) +-- for key,value in pairs(t) do print(key, value) end +-- end + +function zpost() + f_zpost=true local zurl=t_zurl[1] local zarg=t_zarg[1] + -- zarg=zarg.."\n".."energy,value=nb_waiting_"..yellow_id.." val="..#t_zurl +-- zarg=zarg.."\n".."energy,memory=zpost_"..yellow_id.." ram="..node.heap() + if verbose then print("zurl: "..zurl) end if verbose then print("zarg: "..zarg) end + http.post(zurl, 'Content-Type: application/x-www-form-urlencoded\r\n', zarg, function(code, data) + if (code < 0) then + print("HTTP request failed") + print("zuzu", code, data) + if debug_rec~=nil then debug_rec("HTTP request failed: ", code, data) end + else + if verbose then print(code, data) end + end + table.remove(t_zurl, 1) table.remove(t_zarg, 1) + if t_zurl[1]==nil then + f_zpost=false + else + zpost() + end + -- if verbose then print("End zpost:") end + collectgarbage() + -- if verbose then print(node.heap()) end + end) +end + +function http_post(zurl,zarg) + if #t_zurl <=10 then table.insert(t_zurl, zurl) table.insert(t_zarg, zarg) end + -- if verbose then print("Nb wait: "..#t_zurl) print(node.heap()) end + -- if verbose then print("t_zurl:") tprint(t_zurl) print("t_zarg:") tprint(t_zarg) end + if f_zpost==false then zpost() end + -- if verbose then print("End http_post:") end + collectgarbage() + -- if verbose then print(node.heap()) end +end diff --git a/DeepSleep/Pet_tracker_3/goodies/a_test_power_wifi.lua b/DeepSleep/Pet_tracker_3/goodies/a_test_power_wifi.lua new file mode 100644 index 0000000..a1a9a81 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/a_test_power_wifi.lua @@ -0,0 +1,57 @@ +-- Scripts pour tester la consommation des différents mode du WIFI + +print("\n a_test_power_wifi zf181209.1718 \n") + +f= "wifi_ap_stop.lua" if file.exists(f) then dofile(f) end +f= "wifi_cli_conf.lua" if file.exists(f) then dofile(f) end +f= "wifi_cli_start.lua" if file.exists(f) then dofile(f) end +--f= "telnet_srv.lua" if file.exists(f) then dofile(f) end +--f= "web_ide2.lua" if file.exists(f) then dofile(f) end +--f= "dsleep.lua" if file.exists(f) then dofile(f) end + + +print("mode physique: ", wifi.getphymode()) +print("defaut mode: ", wifi.getdefaultmode()) +print("wifi stat status: ", wifi.sta.status()) + + +-- print AP list in old format (format not defined) +function listap(t) + for k,v in pairs(t) do + print(k.." : "..v) + end +end +wifi.sta.getap(listap) + +-- Print AP list that is easier to read +function listap(t) -- (SSID : Authmode, RSSI, BSSID, Channel) + print("\n"..string.format("%32s","SSID").."\tBSSID\t\t\t\t RSSI\t\tAUTHMODE\tCHANNEL") + for ssid,v in pairs(t) do + local authmode, rssi, bssid, channel = string.match(v, "([^,]+),([^,]+),([^,]+),([^,]+)") + print(string.format("%32s",ssid).."\t"..bssid.."\t "..rssi.."\t\t"..authmode.."\t\t\t"..channel) + end +end +wifi.sta.getap(listap) + + + + +-- print AP list in new format +function listap(t) + for k,v in pairs(t) do + print(k.." : "..v) + end +end +wifi.sta.getap(1, listap) + +-- Print AP list that is easier to read +function listap(t) -- (SSID : Authmode, RSSI, BSSID, Channel) + print("\n\t\t\tSSID\t\t\t\t\tBSSID\t\t\t RSSI\t\tAUTHMODE\t\tCHANNEL") + for bssid,v in pairs(t) do + local ssid, rssi, authmode, channel = string.match(v, "([^,]+),([^,]+),([^,]+),([^,]*)") + print(string.format("%32s",ssid).."\t"..bssid.."\t "..rssi.."\t\t"..authmode.."\t\t\t"..channel) + end +end +wifi.sta.getap(1, listap) + + diff --git a/DeepSleep/Pet_tracker_3/goodies/cat.lua b/DeepSleep/Pet_tracker_3/goodies/cat.lua new file mode 100644 index 0000000..856a52d --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/cat.lua @@ -0,0 +1,18 @@ +-- fonction cat() pour afficher le contenu d'un fichier dans la flash +print("\n cat.lua zf192026.0858 \n") + +function cat(zfile) + print("\n"..zfile.."\n-------------------------------") + + zfilei = file.open(zfile, "r") + i=1 + zline=file.readline() + repeat +-- print(i..": "..string.sub(zline,1,string.len(zline)-1)) + print(string.sub(zline,1,string.len(zline)-1)) + i=i+1 zline=file.readline() + until zline== nil + file.close(zfilei) + + print("-------------------------------") +end diff --git a/DeepSleep/Pet_tracker_3/goodies/dir.lua b/DeepSleep/Pet_tracker_3/goodies/dir.lua new file mode 100644 index 0000000..86a73d1 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/dir.lua @@ -0,0 +1,31 @@ +-- fonction dir() pour juste afficher les fichiers avec leur taille + +print("\n dir.lua zf191223.1455 \n") + +function dir() + local zdir={} + local pfile = file.list() + for k,v in pairs(pfile) do + zdir[#zdir+1] = k..string.rep(" ",24-string.len(k)).." : "..v + end + table.sort(zdir) for i=1, #zdir do print(zdir[i]) end + size_file=nil chksum_file=nil k=nil +end + +dir() +print("\nusage:") +print(" dir()") + +--[[ +dir() +dirc() +filec("dir2.lua") + +=node.heap() +clear_dir() +=node.heap() + +for k,v in pairs(_G) do print(k,v) end + +status, err = pcall(function () print(zhash("il était une fois trois petits cochons roses...")) end) if status==false then print("Error: ",err) end +]] diff --git a/DeepSleep/Pet_tracker_3/goodies/dir3.lua b/DeepSleep/Pet_tracker_3/goodies/dir3.lua new file mode 100644 index 0000000..1dd61ea --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/dir3.lua @@ -0,0 +1,52 @@ +-- fonction dir_vers() pour afficher toutes les versions de tous les fichiers *.lua sur le NodeMCU ! +-- fonction filec(fichier) pour afficher la version d'un seul fichiers sur le NodeMCU ! + +function dir3() + + print("\n 0_dir3.lua zf200611.1714 \n") + + function file_vers(name_file) + local z="" + if string.find(name_file,"%.lua") then + z=name_file..":" + -- print("fichier: "..name_file) + local f,i1,i2,j1,j2,k,t = f,i1,i2,j1,j2,k,t + f = file.open(name_file, "r") + while true do + local t = f:readline() if t == nil then break end + -- recherche de l'entête de version [print("\n ] + -- ATTENTION, il faut échapper la '(' avec un % et convertir le '\' en char(92) + k='print%("'..string.char(92)..'n ' + i1,j1 = string.find(t,k) + if i1 ~= nil then + k=string.char(92)..'n"%)' + i2,j2 = string.find(t,k,j1) + z=name_file..": "..string.sub(t,j1+1,i2-2) + break + end + end + f:close() + uart.write(0,".") + end + return z + end + + zdir={} list_files={} + local k,v = k,v local pfile = file.list() + for k,v in pairs(pfile) do + zdir[#zdir+1] = file_vers(k) + end + table.sort(zdir) for i=1, #zdir do print(zdir[i]) end + + dir_vers=nil file_vers=nil list_files=nil zdir=nil + dir3=nil + +end +dir3() + +--[[ + +status, err = pcall(function () print(zhash("il était une fois trois petits cochons roses...")) end) if status==false then print("Error: ",err) end + +]] + diff --git a/DeepSleep/Pet_tracker_3/goodies/head.lua b/DeepSleep/Pet_tracker_3/goodies/head.lua new file mode 100644 index 0000000..0478b8c --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/head.lua @@ -0,0 +1,19 @@ +-- fonction cat() pour afficher les 10 premières lignes d'un fichier dans la flash +print("\n head.lua zf192028.1516 \n") + +function zhead(zfile) + print("\n"..zfile.."\n-------------------------------") + + zfilei = file.open(zfile, "r") + i=1 + zline=file.readline() + repeat +-- print(i..": "..string.sub(zline,1,string.len(zline)-1)) + print(string.sub(zline,1,string.len(zline)-1)) + i=i+1 zline=file.readline() + if i>10 then break end + until zline==nil + file.close(zfilei) + + print("-------------------------------") +end diff --git a/DeepSleep/Pet_tracker_3/goodies/repair.lua b/DeepSleep/Pet_tracker_3/goodies/repair.lua new file mode 100644 index 0000000..691c123 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/repair.lua @@ -0,0 +1,16 @@ +-- Scripts de seconde chance pour réparer une boucle dans le restart + +print("\n repair.lua zf181210.1456 \n") + +--f= "wifi_ap_start.lua" if file.exists(f) then dofile(f) end +--f= "telnet_srv.lua" if file.exists(f) then dofile(f) end + +--f= "az_init_led.lua" if file.exists(f) then dofile(f) end + + +--[[ +jobtimer1=tmr.create() +tmr.alarm(jobtimer1, 5*1000, tmr.ALARM_AUTO, function() + print("repair...") +end) +]] diff --git a/DeepSleep/Pet_tracker_3/goodies/restart.lua b/DeepSleep/Pet_tracker_3/goodies/restart.lua new file mode 100644 index 0000000..85f28eb --- /dev/null +++ b/DeepSleep/Pet_tracker_3/goodies/restart.lua @@ -0,0 +1,12 @@ +-- Scripts pour faire un soft reset + +print("\n restart.lua zf181209.1753 \n") + +restarttimer1=tmr.create() +tmr.alarm(restarttimer1, 2*1000, tmr.ALARM_SINGLE, function() + node.restart() +end) + +print("hello zuzu") + + diff --git a/DeepSleep/Pet_tracker_3/initz.lua b/DeepSleep/Pet_tracker_3/initz.lua new file mode 100644 index 0000000..f226f9b --- /dev/null +++ b/DeepSleep/Pet_tracker_3/initz.lua @@ -0,0 +1,91 @@ +--Script de bootstrap, test au moment du boot qui a été la cause de boot. +-- Si la cause est un power on ou une connexion depuis l'IDE, alors +-- le script repair.lua pendant xx secondes avant de continuer +--Source: https://nodemcu.readthedocs.io/en/master/en/modules/node/#nodebootreason + +print("\n init.lua zf200722.1526 \n") + +verbose = true + +function initz() + + function initz_end() + print("initz_end...") + f= "wifi_init.lua" if file.exists(f) then dofile(f) end + f=nil initz=nil second_chance=nil hvbouton=nil initz_end=nil + print(node.heap()) collectgarbage() print(node.heap()) + print("initz_end out...") + end + + function hvbouton() + gpio.trig(zswitch, "none") zswitch=nil + print("hvbouton...") + print(tmr.now()) + if tmr.now() > 5000000 then + file.putcontents("_setup_wifi_", "toto") + print("on a demandé le setup wifi !") + end + initalarme1:unregister() initalarme1=nil second_chance=nil + gpio.write(zLED, gpio.HIGH) zLED=nil + reset_reason="hvbouton" + initz_end() + end + + function second_chance() + print("seconde chance...") + zLED=4 -- NodeMCU + --zLED=7 -- SonOff + gpio.write(zLED, gpio.LOW) gpio.mode(zLED, gpio.OUTPUT) + initalarme1=tmr.create() + initalarme1:alarm(10*1000, tmr.ALARM_SINGLE, function() + gpio.write(zLED, gpio.HIGH) zLED=nil + gpio.trig(zswitch, "none") zswitch=nil + reset_reason="seconde_chance" + initz_end() + end) + zswitch=3 --switch flash ou SonOff + gpio.mode(zswitch, gpio.INT, gpio.PULLUP) + gpio.trig(zswitch, "both", hvbouton) + end + + _, reset_reason = node.bootreason() + print("reset_reason: ",reset_reason) + if reset_reason == 0 then + print("power on") + second_chance() + elseif reset_reason == 4 then + print("node.restart") + initz_end() + elseif reset_reason == 5 then + print("dsleep wake up") + initz_end() + elseif reset_reason == 6 then + print("external reset") + if rtcmem.read32(10) == 43690 then + print("dsleep wake up") + f = "0_dsleep2.lua" if file.exists(f) then dofile(f) end + else + second_chance() + end + else + print("autre raison") + second_chance() + end +end +initz() + +--[[ +zLED=7 +gpio.mode(zLED, gpio.OUTPUT) +gpio.write(zLED, gpio.LOW) -- actif ! +gpio.write(zLED, gpio.HIGH) + +zBTN=3 +gpio.mode(zBTN, gpio.INPUT) +print(gpio.read(zBTN)) + +zRELAY=6 +gpio.mode(zRELAY, gpio.OUTPUT) +gpio.write(zRELAY, gpio.HIGH) -- actif ! +gpio.write(zRELAY, gpio.LOW) +]] diff --git a/DeepSleep/Pet_tracker_3/luatool.py b/DeepSleep/Pet_tracker_3/luatool.py new file mode 100755 index 0000000..29e183f --- /dev/null +++ b/DeepSleep/Pet_tracker_3/luatool.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- +version = "0.6.8 zf191225.1428" + +print("luatool.py ver " + version) + +#Améliorations +# Pour voir les améliorations il faut 'chercher' les zzz + + +# +# ESP8266 luatool +# Author e-mail: 4ref0nt@gmail.com +# Site: http://esp8266.ru +# Contributions from: https://github.com/sej7278 +# +# 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; either version 2 of the License, or (at your option) any later version. +# +# 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, write to the Free Software Foundation, Inc., 51 Franklin +# Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import sys +import serial +from time import sleep +import socket +import argparse +from os.path import basename + + +tqdm_installed = True +try: + from tqdm import tqdm +except ImportError, e: + if e.message == 'No module named tqdm': + tqdm_installed = False + else: + raise + + + +class TransportError(Exception): + """Custom exception to represent errors with a transport + """ + def __init__(self, message): + self.message = message + + def __str__(self): + return self.message + +class AbstractTransport: + def __init__(self): + raise NotImplementedError('abstract transports cannot be instantiated.') + + def close(self): + raise NotImplementedError('Function not implemented') + + def read(self, length): + raise NotImplementedError('Function not implemented') + + def writeln(self, data, check=1): + raise NotImplementedError('Function not implemented') + + def writer(self, data): + ##zzz191124 enlève la fin de ligne afin de ne pas perturber la vérification finale + data = data.rstrip('\r\n') + self.writeln("file.writeline([==[" + data + "]==])\r") + + def performcheck(self, expected): + line = '' + char = '' + i = -1 + while char != chr(62): # '>' + char = self.read(1) + if char == '': + raise Exception('No proper answer from MCU') + if char == chr(13) or char == chr(10): # LF or CR + if line != '': + #zzz191124 line = line.strip() + # zzz191021 Affiche ce que l'on a reçu du NodeMCU + if args.verbose: + print("\n\nzread0957: {" + line + "\n}\n") + + if line+'\r' == expected and not args.bar: + sys.stdout.write(" -> ok") + elif line+'\r' != expected: + if line[:4] == "lua:": + sys.stdout.write("\r\n\r\nLua ERROR: %s" % line) + raise Exception('ERROR from Lua interpreter\r\n\r\n') + else: + expected = expected.split("\r")[0] + sys.stdout.write("\r\n\r\nERROR") + sys.stdout.write("\r\n send string : '%s'" % expected) + sys.stdout.write("\r\n expected echo : '%s'" % expected) + sys.stdout.write("\r\n but got answer : '%s'" % line) + sys.stdout.write("\r\n\r\n") + raise Exception('Error sending data to MCU\r\n\r\n') + line = '' + else: + line += char + if char == chr(62) and expected[i] == char: + char = '' + i += 1 + + +class SerialTransport(AbstractTransport): + def __init__(self, port, baud, delay): + self.port = port + self.baud = baud + self.serial = None + self.delay = delay + + try: + self.serial = serial.Serial(port, baud) + except serial.SerialException as e: + raise TransportError(e.strerror) + + self.serial.timeout = 3 + self.serial.interCharTimeout = 3 + + # zzz191021 juste après l'ouverture du port série, on attend le caractère '>' + line = '' + char = '' + i = -1 + while char != chr(62): # '>' + char = self.read(1) + if char == '': + raise Exception('No proper answer from MCU') + line += char + i += 1 + if args.verbose: + print("line: ." + line + ".") + + + def writeln(self, data, check=1): + # zzz191020 on fait une petite pause avant l'envoi de chaque ligne + sleep(self.delay) + if self.serial.inWaiting() > 0: + self.serial.flushInput() + if len(data) > 0 and not args.bar: + sys.stdout.write("\r\n->") + sys.stdout.write(data.split("\r")[0]) + ##zzz191124 attend encore un petit peu avant d'envoyer + sleep(self.delay) + self.serial.write(data) + sleep(self.delay) + # zzz191021 Affiche ce que l'on a envoyé au NodeMCU + if args.verbose: + print("\n\nzwrite0952: {" + data + "\n}\n") + if check > 0: + self.performcheck(data) + elif not args.bar: + sys.stdout.write(" -> send without check") + + def read(self, length): + return self.serial.read(length) + + def close(self): + self.serial.flush() + self.serial.close() + + +class TcpSocketTransport(AbstractTransport): + def __init__(self, host, port): + self.host = host + self.port = port + self.socket = None + + try: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + except socket.error as e: + raise TransportError(e.strerror) + + try: + self.socket.connect((host, port)) + except socket.error as e: + raise TransportError(e.strerror) + # read intro from telnet server (see telnet_srv.lua) + self.socket.recv(50) + + def writeln(self, data, check=1): + if len(data) > 0 and not args.bar: + sys.stdout.write("\r\n->") + sys.stdout.write(data.split("\r")[0]) + self.socket.sendall(data) + if check > 0: + self.performcheck(data) + elif not args.bar: + sys.stdout.write(" -> send without check") + + def read(self, length): + return self.socket.recv(length) + + def close(self): + self.socket.close() + + +def decidetransport(cliargs): + if cliargs.ip: + data = cliargs.ip.split(':') + host = data[0] + if len(data) == 2: + port = int(data[1]) + else: + port = 23 + return TcpSocketTransport(host, port) + else: + return SerialTransport(cliargs.port, cliargs.baud, cliargs.delay) + + +if __name__ == '__main__': + # parse arguments or use defaults + parser = argparse.ArgumentParser(description='ESP8266 Lua script uploader.') + parser.add_argument('--bar', action='store_true', help='Show a progress bar for uploads instead of printing each line') + parser.add_argument('--delay', default=0.025, help='Delay in seconds between each write, default 0.03 sec.', type=float) + parser.add_argument('--delete', default=None, help='Delete a lua/lc file from device.') + parser.add_argument('--ip', default=None, help='Connect via telnet server (--ip IP[:port])') + parser.add_argument('--zrestart', action='store_true', help='Restart the NodeMCU.') + parser.add_argument('-a', '--append', action='store_true', help='Append source file to destination file.') + parser.add_argument('-b', '--baud', default=115200, help='Baudrate, default 115200') + parser.add_argument('-c', '--compile', action='store_true', help='Compile lua to lc after upload') + parser.add_argument('-d', '--dofile', action='store_true', help='Run the Lua script after upload') + parser.add_argument('-e', '--echo', action='store_true', help='Echo output of MCU until script is terminated.') + parser.add_argument('-f', '--src', default='main.lua', help='Source file on computer, default main.lua') + parser.add_argument('-i', '--id', action='store_true', help='Query the modules chip id.') + parser.add_argument('-l', '--list', action='store_true', help='List files on device') + parser.add_argument('-p', '--port', default='/dev/ttyUSB0', help='Device name, default /dev/ttyUSB0') + parser.add_argument('-r', '--restart', action='store_true', help='Restart MCU after upload') + parser.add_argument('-t', '--dest', default=None, help='Destination file on MCU, default to source file name') + parser.add_argument('-v', '--verbose', action='store_true', help="Show progress messages.") + parser.add_argument('-w', '--wipe', action='store_true', help='Delete all lua/lc files on device.') + args = parser.parse_args() + + transport = decidetransport(args) + + if args.bar and not tqdm_installed: + sys.stdout.write("You must install the tqdm library to use the bar feature\n") + sys.stdout.write("To install, at the prompt type: \"pip install tqdm\"\n") + sys.exit(0) + + + if args.list: + # zzz191020 Amélioré la sortie du listing des fichiers + #transport.writeln("print('\\n-----');local l = file.list();for k,v in pairs(l) do print(k..', size:'..v)end;print('-----\\n')\r", 0) + # zzz191225 Amélioré encore la sortie du listing des fichiers (sort file) + transport.writeln("zdir={};pfile = file.list();for k,v in pairs(pfile) do zdir[#zdir+1] = k..string.rep(' ',24-string.len(k))..' : '..v end;table.sort(zdir);print('\\n-----');for i=1, #zdir do print(zdir[i]) end;print('-----\\n');zdir=nil;pfile=nil;k=nil;v=nil;i=nil\r", 0) + + while True: + char = transport.read(1) + if char == '' or char == chr(62): # '' or '>' + break + sys.stdout.write(char) + sys.exit(0) + + if args.id: + transport.writeln("=node.chipid()\r", 0) + id="" + while True: + char = transport.read(1) + if char == '' or char == chr(62): + break + if char.isdigit(): + id += char + print("\n"+id) + sys.exit(0) + + # zzz191020 Ajouté la fonction restart seule + if args.zrestart: + transport.writeln("node.restart()\r") + sys.exit(0) + + if args.wipe: + transport.writeln("local l = file.list();for k,v in pairs(l) do print(k)end\r", 0) + file_list = [] + fn = "" + while True: + char = transport.read(1) + if char == '' or char == chr(62): + break + if char not in ['\r', '\n']: + fn += char + else: + if fn: + file_list.append(fn.strip()) + fn = '' + for fn in file_list[1:]: # first line is the list command sent to device + if args.verbose: + sys.stderr.write("Delete file {} from device.\r\n".format(fn)) + transport.writeln("file.remove(\"" + fn + "\")\r") + sys.exit(0) + + if args.delete: + transport.writeln("file.remove(\"" + args.delete + "\")\r") + sys.exit(0) + + if args.dest is None: + args.dest = basename(args.src) + # zzz191020 Affiche le fichier à envoyer + print("File: " + args.src) + + # open source file for reading + try: + try: + f = open(args.src, "rt") + except: + import os + base_dir = os.path.dirname(os.path.realpath(__file__)) + f = open(os.path.join(base_dir, args.src), "rt") + os.chdir(base_dir) + except: + sys.stderr.write("Could not open input file \"%s\"\n" % args.src) + sys.exit(1) + + # Verify the selected file will not exceed the size of the serial buffer. + # The size of the buffer is 256. This script does not accept files with + # lines longer than 230 characters to have some room for command overhead. + num_lines = 0 + for ln in f: + if len(ln) > 230: + sys.stderr.write("File \"%s\" contains a line with more than 240 " + "characters. This exceeds the size of the serial buffer.\n" + % args.src) + f.close() + sys.exit(1) + num_lines += 1 + + # Go back to the beginning of the file after verifying it has the correct + # line length + f.seek(0) + + # set serial timeout + if args.verbose: + sys.stderr.write("Upload starting\r\n") + + # remove existing file on device + if args.append==False: + if args.verbose: + sys.stderr.write("Stage 1. Deleting old file from flash memory") + transport.writeln("file.open(\"" + args.dest + "\", \"w\")\r") + transport.writeln("file.close()\r") + transport.writeln("file.remove(\"" + args.dest + "\")\r") + else: + if args.verbose: + sys.stderr.write("[SKIPPED] Stage 1. Deleting old file from flash memory [SKIPPED]") + + + # read source file line by line and write to device + if args.verbose: + sys.stderr.write("\r\nStage 2. Creating file in flash memory and write first line") + if args.append: + transport.writeln("file.open(\"" + args.dest + "\", \"a+\")\r") + else: + transport.writeln("file.open(\"" + args.dest + "\", \"w+\")\r") + line = f.readline() + if args.verbose: + sys.stderr.write("\r\nStage 3. Start writing data to flash memory...") + if args.bar: + for i in tqdm(range(0, num_lines)): + #zzz191124 transport.writer(line.strip()) + transport.writer(line) + line = f.readline() + else: + while line != '': + #zzz191124 transport.writer(line.strip()) + transport.writer(line) + line = f.readline() + + # close both files + f.close() + if args.verbose: + sys.stderr.write("\r\nStage 4. Flush data and closing file") + transport.writeln("file.flush()\r") + transport.writeln("file.close()\r") + + # compile? + if args.compile: + if args.verbose: + sys.stderr.write("\r\nStage 5. Compiling") + transport.writeln("node.compile(\"" + args.dest + "\")\r") + transport.writeln("file.remove(\"" + args.dest + "\")\r") + + # restart or dofile + if args.restart: + transport.writeln("node.restart()\r") + + if args.dofile: # never exec if restart=1 + transport.writeln("dofile(\"" + args.dest + "\")\r", 0) + + if args.echo: + if args.verbose: + sys.stderr.write("\r\nEchoing MCU output, press Ctrl-C to exit") + while True: + sys.stdout.write(transport.read(1)) + + # close serial port + transport.close() + + # flush screen + sys.stdout.flush() + sys.stderr.flush() + if not args.bar: + sys.stderr.write("\r\n--->>> All done <<<---\r\n") diff --git a/DeepSleep/Pet_tracker_3/oldies/0_dsleep.lua b/DeepSleep/Pet_tracker_3/oldies/0_dsleep.lua new file mode 100644 index 0000000..160c466 --- /dev/null +++ b/DeepSleep/Pet_tracker_3/oldies/0_dsleep.lua @@ -0,0 +1,91 @@ +-- Teste le deep sleep ! +-- s'endore pendant xx secondes après xx secondes + +-- ATTENTION: il faut connecter la pin 0 à la pin RESET avec une résistance de 1k ! + +print("\n dsleep.lua zf200722.1133 \n") + +zLED=4 +f= "flash_led_xfois.lua" if file.exists(f) then dofile(f) end + +function ztime() + tm = rtctime.epoch2cal(rtctime.get()+2*3600) + print(string.format("%04d/%02d/%02d %02d:%02d:%02d", tm["year"], tm["mon"], tm["day"], tm["hour"], tm["min"], tm["sec"])) +end + +function dsleep_on() + print("timer dsleep on...") + ztmr_SLEEP = tmr.create() + ztmr_SLEEP:alarm(2*1000, tmr.ALARM_SINGLE, function () + print("Je dors...") + tmr.delay(100*1000) + -- node.dsleep(4*1000*1000) + -- print(node.bootreason()) + rtcmem.write32(10, 43690) --flag pour détecter le réveil dsleep + -- print("le flag est à "..rtcmem.read32(10)) + wifi.setmode(wifi.NULLMODE,true) + rtctime.dsleep(4*1000*1000) + end) +end + +--[[ +dsleep_on() +print(node.bootreason()) +print("le flag est à "..rtcmem.read32(10)) + +f= "wifi_info.lua" if file.exists(f) then dofile(f) end + +function ztime() + tm = rtctime.epoch2cal(rtctime.get()+2*3600) + print(string.format("%04d/%02d/%02d %02d:%02d:%02d", tm["year"], tm["mon"], tm["day"], tm["hour"], tm["min"], tm["sec"])) +end + +print(ztime()) + + +]] + +function dsleep_off() + print("timer dsleep off...") + ztmr_SLEEP:unregister() +end + +function watch_wifi_on() + dsleep_on() + ztmr_watch_wifi_on = tmr.create() + ztmr_watch_wifi_on:alarm(1*1000, tmr.ALARM_AUTO , function() + if wifi.sta.getip() == nil then +-- print("Unconnected... (on)") + else + ztmr_watch_wifi_on:stop() + print("Connected... (on)") +-- f= "wifi_info.lua" if file.exists(f) then dofile(f) end + watch_wifi_off() + end + end) +end + +function watch_wifi_off() + dsleep_off() + ztmr_watch_wifi_on:unregister() + ztmr_watch_wifi_off = tmr.create() + ztmr_watch_wifi_off:alarm(1*1000, tmr.ALARM_AUTO , function() + if wifi.sta.getip() == nil then + ztmr_watch_wifi_off:stop() + print("Unconnected... (off)") + watch_wifi_on() + ztmr_watch_wifi_off:unregister() + else +-- print("Connected... (off)") + xfois = 2 + blink_LED () + end + end) +end + +print("Coucou, je suis réveillé...") +print("Et il est: ") +ztime() + +watch_wifi_on() + diff --git a/DeepSleep/Pet_tracker_3/schemas/water-level.fzz b/DeepSleep/Pet_tracker_3/schemas/water-level.fzz new file mode 100644 index 0000000000000000000000000000000000000000..75ab8bc1d1c9830cac407145514dbb0059ac9e65 GIT binary patch literal 77042 zcmY(qV~{vL&?Y#x&EMFzZQHhO+qP}nwr$(CXZFAE?bY3WN~OEf>8>PCR~qtCz#u39 z000mGsi~XtM+(drE+7B^iUPmC4IG_m#f4~;6zQ2g^-Nir8R%I#j2R4=8JP`P zS=pJG4493K4Gc^fjM*8P*!37_O+D>fRCSaN#Zi3T)V61a9vh3F0%V8dE3llGDRVLe zovrf9Wf%G9j;%Oj!j-=E-dvFa_4SL z2w*)LFIw%u^R?xyirtOJEZL(mAXra>oNk!?Ysh6afJElTIFK7}N?>Y)B|1&u^_^uV zZ_^IrxvAXi#m2}vTX-)v!Xhgxkl<1s){Blui;otF1VKNYD|TuGTQ%Lkv2z6bx=Z>m;GTvfV%|7a}D|Y#UKN zSLi8~0hX3*Fde4Mc*m7>aqlyVTpv5YNIgU%qOGtf5$hKPzgul845m$YwDNE@U|&I@ zXX%-M!kkh6GJk2m-q`Y9wcMT|&O}OVR?=$O;U7GDzDb0dO{(}*39sK_3~4bKN8#By zuS>PtLQx1!%529*UL+{U81=I!6htw{e0RFq%4yZ_si2D{^^#0#(S5(X!RcCZdyXmR zsFF=tWNqSs?@p>>^@BOl#5ddb8usVi2`A+xDU?J#NnrhW_)aJgP8;-!beK@Xa_Js|rJ@D=&iy8wJmh)ZDo( z2O>w}m52pmL8w9Km01BuVc(&>Ka~^(696R?$UP?1)Q5j~4}GdctHCvA-rpGyxta@| znKRG_N=3nnJ{F(3E;j^<5|ImLbZnId=cd)>X3u7#0CKhfQoX8?G4sK6#o6hSJweH6 z=FOP06KgC~*p91j;aua{nq&9Yk1NT68It4LweI@Gd}CnVT}A#!_c3AL9>vECODc zFt*X{U5&%F52$8YaEi=ZgySwvrY?~}^upYdnC_pf zQ&A<>Q8EI9oVjz4XhXE6zloOI0eYyaZco7F&F%f{b!83 z6Ny2M)r6&Ug6E$d4IW%A9UC_rHn&>+RMY^04_jN>uD9FXo}@4N-VBeAT|eIHTh#B$ z8th&T*dFS2e5uyfm2FSgg&hfB^2KUyDce50)#pjt6YSg#*c_sZ_|(mfO55JfV_PEL zq@$PI6!!g>=C5IhMq2V8u$R~$p#1*@VMxH92O z+gV%K>KPiFuo*JbGnp`(G8r>-FmQ0Nu$!9Fv$7a5GqJH5nCdb8Z!{apnsD4=#qghd zQNrh5PE*7ewq&W1rrd}jYt3RXVdF6&M@nQrMMFv~boMmIE4W)U+9U8N3XC#e3KeGn zj;2F{{KdXEAn-;0yqe2}US*HW>F47Sr8&XS--EIq)64$1Cp&lY`*5|8a0~i6IfDPy zQ7>0=)jz6swABJd3%vHgH*39}Wf2+K(qU-@%#7ju`P8v^qn^2WmGKl;2|~y{hS(B2 zhl`#k>0mDU_LM?5NAx{Gm_W7Fu&K(@!SaVBssE4T7#jA77G73Svq#m7G$z*nS2A?~ z=!=~ZvbDl^ACRaGY<3zn=6Nwg#IzZ7%;JON#Zk5=x+2Ypo&kvsVBFD=s_t8S>EJdo zb#wXg{EBby7i{nM^Yd=)j~~4mb$P|$(;uq+6sD_43)d9Ad00Ea1R=&2XzqC+<;ZTP z2D{oNJ&jD%!CFLC90`xF^Z0n>cHG7Q0%HEUJW26O>P)^2}hLtPhXZUqG+X}m#vij>u+bXZ6+w^P$_NtTXV zT6#b0ALxAq6dmNRG_gJ??*K1HN<4@@C3+H#jc6lmg+3v04ST8RP@eylKVQG;1p>8F zFxtOB`oTw1#v$Z;vrE*{$qGz$_FT|cv|@N!3hhg|+^pOq+psY;F#6PWmw{m&|JMhJ zS7z7VeS^N0+hAh44Sll)Vj&p?SHg&v0zBc|i`-)tIB2|FOd1?LQUP}5!8x+7FAaY2 z{gg;;ZnBa5{VN*RwRHAW$l3>} zAjsG|I`begr%msYPr{T{RjeG$yRREt^y%sJ*E{d(pLiCUm&6w9^dI7zOg0ywtk-$5 zo@iIjLxf4fHo)8E)WV@^Y7cW-!_O>TVqKGE-lDjRnW91BWF3RgtnzaMlwfv3xLKIv zwykbE4JoqZf5~og8ig8Exa8feL}Skp%#(EdWtzFMzChn8Y(ojmm1r(AH%tYGq*bN8 zuJ@!>chrTrUTEnFK4Mdeg~qb^Xomalr-7SZ@%}SrQ*rQ{HSRs}ir^jK@xL;r2jB#d zrUd=eq0GYX+@d_Ksq0J@YTM{81ou1w=#!`n=#10xc_U?EtJZqe&kJwD5-XI4O@Z-1 zVjj+OLvGt%oW1m~ZxY5}D5fv}R7Bm*i@WMfCz|dmGz--f+Kw5RYQ-AnP3Z5AIcc67 zL;|%d+d*YDXTED`PJqn?>l#6WX2;l}3z|=5Hs#F7?#;y;z*QE30csXRCQQ!X6;)|Q z@%3N5P50{PWzEdy9%S0S&DAv7{Wh~SblxS%c8`{up^M}?_X8d$g&;~8fbmFa)X&ub zRiN@Eapgmh8d9qMKODCIXP5PHY?m*fpsim1 zbJkw#9PAbWB}*7k-;TS(uj;O?PMJ@dy5qL_yQaz2j*_;N&}DU~@A87Q^#%*Uzc;z#^56Pb9YN@JhsP}y@qrYnXZ=n znyJsqlJaX>DyAA~n_9J@Qe7)C32+hU z+R%mjQ~SS@VnW@TsVYU|C;_hs+z+7u!gWx!& z%Hg1@+xOks!^P9-;o|q%zVq+E+2Q@*^ZDrR&ezjP0PdK-CADb&TR#JrqwV_e{^(_E z>_2hAI1wtVm2r4FVzsZJ(eIl-Z7i36KSzCig{zrrZE1LN2J=xva|Ih=OD|vWadxyA zc&G`{>U&1H&!D&$BL|;1Sy6s{-yhC@IYIl*ULR>$OZRD0ReimEeP6$;KdxS%4+#4v zp2?T^E9KWC2R}PMHF$?@e~$Klz3yI~Z}t#6A7qrlF@3>$cC~hLX>Wg?ejl%&&R_2D z5xpJzhQxx7SDl{`9OBR9#rneo$etfdZGWG3dOl)(PQW9oe=dF=U-!4Szeib7acOaH zYkWVMj|+-L85~G&YLdX%H7$eVNBm+{Eh>>eqc!2!D)9=C1V8i@dw;z>zW-_4vsK9V zQSueH_2+wkbrzrqCn~cbJ!t*!-yJW<^!vx?O^x5b+FRS(TRp#5m-jahcbBuyPT%j3 zmaU+noh~o$SxjD5$<@+c#xeIS+-2LB@e3iGxzOTKnK8v?(XcgFekSenL zFEM?+r@9YkkkX18(fQ#^OOP#|LA+zUWxQu=kT8@N2uTU3Q3zm>cH?$xZQe&w!t5pk91`QF7|!(PN*#RbGLyYT>#x}gk<;GZnEZ81X= zVsQD}faGWu;-{OY!boy@4F9t+T&pJWO5ehwI91uP|7Y#Ew93}kNLA)mf!=r4RMs?D z9ZsC7>9HVKtN(khJJg<&R1!v>@-{=S7aI(A$$@9ml@5o+(DC`|!-jiqDasv?ktNw& zRa-K$=8Ng4SJUmCn~NRL+lT~T)yEKC?{B8`XQeyWQFUI;_d!+NAN_@lrpgOxr`MOF zpG9x>w33YJW*>N8Q{UIm-LL#xsZ9%bw;y~o>#DcLn<?Gzv{-rvnrD+8nVPe{x8WzdE`x(8t!BEyONcs=a3r?4JVt&LI$?M5(OaExdjyzm&bYlow6<5A+Rs*G zTb;3ILvv+U_6vRB|FP44yy|PN>^J(L|7X%wUwLI;k(+%lrWvYL%J1Sh~(rfDE^qDyaizIS4SW@Pmi&Jc8QJRX%* zay8Q^rB#wu{rP+Q@c8h2HIQgjj}yTisF3AtYhYDDQ0-^c;w>tJLYHu`n#MZVn#M#{DA_TIJdf{>t5>A?UG%B$w)CV+ca;k``_gnfu;s*4 zBh**Uy|u>q3_ah}Q^SREyYah<`^3I=)EU&4edC1t`JXfBspSe?zi_KzJHXjkQP z&fD$jc!yyp{<7Q~48M+`zD8)$X$Zl+ zT0_-YU)WRiF4&FqQGJW7)q7y*>>uk6^@0nEw{DKUKa*^BHxMBFbr5MacRKe25l|eU zBm8#3E)!2Q6UZk6sh&fPF*Er6OP2q#U0X`EzmC&-h8Xi**dfDl6K?AaK4Jpjaf68B zJy`F<_8Dy0`@KVs{TcL9V&Ck`vS0qBhTmv1BN+Ygdzds+KW&~0t4enLuos~i<9(pp zK9+qe+p1bxTVpF5vg`YBd|&cIdf2)9dxG!#x_tY4==%G@?px^n*x~f_lG6+TbeaXB14_7&V zcFnSRi<#9a%Z)4ui~Hd=CDgDNHNi(l_oHPeY9?*U-6n8<9pB#F-yZhl?*9E9{eJ$u z{D;$HUw3jWhwSb5P(40xpL~6voTY1>ic~$Roj%?o>r|s)YDVtwAFmIOWe2YZO%!Ln zot{5_x3_Z`3OHOi(+AjN1XrnD+X2>h+AYHWxrOdwVxi|Q{e=_IGO1f?t!l-HnK$pO-! zupZp9Cn*sDwLR?A^Ek3GIdtC(&AHVXlb)>l_U_jc>(G3d>R?_Ac;vOe8W)AR-x|g4YyhyC@CnCY_`vCD2K2H4YpUUUcK^*vnaLG~(3!f#4 zm}!Mc7?s+@4LE`76cGo^ET@Z>M29O8`_1@|er4K&0?zmXo3SXe%RNg4JW&3gmd(BR z1qs)w!z2~6EpuB45MI_(fhu0@B=xQC|_(_~30FC6byiZisnNI6nz zRICl5ax(JImIVOVgFT7bWd_)Iz5H9JyMHi;DEp5`^mTV_3ugulZRFFi3+YPc@^g^7 zG&2-1nsXM5_$$0_vYmFavQjPXvcrWwi%&j_RbR|GOT`1mvXWfGgHBL%sb|D1#_lYFv-Fm(2b4?G)TTHNA$Xs?W!v>Z=&y2QPyO`g$YjMMlhMVwG8Okdkykpz1+ppb zW{R7U=t;Ay)|1Dfr$z^R(+NruyVb}{mYP3}G%wGWpTCnEsJnCHHAZujdB5ZPT7T#R zVVSl-tsL=c>^<-bT->XImiEYw6k-C+tYkHv7aw%)!%!?j%ZMFm*Az=wu8Bp~Ui4+1&WDj$6jjfGqo_%W9G zFK{F%HL-@eAdW)>|vWeh84x?KR>e+E^@aSvrIoc8=axrBr>dK0Pw*k)yOqDk zPo{9%UASzS=S0Yk&te8E2SIPPR-(x~-}3+XK-gHhz4YkdA?dh7DKo+3))J({#p zw6H`QWt&K|=`gy^ywYWN3OcHHQwaU3I%MOQ+}*=R_0;-);7@sc&=H8!?&6uxo>$Hx zx3PR4xBF3-<-vg^(au-7)_s>K!=*XYyk6hxF)AK(xL+?X(b?2&YQL7gUr1m@WC6 zUTgn!#;bpvwtVJ9Z|H3e$&j;rk!}7xRiEpNnp1qAAT&1LOwx!v+?ehAm4Rjnl=ql^ z24(xz;lf&jOlt{6^~R$((xvY*H_2EOYo5F~#8#kZIRaybBWVU>v-W9U-X`NdosvlS zXkq7`U7ENGV5-H_xw+k9Y_}Jr z>FT3`)rwXKG5_6ZRlEa%S+2}rX#iMJDiv)!i4Ijo12t7`B3zZ(y97^zhJtQ1h`>nr z+B!bPCcKy$vm#2IhK)2Ve@0U}q?&1Ds#>EW&xBA!ZA_KT*DZ2bB)FBLqNkU5@-S$` zqDv}~lGZy=ZvQjbZ3Z3TK8+&M?Kuj8M?)Y=^iqv}@uo15-Of3|SV7J|`71#|lJ$Bd)fGqC;|h#xRtHF{T)X=AmXs;4 z-!q6XqzHo}s5GJcDn||4XDt(E1IMPFel<%q)H?0Ta6J2VBwN}6;cncLV@A5m#F%Tu z(3Tu?YSc=LLZgZ=XROmkdSkr$Q0#V>^Gc%0WTc@{or%lVgQ^VeHQ3FJs~5vehd0c4 z2q($NM1ZgcaoY4HLaV|2*yGbC$CQ#b(S}g7)1@BMRMx+m5Z(0z#^e}5{ZYBNVL1Vj zObWz?+8p*SneS8p>s@$_0@=}X;~?5^13zG+ zIkmdGv{lQ_0YDNf>LhAVzF?-R4PzbXij^f2Jb%Jd6A98o&XWgIVqTD1P-^Esjb$~+ zsZ7D)Qu;`e;YKEV_^P1CA(~q1)?jEHoZb;p1@96?I-$CXyf=Fnfi!8G-k-l zSWYK9M24q$1@G0EUWTT=zyghgAo@YYYO%& z@Pxs}k66|ppyPB%i-Oh2NZgfH+|&~g6;5vH4Uea0f%qs3j@vFnO13m0P+zys$j5lC z6f+^!P>ZduEQG4W1d=P`j3Y*SH(B=DF{hd3OrVBx1RBxsOv*6*UusG+OWaY9X2LPb zw3h<6%gnvN0((DD?M00 zJuS{EUQ?bqa7+XRwwlZh1P`lEEaQxnBXAo7CQuqxJ-DQn=s!aP=azm7MV_e^4xrs- zPaHTaE1{54j}E_ByVD^eWi*_#G6OCYP#5sE@Pkj6se zk`2tQydbq3gD`Y}I`XG8_Hfjl=Ed{gDAn$+(&*Og8U@4&-aXBW-nABiq_dKf%MG}V zTl09LkDXjB>iz9S4+%raBq}L2nAvDSpMD;|H^-$MhC2i>C0QnF>1S-RmGmp&k0}=@ zH;1~MQo3MfXUZF_4=pRB)mq?F2H->>SB+ZDx+ec%3^yAJL%U-aT4ycVruY z7UMcnDkf&H7Rx4T@dZl^%B2r2Czh058h9D&%UKUy=>|o!_H{B6Zd2{Eu;vr!wyL@= zRRU9KjsPf0ZX|K`jekq_rmw88vUKpA1}#7RvWGNQ!f#U?wmlvAF{z*}pWobD0ez>u zb)vsmwzU+db;5d9fZg3vGZ)IGR`AyE*~Fu-qWLY73qlk88v4zc%uv{w5{-aR?#>N> z)Xkg`jD%EGgyc&!<|4U{Ll#{wJ}hBvzuoaM>t(oTAmc-RUgRB0RN&@}1RzEVid+l| zf!C~)p~N-ECgA2$NaLH3nIseK*;|XOV~I3fTEF?s@iMNA*eg-^5+vo$JeQ^})kdJx zs9`E^6a_!AWz7?&cDxrFP)oNAjysVf8yV zUNbbplBDLwc+9vEPBoQd5(;KpvP2qkct!a`C=;xN!cXf~c%ZW};ppZJF3G{YmC{OAb*1>b!`Z2uDWN>FOV6y-WrcTSGVLyTB1s-A`L_yGbG_E5FrbC+ zWU4SpH98Z{+8O8@h+Rmn)#~R`7Xuvf9YfjbI^!ZtgvLxVb++Z0_O+r{Mz{9oLJCSm zb%m_ncNLEl(gju>N~hq4RRBkknn7SSgauP#vQ?kwAWwxTSr&hQy@{3cjdxlBFu$igmIf7~}vA>nw^eb{=DeM zdO?6w*&a!$lYGt_@-?XzCy0C(TbcP(7=M7}GP3mm)+r17Dm8()d&x#<$TkMBL8;MF z_p8s#V}%u{x>a>VGcE+BX+11-&_=GzRo>NoT&&p(#M-s}Bg$aP(dC&st6le~Amt>z zxwTUFhxXbl`HA;lAlcXyoIR<;!cj?O@vD*l6zGH{7kE^;F;1RnIc}5&r*FSwKx4nA z5kr_Beb<`Yvm*L9tjLv*HRzjMxd4IS zx@HiWvS$4vtw+j`UG`^5avQ&62p%J@M?aQCh@I5;lTmI!80>HZG<7*|(fY6WOlQ)Su2xMtgN2t&Yw3WlEE0Ux&Fc?8qY3pGb~cPEPRb#02!Z>&#J{d>Z* z+&U_x>kL%~b&JE?ZdG#k;0&TtRIl+wssd{=FJUwrEGKtFbK0}(in*fh`}Gl8Wb7Z&l8i4)ao|M;*prPB8vBLT8Ze zaY!hSDO-#>dn&I5}=W-s_4a-|Bcshc3$IQzaie7e0LIpzf z$vId*YP(U=bUWLfUH>Z>O5dnvQ5jRRI2%c*P@zis^q{NX+yk1SFyVKTu{=A;__{r< zcC36e;8dM?^ZqLIXfseN6=gYm4n286oGPP!jNkhQ1zPQM3%F5DgK_rze8KWXQ>(g^ zS+P!MGu5pMrHUohoTY5VIAN5o>(0?P+BAM^lCSwhp4T#QrAQ(8{y49RSU4R!%wWR% zrM*bqPzW{h9}r@@ynuzF38qQ)zj9Q7ZMMrc9L!Cy+T@x+V$Pg(oVZWQ0_u|otEH8l zqY-;@ejl%`s%N=23A`%92P%%a5#R7~sLGv{ zz)W(F2mjqP_IQGOL3#hk$UEo9a`ENWw%}35{Ut&LRyS#K3^1&ImgX0?ptUqG^|7<( zXIDx(e0rUo=DI1H4UDud6Yl`dTScz<&ZDYLO(RviA4VDE{{2=p{@PC+cMtp{A)2+e zm9ovyQ$kpiSuG!ac$xZlYC)31(Cq4L!svZ+o}s#17QDjx(?nrEw~0Z9EZb{VuMbJ< zGnb#pfr}mdQ-K+y)uoU&uR zb>zblb(aiNT@ZbzWklb{ggtlPbM?ZIERzgsxZ%NHG;G$F>?Ngk7V(5~vexBSGRoNt z$%=|G39qgFdv9B2@G-xt#1;~g4|WjC7<6EooBH8Y604>9_c|ARmAo~&t%kTk@KN_mIE*{y8~x) zssW^J)AOG*X|gBZyr3Gr;^%!bCRY5K!g-RK3k{a*tK+0A9F3X5p>)@_ZzpDXC^LPO zTHEYn5xfFMYHW6ZoK=4B@@!d&0w4ys@CqCW;CeWdzykr%~ z&{xe4h~Rm`8K+Gpujk1N#YWg>;C%8?u;<0xdH9<|I0(`JW>gS(%b-`D9NAXno1m}my}pZm}2gV1|Mu;26HirwEy{o@_< z-qS$LK4^Inb72cd=Lb_C&7u9t#V|NeV3xl9GqdAqcnaG=us#Cl4o~gY`B` zN`PGO=JLI=5L8&~lIej7POOKw9O}1AqXT=4UZ~iCNB7vu@;!a=f^h{QLVBSmu9IIj zAb*MxI_0zmdp{&mZVMOKvC+~nmpa&`dzn#Ssc%-Ez&>Zh!Eh;O@nm!5qHVh9^U|zc zTmkqjv)J9ez6G_j2FKjV<4jrkMLLS=aGZo84=y+sGWzxekdkSY0$n>p`eOOB z4>Cr46DSTGihv(emhROo>7xTdPC^f(mP!7y#~3!x${>|CUyO{rRQX)$N%@?b-+n~!@}ZVh z5WVx_(tJ}RX%^?6p>6jcqz&a20c`|aTOD(=#M{E2-f5MalaYG zpkV)+zzj25RxV78H}hGQ^Fq`}2bXIeoJCP^=Pzdm+v=Un{4_70t{hdApXd*hIL{9; zcz0Yl{&5@qg_SD*X_S>^i}B4Wn5oz<}#X`YhC8>xFFkExkPit<5pR@ zMCrux%~XX19=xn1)=XS7$JU-Fl=Be~sH7PUcmyf?6bT2|7^ee^#I}lcYLm8k7Rn%J=)t?&OF6Svv73YE>P7t=V1={-PezOf;h>JNz&N8EEk^(gh@`NOv zlFxJJHNn~|Mxre>{?-KQYUn1vb88l84gC3hg;l?qvAh-l%gh8AKOM{>2=$IKVy6BN zkU!}DKRG@g(zxlauC1|yos+SvtUNtG7eBY3SGiayK0mLY`-{`Zv$b8&Cl1H{j=o_+ zZ!%lg-L2y;_x9hcE18&J3rUC#CJVkB_Wj|DC3VnCqJZNlHQ#r$%_`lKOA#&)MQ}4V z-l~IJ(J$<8zM=cBJ3PMaoj%_0vlsi9gU8>SgSWS#x3;pWyRd`2$fqTgvsS&#*9dT8 zr%gb>PO*9YGP5MJ`C-D>t6BIpHb0;CPQD!5HBMhySWY?7_)-x$s@%8e>VFrxvzciG zvgb^j-B9Q6C2Bl7e?KESwZcD4X$Ki$)})BJ8s$SnO%QXYuhu_JP&OHUrO6*=n7dzF zliG6yez>}4W$^?@%^Z1Z3{c`_4~>GQb)x0oX<`n~gj&NB`KHCS{}Mf)7l(l#0h1hJsjI%vU9jrapAF!=*T zdPshIqUylsl>Hc+Pxm-ff&pPEr}FUz!V*G&{f~$T5U%UZ^@#;BZ>jI42GA5(BQsE| zT!(_?A<(=IPW(}jFC~GAyxt!1yN&c>d8>v(0-0COlnYmSA=YKo>s*P2@r& z2jW7RHyJ)I$amb-Cl@&v8MY3Pq9m?2sisI4A}P#P#;j*UjvWpzVkgB3g%radJa83c zPtFDlfDAJjG#JnKM*md-E3w&xNvGm16W}$7R%GV}9))=Gey2KtXM^)TLQeh}=!`Zo z2qkbnBE`)G9&D&54W8MpdYm>HSguzu0Mb{lW!Sf!q>DZZl)(6}!Jxnq|GD2fMY238 zK{9j|NV7^_sxkW2APq4PJNPp>H{{b_H6a`dI!1?}QJe*pwnMzLWgaU=J7U;9-{@Mvb@N-An>nm|1iy$SaRlGcKi(!`XZD`% zPAInr?<>80xx(|r%%X|7$e(8(^p+Ec@Ht7zLdgd-X1K60KC9GhB+LAw*GK@N@Gwa{;mK^ z8f#r?IV+vk$kB^`7~CoJr4)6H4TI`vyXaDUfmSglg(4WPqDY;_u#I9eSy?;-NhIU2 zl3c03S6Ow)Ki_1vcyTU47OKxAgaB9wz;0Y0R2(VM+CW8Hty5^oL&EiN}* zS^Ku2+RZrFL66$GLVB~Wz&by8&s`XW+{7CLN|4wK(p0~Y7Xp{}j3VEIR zCXY#%FT>waIDhDX#^QY#tiRKD+c<@KgAj?Z(g{^1uzjR>9WN{zf)FBryoKbsBmtvj zeKdAvKyklI+FGo9qjue1`ktIv3U@!glw4DQ0T?uTvM?>A1)_~Oni)bEt+OnZ;D^>6 zJs@KVUrQriUA6_%#XkdF1&rAiS-2NUaVBtBnv+!m%}X62DIH7Rp%N*>XPvLoqyiW} zOeIz*M5I*pgw`mfIIIM;0LmiRcR^4mpqYMj9!(gge1#(pZu!13PhkWoJ&NbbiXm7F z=|UYv#jss*nWQi<5d&)?8V!Xk$-U4I*c&z3uF{{#&_rZ-2XaPc0a(YR_-24SK?2x> zyt-76!-t)5J0Z6Ft9X>`zFPybtt%-_!@ z@G2rUbMl-kB|!v`W~zOT7!z;eakfsN4rD}y2o99he_o@2B|kI6oGjiL?!3R9uo4`a z&X6;ro9vOmil}g?F=5zl9}_1S0N@>bK(_=GEtlipa(Fm&Pr0!v0Cj$i7!-a8M}Via z|7@eA7!nb5kjM`Xhp6~6#YC~Mu%RLjDHiIhm*Ajdx?fL81zJyb_?-lrF-k@v=KG6?m#L3^;xVnCIl>;Hx}eS&nO-6hxG70FtW&TQ?-p&V^RZX09CFAI;GK9 z?g(R%Wc@c*X(@^2ES4Bceg`yEpr9#o*+u22JFt!${!RdIesGU+3tWB%S91BQ#Z-c8 zbQGQ`!QXuy|B|+mC?S%^TY#C3e)K@@P@APvgl3RJx@zES{(G|u#(3F;Ao`ql9PeBN zXUqmCKvYR$K8bg`jW2S-qAk4V$@90gax=KBI)Stz z@qNt<6USX|P;*dU?9@XT5kueu5v3Wy0e}Mw5a;A60h$1@A^DQZy73mq_kJf8d0`iN zx9TjS7v1;!=^rNcAV3ldLn-MLGn`?{X($P!6eIpKkTUv^@@ex$Fm#&B>~QOQieO4C zD;*MIuOo|@FlPKw6!Eyh5Oswe&kymY(WgXrT}umH=mgW8Qq%xksB@KKOfDR&A_}Zd zpfts74GByp)%u^rH}+ld7N^ z60wnfha>Biu11``C`ta^yJXDj9Re{Rzmr3X+A5abQGJbIekCD&g8c*!Y6w{i4>%lV z#@+%TN+F$SBdz1#sMcHL=Sj4xoWLlhI8AEtpD-F#<(dR1YID=-)OLq zs(Q^v%8GS-0Kgfdgp&I-tN$doDD&gH{}z~~l;a4I8{fEOz5i>XF!Y{Ksx!WEwIx04~SXZKb`0`TPRcl6_MM6?SH z6+yVDi0X`S(4`%^;7bJ1;=-30W7&8|Qx5G?4fk;C3Z)m0Fc;WIT`lpjYS9rnIC+ps z*Nm99`=*Ob=!8~Z`RCj)0Djh`imRfhi@L4aK~D{iUM zol%x)pj;X1gUB^lz6}Lg` zf)VKBEQl#QvDqOe5^|4gloiUxy`i@+57Qz0`>m3^@<*oRPr~`dhm&OuCne6eQ_Epy zI;D=c1&&n%!md$?ID!b^LKxXB7A&A>KLpVE+{@0vD^5#wvd~PyX zK@*t1VF)B5hdmr$e%4m?IptcbdxX|-wy5_`?jtWB_dD5DB?%ZyHPHBjhsJsV?O=e= z74s`n4q+Ql4u+*g4n^zKwiot|$h4FYEED%emiu z>-S5XpT~f$VJlcz2T5Y2s-Tgbks%Fq?}4ejK5lm_`M#<^Mmz<%rxwCAI2Vru3@2+3 z&NS?af1j|&$q|3OP#miu-n6J7M?~>UnMjaO0okaDK`)(g6|{mS)U2>GlQcHVi?br2 zGRWpF4oC19nAZ|UnK8(I6=o!dFwG81zIZRBK^IlYP>nZ+f*dg#eVrM=1&~Tyd5~Wt zWGSx6K2QC4nd)E&2Kl}vy-1aRA8dS`jwY9MDdcD#Kw1D&j9j94jt|x)RC5yiGAS{i z6lYWaTIx*jh@m};T)!?Kd(DnD8Nne&wV)S)qqy3m4*7-=#?&<{7<)d@ZwzQkJjm%_ zzaxIsraY+gEsGlDH6Y|o*9rrL*iSsT%)wrte1Ap0;knvkz6Y%A@Hpz?W0rTk(qbFkWZW4iz-;;sp}O8-W!T9nQc+@L94XhZa>DPR~%Ic)V>F9JIh8 z)MSPm(QKTlk0}gG>ya9&ocsJL&k1%ULb4B4%rJ#YT7daU!q6)(RjA3JwX5#&{uXq+ z`;PXTxpc4Nnn!aK7jTcCp`Fpsf#B!oe?Pk;|Cn~tndsmeN)=)zQ{jgetJIO`{Cor- z4RI0 zb5Y`x?@n~@$dr9X$ovax1JYEE78LcrSbN7HOQN<-w`_LVw(Y7e+qP}nwr$(Cx@_C( za&_5z>iy1q5oc!3pNTmCc0|U?ojcdsk$9f#x+}&~vXGL!iLJrT&?4l}ME!St6O0l* ztd)3?RwIr-O^}MohM?Fbs*0nb!YVR>2O&0tD2*#^z{%`mL?(qQeg!WH#v*P~Mi^)w zchD*FODq*p-DZ(~X^a@FkZP;4Rtb=rlHR2>N;L(vD(VwcQDW?f|Gi5SgEm)yWHvvV zz-nkG{H?K^{`bVpbcAz~aTY97kfd#zw2Ej#0?U|NTh2vnh#etP_>$8uy~vr`ibohL z((}S~JUL@KU)^+6O(CN1SQ4+ClY2$1LNPdwP%t%pf`X=ovNIlOqm*S4crltbu+)H+ zF|47S%Kd7XXzf%|Uv!e8K!+Cz2pfk1XBNro$Xp>UhILG+AoK6lhpWhwX@xb%%MS5pRwd(_#)GwhZpWTjtn3#7ZL z%730tp3`?CW-mgTxU(=1C!KVZtSd&wg@C@$hRKeE{v0OV1Sj2Oh-_z+EKG5NzVMen zHuVC=a4B6qnD6S;VqU>i6P>Ob{A_t)k8=0#bX9V1O5+@~dq&KrAMwFt@(kaxZj6-; zZ?9S|eO=8mCM+Z!X;u4qj(mk$rX3ub)D;+w>D!~a#WV^`xR)X_2xX&}t_2Fs;jeTs zS$bxd8=W~y{MW2edB^B44Dl7`q zfr95HeRF8-Gp2RvD(X>xxYaFMJ|ElJMnzBoDNjDK$H#Q%;dCLsI-&5* zu$#g>x6z0@ec8l9FcKb?X$%d_=zsIw5CLE5WqdaS2Fnfuq?DFA1JhBZBqxsgVL_Vb z3S4702`a0OFsdr#wDmnGOivt59MX+MI{5+>J!jk-gO3XP#_Qy1sl~*mgoXM>aROr@ z#z-d;v6N#f2O6=^aNR4VBDRO{B=Am08QYAliZZs?jb0soMAE_r{i4vnlzqDzeC8KC zxxC^$o2aq~#w{5|j=Z&Z(~p-kFhNy`j{>NHQ2&TZ`_bNr7A`?^lOLTebs6&lN- zBnYBUfLgX9;RigOn5O-E{)Lh$5|__pLzihYJSHQjg`QTnLm25C)W2EMEI_((YyjBW zcm568KVXwihWT5PD@PvuMhq3nrK$Lyc~pWD=H9FyPSSE~IZe~i1x2v#XkiSdlWgil zB~92)v$OGcbC2p<56j>pj=oZ$0$k}AIDlwEOI12ans!1p@fcU~J_So8T5dwQqyow* zjk)Y9`KzktzFN&rH5hB{Pc1dFGIY7T5nhtWW|v>%35+3wWuMh;^}o|GW8MYt;pd&;)ea z9O^$E0wP9ns2rGpfnOUm(9v*02T;79Kgt1Ny59#a=;ntGi~Ec)?jaB=Ng5KnYBLrm zJ#Le7uWhC-C=xuu83)c%8>{d41)rZevqZsB`_gi3k7N`SK9PbGj#B!>3QjGhKBGwR zWY{obVM5eQ@x7~x{R~`O`_eE{Fj9r$fC(vm2bsO}&r-lQY_|fG@2j=9Pb8!lBkH?( zVdm<*aYY3<>q8(+zDAw!#Ds+UeZGPi+3RA?8l*EdBL5;%d$ruTD29t-q#qFcfR&E) zp%sk!uz5BnzcgXOPRYF-=q1P>PV^8UNOxXXMJ6M~f*2EOBu~;MUOZ9y8A;Q>w~hPA zY73Jg=F#Ln|FT7a_WdIf!a>}e{tOn%mZJVCvETxeZ!82?^TO5Ks30lSKO|B`XR90( z$E2(?D=I>*imZ@;5W%BKgk=^99cGY#pRVF%zbOoSZ0bfCsZCl2cYgG|6>)NK8Yj5q17Zg zQsp`lPzgM+aoxd>o-k4jjN;-!*Y133HN{RRo&2ubi=)CAFX1#g_9LCJk3{4c(_$}{ zKYanYWMM`dvP{jv9-H5f#0l}dGv08I3rm(DmJm7HD54uEL!Us3vBq%2LsZ(~CrxlM z@|2ZS>X;v_9yil)-AmL2p*=Fy+^Z;ZoOY$vg0@nab8L4W$5mCfXkChQ4pa|Lt&L76 z2s9MF$FfZt&oiu5FfLUf1c}o~lPeZ|pB&3Ll~6Zjf#y}ZLh`HlD-^2DJ!A1%9{W1p zG5Jk>(|>_0wflB@d$X`#q*MrIM}LCGd2{qwc)Y~s!6|hk{mUvSTsBgHa8R{Oh-S{j zhlsa&zPkk&VNTqEs;s`sfjIi@Y7S{N%COMkOTSID?2y_si|IzeMT(!yVC=-WQOnh* z*OAT0Y?icfwB|n91$#2Y#+B+wwwspReA0qu<047MWd&xT3{`1Uvj%;uN&^%jwD^Lu zlkSQgRcW9B)na-uE)Z71zNkG-8B9?MC4M{oxH`WI%oIHrwbb*tZa>R-eLZfPM^7D@ zK@&JWMP!AzV?RRi7nR&H>bOxK``FwN1;IkW7RA?&v&gMs)$Lu&!Z*prZHgVe)Q+y-2h=7j%{T=NOHf zd?CeZXr|oY$UI`$i&6kgo679;-Scy}Tn$LA24zcaKc(MbU!^k9J3bzt3KNHmLHufB z3cA@(N^92n*n>}6Z>9o$4HH zFIR$$ex0&Uc#!a$sv)2EJs~M6KEXLn1Rl7cp$;`hqfA!j#r&_7yPk(f%#bO$_^6Ay z_A>YH1u4glf>#p0=yDv$U@P##4v+Vm#ac{ajK4HuTFO$kfuY3;!aa+e)G~>%LvP|) zv=&Nnl}6*lubcob%Yauq$@P$9)<5D0n7A1UU06z>b@@&*5=~guuV7LM$dpQA3e)jL z(z6;vh1&vxRoD~fL+c(Db|WB_Ma^@RTV>u(CIy~w=ocQCxmG5DW*->&dGRk}2Lug? zv<@94x)rHG4TaFp;jli_;e}IzVBE#>i?;HGN>Q-EeFRn`)&WE=(G14Ms`v_B)5HbZ zDdUMp5PD-|Ir0%Xu2E3ZjVs4?WY_!D-ylGnZm8y>=}aqws%@F%t(ZEZ{Hhyw=M3QL z8y2tISZ_JyYO6!lAP*Zk&`F0iHec^VgY7+?pJctAim-+SETdQ$@wSDfE?NB7=vo4a zE{Ov@q|cmXh?{>v-6kjzJP4W{Wt<4a^3b78C@bolNFXyD7TthU3;6N3ObF06)iiZa zYgChfJIV%vtF8D~Yzu3i*R*tg`3UD3nge6*Sc+Lm<}Gyo9(JOTHPfS$4K3QWiVVB; zLVK;!R^?*br}W|nifCgn?IlbM`_XZ@B#4R^NVG?D*qsiAVX>IWyGgB4!2FMbNXr@TE*-k-=5Y~#NjX3&x=>Yi{ss!o z>bPLRj9E5HE)5gZ0u!@qFd8P)YTDT>IDrc0-rkdQk%Dtnk6`r_|^ic!7WS2L0 zt>j*SptV#jLk8gmDEdTE&*AG27E)5LyBMOtiAez}VQN1?GoQlp90tpR*Paw@HOchH zrlutF_7%G7lIn+BV=P5dHfsMpk+kqb%~rkkRU{FL_GLsJtWiM^7@g2{vQ_zn*vyaY zDl}I-i2pi!Ya5CHJ!lWHx)Z3GU*-6-B;`9RNe?PilBSk}luUx=RwW?bJ9vmis#34w z9z1IHW*4Uc6P2{c`r%I@D6VP492mFHTmv=X!oYZCQJK0@$BhVaHrtX^-_V%bg3ZOG zdLJGMp)zr}H@U-T!J%yVR-wu)F%NT!TUaQ=g3J>EFSSqaTV;E!hK+Mdsr3ih*gTZa z+2vSUY*r{KEx_uxQpgbW-}56lQYxHA4R>WyH4&HP7cpdvYoe9B|K=dZiznQ`3jJj(IXRwB?raM2yU9p?(Htra&b!br z$rsddUE&kse1`Qc3X(N&(OIeqTt8DRcjX(wL?gq9St4g6Q3v;5*A?*Xi~>8C|5G69 zFnSvoGdQxkGo=OUb$kdD%bjNGiJoqNW<;MHvK%A`e~HF+7fctLD>4J7>%w+4cYYoX zT2wDlXsv0!vA?qnR>JFjiv^4czI+r-r(wZdyrP&hp;u0I{2vv3^M*N^^yZ(gtwh9< z^|Ms-te-|OlOZW+BTn@`=6TmzC9?Qt3^x3pAVD{~3EGl|*8kr?Yj>Fgxv z5ym0ozyv6WZvCULGwfucDFC*W;juivaWF6_JG3S`so92W;zaN3+bjTWgM zqgKh++EVAsn`KAKMC7YeHVs2%&Ru^CdIOBc0C|7UD0hLx%Yx|+S_wnSMYl;6kMBi} zU#v9ko@Tqpj7*9+;i%wf5E3G$e6v@^(~XNfbcAh!&A*K^eN7mQjyIp0>d90Pi%8{{ z!^*u2xKJ^HankW35D-Rm)ZxF>)zSWqM-;?GvK%cy^@7B7YSESv=XO~j3%kHQL8BZ<-R>fGF6g(ywYWggeGW8|Q*EPx3Sppd?ibhlkL{L+2IYrlh zB{W281j;olRv5kiw(N0i3@CSgay$sL3LXY|7iyb9Qb2}FweO0FK(bo3 zOn5p&d`Nx!orxB+^4@^`hoQINQDVOy-tjaC0a9|CA95Gj9wkz%u73m`x8XaFmjX@& zPPo**e!}CrV#E(Ij*l3-6tHk{`N)aEXm0w&g5i(_i#ZeP!q(2#uft4O!c)CyTBGdZ zUK%|Cv>jZeJT%~a@L5HR6yQ~#1R{M1z4@9WfF7&#Iv<|bt@KK)W`(c(N`CH(#j)U> zz4)mX+Px0*H?n^VjczBko7zrItG40$=;Noy?Bsg5yZa+v&b-s7r~mtWErqAire$u@ z-M^I8qs%0<-`L)Pp|ve(YEK83JwUq8tj%F9z5k%v7`bUmi@Ir9UR^$Uw?y-%w!6Pe z-fpWxa_OkKbGg4mK$ov;)~}DNx3lBpM~(9J`e6P3Rz7w4(-Fz$^1HDP(N1E!zI=+LREsgsh|3sNWH3vCHuB2@m7D#_b^<TA8t&u9I9;@s;E_? zHS!v_exE^&HIz0Gw7a*L_h7}m$p>>b)7XWntL4NZtLQG&W>1dv-22>`bfd{|?U7-}Yfykr-_bMRSbs6NCw0t1Wo*9atG6W5hf3ODh)K(7dQ{?P`V#F zmOVALRA0&RBvu=QqutTEw7iK|g=;KJxFV+-r^wPmmbFGgbxL325uXqXIACrE~@^D=RpjcuE2`r!63u}+4NRo8A^VG#QPlUQ~x}UV=NT1hh zuT^(9Vp#nLndT`(cd@1GHQO&FpS*6zh+zQ#F~xE8e_x%xKf8DW>R;0B>*d?ymuC(+ za+?*bT+y#I%^bs4iihYvcK<3y*(yz%w1}|TDubGhx?|y}j;UaBwUoNwc)Q;&A-~SA zE-yft$0CnKQnCsr*`S-o39c8k9dP0#`OYg0t8s~X{zLU3mT^vb($9xk$HsrHh~cIN zM6$NR3aXFBL`;e9k>(o~H4j!g&=!I|<>3WUp&F$!%@la3aT3$^T2zkop?L#aya-K) z;c-3(G2Vt|!`araQg0*NvlvBZ8{Eo*eu?tuc?2e$G%!eo6MY_B0C_OfJF7v{+}i2 ze8G!{#x{8?tI<|TN2J@Eugg;RdB!aJ`tA7nuiw6&fM8e02k>JUS}LEaUh?1mehF?~ z@Hg8vxoa=KE3Yy{jCaq6+1d5Q{$JcVsXhLFd$+s2-ke;d4s(9=-|pgbMjlN->j7Yh z*Py9Q*!u9@U)gi*zh-k5(O6F{tN)EuV+-faFQo`WT_!qBb(M!8%*2-lsLCXMbc^50W7Bd$U`FzWl_u{>%$i(?U|v!vuT3eRtUn-C zodomKz}rAlR;O5%CVv7LQ>>omuZ=B*?qAB6I|-^}D`OJ)1%d?#o_tcg(rkA8KQC|7 zr;w=ac#so`+YT5_OmO7V_U-a1JeIS zhNIxF`m-zib_r`5&PVj;g(?&`>qrx)$LmQNT#=byrj4;5DmdoUxB*K-&=KUd>V9uF7h z6V`v^wcZC5xo#~H){VyYFL8-5V}r)UEFfuZtF-hyDBo>P(B+}zuLtOIa}qHF20D!R zJh3ZTwNp|aO*K>^l$QcrM`{*QMAPC3ov08@0i%C-t>h@uo-+urP>O%u5tY|vo-8WD z`ads4K#THFnPvV575Asy=47)6SzTQ`MlOGVj(-R{Xtn=K)G}$#LC@;2|4y^Y<;r=J zzI=W}Lh_BTH0`2P?p*YbVc6?k)r7xuGTYx_M$ zv33Pj?Z*2tU;id>g^y+`hPy~n3UM#QuM&+V$B{CZsYUm4xNw&}|EaX3B*JGC2fw`X zlOqY_ziILBDKR^i?F4?Cs;`sR+RBlY>3j0v&4LKV&y;BM!VdeS$kKw5^OC`iJ^qQO zE#bobXI{gj91#H58j)rL+4 zx!}!pO`;o_S)tNsc?d)a;Csg3r=Vf0s9{D&qP6mv>u1qe(1D2(mVn&YE~?i@l+(SV3fy zwA7np5N0;^tLPT`n??WSTq*2C`a7YRrKz;2 zifFS8Nm0i*v>44sBED)Y#RXJ?#T>2_JpQv2%cHZuqRoNlY7HM(5I%t-`1>6*+v!2S5k|=C2p`19XBrcXii`>#! zz}BPuodIcC;5@S_M&HxC7*oFt50gf>mQa%7q8;bYfA&I)D6W*Uk% zKPNaX*Z|?IwR~lhZ!xEuNV^0%C?ENrVomBuNnF?~&Hpzs{(@2Cc_aJPiiD-sfu6JZ3R42xhT$`pHu{wAtaMzAH&8%btHB=(dNkn8*g&B`f zHRYDMV;(F-<5X|T{SO6BsyyUF-r~vRdSQQ$L8s1DK8P=9ExbI93$vDEBb(tSSXt=H zHZ;(yi|;f)uiU+~mNs7)uRi!B03*9DRb|2_wwKilrfG8d&7-p`&l^3RFltV{GN6m8 z!{!jLH0xxO?90}2G#!ZPHqS_vVJ;@kGlIMKQ*n7cCD6e;*%mi>8v!eviz&E$#&nqI$EO(IgR`mq17Ir<|b0~2F4v>1F(jJ zHR0I4LR+;unyPcUs#Wv1*;S?=aeBHy$;^f&plvd|+nDB~coe*=SE)$G#(k922$ut zmYm<2XL{ARd1RL8r?$ZNH(^Xe&Xi|KFPjH(GKUAZPSvR0@LOAqx|&Tp3D^gI+EDxT zy$#J<=6ce!o20!+_LL=-xjSDvPD)+L9=eE?r38cPGpF*p`1MTz^KN9K(5H_tndEE! z8hzZH_V@ASj+2ML1cHR{jaC&kPqTb6eRmLCZd@UAY(1U2^2W^j_@A=fn7t7=9M>>! zTd6MXQCGX)(tQri_f^{PT74;?1sOdwo*~I!`C%;0?TzkUp7YSv*N|?%>@`Wc^k*F} zXTU96Xe(CE&NdJ}*hg)yGwC(>smT2n=^Er$`_V) zTU+zk%du&)@o5%M$hF;`wH=WXvmnVw!TIkDYgInEDDEvc>61 z#;CS6EIU~RK=QYQ8^(L(hd23SpZhl|rdeA4Y~cUMPg|DjSg#zj%GwRN(0m0fhiX4) zv!<&dT;sJopG{fJDUn758jUh+uBFP3CVR%?)>9iyM}+jH5N87?@lO9-2+?OD6J=X^ zxeCwxp814;mlB)(FJAnKfG(jb@#u=1^g(CWGdU}CRkp~w*BUkJUhtu_8_^SI(~`i; z4}wAdmv>0{@LGh}o#9S_o9k?1(eSL-WArXD5!^Ri@4PO-jsf|^;*J3T>GeHqmV6_4 zWh2O$O^d)eT$ms#3)>T|wU{)7=si<-&8d7E@sAqtC|WB7yq2g?dVG%B(*pFVC>8tC zH>eMpYN6>~JBXqZQut0v-A+G}VGVSN36R-34|zD`v`i^}DXxx$Dn%#i)Dbdgr2>N= zulGXlyRc&etz|^)mloedGbjNuOVvd&kkT2P8}WA4FTVW1EfLFHUFlZQC|f$ghuzv! zqUnR731JIBI_)X9qV-~d?<+3Ftt>Vx-2!)Los}Ya^OnAI*oL+mJ>E)QK%`E#wE;x4 z-%AFaYZDAO16(I=f<;rqDjKqO*n1R*!9t6CKT(Ie9AOH$PRHQPM=!iNU5?dT61k+Y zGq1k1tu1?o@~=KsI~e5;Ac$2L>RoNzHx-nZ;}}u(Tq!{6mMa~Kw#*DVODM!Y`eg%6 zGI64#Hy}FQPQKsyuub)Y0j9G(lF}yxT{abJQ!V!q`H!};^Js7^m&kXbIJA1a3YA*m zJbhGSwB(yZ*bsDB>HD?kmGPp=G(Bqi;#rqMG7R3k`d?>@TOO*w4vyA?) zdGXpb3bw(!L-hPCGMZ>z-GYBDHxxuES_sd-gXg?30u@3|?(w9TmIw3DwO{yVi4htb z+^oD7g}0vOv?Ga=!C9=^Q3&@Nws4u6&p*q@)qJ=WA#ZdJsiyWYiSx(pas+fmn8U&4 zBp2k69#)YnPD+pLfP21;S7TD~Y7Be?BeiK*j(jV&P_I{2&K96m zmu=fFM^3#-bu~jFev;vU$~KW;-219AP;c%P=@9MimGI|BA0MVQ>g0~!a@6ayO2XJ) z>$&oid}~e^;fCwz$NkGUUgrfKQ!EmyQ{pMRtlsQ&;T@pDgV^a`?OvLI_qKt5-^Plk z3)9M8P3HOQ9217kqIIilg{cA+mSmiKJ1OmWUK%ZJzGk2nnK`4S?8=+!riYuU)v8pP z{{m9|MK_#MP}(TcE!j$*%o}zdn*LzT8-1k*QNg||rb@x)$E~8MZtq}0u=5POM=^POX} zbn2Owu;7!Y9anyZ3KS;f;p!P>Cs z8h0VAcM{!k&*_X3J(q{LotJ;TP19AIl~>SNje+@6%iI|Ui%Io+ znJ9KEUiU7*>&4kYihJfzi_IOySK+L5yN4zHuuR|M+6LD%FWHaF{0-bbc5~0@B(Kse zxYd#a$3*^#kkZvheDNmP_{l|YvS2z`=5>#LtJ=Mxd|+)4Oce5>!a}gm7$Vw zoVz*|1=qON9!5&bS`mlmAvzdzva$-byx-mK##7uCUB^aRb`cM#x6I=jQrEF{nszXX zq7U=*ZEl$Q60u!o7UxQl`EdHQ?Ktg?4cu|ZBvuMnzyHcGPfZC+*jikjd{{j$&+^1q zO2c+ZARFYRCf1Sp;Mf}iHf?|L1&%s%sZo&g*8Ife1$Dov#hX88yi~sNj{nlY*HQSW zOHq9fgjPoJrJDtn%9<8z1uO90@L zZLDIVoU8_c#hH;rr1>(uP;IHW${oXKbmow32*7luM1Qzu_aNyBDWf%DIwduuzr%e< z!s_T&NV%-~xpFS_8`|LN>TBPuj&0!N8TPKz1|30^$9Kd7Z`?dDM#RcL)7bBPBjN7+ zMfFT{xuY>t7(5;pA-#nBds)WsXOXmv{SXF1Y3%fanK1tCb3z}VX1sMI%3_`wsh8=R zDddG{YZ~H5W zWDu_Y@DsrLiLA%%g+aq~M8%++z-6r9?j6E79L{czKl1Yak4wu}k1vm|{!TZ5x3l2H z$brMx^UO&3)EHu%Kt-TF?(W{s4=V0}6ifQemGuNj#;EuEU=F^Uv{~^Yu9DDvljBEZkF`oQCiSd#jVm$Z1iE*r6@%8_O7)J@nLHnJ0 zg!SLVcs~#QG3fFiZ%v2`_F+dM<^GvDLf71NhbCn_B~vX`OAZ;5cgwT(v3XGQtYUYM z2G%rwlkxdm;p&Aw>KtR61{%)tF2|eyHz#hBGFh2}f%a?nhoUEj==+XQ1aOE-z(7^< z=Oa&9%hOnsf*o~3M+Vl&Na*9_(@cLJuaPbthJXPfG)ef zUjTx#Zs`T|_C7z~5AL509g21PKR!<%|7HyD2X^%N0F1a^1&YxM#oeu&D@A%`=6 z-_JK!BZ&-k(O+dQB{%wB=Lel#pO5bhdjkB2?ts0Wr|Zjsg2k=vmr(t@t?;#*{{K(M z{r4>a;4AgW|8(X0WK3D(PXu3lc|LybC@suQ1Z)f9_gS#9(dbcm@kDg6@t_cQs;>2n z5<%Ut_FAM+Zp`svyZ^^ws(rlMeY85FKnLIp@V~|fJVDr)*Ps9amAe7~Dg2-D0sr^O zK>u5cz>2Q5^OjiJSDt>Lq19BT7V_1La3d?)lp{Htq#KP?&x37rnL#E>l!{^t-PzjQ zHDmw;lrGROE>)!}T=w;=EzYy&>!tSW{i*()y_zQZk!QP3&HUa1F3&`?%aK#7&OUzc ze;@YtWv8NU?b-G9r`0oEoZgMQJ+E7D>I?wpTr&aGOCz2=+qUuzclB&{(dQ$pr+;>5 z45lilq!bhzCtCzfx3~5jTe{EvKMv+4f;G(>hCX<9d6ra^q~7-q9bMDodQJ9Rp$GT; zI+nld?>9F0hV9*axikF!-#PxLZ)Va~S>oo9GYK?%8k8 z)R3%tv)HXh(kyE#KEBFVr|J(o+rw(;V9Gv=4X#h$k85l8#10AGFB`t$2<`|_6Q-d540mjGKf01q4M%*&b2ol}x|O{Y!`6t?4LLN97uKcb0u?b}s$h>3Y~RYXE#c2;-Dk z7yMPu#RtvooIh{`PeC{3gck80- zfNSa{RgZW{|75u629IC3W#70mGOZx)nlw_SW z`>?vm_0}$PKD_KSH{Z3_Vcl+rD=ed_IK|llDmAy-h4e1_{XQbYU#xFMaY+rf0CneY(j$Rs9pW{vp9b_%< z6$v-}kHsx{1g`WMfzXI%WZ>U7rwyD;fz66H6l7He59G0U4E7w=3~Sl0n3D%=>&FqY z7M$HXv_v<8!Nia+3bQWg*aFa_Q%Ml>v^{4Rz>;=uGZ5?P5x#kw?VT&7k`Tq=8%;co zsIg&DjT|%hDjp2%mux+|HsIZ^%^p{SG^TouYnF&2MN&?sQp=e*rh5z{y zVXiXHF*DpTnPZr<469?mYtA^Lrj}Y)4 z0EtPOG{bi@wy8$c+Miu${&*+idYM>*-55D%;9zy*`V@ID`J8tcjr&AJVVv1ggtyQR z3sDVEj3JnXb7ja!!NIjlMS%y4PdU?yRumiw$F`pxYQ&u21Vy!CjN?M-IcCQkXn!{$ z9Ap&7nJQ%%#??7I)v9NND}k-9;mop#=T4X5F?QB@q#+4dZx=HV=B5nc&VD z>BF^pplduDk+i0p47a5JZdIr51DcZZ30MN01PG7Ekk1Dlb zQ~4QwbKqI>ne-7QF~^VN(gC z%&TZ9RYpzwU%!BItt$L7ky(bTF=*5-Qk02ZDI6Hc_CIF;VN@BfY&)pwkh?{{@Mj)2 zHJmxuV4OMgte7m4t+ldNXwi%tQD;W8gg4SSw|q*lmE~FrY-n7FxRhcoHP8Y+9zkO} zG38e>>Q2w+-M~F5MKzu>G_?xr(yEwhohz$;7ZD}gRdMtwIN@HvpJ+Tf@cxY3C?91- zAAXf!Gy9v@DVM=^p;3tqN=uXWGn2=SDor-xl}xeRnpLAQK#8icW}t#4hg_&d3mW#6 zb3GK|%u2CX6q*Wo@aX-24A{tnxSGxDP|Sk4(j`_f4Kyno=e_=O6b8OOClY6RL8NMw zwnhKJ#*=!?h$<_V4U$>bdsW@21fS&tq;lrxDE>(BnVm55nJKT>nwT~o^DN^{y3cV-$1Iz;>65m;wrS9!kg#Eq*We=qKC@bNpD$zPBj^)WU8d5i8gKf*@cwHT#$5JifPIf zY$?Q%!&y_0UiM^jlZGQcb-V$dYp+U);$(6TRw=Wh zWuuA{z7@hrkNE+a_#~@T<(B6Nr#Q;DQ;A%$%I(HwNLEpv8MeZdR^`aE^}cYU$@Wie zm?P^Wy;7q%ro<|iIc>aEgBm(sHJo|3`BjMo9H;c-SJen-)|WcnNLg@i1H)dgyQIb~ zFM=2ohjQ(V^hKLrEUKo(7pVq=be%Rc>!liuJq#!4gCM?P|T1qz2~47Oe8t5){(Em|`p+W(})GC-kOU zIut|LuGOpbWc7zG7JWlJDa0d%y9RL!kA0Rlib5XysfrqcD^4`%K|g;T%qhN06Bapp zszawXs0jz-w6G1qDmS{-4clZDfW!+F6fNyGeT* zP-4cthM&*JeOH{Vr8&fc;!Ig{?IbH{r^Cx>f^+!nZlsn)Z9)%d`@D+Ujk^wMiUiiS z(yLa|+DICuVyQYS!3*1`Ge_^)t znMyD8#nWb>CPclRbW&nablv13S~bL$9(Ezc$f}@mh`TBMIP=>$2x$(`HtUhiTOXsI zARe}zdHZ|6GAbAxf<*jMOEVhcR3V#ol5dL;a~Av=EoGlY6{GDxWBfi6`~>#g5@3p7 zseCuL{g)#VqS39cY>WO(Rt-yzebx1TjH4`8$|UKVQYGP6Barq|0E-cDwg3pvQyxnO zDE%z@eXaV_U8`g_2mF(hikWet~Vh0Tv zA9)DbBL8lzK~5*G%sSyxk`x^fzZ>Z*u2~4^2?c^gH7Up8{M|XZ4@jWAhBTK*$=Gmt zWNbBMI}zB@uaM@4wNH;-b11Yh=`-IL#)+rGgJ#)R(o^3gO8O2@R-a!!=_>l7OCjBy zp5I!yAbqR86KcF_;qhZCVS%KUvtS{t9cbTfb7=Tc)T5<}J_>Z-}8Lpy^ytA`o}yBrqVO zzneV;3)qrR=kF@bj?Ylhz=D(u^Y(p58L^G>7ditKs^ACA^XgwJPnQQ3nf!m>HdD7F zy<-l%Tngkf`OIVn7h8P?+$j<}WrPCv?!`syI;uun&EnuD--oNv-6-@TPc|7>r2z(% zBwEQ4WTpt5sE+SN;SLL~PzNA~>@Oe~V$cQOCYH&VQ-F6|4ZvKjZxk%ZgB3Mu1)QLn zQrS7w-bpBc4wg-2V;uK@dHuU}2w;U_=JBwbR(%5L+3+J%L2d@sb^F$hPw@Yljw@nd zi3e7(aTHWXfeMNSAkNl?YChe!8zanc*2~4SHD0V92hdaJu{L%UR@O8)x@C0Pb1D_- z6kUDj=OuTum4gP>k>nlPiQHq3(2nEL2Lg&`!Ndn9SwZ^;(BUDmv&4i3)HiFN^3^d} zW+E8o(S{5g7X9*L2B3OA$DD<#u}7Cby02}EwG)dbc>KpGWv;dK%`L0(p?h$vv>a^Is?1UhJBKZ`^D zXJc*0xoK6fiaX?c8N+fjddY;5f08%+sF97(#-@#HzceT~PFAxr^e?Q^+oBT!9*m}{ zy`KPOD1koPM-WWv;V;@x@wfl|J4mPD6pviIbVs;b_oq?V-@vYAA{o+U*4V=l5@y@8E?nNb z@i@mNed1Wckk{-uFE3GJsgPjz$DI#kjj2uB9<7Qz%kh?O(7=B-!s&a2z)}iPSX)an z;NR$SkPVI^*UHfM>g~+J;wFIIOVH>p@Esj}3jJcd@R9iUOClZCD$61!FZ<1|y9Y43 z(j>KMBxT87iZz3+uJbM{?BTvQ2dU|{e+(`yr`i9{e}<>VWn>phN^E=M4}9muTXue5 z_vgo8@HA#d%1pu3FR-gZ!$V3LFE|-UDfiY|&w}FYgApfh)4|q3){%Iqz*goORw=eDUi>JMVpuo0ucxE6HX2+ZKwP4v*un!tX^N za%tPSuYv)Rx8W~Qw=I^=gm=Yf&KrgaNjrCoO!p6S-?}wED5auj2mgi2-c5axCH zHFxih?u^+j>+Z9Rt>K*!_UlT!7hFg0igt@dPjB1XpN}9<=(!>oFFB(OXQV-;V*B8ykY-cyA41mr;Hrb^n$PE*Z&b+ zGN6WYE)yZEm1SPHy*b(S{oI9)(U>;4e@a{viafQcPX1fuB%taQd_zSt;gHbjKmFMY zP-;);Aw#o%O5{BVeC;o5eTG(gd3U+?Ted|*7t8n6pOmgX>Bt@3=jPGr7vrpSG9Z^53|fxBAz@NEnO&8SvC*M0@gTcpGcxju@Z1UvZZ?oo^-~ShsY|JJgGihR*<3U@MaY)3Zq~T)zvh---)bK)v%d!`I77 zubx2jZ{Goh9w+QGrir$6<|EwIaO6wkIYr`VU0%2Exu4JdXSM(T7A%T&NbL--4Fog; z2?E6TKi>>m8r$3bUqy=k*MR>gOf;UfJ@F?@)L(Sx8FH&bmCZ+>1)E?!sa4M)7JRk4 z5Axfn#aoL7e$EA!k52QxUH}e)U{J=4ytz!fZLSr2p0xQDD(PPw;ll&q=_~vt{ML>@ zV3%*Z|C>snIUnEd)*gTN$4uz^wFG#(t(Utg5095$d$F(E6WHGC`#%2~mItgce4jkT z@AdM9q`eU6>-Tzly`MI#*MHxq_vZkJ=YBFi^`S@Cs}+u5s(haRWnX4*t*P(v?)vrk ze%?G^-0kdK>-#<3eO=PH89>_A^3%{D>b*9n=;{0?a_;ne=aQy$ZW`xyCLFP_Sm6Uk z{FC;xpW_1XeeK_KxZAD%vPNKcRG=PCVASm1?ry)w*W35&-X;TmS~%WxQT=ne9>LvS zPycJ|39&ui9}a1IS08Zxb$u@XqfPYtc)h#!eZBsEW(bF>Z!ot-*vyIT=kD?K=m{tC z`^OM|w|8c5*Z=Jip*x78K3T4RSbuwa^V6*ixc>J(vvd8m^gpQj$LLDhE^O3J(y`I8 z?WAMdwr$(CZQHilv7L@>+uF(fK6`)f_p8=bzt&izs>Ygi&2t_*9-pt`pajyGRxv%Gg|Y zwc&pMsK1b!?IyxBVe0vOP|>Lq(}A%;m5*#l((!p&tRd|(vq|4vu(i=yrd>x(a7!PR z9DdqP`Aq3~J*ZjRth9OLjlS}0nChTi-OMbA6fV8d`a|^xhzZ|qX+kmT3TW;fC=RMf zoxsM-O>1d~O14cGQ3-o`TGCvhd3A5pF5MfLvgj0uy>-mh9$ov2 zi*V6qZjr%{?7uc;(Kw3sEy-u}eovgD;v1YUw z11Ga2)562y<{X(!I4{Cz-Y`vVP3P#Z@-W z;xg0kerj_T3=DfHF*AX-CkFu5JyiAT@lbbJeAK1=kL#D_smVq_ONzU?%wGPjzRsDM zrmyz7)y7|2+Q*j5)Zsy#CG5@v{haUfEA0zs?FOM)-b>C%G;v;9LI)dr7fUrY-u_tf z?>FU?uzc{E)oq>avVT}|M~P>=4aK+^)4pZVFPDzF?5~R-Srq@zqfG1#kddb<%8d(} z)bd}g;9Uq0;0tF#y$~{taPo6sYqIj%U)_1FeP$wf_C)oy0u!)SD;98C}($rK*C<%kXOp5^S*F^#eVgOU$S+WsEOg)*elNsV@?q}p?l#RI=#7i_&B^RZoIAa(|X?xp16DsF5vn+ z==6LZd|#~7I-g4^KJU_BH;->N)mK~V-4N49-w$XQLotm-I-bqKBe)isC7bG;YP)q! zNCO4(=a=D{FZMD*T!JQS-yNmpRovJl~m!QN`1(b z5@!~cqLsQ{-k&mpR<+lMoy0JxN>}o}tMz*qg({e(VL$hmMkt zr>ie);pYWn^zVK+kld?mAY#qU>7$xC92R@%II;xs;!s6z57+W@0`6|GXq8`_iFC2n zThgN@ulhGeulVstIY4urFZ2Bmdl~VwuE{3BKG+Ls%{~_X3m1%9R#+Uaon3D1p4iM> zwaPBPe2707zKcPk7J~TZJwG!rVojy+L!1fgAxR=v$_q6N8hc$Bcje478dbpB+zd|c z_m{^+VFNtn+7?*lbH5c(GS|Qr!io5+K(!~mLs~EgEt4t484f*WuhhYqx~XIl0L*n; zc2UJ0);=u5z;2UdI#ThtabJMCv8l2d2jM)qs-UvNO@4U13Al&mj$!irG7AgDOQAX4TJN4 zSa~v=8HJ5z$ukQlCaK1zRS&zI1_hgfzwqwTf>V58GR%dR4NHnxS95_uErilpcX-+P zwCw)qTu!a3Uc``&Tfr{FGbj zh|%m%T0$LQ-Ern_!~vTbLz;p{&cvu1J1JkYS+_(*2IE%2ilboGq$7QV1B3)qjyMO> zofLA307}RwER7Es6>$yv=166V1B+#@0OtC8VKD6K*n!8xavfs)+4`2 zlgwYy7{)hF?^or*q=byPm)X|1TU$Xs+{{CafzV#u?@B@4D-pwfvafov*jmps zWusjiRNAHpJE(z?HT#if%v(}3_;akbk_!lFb$TBZri zSc3S-f?4@LD=ihEk@_NdBOo0X{&Dmm@X!T`Dl9#KtT4czIM0V{NMNQY!@OJOubAkKfTYgy{*lLR)9rP$%2{W5K?2;pxeX=;#anS2c{=$uSo8`gX2O;MEQ0D~R-79pB@krFjlv#Z|B?d$Snkg%~yNSm428WG1`QwYJyh7w=Uz6OXQR1&t z$-{l!Nm4mIhBApySIFkB3gX%9VB(GB^?+EKBo>XI8yBwx6?TIGPK; zWH2j7H=(r=gf9A0y3%Ui9Le`?jeJJv&{kG$=Mk(l2*qnl9F3+8N0bj+$sGK>eNZwR zV7}>NY!Ri+kiXFmDZMBZDit2BqZ6dHo4MjUM8Y(=W(23gM~ebmO(Z4y(axG^XHu8~ z&Ld8C6#u!OX7IL#51Gc*Tl95V=5#KJkw$w>HHnprIa3B3%!j*FopB=Qo1kZ-;ot6O zohlT^<^SAIRE};869MiJ%YLke|J(gE-#2>o`f)$&t@7R5of?1K&wK(EXJlxj-5>XJ zzivXV7Ej{$6_qK}xBHpQ$9}wiDSgIX@T{YT}Z+Za$N!gI4Oht0*arUS1pirM*k8{*j*zm zi0V}{YhYj~H*Lm|FKdUf-D_|5Su)fpj@J*9hPO8FoaPh=k|4)+=C!am%rv9m8VeRX zjPr0HVb3R_^rhCJqG|=0(|JWN9js7SJV$RiNh@|RJg>b>(*4B2T zZc4pXfYDtkmk=AgfzC!s1b!$NG;}Zei}P4_Kh&~A}yIuc?Rb$umx+V zgUP|p{xm$8-hV+ZkRSvAB;^jeCu5$z@5zk{YEU;qXE0b7l9ts4=`_I7F2Tr4Fm*-S zRM7cJsCU>GuOQ2H@N@iC$jBUJRvc905arOWso{5r^Pu=N#Rsid;brSukciJ4ClnL{ z)=JqhD`>@DSJuLn!ovS56O5uMXExY&T78d{Q<*^YmhU}w&OmI1uANJG8VhH1Rn$j+q*AM%t^v!-gM1Gm;K{w@+%M<>vpBoF#gdG2| zpT^+`)M3yHc26u!A~o3cC4ALZigvW;_e+wj$C+yiH4DK1_#+eqx4;dFN5>)1&rv90R8s^wkIrE&ZZm-|By|KX@y)A< zb8sg)XT4W7(P=FCfdjV-Tx1M~i|EI7^ktRoeqlyMCjQ5M+U%LrIN1EKpUlNPpvHQ# zYz)nqKkR4!f9xmZ5Bo{_&3?}QkNpf8`v2HZHO6oDb9L7N_5Wf&IsRimC;k6l_OtH) zkNr&fVL!=!*iS9fANJE}>KE)1N!?ml!@t$vO~w-Rxix&imcL309?WCPHd=q!&-)+t z6XnTaQVw1Gw2Cj$`u}1-!-2lp&-m$Y_R|vUhyC;i5%u&^{b4_mW@AH=%SWfM=1b42 z;RQ@|OZoRK#ut^GBe?17%2@VX$oq#hx3x3kFO%)GFs{>S*Q+{DmHU%v4**I@uEcS6 z48KZtCU&i`vb511`^<;^vIaE%gv@j#afK53{2Ml^(pI6q6|Cpn?H0Y|Ik}MUvT9jGDb{&WjuQ3A5m-*5~D=C9a~G0 zO|+4QbIUuv8D7S@Au~lN-Xv4GBjKsB!~bDF7q9ZFYbr^ zl=x;pQT9Iq7gl>p3}%y}&)Qb$JW8rR!`18jr72q${x6UW%yI^6HOY}&xv)}@?Ys;Vd1-w@To-CPCdkyUDbtv0i~JBMj!(+frGaM8bA zq^k3|J`L)3=xL5Bon*6f(Ttse_HU7M@#T8GJlZ0#6y5_^2Yn}e)XC7)Nye`BJfmD| z?*Pyc8yJtxLjezOW=XU8Q9D@ddN-P@PO)+RHImULZbr!Im)wK(jgESlk488So&XgR zF|NM(9SuQ)`&w(o4$z~KQ9TonV4O=7X>i%BP7{zOBIGTLKSZ9* z%lW6fuR!t}{cVIDx}CrH{p*Nh(*ad=aZ|CdYyTuUC%W6>t7PrVQ^gEl7xH&JpPUa~ zpXQVY7>Nm-pD&$?Wn(|brphVg0LOxw)P9fVN;-al?jteBJy*BplC#u+P=B;$g42UV zmBJx->oKHj2~$r1{pdepbhDYuwFqk_ca(}?PuuuRhnm=Iw|^;p1eV-g)%?Yf{?IXe zUX~z5=BhVx;47FC^iUnTOuwMg2^dlA^%>RMWe=1`v)u<{z5V1Ye{WlUB#}-OHF@Jn z-PE+ED=l`f+(s_>@e56H-ptj;Y2N{Aiyd-EzjW%C;8=}C+JgRRb1FGwpi3O+I0YL7 z`ZU<_$=C`1=X@;8qD7iY_H{yCRam=!fDOKty+4RR)u)@HwL~RVp4xP z+XqXGN5}M8P)$A6pAz83DZxZ?^Gv!bcuV{2U!sf5PPauPs@`x+5JD7HIheRk@~&P} zx&-4!{o9(>Ai!-GtfW&{96uEdE4Z@?f5Fk*Bp%p6%Sju$Oy$rJsP+2i7B1t38eM@e z+|G^G0_1+RG8q_wX>7sU?WeNSDgTSP8QWjKEa93aLK$qYYM&<9aK^=J>(!-!lB+gB z)4J#PrCVDKBi?C>b&Re+TMj?eS4}}jMAXV?>jJkd6#CT5`x}15@YaqJYL4z9LYffZ zS{0?;)R3eB^hjX6a!^g*6|RQv5r)Ivs$%5L5lF41Ugncn3E5JJb8vH6;Ghip^`-#>LXX5 zT8O`tV(^}V#+>83M!b+h6A4nD{`rWxp8tH%=HNBbs0H~BVZ+UHi~^u zEZm>P;c-pl1Wf@XFj+_La&XXkKQ~y^VDTqoV8Xhpj6GX|gEo4i%Drr1+QV=06U9)P z?U%c$JO}Ocx(ka*fV-eFfzy}3OjaO@7T%uTu&=2ES2{_GGgrE#bxAbjU$&6XXm0DumeFnK`U z3>KCJ%hTp$MEzlG9x+9;w6&BUkNue)}xD zX1r)Md!t#kP|_kBhm~G-I@4qn;jbk*PnWYb5o>kYfn(O_eD3~rwlV#;?1VAGVsS(2 zmM<&C{%%S$Ji~jZslo2tP5Lz{^8mGz$=oL*$)mU(=3nuKl`Q8X>7d&-M><>%mPN^( zJg5=6Vc(bYigrPO7o_K&l&E8F)c@SiUY|*1!hdQNnKNh>9tyJm)GJn5Q+2SgW%-m2 z7(cvh9cVeq8@BZ|%;T>B(3l1_c#gv>jEo_eId2C4z;VBsb7EPiWwI<67#5~YT8&cQ zSwQZ#kESQGwt27gb5<471^Y z-b6RRbaQ%a^pd>S$?VCKRnAP|5o@``HCe{LhF&)dd&!rEaCRXsh9e{S0DfvdJ~4moXI>o$@nI zwW{IrZQ=amWrD|!IKrDoVau?6P&>sLOBv0jHsnv_xWwNx0fnGK&qFJEK}?D# z1uK5Y@Q%~?q_i*1YITg7RA}6!&)T4?BUO1HnH8KK11TGfRJGeFHmyTllB` z_&uvLgal^!bnJ3}@g0t)nL(wnC8zWT|1R8O$x@L+8bSd?20#U(F&p1rkaR;=@7E}) z4DwkvF~aXji^d#Lv_&^Tenr+MTYA^l?zR}I1j(#jcxIin0e`b0j@4{r z-D|LL4f*ZI`s*|p(vPC`AX_r57W_N2o6uJZv}i^uRA4F(4c#gB66&WGv^acQzXL3K z9CgNNS0(E8@>1FtIyXEEb1ks!LEj#C{cZK+s|qlpg1HOLm32lT16h-2Va)*kF<@)& z|Fs&J7E#f^FMtIe0OouzcGKY_ttSeeMUJ|4I9$IQd862=IxH^ z8r1gR9;}ZSvMxu%J?T_CcS4_2#`M3ETt{vzC$v>OfzfnAS?&9;C9KqlQ%RYLaVcBIiW^qQ(Hn?urlCV@#O9} zV<_1K$wFvpe+fS?QO3O z-tWoY9Q$2ACD81n)s_SF0}FU~KJj#c>WK`Bk6<^!RA)DjNG?%5VmA@6af1~BzsPs* zQ>Ir7;r5*MfiDbTr2H6N<7i!+FNF+nXNM=(yU$14yZ$rElV8>8xz2}|K)?O_aXX)P zyvU|Qw;6K%Hft;C%0uKfjh!4S6bKnIIo}+rcia*d(H{lS;73s=PI0|UnF`+FxYEmmYXH)H zkC=_!iLKWxibOx5B((w@>#XCuO9vwM;4PEO?VAXIpri#2KQE@~NYjI|%>9+|S*I&- zDS{8_?g2+|cMIP|_5|y$Xru0IrcOPLlksn@e}3=bW4PvfFB-Vm>*L<_>0`Q;6Rc}C zytg{q_D-563%D{9a)PzCsG;Q_*Z|x}9X{fwwIbU0OK1IZtff2ZlxB&28lFki1d!+& ze`|LpZcQwDsNUR(oE<_Yy`KA>2mx8_#9{tgF@I-o*mipEv4(LQCwMl_#ip`f9)C=x zT%TE%v&%p^^%z|Hx@(oOzJGqO-y+up;`t4-a`%vEm%!3+8Bo_>WOlhz_@9Z|@mmgH zH^|JWtU1eQYx+IUmx?~s7)zKCK8m8E^G2>l0m?0=0_5Mm=P@`%Mj-Jz9IT2@u?gQJ z`7*U+;VYj`uXW!qYkt>IkD#XA0(X2}{GG#@`0 zC=}y+WFqnKGlQV0_WiVB2=Oa>{7;@Kx&^jg8Sbuvp8lHH48JSsWdVwP9NW}ipQ$=I z>Eb?FR~cxVEK6G~bd5IFN8L_Mb?r90LQgPp?>U$*qeCwbQ3=xxb`_@&m*E*n|C(MF zalgLuJ6dT<9%xHCJXwKf4=OqB5h@Mg3=xKe>Sz8|N9qfU$%+4+1B+pRVp3B-^}Qc6 zeHX(L?Me+aoQ9^_Tc>=7UdSz!oK|IUf#4FRP_*TT2IW{Z4>Sj=l30_Ex;RBPy3rP_ z0Y595#_GiID~}3zfMSTnS)%O(i%+3IRZPV$88=}vRhqkb$E-qCo(87mS_tCS((JmX zyeGSUqeIsWtO z&gaSf!$U~N6OZRNJKMjJu?4dKwZ!6r+vDc*(QmR%_^h&v59yOepO^55!#0E$75C~B z!=1r|gvuoHBJKXP5e_18zvdYvWaRhTJ2Lc_qz6;EX{RY^&cffpC|dK@j@eJpuj!Nf zahse?E>3Uf=bDcB$&RyOmJl7T?C!3fuFpru*Am1RxN<@7PLHpn7?zlpmM5;q-kcm5 zW$sGVrOkcXLC-Km6iX>=0g2c<;C9(<-Q0j6pB?iO+1+a@C(mTMEuldZgtL+7%H4(x zE)Q4cx+-6jDG<)HXPnChNUq^eD^IO#eOMt`3%pxz1!lGq?VQ}@twuOURL6?;R>6%k+*pvqRBPprta`!^hhk^V-_y&NT*Be{o z>tL2|XBe&VNecqicHBudLz_$i<=>FT=NdaG}2YGN=Yj%(m4` zaU}9g$RMDIPk^b_(C_l6*|q25;Dh>uq!NVqsf5s|)d-D7G1xQ zIK}84giuB2LFT@7PC$?H+_S*ZlJGINfV#w76^I68>a^5d#oA(d*rh;wd!vC(e%5rv z=(t2%U}7vGaFJJt9b0D)`Tv%DR24OQK2b&o9qOD1;nQYm(r5;?mqENkp)7p8MSCGn zfSzFFmA3cgx@wtv&{Tn9!$+aE1l15o4Nt|~5?XXjG|H8}Xv*f9rYsMr_-22NpedVT zWr-2p4V%AhI`Z#nVirMFQk6wQd~dDez<;s%yryXRy#y-+1(Sk`tHIUw(}R=yax%SJ7okwE_Tb_mC`4ZnO-7(5JPIq05eevC{AXG5_d7wwa8K`Ak2&jhTBXGb0n&y1 zP}HD~x*tm^G+62&s|y;GwCxE{lfpL!f?7l^IPHj&^OgMSL{?758Gf0<8by5TB?J+2 z19 zlp|^jJ9W=R=c9ik_memUT2zVRN9+tNuv!z=YZO6HChUX3oWZtjj2J2KG%2Lr1Fka% zipxop5Xte)S?|!c7K!EY25$=*FFJ|UrZ*P0G}^NcuG;xQx7A3wEr1aj74r4j#vzkx z)6iSzd&Vr<&W&WpZxUHzE4==~=gwrPX0c)IE>KN7%vKbUc8=31mqc(J!7eg~=+IIs zZp0r;EFVi`R7&H2Gz!NE2Cj-AN%E0)lz*uzg+@mQveyvPK6|x9cK*w?Gl8LIut4kj zDWrl7=|s5$F)ua2%7?K=iI= zF{l&U?56)Bj&uR;Cw3ZCAg;I)iL?t!`HgMe1o`7bkO1R}Q5hyt+;|FTa_r0Tq+b*bK;6w7&GgZ zu98Oh78RI~BqhHoE^*NHHPYgAFw$IYm09NwZ5i_arb6~)RSFz=M4aj+_JXG*K}mzJ z%#x9pmMX|HuB^T`*=UrL;$uiMxx5dCU4E!$Vc%w8UoK(KjUd96gvPaTgY!Lee?)34lbNm8T3gg&rMGb3zItbR22J zf6$nt#--hnX$U2%@->Ay#iofUfihX63U!CcPZkVJurrUNcxb|>q+!V0RmZ1#t@Btr zRR!P;D@h9s3LS1H(kLWVh8%$G1e*rdn$}elsHegkLLq`So?_+xa;d*Gk*|Y4G@s_D z=ChSR!j4D~_g5Bg7nGN!7cdB#&EMUpmkDGUQIUH&1Z5*oQdV5eT3%P;`lCp)iW425 z$|xV4H>M6sWr2@y(tu%4AibqKrjdFa=5%skX z?7KwM{@o5}pXFJ?UPo4Fl=YZqv`471dBAj5Pk z|6sa9ENK$#E(%pJ&2mv_pK)X}990lJi*a*gERI;I->(3Pu+2N+ zi6IRT!82vTm+XPw?>11pLyAQ@!zOJW>A;t z6|P}6HC#nVB(i8<{@x;_a@dvyMG=7b7I-B>g^qy{(K(>luhy}91bV}W=I6p#zXR?f zf@$wdAz=|I{xg9=11+7kaKE%^MDOV3<8i`u>A~AM;{on`kda$xdj0t)HI9)~)o*TF zkaRYKGpl$`?ZGn1AQkeFO;gZH$2JG3d;<=l5T)d@>~=(0}|3j-C8enBD0x zug_R+T&Gwirv5lLxQep*IEOQY+pCj zfxrlDOk`jcgWH3 z!ttjY&?WhWHD_E!&r?-riz+H@vLMY(w#@(Wz+aUr;QhND*hqrJsEIb;jlKB_rIWr< zYA#o0;cL{ZIQ`Z+VGKaM6XSh(uM|+K)=Py6e3Ij zyk|p)DeKf5NGjKC;s5BI=G)YZ+>6apy!akp31-P<+fd6)yxt>$J1FA3C^t9=Na8o- z$7w@3TW1GX_ zpggrlbrJ=&0|qIUA&r^uhAaJ&^WR08&9%LnABT6jd3 zVvjyW5$Ql%;ft?C@v*DJC|{rtR|`9fZ6rp3BEXmja`HlFOJY>)Ig(QiPX!`%4U`g= zB6&y#mHHHpBoyU?VipeX{oPLPXP`{w6h|peWs!6&@a@1kHm& ziVr%WfFTyWDGb{uUP$UeDK$q9xy9eo&W-|P`@EWj#%)6t!$MWdZi6+~f=0hl(2^i!8okpm`?DuvfV*c`%onAwCEJ|?g z+P=;}_%W!MzVDx_!W$yVxhOm8j2uSl!GSm;NKq5E0?KHQT&}oi5+^Dk+(eV>Is7A- zuiiM;)H>FPxjn5}7P7#&Uu3 zftt=*ME*o%9t}7p<{-0wF@@Q9nRc+U`#UmDU7TekrQVtHl8opKbryb?0GjcI!3E9H zW%zD8vJGSv8pC4fM8`G`YBjxmq(lZKjjq{BxXS!coKVC>Swuw z8b2zhm%2WDr9o#Y-*%K0bO3i6h71UTf-(QC#8D0>vJ`}#ym}Eu+ZHfbfKS; z6EtK>Wn`ICxN&p{dXvg}0$s1U6jj(U;wJ>QP9w2C^nZURRvhg?FQ}p6l^=-R+Jv;| z#ONcT_)un2YZ%1LMVaNN$g`@5*pc)yf(!GUFj~fE*%BJF6tKkr9zgd;B`@JB=nW{z z26S!}>DV$fC&80PrqB&hiIbs6b{TT9qn&NuSD=|COg zG1`1hc6eWmj7m%zaPxT+#bm{8;%(<7IqD=VumD+{?kGth?Y+j<4C#pC!XiP90T?mw z5m54>&G~bqbQK0_XtG#F+ue+=<4Zb5Z~6dI4G&vB$F9r`_%o%k{9 z(xV|_DZ4OQLZqz_2_g;2*FcHpzr-8E#94Nuk9K0RinkJ_L_9D_&k;Lw$twOlXG+>R zx#Qvgp`0=TFS*WnG!~jSvc;^FhkD@pIm8c&#u;yl&U9sUC->?O?X7k43RGB4v4xw5 z4%~I>2JH~?rFBTW` zR}u9B(Q_O*BhkDo1e*=Gzm1aaxf0$}l)`wO;JBvpjq}G6mHX_W)e<6`E8~m-RGoj< zS8CbXyN7e1k{}ZHL)>5!m7$nK zgDOYDe;+l8BBe>f+a|j4Tcl#Lp7Vmn>PuT4CPd+jauDW);OCI_hXUju9YFNV0~Gh` z%uO_>f0r7NU#27j5d#DHLoC3jA2tZMjVY%`$%&SgXXr{r{!T3kBkp(COG?~36R2uT z)%);^Mz#4FiP-@m)|DJ{fQbKbm>U`3{ z9K7WS4&$nA()kaM;xq7fjkZ@hf-btBlDJ1pzQfATwu(4rC&l_t9;o2EG!x5k>=zAL zCn9DMx5|{yAg;uz(IK0IU$Ig0Ur@}crowe{REx=0$UZMW46(<6nZ4g!dSYT@A|BFB z8epx31PZd7O~;k+pxbx!ml}OOXuM?00+3$C;2O!T*#Fz84h^^cI>tWk=n*C%nLjWM z+?1?Yx1&HpS_3tHXI(qNHmh@SyMmi+W#9Pab zrkp-Myviw@@?2InGVOK3VeETi!BH_6VPclz`nKhW+R3VkLlTm%vU#m13D5=dv~ft@@l1L&bb zS1#b`vb-Q=cZ`+iLc%zmIeeu!#xf*9G}fxa`261$WrdPi`a^K#qG6d9)KV`L!9ure zybKC-*f;dDq`yFT(u}=m`-wkU+*c{vw^Fwiz~qGTG;ai7`^1pua*xQ}wQ=3mM1`ut zDe}Hewv@73irB$q9<|PE5NN%)B;VE#Nwie=M_*+6u|_HI!cE0g3OOM1&*j$c!4nyB zqN`u>!iT_x`pGc<6#QWm@e-45hb$64cZ}KTFj6UX?~1nBOCF#m$PO`+cGrx4o`>@a za->vQep7%NuL|}u#q-eQDI9+1rG`?GONo(RsvseElTIE4#1X_G6bI+7<_FD7$!h9z zL~k8l9#d_+G{Xo;PX=n<7VfQVi?<+NkUc7j-=hKGWhxRESouJw*YTzVaL&3V=rc(W z`H9CmQa3S`f#N`tE{fS}tXom-`|43&CkA8zp_XnE*;MTV=Vy{+x6)Fo z@GwDqSIgw)cLQ3pOr)iCS*b4V)dX7g{y5}P<;1qL2nLLDnXMJEMV?k-Imkq~H>GPP zAK0t}uBodWoL==`sy7@584>nR@tM;yZP?5(N=E^(*qPB>b1O5WwdXy2F{xARc$5^m zlggIL1v8(AOz1paGCRuy4a;q#w_;f1|MW7f%g<;%xU9Tr)C9UW+X_d#!~xsJJQN%P z6d>KS>H`N{M+>ZcC`Gk99$u95offSrJ1Mj8sBXV8&V&CjPMLY$zxvTRiS^HX4`d#s zb>943g9GJ_RJX|6VoC&sBOKP3MZlnOj6lkPu9fjN=#|r{y}CD)tTz~Orh{rM<1TsV z5#ibvX*Q?=^)myn;=c1lAVLjOZuCX%w&Uc>_vm}I00$kWltmMosl!`d<{~&Tk z(g#qW(ot*@*U!rgA_fKf2c=wYYs)ELfB8mm(l_JUP>xJ zpsbWQ`5tg^LPH5BBiGuuzs$C^lH3TZ*l3SML-8p%9se@)w^Ut^j3$fQ!jY111msOT z?q8<7hF%=eLTojoG^E0=zw2k52~_%`LHX}`VD3quDGaD93M35G88#@zU_U;` zsF9?Vt&(N27&WL$n2eH(=c~kDB|B&P%Sg{D1xkw@C7~C~QQ%QKrm1?=M*N}0MSClt24=tsV0UL|+12T-cC#~}GAC}zJ^nV^JC^)TTpccuGsjd@n4Ych8>0MTYzlP>r;VHpz>#~CG&ZNU}^ z7VG4`j^7FR)wI+SKNx4%_*vu;3_?)k#8NtYB`rdX94(hofkpU;5;6Mqvh9jgdp{Cx~d;L}Y?UvRehc(3O&PdOw9OSFI-r3)UA{hBYN- zcdtPyHkOzR5mEBTz%K!RPAcIbnMwH)#^8RlRxygxLu(FI{1o;B)(`cei-^3VSsQ7D z?IV-#MYKQd;GNZ(q&y7oD0Gg?|A1(|PSzOl`(9W{lfy+7Rf~{8F74pb; z_RZ>w$^ij@l_=kG8WANHx7<*M6wP1)Gj+><_s1d@v7y+=k_$xR$r-AR>$0*b9i3_r zVmp)O4J9hJXws0}I?I6pb*nzr zeVAXodR=v->enyZP9BOHETc;&xAe*+uH{!~ zD5pZn!yq{pa}qYUPoU|`S;{*I4I=DWL@0!WMpd=ndlGUBs+rRWL~KEn0v)?i(Oeq; zQJAY_fC)I5xR0tVEKjLNWv5ac^b3;8kIrwMZu~K1arn_VnMFq+ldZJ0)1-`#eDS>Z( zBKmw+bjc{^cr){qB&CMC)_i%FnY1`}&ODm}gZPcmU;^56*07BQRaZIRMj`Od&)0oijW6d~4#y)|YD9{gtsptcxNw(lBT%@p8o9+*|s6xeQZI#F676?=_)I*ZtI z$DU@9H;{{HlUF+o(>c6WGrc7w^4I^+I?TYZ!=N7sgPg~do^hV*>q3R2L@gk82$=Xj3cXdvm#X^toV5mndLPaGO z5s2_RAhH}~mn6efJ6ButcvKZi&I5EN{W{Ds`aPyA z8|1SPB20$4!!$Ue$^rKbVhaq3Es;vzgEdTf8oxq@t+sV+X%Y-Omn+ilo3@W;+n>gM z2f%yz%tkGDZb6FwzVV57$06@xoygd~3udm9Oc6Kfs`q-W0i z80Soh1;;%!cIUnUUJpTxG?b8u9o@yVbw&9vyaZ1X8p)r7>D4m}I{op9cQd+O7IcPm z>^Do>8{aNt0U0;8=1Jw!^Xn1SM8I}%^3tIAm;E;t9YTSpZDY`sV&tZ?c5j-DqT6_Q zUiji$(yAt=;#=8;4=CG&7rxTBYACm6)VV)?jntYU)bJ{sRZW@(U!$#|rK1-gv3mSG zz7SlKPm2dzFZ>5gVJ2pT)cl{z3y^6EYWAbslwD0LR^@)aF8=_ouf&4)q&lfeCdQ{} zeegBheZASpUru6M+2!*a@waz+dOTl`-;b})LT6uleVu&Vew;nsy`4T@F=L!x?Y~}6 zHb!F+2N7rG%fBzEbrF8#{W=U*-_PTAmCTBh6YK+yUh)rTS3JN{QF%j%yq!hOPo|dl zwyw4|+~j4y=BiE2M!OZLmVWZf@mC=QQU$rH=~dk7o0>*68Z#?=rwgecCkI0Lae>($qS}_-_Li2< zhY{%2tNm~sP+L)64*pZGY8`@bTOXqjQ9KRsL}>oy@Ra?>o}MPic=FuHMg6O7lO6u$ zCI2K;{CRrI$8FloeSB}Tm5~jmyXzG8VH`V^l0>YhMVEZ>G zDy>oW8UCU-v)(WP;7Ww^KmgL1gVORq78x)*2J$-+|M(vv^)=Qa!2oF|Azf><8a3uS zdi(xYP8zZ)k&n7LaS?@egxBk2V;C!^78a!On~^&8MW9c4Q}zRL>`$;uXMl+^4joRb zXJmKPp!v4C)e(wVJpi#dMmi+VqH_=E9TLH>%*wYJzlWcjpV!x`UF@pewe3xA;4sav z_P7RZAB`@=G4DGUJlf$JwEgv6Sq(NZ+wv33#H9H_s%g1>dr)ujZI=d>i}5bv1u_4_ zKa0;dw!zkreYjC@54IT`SZ{<$7@PA6^;CA>6u6w~#3jYG4%wo7zuBJfi#bMqe|3H? z?>`vRd>Fah1#J-f+d-}9SEq^GZ$)r)w%#7XLWaZ;_g(ml6bcA;w1MvhFh*JC7`LlZ zRx>FqQDEyu$Zw50AJ@uB8$!9QCeNUWeTv~5%o_EO8SrkuOwgN~0sdSE*JX;{My$LP zxl{v53yai%T3TTEbr5j)OLF=_QFC{VLe*Su@7ck9_t(B6f4e_^^nrL|e>IL_E(Nt4 z%E89^^Wdk80_30CU$2%Q4}Laxczs|Z4n{YA9cw2ar;ps1p9e`GsShf(h{)OAn;x3dP9F|Ahj_8oKd}o?rErJ-&0KzmU^N(8Dyk2a0hKnEK{8L zQxkD@Fu3@%cr`1RgG+2JgMT1o!SDwv-2wgWnIJt__jM;}ZfGaycLU51_rno#`XhwA z5mcg^GWTr&992J%K@ZBBXxe_1Kvt5#FO^gMxXzz@Kyqw?+AZ~;8t6(xG;}q8r8o@2 zq>w5|9$xU(YUWoSdOpl9FOKeL^uqNwOOkFPtz22GK>^288lVJPlst#dcVOJgxO!e;M$YnBwl6Quso??zG z9q6>gkXq|2bt^=tVQ@esp=?=w`cZ1`Z5{Sy6l8%3S3W4O;GY09G&nTeCIf9T?%I6 z6@r`@B`#kY=Ci8M>}v6~TFJ&Hv_#W8S{C4cU4jj#%JB9B!6QgLa^InC5l{uGD|p3V zkH|_iRhylgxHW_{*$`CtZfG&K47^~vx^r!7160gyj|OE7^kWv0?i808X?tsZYB4Km z=s3g~`Vs=Vfl=L>>EawXU`m`(D?t(+UO1{dK~P|PB1md_mY6AmpfW+Vp+U458OxAI zVx(#H8mRU4R<`jv7!3qQA}P9`SI|2yNCle5qVwp#loJGHk932;#QbB`>_+ zfsqWD1mfP)Ujs9m*SVI;=s%sy0Winx>Q^Z0BYiU$W07%@Rh( z_;7Pc*IwGSXN%W+nD?=z?;6>pRbnjqnkuZV!>Zu|4s?4y_JR76wQpfbzOIQhz2MpASu4s<^{H9P1H{L%N)LHcbSNtG1OV_5P;40Y`CPxK}V&Tx)0WmF5xyy>eHD4k`r&g2{L{Q3Uox!ojGHUU+IY3=;F& zdBB{2{n@0mdaakMs|=A*Bin%DZEkH%C^cM2z{Oz7^H4e<@{fqcBv;HCy|7HEAL)eH zj5xM%P&>BBvq5ZF{d1Tb6z zp(t@n9d>4OUDlX)!aal(H85{Z7M?^PP51w^A5M~tv9*Rn8Ad@}oe8`e-%id|3n#jfmdBNhJ9~$EK0}s2{cl}8vANAiivlwTEbZzNSrHx(|(aht0-F42kR5 zCF?4{fG*3zT+g^Fv8u*wnj8Bb6G6*u4AN*sm`-S>fEFvdgSA-X#i+a9H;s+bI!+EV z_i&!3Izzuv(|xvzaLV^0gJ)BC?%Xt@dkNifZ4N-<4QxO((wcSZKcV^$^{lsOoCZ{> z!vS4J)84CJx@E?8!t{1efS5bNW>$G>eho_zA4~3D;eO;2hEMz+Iwa;iNTajFNiqGH zfVKe?OLOx`eZ#QlVJH+if8Bso>^FJ55rp#kC7*O~T_>@lQrlDsw_3i@2W-XM?j^cr zL+yp@S!vvbNNU13tmdyt4l!#3qtnkO-_Kf|ZHWRQYlH6aCG{y2 z(8{nx3ikE=>WDfbMywZ5LUbLK=1tt*;=c>Qz8LaX9VEsrL;xPt)^!YUN|cTisHL@% z1epyD|04)P>`}aZ`|u-U_Lso(k8!~8?)*idRXH&|vZhv@g|`%D6t275DNK2*o)uUN zmMAaL&l{~VG!rj`X_l~WLpr3(Ce%W!9mM!V(JacRObtRb`YSE7;XenyEl}7`ZZ3Y9 ztWkD8*M6KY=bTf;-5Ly}m<$_Ef8jEa1j1*yFP7mJUHj>~a|9U%xa2i#)=t={8ACh@ zChygm@tQ*uKF(Nlj<;pP_rK?o{!yrR+I#rHO_x*b(4$b-LS2mA&w2OfUpxJ!)v-U; zk8bKD8(onJ3_t5Q%AI*5^2%05PQV$P6`!@1RK8NtagsCWL}#pvbZE~yYN-%L)O#JP zy0u6sS^I3QTH~AHyVW4@$Pee9QhPZOdx2BwDx~ zsLD;ISrL#KiwjDz(fv>)*6-@<@3hm}dYplirh!CNm*K__77OeOQ}UW}q^k;)GH=Qu{HcL72mZH1__OMg`<}JlYR81DveYDrT%dUc~d?j=c%F;@U`?im8F|#l>xg~fPH9<+nx@7=dcg!SE}q^ zT5{Lt-b>{>?2uQY2#c7Z=nFEd%GRO@;f(3WurD(1q~l8!`f1nXPPbEwWc+Ivj)x>? zbveE6$;j>bg_Iy|u0NVIr}c4nkDDu8xwYhKl}MaASU^uG!Jkofb^`2(P{4aPxY@H3 zc7@+HeUAN2?o@rZ&n)?Sd^tNBbva`e1M{2d_bt*8#zXwp;5cN>nZ{8^#*LWf7{BPf z%Jx)te5irbBF9AU8ch2{CgB(dfUsS2knz> zXlYU!ja%vGv%5(exccduXvf{m11Nujc)3~w>O9m*Yxcanjrs*vs(bg1a@1~4Ygv)F zYJ1N6@oHjd?8@H5$6^Ri<%!v3W}j|KASr~s;`fm*RxN2Y1lmH4+7jT;Yy4Z!)o?em zhWujsDtQDog>{Kt{{A1%=0K3j|nF6zMf=I(=8pTto3)w;xzZ18Mz` zyL7o(w}r`DT2!|N+iPG9I0D(s!e?mND1SLaHa{r$n;hF`K`J$3YM)FThIcv(j4a>nRsc>9=N&7y z^+65lIp56pO$p!?V93MJY!3$U;r2@va66IUW{EngTsB3#Xy%&8t0isH!I9#SPB@sU z;_hX{x?LfrNZRQ&ZhAR!CzEFFgmp&Lv7k2+RxTIrs@%}CqK%{tu~L3Vud+)&ow%VC zPsW7SrdZ6V$Q7DTnxb)1@LEsy7(dJ9jL7usZjD6!yJ(l51ROwrHo0bJphJN3*I`-d zX}8v&Y!on}+vYW$731#ZklFFtjqw7zl(WwB9mHHESC*r8Jek33kT{W&Cl67nLC~5$ zyL{cqAnDUB4v)!#;=Y-Yj^@H_r#8CkeloNB;z~UWCC%YPq>Tt+SQm^jZcuCNA? zQ(DY6%fBt?jP~BOZ4tD@G@7C^KL_(z5tEnkW2P zPPTt-S;Z?u&h86$XCAXxOSf{Se!Y!z7!6m`z>#(*P|a?>S_t(XmV7s2wd4=K-lw{& z&n+;;kx(MZ+a-)}m=MDQXN<@8!bko3N5_l@VdO=lGdQeo?tv&zF}17fYJG zMx&nI*5}GNm#}%YZu09U(Zkwp`|M1qF9cgb z?Opm*)C({|P2Gu=JsFG%Y*+Vm`NM$A)5Jo8sXVmhJ|Pjuiz&)c-FDUmb1k`TuvqW_ zK}Whi*1#)^Kofk8>WxLsN1J-g+Q`oI7>!R}Cb7!88n5rhL{*u!I%r2+>o{GO>SjJo zGx6QSxjNNK^Jb0$)A%6D*fJI7sQU6<6-`Sv^TwZ))fi6QBg!?-nMaWlzL__W5XPG~ zD7QO-%C%Ua8X5r{#2>zzx+CKj8{YF2yb2BPqj$lILMfChL*%Y}E%&=t<1dblw1BKE zB8E$vi9(!eLN@6E>wR?)gA% z2-38%(21u}V0jyj2CBCsJNT*_R-}7vzG?L6QK`C-O;=wE+dkmAJDkE~B?Y~yZrMA4 zo5wGGtIY_qKSw$zZh0fWM^I^p4%IPS0%#B*<1@La>|#h%aAO9^Vy1wz36-BtSgKCr z##pM*2dsOTd&!Piz2yM2DbUhdCvYFx%px;q7m1wER>(h4ZODwR)(FvE1F-qGXNqG* z9LzTT9)AV9Ef@VO}E$ZxW9P{o0r zrv07yprL&PvgOhrCEayv?36{0;y$JeXJLPc7;uk>8%R-+KVQ#q3A-gGV8!L8Aevp$rG(|LP#K4>_N@B4XQUaX&at(`?~ibIb-8vy?nzDfWT z0Ku`0CkCk?BB(3SSl1y0Xso2Z5$gNx4(F%G1mRyelHa*i12r_u?o%_#yra(1!y1iu zz83k*0%I9g<`rcvFjTB4pmJ<<$&OPAn(PS%fiDGHQ~=ONf-O?|95Ue5G6m3sfe_s+ zBtB5!ndl3~WD3_vQ9okE2TaV?+o*eL=*=tM6Fe&b5cTGA*?ea>T#&Qh^`ga20pX=5 z$F|t*DgH3uA2nVD4(^PYZHWJp$RoFDWrp@!f73muN{1aZj|=SPe-bsn?TSMEOK$t<|C_oe{r{rw#W&F&{V(bsP=km{^c590tnRZ- zmQ&})BeeHh>&AuXbG^FVaploCbVee&MtxQ_-N|HSB6{!7v!}JLapsk?2d7R|%J6vP z(fRO>T(9R_>K(^NzB<~)nQJrOOi!nFH*EtxdL(>ib#Jm_dSVui3A&x{SWj=aMs@4j z4;`O&w4b(Vd+!_BE66uRbw;$I;jq1hGcvw`!%kby<*R!$6$)w`g5ytn`#91!? z4@>?^#<-)Wjq;HoM_iyyUop*2Kk4{D=)WMslj%7)7QhtN|^7iE*iE* z8tsqA4tKSxfRz0wZ$;zM-YUAt+*x10X1bD8JGI3p6FBp(ES4%mIpkE8t{%FU#mO_* za|3GRKuSK#*A8#D&x^MU2%Mr0f8e>xpgKq+qdE+`>y(YNEBE#{Pvw(Cr_AY?AG_x7 zgZy@r8kWV@N_YXY@gr;1BSv75mWSA3YZsqxO`PNB#3O$B@NBkvaSqxhGHbW^$V(s1$tE84Zu;1{eU0h|BEbade~SVC15L z{9vlTCDVG-o!ki^%ssI!xc58BT)bIJ=fO6)U7Jl>)RyK78TmWcD@-gjggKcG!5%Y) zNRkxvvqwD4N0ot1YkJ+%vfx88Kra4Wx%bX%LbWO+OlhjG=9_(5KRGpIW%oYvB|S?K zN-u$&tw`r6sX`-E802TlU#acr3v!eP=6I4Icui!sFu)5v47qw+n_L4>Q>Goe$4vC4 z!&gK5YJJ(@57dzv!Nt7Guk8g49%3qvlM_0qR>s!}X_6)2FD3MS*ifl6n-HZ%%|(O}Ca4 z7%kn~WB&PFkj?oFQ*Y8wVHSr<9l1gwt;2S{P*G^zMThg^{C-b5jO=nJBEIIX#HN@$ zjT)^Y3Oy5xCHlr(uF6?XrF)5Is`cvxg0=>!vPw(5uzyiie~4#8o8}Gv)s+C#H?^^1Ixv^gTyprujFj$<%XDjjR(Pnj|#`3ZVj8I@=YdeLU>&(RbuV^BWfYUg&Z+@&PYcC^eH`pNGCsWMrg*&zog9PwjIe; z(qWC$F&8Ql-7@NPbP4OlXpI8sQ6+P09qd z-D<8uKrn{%Q*u&xPXgDn+W&k77-$*2`T!v5YpI+T?y7D>VAPbRFO(FKL8@eR|AIh8 z^YC(6QB|4WjOxj68flg(rR-w&g2p5n7gZV2+WtEDNu)&$>%2K7xtKiK=Am^OHU*3R z!qzPp;HY3U`Q#MGgMo;nIKN;w2~mYY6TcKxG)o>8tvK@4Cg(~Gs#Zg)j_4F0ch$7* zec-1Q#|DrijeOw208i<^`=Nbn@Qi5MJj+@g=dr@usQ(C20hfh6je@~6Dq5RniYu_- zVWYuor4EouJNs+kJFynEyz{mf9CNo9)FX$%&uN9-HeG%{Q!vU}qmW4{2EnYVMeMps1GyUyYLNB+ z1}97W=NV_!abBRV5~32_I({LrXof_~T4?vRm@Pc0+Ki}MVv~G0*v?{BdHYPANM%=W zR`o*y!zL#D!zS~6QmE*CWfCf}!r!R7Oe)&*4g9lcuuaMi8qh)&`{_s-mNiToOt{t` z3^t6*q+k_voz#EVuj^8zVU%@}>U5paU^%6n*XXaOS_;l-ct~Og zD~!{ilaej#m^5S+!?k>rhXCmhNW-D%CS6M9xA0fNCITm;leWY@0@bPqNzVJPy*_n8 zsb@mfF=^D}MuFc(gXvHWQXGwUp%*jF8!)Og;L?25$ubg>fkQD$m207iei_iFM5?0Dg1&}PvIIHO|lH8 z0)FyzsN0g3(NeL)7)7XWWMN3;7IR8SL%|cXWJI|Y%c$L9FsbHAbOeDBY`e}?dfedf z_t&pM){YTVwhE3sM&` zZ;8_(_t*?~8OSC#42~TckLs|usf(Pgycf`n!?veNj4G3?WN>+d)JbsK$AM6%Q8*)f4svQtZVm~DEW!35ho%Rs4i#l zykd2QArVKkD=)-KS{2ivaWawZ1gcXAtCDG6g=3-MHn28LiM17FJ(3FF1C&`Vgg#i| z_EJIPeM@r)#NgUOG5&PQ(ujyX)|ID%6@#KlXgJkT5V)q>e7ZZgg0jW7yfP+Dp0&|e z99aRCv7W&+vLfo$J91QrniXO_n#y`$4Aoy+GNRs&(z1rMvW7vCNn*6UK?C$oYuh9sZ@Fex6{F6-!d_wyAV;Zu}5i9jjOt-Lihn?o-R?T zzmlo#+_2@OfZkWMKt-cDT(0Pc`^L%}j4IkX_=ibGV-2@@@j2w0h7`CsS*!mdznY?l zFsB6_hS~-7zP8@9xhKN48*{snw5MA;O@bzme_q{$N{&-}5s&9!WB+Ci-S4J~c@34% z#?Rbqc^jiQ*J4B3Lebld$EF7TT*OKgp7oqfQLEU(uj4Mvsut}&$d)S6qlyXjET)9_ zbmV1xr%~}+w}WzxIbHNF`u-~W-s`Nya@2r)=2g{WU7GZ_O)%=h{XBMWk>Gq zsUMmc4Z2@W%+s0M;>Fwk^{d~E@7J}K+SEbq&Kti%%*1xG_&e~^^}lk*#A*jxMI^$b z)5ol?ERGw=@|uP#&yLMDe+m-9 z9$;{t4S8+HE+FJEoaD|R7lEOcJkXKVbi0e=Utp7mMVh1S!sGp6dLYI;58FXHg>@hp zS|6rznODYMiLD<{FcVZKvK}f#W^f>7Q0Cjn-GR=jV6GghCK#(xj+P8+AC!M5n^!;GVZoy_w!7KJk`!oQEc#7$q45ML7rI z$^%nxx!EJ9KHGLSZI6H9hM}U}%SZos=iyi&EBT4nqaC2z9ZK{lL{ai;dYb6O**bCL z;8u<>+NK3Znf-P6L;DP2M4HjnHJL5opgJvGy|S(m)qg2_e80CEurytXoq%;f*#Njc z6pQ|zE%%-!xJE48s@N1-oB?>&`*|Dg-@a6Q%Te4U9*_$k6I=#3@8tzQpDn{vY2-h8 zMYv{MF68zz2x_?0SUPZm&sM1v9v-!i<#cvM&W2g{SmW&Ru-qK2i4U-(rr^iwgyb?2 zz`#fH*gF5SoP)_FMmcmj7>6&NiB0|0L+S5(c?=xw5UMD@GnEQHngetQ9^A1UdySdF zhhPLs2kt2AvM`-($?j@EV3&IFU#@ULdJTYuHyAULj&E$)gU!awJ zoFjN@28p^FL_uwR$vS$i&pF4Xt-YHpcw)w`nCrc7r-2}QfP8ziQ@E8|5 z@{fgEBmkZXzZ{YgiA$(>3uh&DPA1b-p5i%*0w3Jy3Vj{22?hP4KafE@93-` z2G`aOBy+T&H)1{DTe|db_opR$2_TZmQKNXyGsgG#OM4i0s=dTc~$Yl zt$bn(p@F!s{Y&Veo@M%L4Ippj)311(qY(yl@E!Br_*M}qN#Oe-R`*bm?H12T-a+vJ zj<8;ExJ};sbtu$$bh49cfNY>s_@1@hUD^(tr*wG^&#LBJdoxGQgJVuMw5UYZ>AOZ6 z%XL-y_oT=SI8QptRfE;Fe_tej!C0Y)U)X#3p&t#fTt9Ih>ROglueDY`vpm#ysvyu_ zxTDgwM$nI;757`#oF>R|6qVS8k-)ClQj?T026KI1nuO}x>61u9O>}+y#AehV_7Vq~ zu1k@$iBH)>0<-GIGmU?ddmpFI?(Nw^$$GX z_8o6gC-tWf5|185nV;4j=1U#F!2#ahuMrImQX9q+7VO0+(`atn98F>NL){J_rkA)x zD;my-hxH!|KHq{?S4&-`Kk{NV6)|#pKaX3(JMgPzumNgoFgaoa0{eNljwrgkFx#a$ z-!{v`hh+)B5~i8Rve3CWDvnMKCQf2o=|%6wzq*BG6V-P2&rK#MydPwVgHlVZxkT^G zw)_UG8{UrF72h^~r)=NHUcKhc(L3G$)RwNz&AYn!F-i@-F+9acd~VeCV>@34Geh5m zV+kl%uHX$9Y%2Z2_#e$P_p`r){)oeS^Reh=&_7pvwdclp5cs)wjoJE05oO#ZDEIorhK5F-d3U+G!+G%VXj?Dlp2N)o z#)gVTl4ZxP#>@c5lFymXOJ3~j4iBH77G=wyh!7paMx$y45M^5;to#A${tC21X9)FjL*?xHm2OZK{XhJ6*E(;F*Sa@39yt=K9 zO1~^-M=}U~98M~2YfhyObD-R$F}>+UD{j7dvi45V2{ik3QnYLs^1AlkbLr{gB4gb6 zXjao>v$JhHoyQtpHe|`##q9Y0vt9K1(VR{lU3D5e-U$8Vuo*iuZqlZWRFsKUoB3L? z2v?*U%!&v{`ww)lZt54hC$$Q{`U~Boh3>b9yuCo%<|f|99paZ;eK>QBtV8H+qibn= znf9`EzuG=p!O|n2iaMF(XY+SsgxhJOG<|S;SaZJcHRPzbiXOSym;V;HL6<|Y%;-c% z>yTW`RgK)zVNc$zqQ^?u)}t%g*vB3VHy?=I+&FZ0TJR7LgjcFsZFYYq{CG8HF8<&QIAQu6leX>0OHN$rqvZAQr)`DF zND-o z;{{u)a8pTF3p&_~D1vnxSD!kU)H(iJ+e|>0bvYYJfx}@FZKeGZhzA z?PV)qk8gQ+P2#GPJ&S%Kuw-!;VUA}#1zt5f;&ciEc2?J0O0H@aF7yU6_t}fI924GI zTpUk-_PLT57A!kG_WK$s-YQ(URGy=a|G~x(({e{OMGdVn6k{)W53|nA=tX33v-9`d zZudYYzVvpvfBR;>_8k(Xg~4zES6aI7*b7Trn%f=B5iw$JSg3KIEM0b5_PbwzIBnT2 zcbk5;5ASG}k_^Q<_&&lQAeN9vATlJx$LPQt@x%5f&Qvpr+34OSdsK^(`coI)v~x4H zLg=d)(M_n_9e1Ywt;}aVv9xD~9H0mzf+y*6rHA#oHU3!Q9qd=X>s)Y)S(dU}=B--% zQePhA8Hrg;J6;N-HM49~Y`8ksqbqpfr&g7+E5mF`BB!11uIVf%g?682*Un(`PlMUS zuFpqpEq{ca0`kL8n!;xI2yv4jT;^Jx&Y)NmQy<^0Ms)c~Y`!f|`lM#)=cm!c2z!fN zZG-^Bj}^9rC=kxUSuikH^GRS>X3;J*Li)JlP$LuClNf`i50o(n!@v;X6VpMAG(xX9 z?g4|Z@;?+3y6UoxR(0!g@6g<;8L&6^)L}wZJPm#B*Ko!Fpm)%Lex;sM>?pd*u#|nt zma;mOLA#$@Y4JT%ax_hX!`6Bd0{{ptBn*(WLs7~Q^fY5^u}RWX`1Au0qXQ`5^!I>g zkmBR^p9k$Tha-5i1{^^LIN=gY0#kZ5^LTFV_v>Pa_VM!m35}yCWL^LLo#k2(0bgM%@bY)a&xSJKng6IWNU6 zV)m?715nQ?xCS^RwI6^;1c8Hc6`!_0L{2(h6UbtfMMB_R*XgK_6=$$X*-(1IHj3*tgsFEWme5A;;p?l(A;HVJ;r7usi}=J1V5E;BpwYEjjrqNcbs zPd|hwi;Z8nNo40Ay9ef+SD1$@L(vxAZBog*_!Hl3w{5Yj;AF-K7{oG;%prphW(kOz z$Tob{&3MqmpByKuX3U?*h3_d}X;B6sltkAK2FYQyQktm_HtLy`n6tA9e?SqwzX%Fo z7M^=ZDdyvdH_AOgOKFi|Y7|1gu~d-JODi`p*aJU#E^H*-oVOtJ?z-hkrUQ_O-IDXX zrao!J9|BICq^*V4jVBZEY5p6hCp6&Chkfycg*7uI#n=Q)x+JyEb8kXRroq6`=*MJb zzdg~=wlAd{xWJ9$wtQwLDarwRNj8}-W#q%BH9El^x<2>f1;o;0T9#p=hr$6b9NF; zIAi*glw=V^)Xg0)`o}ghG+8ZMB#L%pfy}+NX9$fum^#{#L&9{a+0M7O*#fhIssUQ4 zMNa*hMoMlsmmk~PFiXEzNt!Hu(qdz?$gDjk$?ku1i4Rz^=i3;s#OSf)qb+ggY@a71D zQ05DR0Fr!k>>e!jw;)*Bi6|Ru9w40tQ*TvCh?OD*ABQGJtGUs4C-mKoJiZMI$kNOJ zD0QmA5KP}7f^N}N7eryDM|>?4pX$g;-y_dtejmv`I|P8E4zM=4^%vL9?H(XVl4g)$ zEYRTt!s`As~J)w z6Hx2ulSjX4)$U7Vx3m)24F3t^$K&xvDtj$|WAm^uOMR;+3q^~+Ma+r+PF+>j(XgTA zQmBP`Jv!@n{O;?5JIC&4|M*OZI5s!^Ww*OR=wEJ0mnv?0b<`09)RjNi*FglrK!qce z`)jYUtv4BGW3#TZ;(VyU*oN3fi7yP3nN?8irmB4GhhmlDQ3ZE;9uCWHA_P2e^!KQG z3T~AY5!Z|bfx#+{(*@r3V~q`97boUd);Q6_GBA~qa9CZ9qQt33_b?@rg-=OpN>mfB|EoPyi+~)k!Z> zBbCpj=}e_TL(;Uki1f3K&t~!JmQ0LVh6@L#Gce`pwhtZ@#Y-hhvqBoJXLu^kLa}vh zRzFnZN$1U7#*Yap2??jky=NH@kcD^B=y$=a&1vHo#OGnX z2eK~cz;r*Hs{xP>s7B<34A zEs~3YWWX596M#*VNLB37ZUAy66+1ayILxP(m`L8sF2{{XAd=EJP)VsTn}F_uIk;*d zIx4+wLg3~Pa=ZgAfUU(H8kl3nTS<$=Jp3xa_Knx#Fcvr1`&p3CaTuEueN;TB(p`Ztck{Z1XW5rWeM9#6 zhFcEhSDJdu(Vkf~-4Yhv?lG=j3~DKq$dp=Gu_*qCpyrL!JFBTh^pjWFZ+hC=20)N6 z1VC5@ctr5PL!`zR5)TDvs{lOBfAm%>1mO73-g8-@Jb*%8+K(c9JGy=}kbnyCobU}{ zS>(B357k%8Hur8X-coQ+sZ=lR2V5ThsZeg&g56~A*CXR{P|y9Q9dw`EUFDxMHCZop zM?3k_idxMFd#z^|$JL76%J11>oAFgi-AcpSj`n~J$67Bp;as8Gj?~x?%9JXPd^!CG$967JHiUv68>+`dw%HoRC`l7NAc(x`mFE~?$ zxDD&T#v16sHhl zXDdx2!X!t717g4!E98My`Dzi!|0-Dx+*eR{#{F^S47{W4yd2^&NhJrzL@5JAUJH&y zyYm%Mv%HflKY|$fIazX4812sH;^y&mI0;rgP#ye|^kG1(h0NvuK>zPPd1ZyqXAn35 z05t&sfcSse4ksgX6B`3(3#0#Ep85ZM*ROwmlzw5iA%@h~ds0h)fzrKSaKzWHEvED< zqeUig12lB@R45ovoenDsMPeiQcELT_O7U)axMT+MFVFOuvp0Qh&+_}qAX6Qli+-O* z=O2i{#)3tfs6&UwKYm|#-UsVSioYyS2{g;l+|!qKsk6t!gU9J68grbqpA_2v+)UaZ zaLwFu$tfn;se!(P*4W2g2I+AH-!=mfs`lW^tZ zreSbk1g(W93b861YO*?)p@#c!hv&ahMGtVjxX=svEY*8%T)S(jLVGJt8Mim6#&BHYxsB`5T+D00}_{sEP(0d+5)@@^t^_WfD#ha2o z2}EjgnS~A)D>?oNA*0;juVYKs2fPRQ)+wz}JmL!pM0rp4+N}Z5VkCtI1n=9HwO~(lw22SM=r>+mXVzV6VK3@G07hspu<%G@8miZT1`Mm9`cc!hI>|WyU?Dq-ps0Py ztkK*t)W3;=c2jzZw8FyOv3(a6hF|ab@)RP!y=c z8CSyKczyiUO})8yM;2~NdF!r`*;lUon|0aS>)V|bn*Z}B#qY8cOSW#}+JNex6v}%X zD@)M5$GwKKm6F9b=StfbI23&((=4Cmk4H9TX040!ThL0+G|vK60>|>M znQtg1OGsRg2ec9yUvs)cVXB$Gn14Pn&RwN zt{CG$ZX?H+%pY6|W4ag=6(JZlEK|xU6;sYN)=+&yU z9{IyKQIabUq5W%7OoJNizz*!f>N>ZpB?8NqLK|wsGz*Ot`c-H3;V5zvQj-q}#|{W+ z<&8fqOUa2Ca_9y-|6nFf^1Z}K=N+R;UBWec<60jap8EM@n>iS7_0{zFa#}^6X>7Ta z4!Yxp-mWAUm(pHHSvE-NLqOpSlVGn@g`kv{=$mW{z{sSHMx{FIu40X+fkWlzw-Ey^ zV-7qgOye$EzFA*er!4Q72^YM=AQkHok&wlq_=K+s5CMckPW);KGeL$g$Gw6QW{V?z zArX%=7}6yh!$4NiwM&x|&Pz&kg^1AoLR`d=K9GpRB@aCnG#ec{{;DCadVwHi29a1w zJdVe~U{UgN0O(=pD)2>NWP>Apc!4K47w<=85fLc^^2~^h#|V3&JF!Mfj8Tf%MxPFT zwC?CdA@RqOe80pLNlFD~4h~)Eo^ng!D3%@lBO!H5NT{Oh?(lrLc1)Tv3ik`BCjr4B zyTh5rD(S}>KpONDlMxXIC=vI;f$k`R&_mjf7w&i zL1a<9)%FDnjKc`wHHE!7%X~ycSdOC$gW2i0-<;H;M?TFntt-<{*u&&&Q&2U$(mcg% z{WVkSUz`60o8hj(%S{ z3itumbBWEnT+D36Vc;h;t`2B2sww!Va8yg$1 zszb8e@*Kr10rDb6S+WIg!`T);rikh6HoW2HA~iCcz2{_zk*q3Iv=`=oSwnXCx_ZCe zQ|owEC3R?~EHz|gMF=2|6_B8rdUZ4CFj99E#BN@LA`@|8n7VR) z-=06Qncq&Jvp}flDCF4l1rvSr8oSg+H|ixQ{E-nF-fKyFPR zICqSuwUnGrU^CmGMV9D3>BCD}7jWY0v0W9Mwoo+IQcPa7oZDr~BL7JbF7H$Ua!M94 zJ1R)S&b0Xn=p;ztM)(2P+FH=xjg5dRQghl`mwAX{&ZLu?B;&l9sPME>5gG3;muzu5 znDS}J{?xg?bTrP5Pp#y0y97m!o6?$EdecZyNyH^9iSh=O7*6Eak(6QBYK?&TaU8v|3JsDi?i z5=FbnD#1W~%Zos%6arBqB=pT-jDi{gGci`jyjRC$`M)|l3#c}iu2Cn!-AZwHD_Wca z#ogTdu-FjrB71rj6Ydp&NRvn;u1ksBPZR<_?TE@Z3aC*Le|j7;)R#nLI{~F$Df?b zhmLa_Z^T%hDI$%>ztdMr`s9VFCuF5h;XqA6D*F_@e)DvV$Bw+54x>;Cnps>TlY%oN zBt_QzLKj6^bu(s@H5qejBd}?hWO_jS-n$L+Gp{W1mb;>-woxBdsb@u6h^X#2mn9p% z*gE44+s*iw&|5!n(refo%>Gdey~sT;Di-gGLaL*;Li){dY6Pe9l~yVbc@+q#Yh`1V z)GoDCfBQnwFBkn%pSACR z(B`m2F-LN0XhY{jzd(o;fzVUiW-J#y$oyO)Njpd{T$6!Z=axkK6HXju3BEz5BV;c< zsnu}v@s_X2;@UDBnEXi0I-{>>?0t2Jav-`kvBsXELlSoNxa@6ZTv%DoH7AklyKV?H z)mXh%E49guwk;$}rUd~DRT=YwQW#`w4L6pu(Y3tJI^8)6y^Bm9)X50VBT(|&g}Y0& z-dZ?|0rCOMf;dz7NYZ{zoDDyV4-T(tc`d(Kql4CrM3n=B*}R>@GpGo5c#ltxaCv21 zcpw#io0S;&JB#|N2=o5>So1%f!Pe-glTJ2iZgcNDfLB z1P7DWKAzEApdT!(D|yYr*`ZC59J#dh>DmCm3OY_S4Xzf+8|Ic7h`(nCXMyu?&^X1N zVzkPrI^tdFZYk~5!4HEBe8t{AZtg)A%H?SEzb_d#FGfYTabl;sUEL7B|nI!t-2K#X382`Wc7j& z)66>(XJvPDrK@Evf!!aYR^B@odr9EJ=$=a0(4bt1e=Q3 zrNdjbeQox5x!UC!>zG3Y#p|Df)6ip=y5gfTL>fwx3gUDbJECgegx4w;7v%`Wmv;+x zoPRplhgecTw8#MfB0mrS`*B@=ZQ*6(!Qo`%W8?J9*8jj{DS2Hsq5Vp$dVel0gJD2? zORaUmbN{eG%x<3~$0y)1^pFZ&n$QuQIJdRMfAqyuJla}XnE9Zqc#3-lx#*#0q?G?eBg-W;a9BkRdJH3OI<&YJP1M#4V}{b z)=vHw(b>d`9+7zgUaM3pU7p5wJz-)l*1OCrVj5A*h=+DcX4d0ef!>GKRxm63e5J;U zX^d^+-KBt8y>ft`z`+Pg^mWg6wC??)IF4!kinvP1o>=7BYm1P%jK$ay6B@g*+-4K(>TP(`-S<=J$b~#TQuXOJw0+k$nD$_L!oE7tcS<)3gU0$- z&5}Q=+!fA{-T?s3CHBWgxv=<0NWIZ$!vm3SlLS2<5utKTe{bk;AIcBjTe|A&>M zor&1QF4(TGaL5$=%3^Z9^6tj|81_@iK1p6S(Z}(GE9kZ>&>`Z}(%uKTtHi5`3%k3h z$3oDf?vwKY!7eMBYu zu)?%vY2o^fa)b+hJcL69$B*Eh*wqp7?z;1A9Z?>_4aJblSkfNE`MS9I{!v~ zB0OzM++SmQiBOJqDW*W~bF5kU1r{juTGuK-Aj)*db>H!}mh4x+-BlKm8v?13s~Ov^ zlvzE;{Fkc$IlaZm385!%Hh3fBLE}MQ{m>1wT$6 zM5%z=OZ(FaFM-MW{>xiHpLS<#xjBqQB!6P54|=#4BzXJ&eyR;oyaRqf;P}8t5z9z* zi2SK-BlT_IH)LaiN`&X+mU!nviM0avoj#`I3>SNSyYL3CiMG3~jg(juYdC!EXZu}H zEm+2Nyn03eaJ;yMP};ODR)>20Y1+_2h`x>L@;Tn?F7A+up{Q%9i$Z~*}z$lFba{DiBN~n?#IRxapo^4hB(Z@>z#A24K@qxnybODgpyg#cJ{2& zc!KZAhr~=Ko!Aw;trUVnIA3p-&*_h&uP7b5vB#2#*xfEqn`Iy7y{pVpHOIuEjIv)X zluZ~A)7!megbrt6KX0^EmKESdmt~LoK!GO~U$jrbh|4Hlyif5yzS>ON{LI>?VYy{` zb7R9q@M=g`n7;uQaC^ZBi|JSmBD@^;Q}eXop7N5`;@3i=ORr52r1*guH0dO*kVL2W z5F*D*UojIkuDGIc=p`LHr>+-G^{i(mZRnNLs51rDEVump_`Hp>F_P;!(WkiTQg>JC<`kG zkv~@~T&r&z(;mJGqEne2t9d?z6AJiMkQCSm8ZdSwY%Uz`o%WsjkvAld)lFSoj=O}^ z$cqJE=2V`PTvSa@^w^X5Jmux>G5unDR(-~IG}7+!phj;d&@!>tu=FMB^+cB^+pUnF zthI(u#8>E#>zh8RmEq!iN!ZM?^$Bz{ zKPH^+6k@;LJlKyLsM>KJ@1NbCEqOI(zHkY8jvYBv$+`VQE8kW4m$A=b{{;LfD?ZJU z;Ot-~zOW5$FYLT|%~L>(!HTP4EH|H8d12{YqoK)DWIH-{PneLe<;vFA2@l#%4?JF}ydp`s0 zjo2>DoJ3dugfGH=9n2iE{-{h0i;S!+ra*qLl7kU>treP(oaM4cz4n?ZhRL8#lvg?8 zj+>OX3-V%Ll2aTGxf-$%kc=CE;6s-bpjto?BSg z#l^5&&_!D$+1q@$-prX%24LZd7+d@W)HiQOwlaKqy?;S?<7_$bQh)42x80g^_GAX* znkRLwsVa`wBK^W#7KAg4+^1jPx>wZ-vOuFf8^nq>kR;(TM$>FssK%GP?q9S;N~Z3I zIK#;yFc>(mba*N~^nsx<+o4&bJf}G7hrT5&C7JB)Q1^uNY=6SMj&M}~lE8qrPr)ii z@h{Ve5zF@_Q;{}xnsnWprgIS&Cg5>fb;GgRc~YfUUne|2j1-F+!ZNbSY$OOrzaqUd z`Z(`j{7=JWt#&J2{av2Cz;J!d_Sg`YOXDUz_e5noJ64}BQX9~jEVC60_*Rd)Ti9F5 zILhLCRn$KH*ejH;%W;@bZ1#HE%X)?51Nfa}Rcv^RskJx?q3jP$a5~LJ+CN;&r z!rN##3r6|YKU$oo4oZeJBWLJZ$~V{F4I@z&IjS>ru!Xei(isL~bUxm;f|B#rA^3y+ zV>SYR=_~cdNqINivzRqRO|lFR<+ zCNOGYpl2OePM{9wBMjwp+1pvYraeuEw5$hKvM^rE2g9%5W-8?4>|XfEp70JA`(xT? zUwB;rr=FLPB;fUxO@W`r_GBGbi*5|e07p_iOmXb954;`(K8n0u#D|xZNS&b9(K_?E zhe{h0uA~YYM)ST}-zcDeF~{R00qeDQ^gEVs6+tIO1CIS ztCJQv@mJ}&BEQp20&H0$Rp{EHV#|y2{asXVEBU3_ga~O}}mF@naPTh|Jo9IFk zhnW=j7^B$yljTD9%#*CTsC)x<{eVLG^7 zclQnHHWJH#Fb?r{vb6kscF%*pQ+#f`(k_MDBiwW@W$F_PPb*yGQw*Qu>`6|kcSjN} z9Kq?DH(b)MzEwRH9&_IchG49J>HR1GtP*5lG-t+Du#tLCLc5=SNTxWlm{d-OCDPj# zLJRlRx%$xmjgpfDW&Pt4#>e+Pr3b?B!NEHBW!yt=u4i{GyAx@81(-ZJi%O=gxnGT{ zgRod8>3{Alt$T&zAtgya;lm%0Z0)MoD*$M;8^qD0KW2ub%Nr49PE34*ra1ocG8`>4 z?_dh`B^vmA*`LD^Pw5zYE90o)%j90%(76ADr&_8n50Tvs4V`-n{2M_l$w4LOzGqC- zE*b{2+S|=iMQn$9h6gTwryb8W{uj_+cTO|HHsKe;$#?nuK)gPt`Pq)6JNKhti)8Cy zx6taZ%fbAfJ{DiAqN8RtCg!)1qEZ-DC(X@GD@wg*pXKBe-h8&36#eEmRJDUJ@?*W7 z-vm*^etMeX7dm*`e=Shww2=sUFYFs;Iqg=v%dsv^_U z1n%f#ksfNl7-vF*tjz(nUdI_S}gXfbIcgf4=9Uu>*N>$*ne6J9U0FVsB zk`E05(tBTe1_NYvLQ!$M&x^F>)Iq&m9Z7(M^i71js!dVcW@jIJ9I4}X6g(aHk@<>z zAL8tKvP(Tu2b9+XrCBe86NMa;#*19Pfcq4N<2v?-P7!a00%|{g;~MuK<|^Y0Nn<2= z*WbY4?i>k`jWI8*BOs`e?#>o_(mGF!pGmax0LX5jt3>pbDC*;|lq)nCR^>uSNINq^ z`*!?7b+0Pe5uGSWbFGtikHq(B6JhqQoNuymWvbb!eTOko!u9iQ}FlQsSnFZ3gan&*HoNNmY z=}Wpkc@TLR^`{Cd@-|^Gg%L=gyUr8eXL1|7S<)lZCOk=D307n3cuv9+4nh=0QS3A~XQxwU55YewcG3B?4=uK9JNt)6*&FjJVWh>jk&OnqK|k z`l%CfX~8p+(s|X+5!(V=!ZjXTRw0@)tMGh9Lh-M_YkSj1ubC|GPk6iBaF~#tP3Ofk z1}}{lWmglhs9FZ>Isp|;EB#Y%JeqJCWHF2U=MvvOcdFPGT(1V60AH3_7r4YB2s#8i z^0-}p-kLDqXb>u8H^VYSJ}i@j&dP-~4I^p@5}fkU0yWAvPPu3m%d_wa)PeJf+I}TU zo)i>HEHEb)lW7ftL=IYeN+)J%lA57Y&vQxcLIgoLGy@GQ>G;yR`Pr+HYHH4Vafy!fIdvh#}7B@Cb+u( zOzH>5v6-=6%RHfi%*-WiomLmiR|fqcve7?jFsH4l3D=ECcpi@3RKTKk+b0MjAClS! zYk9MFW3L+Ejd>u@NYtMip#efCyjTITWIIMOO5;V>(`j4U&juUUgGMPN<0l0fjeiu` zWezkEqk~w{GM&>ab*PTVxS8aY7M)HD>$Q?$L^to3_%*nT0_#{#=D0$cSH!|`qf(ff zfW76ki-2`PysF$+=YY|q6-9Ftx6kNq8%08Td<&xUGP=guSSZ?dhqSF8!yJ(yALgg> zTv@wUp!J>clP$ibXB_Tz@60yOfa^pA;=xaSa4T_>Yx#TtcSC<$4N>re4kBeIoc6Lf zQvy}J^IjJu0S4&;C|+tNj_Z)xL_sGuv~j+&3!7=> zx?uc(&UJXYgHjkCKEUeAY2?W!HPxltR7HH=>lqbE8tqow{XrxzfW_F~JP~lU-Wv*u z+5Bz|)fk*r*i9v3Fu<$|AmWly#XELgVkc+yB*m^r4$a5l-AAS=#H9CPjY2zCvj^## zRoGBYenN5FlzTn_K>p;R33rRL3-fW4tgBn^`7(B|V0$-SLKRvT0YdK=In!+{)c= zoc(xszJ$mo^h6dLhP4Hm8szFOoX znEZ18>B$>m(WIC3LL}w2Hgj=y=9O8>RaBKdmC-D3E%MLgGwdd4^1UoTJN(ByP*vu1bY`` zq4~Qt-F{}*b9=_k{IX?R&*KifN&XWTQ{mHf|8qKnT%{&ku>F>|(rXSd1@1^0K$JS2 z5Rc;k^Cj4Qs}Uan(x`-4LY?L6^>6Zjq4r>t=!N-3s5;j?Ga3+JGb43ZtX9o>fPxB; zta1Dig5q%i>xQkh#z2JTiSDf6_^PJt0$sc2l2XLcEe00}x4-7vGU35x1*s_5`LX)% zz>`Y!N2A4(q9g4vxK-`av1n&pe%fRZgTXctTOp>B%2vhz?B2q;6~gK+U~qZ5mJlbO zI#7S!-b{Z&<5^1QU_0f!YM3p7>{j`n8UY_osFYObQJhBnCZKdVXt-%k`eksaCUK0= zroKB1rlOEy!z-3dBo9)DB7OgqWL;qR#1G@IF_^WevEMw9#uCE9f9P&1_A}z!uu};z zYi8o7y$>gNEAN$!UCoYDgHVgg2Ed83m0^z-3dhYgdK?h2#M3qJnvH&`$0HFwbTvt} z)OU+oNgGhTl_51!(TU&xYSe37Z)xIlluVu3v&<24uk1o1X}E5y!3m_PRGyZ3t->gY z-BS1th{M1qU2m+mEjkG<(UmCyYE({@h9-rfa8*8<3}NbO9iY>qlerQhmXpcOm#X#C zf>f&4`kukgErqE$lgt^{m7a}Qs9J^8!`oPJ59&aYT4TCsvc1tN1G80-F!=Ic11xd( zXjM_`WhT{HByd-Q|F#ZS<&V}uWU{En46(Xe)B(i2geQ@JcRR#FU7O*SX3&UNk}D$k zRGmm%Df#q$(Q-kI}zHJpc@#sz8;X#{8CKL=MzVT~W+Mt?ow0-L$0( zo3D#9x9}T(^W6d~Tn&vw-2?FmT^7h*XAUl znt@z`b&Fdv9Ky-I+h3&PQ%04~um$8Rw0-)N3gi7Y6m%i&tLB5vQWi#A6w1ve2G6o9 zELLv(iFBIm!*0@kN+L-PQ>Kv_d1zZ!+FRfPUysM@f}}dLv8vZM<{F{n%E!ZD{4?fZ>=Fr{KIE}j5xk_R!<^yYI#K`Hq-Dnp zxP;p~#Ao!OlGUT^<~hX)zr|e*mop?m1inCR&8<>KGFd~8>$=xP<_BZy0!bQMUE^<` z;<2-i*#ccS+B37QR}Qh{$^t$HV(P4c|0 zcBSMSHoOYer?jlQXo%m5*lpGO`TnZ@sRY0%4Ux+&imLM$v<(L!cYtlBV2=yY`+nHiDe%8sF(HttO@`ZY1Wfl78^-K`nWm8P0@l;cZ&f!SYomU;L7w}KhZ{1dm*F1$`TR_wDgYa=vT->f|b z>`kL8k#r#>ks=w|etir`oN_YksKFZj7>)kU4Oc{;gqw|d#F$1c7efRi80}q?LE{Dm zwU0m)9*a?)WnArzlknI5{?yYEywxh?p3_$>MP1$lWb_^hlgENr>01$lS{&A1<* zS-Sm6C?sn8;rr;iv@8Bk+~98l^pX2F;eYPG^zT49=MbJ*GXUU$6a-NCmk9rel8Olc zJi0YMv-GgBu(ouy@UZ?Z6tAGIl^`#-t+fp|H?NhTCAX!8ppc+|rLDEC02ddpkQukx ze<}7@%b)UHAUoMrJmyF%_67jd{~LD3>Hom~&n5kT&`H)=J+X`cfDsfBK2IRV@jt}> zFaY&W(f*9V`VIPe@f-9XNcOLYtUq~w`tASb-Q4}d`|Y3iKN-5;rT&eg{^>*b8@i4F v{Cj@ 30 then + print("pas de wifi :-(") + file.putcontents("_setup_wifi_", "toto") + print("on restart pour le setup wifi") + node.restart() + --tmr_wifi_init2:unregister() tmr_wifi_init2=nil + --wifi.setmode(wifi.SOFTAP,true) + --wifi_init_end() + end + else + wifi_init_end() + end + end) + end +end + +wifi_init() + +--[[ +file.putcontents("_setup_wifi_", "toto") +file.remove("eus_params.lua") +]]