From e383a112918db6b17567efe7af7936657b2c2628 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Fri, 30 Dec 2016 02:44:45 +0200 Subject: [PATCH] Add Server and UDP and fix WiFi.hostByName --- libraries/WiFi/src/WiFi.h | 4 + libraries/WiFi/src/WiFiGeneric.cpp | 46 ++-- libraries/WiFi/src/WiFiServer.cpp | 77 ++++++ libraries/WiFi/src/WiFiServer.h | 53 ++++ libraries/WiFi/src/WiFiUdp.cpp | 254 ++++++++++++++++++ libraries/WiFi/src/WiFiUdp.h | 77 ++++++ tools/sdk/bin/bootloader.bin | Bin 4592 -> 4688 bytes tools/sdk/include/config/sdkconfig.h | 2 + tools/sdk/include/driver/driver/gpio.h | 137 ++++------ tools/sdk/include/driver/driver/ledc.h | 6 +- tools/sdk/include/driver/driver/rmt.h | 6 +- tools/sdk/include/driver/driver/uart.h | 62 ++++- tools/sdk/include/esp32/esp_wifi.h | 8 +- tools/sdk/include/esp32/esp_wifi_types.h | 12 +- tools/sdk/include/esp32/soc/cpu.h | 10 + tools/sdk/include/ethernet/esp_eth.h | 41 ++- .../freertos/freertos/heap_regions_debug.h | 9 +- .../sdk/include/freertos/freertos/portmacro.h | 1 - tools/sdk/include/lwip/lwip/pbuf.h | 9 +- tools/sdk/include/lwip/lwipopts.h | 4 +- tools/sdk/include/lwip/port/lwipopts.h | 4 +- tools/sdk/include/spi_flash/esp_spi_flash.h | 26 +- tools/sdk/lib/libapp_update.a | Bin 31292 -> 31292 bytes tools/sdk/lib/libbootloader_support.a | Bin 96638 -> 96382 bytes tools/sdk/lib/libbt.a | Bin 7981146 -> 7981170 bytes tools/sdk/lib/libdriver.a | Bin 603880 -> 626624 bytes tools/sdk/lib/libesp32.a | Bin 433046 -> 421426 bytes tools/sdk/lib/libethernet.a | Bin 77708 -> 82840 bytes tools/sdk/lib/libexpat.a | Bin 1251596 -> 1030628 bytes tools/sdk/lib/libfreertos.a | Bin 499758 -> 500138 bytes tools/sdk/lib/libjson.a | Bin 209284 -> 183460 bytes tools/sdk/lib/liblog.a | Bin 21194 -> 21194 bytes tools/sdk/lib/liblwip.a | Bin 1632466 -> 1634662 bytes tools/sdk/lib/libmbedtls.a | Bin 3193590 -> 3193590 bytes tools/sdk/lib/libmicro-ecc.a | Bin 157620 -> 160248 bytes tools/sdk/lib/libnewlib.a | Bin 79712 -> 79748 bytes tools/sdk/lib/libnghttp.a | Bin 1537150 -> 1281422 bytes tools/sdk/lib/libnvs_flash.a | Bin 545194 -> 543400 bytes tools/sdk/lib/libopenssl.a | Bin 255072 -> 255072 bytes tools/sdk/lib/libspi_flash.a | Bin 91922 -> 91790 bytes tools/sdk/lib/libtcpip_adapter.a | Bin 68990 -> 69046 bytes tools/sdk/lib/libulp.a | Bin 21548 -> 21548 bytes tools/sdk/lib/libvfs.a | Bin 63414 -> 63414 bytes tools/sdk/lib/libwpa_supplicant.a | Bin 466236 -> 466236 bytes tools/sdk/lib/libxtensa-debug-module.a | Bin 12054 -> 12054 bytes 45 files changed, 682 insertions(+), 166 deletions(-) create mode 100644 libraries/WiFi/src/WiFiServer.cpp create mode 100644 libraries/WiFi/src/WiFiServer.h create mode 100644 libraries/WiFi/src/WiFiUdp.cpp create mode 100644 libraries/WiFi/src/WiFiUdp.h diff --git a/libraries/WiFi/src/WiFi.h b/libraries/WiFi/src/WiFi.h index d56d2b7e..53c5b2e9 100644 --- a/libraries/WiFi/src/WiFi.h +++ b/libraries/WiFi/src/WiFi.h @@ -34,6 +34,8 @@ #include "WiFiGeneric.h" #include "WiFiClient.h" +#include "WiFiServer.h" +#include "WiFiUdp.h" class WiFiClass : public WiFiGenericClass, public WiFiSTAClass, public WiFiScanClass, public WiFiAPClass { @@ -55,6 +57,8 @@ public: public: void printDiag(Print& dest); friend class WiFiClient; + friend class WiFiServer; + friend class WiFiUDP; }; extern WiFiClass WiFi; diff --git a/libraries/WiFi/src/WiFiGeneric.cpp b/libraries/WiFi/src/WiFiGeneric.cpp index a8a1a07f..a0236a20 100644 --- a/libraries/WiFi/src/WiFiGeneric.cpp +++ b/libraries/WiFi/src/WiFiGeneric.cpp @@ -304,7 +304,21 @@ bool WiFiGenericClass::enableAP(bool enable) // ------------------------------------------------ Generic Network function --------------------------------------------- // ----------------------------------------------------------------------------------------------------------------------- -void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg); +static bool _dns_busy = false; + +/** + * DNS callback + * @param name + * @param ipaddr + * @param callback_arg + */ +static void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) +{ + if(ipaddr) { + (*reinterpret_cast(callback_arg)) = ipaddr->u_addr.ip4.addr; + } + _dns_busy = false; +} /** * Resolve the given hostname to an IP address. @@ -313,36 +327,24 @@ void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *ca * @return 1 if aIPAddrString was successfully converted to an IP address, * else error code */ -static bool _dns_busy = false; - int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult) { ip_addr_t addr; aResult = static_cast(0); + + _dns_busy = true; err_t err = dns_gethostbyname(aHostname, &addr, &wifi_dns_found_callback, &aResult); - _dns_busy = err == ERR_INPROGRESS; - while(_dns_busy); - if(err == ERR_INPROGRESS && aResult) { - //found by search - } else if(err == ERR_OK && addr.u_addr.ip4.addr) { + if(err == ERR_OK && addr.u_addr.ip4.addr) { aResult = addr.u_addr.ip4.addr; + _dns_busy = false; + } else if(err == ERR_INPROGRESS) { + while(_dns_busy){ + delay(1); + } } else { + _dns_busy = false; return 0; } return 1; } -/** - * DNS callback - * @param name - * @param ipaddr - * @param callback_arg - */ -void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) -{ - if(ipaddr) { - (*reinterpret_cast(callback_arg)) = ipaddr->u_addr.ip4.addr; - } - _dns_busy = false; -} - diff --git a/libraries/WiFi/src/WiFiServer.cpp b/libraries/WiFi/src/WiFiServer.cpp new file mode 100644 index 00000000..a10e3b16 --- /dev/null +++ b/libraries/WiFi/src/WiFiServer.cpp @@ -0,0 +1,77 @@ +/* + Server.cpp - Server class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WiFiServer.h" +#include +#include + +#undef write + +int WiFiServer::setTimeout(uint32_t seconds){ + struct timeval tv; + tv.tv_sec = seconds; + tv.tv_usec = 0; + if(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval)) < 0) + return -1; + return setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval)); +} + +size_t WiFiServer::write(const uint8_t *data, size_t len){ + return 0; +} + +void WiFiServer::stopAll(){} + +WiFiClient WiFiServer::available(){ + if(!_listening) + return WiFiClient(); + struct sockaddr_in _client; + int cs = sizeof(struct sockaddr_in); + int client_sock = accept(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs); + if(client_sock >= 0){ + int val = 1; + if(setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(int)) == ESP_OK) + return WiFiClient(client_sock); + } + return WiFiClient(); +} + +void WiFiServer::begin(){ + if(_listening) + return; + struct sockaddr_in server; + sockfd = socket(AF_INET , SOCK_STREAM, 0); + if (sockfd < 0) + return; + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(_port); + if(bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + return; + if(listen(sockfd , _max_clients) < 0) + return; + fcntl(sockfd, F_SETFL, O_NONBLOCK); + _listening = true; +} + +void WiFiServer::end(){ + close(sockfd); + sockfd = -1; + _listening = false; +} + diff --git a/libraries/WiFi/src/WiFiServer.h b/libraries/WiFi/src/WiFiServer.h new file mode 100644 index 00000000..b2d9996f --- /dev/null +++ b/libraries/WiFi/src/WiFiServer.h @@ -0,0 +1,53 @@ +/* + Server.h - Server class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _WIFISERVER_H_ +#define _WIFISERVER_H_ + +#include "Arduino.h" +#include "Server.h" +#include "WiFiClient.h" + +class WiFiServer : public Server { + private: + int sockfd; + uint16_t _port; + uint8_t _max_clients; + bool _listening; + + public: + void listenOnLocalhost(){} + + WiFiServer(uint16_t port=80, uint8_t max_clients=4):sockfd(-1),_port(port),_max_clients(max_clients),_listening(false){} + ~WiFiServer(){ end();} + WiFiClient available(); + WiFiClient accept(){return available();} + void begin(); + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data){ + return write(&data, 1); + } + using Print::write; + + void end(); + operator bool(){return _listening;} + int setTimeout(uint32_t seconds); + void stopAll(); +}; + +#endif /* _WIFISERVER_H_ */ diff --git a/libraries/WiFi/src/WiFiUdp.cpp b/libraries/WiFi/src/WiFiUdp.cpp new file mode 100644 index 00000000..1b53cb32 --- /dev/null +++ b/libraries/WiFi/src/WiFiUdp.cpp @@ -0,0 +1,254 @@ +/* + Udp.cpp - UDP class for Raspberry Pi + Copyright (c) 2016 Hristo Gochkov All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WiFiUdp.h" +#include +#include +#include + +#undef write +#undef read + +WiFiUDP::WiFiUDP() +: udp_server(-1) +, server_port(0) +, remote_port(0) +, tx_buffer(0) +, tx_buffer_len(0) +, rx_buffer(0) +{} + +WiFiUDP::~WiFiUDP(){ + stop(); +} + +uint8_t WiFiUDP::begin(IPAddress address, uint16_t port){ + stop(); + + server_port = port; + + tx_buffer = new char[1460]; + if(!tx_buffer){ + log_e("could not create tx buffer: %d", errno); + return 0; + } + + if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1){ + log_e("could not create socket: %d", errno); + return 0; + } + + int yes = 1; + if (setsockopt(udp_server,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0) { + log_e("could not set socket option: %d", errno); + stop(); + return 0; + } + + struct sockaddr_in addr; + memset((char *) &addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(server_port); + addr.sin_addr.s_addr = (in_addr_t)address; + if(bind(udp_server , (struct sockaddr*)&addr, sizeof(addr)) == -1){ + log_e("could not bind socket: %d", errno); + stop(); + return 0; + } + fcntl(udp_server, F_SETFL, O_NONBLOCK); + return 1; +} + +uint8_t WiFiUDP::begin(uint16_t p){ + return begin(IPAddress(INADDR_ANY), p); +} + +uint8_t WiFiUDP::beginMulticast(IPAddress a, uint16_t p){ + if(begin(IPAddress(INADDR_ANY), p)){ + if(a != 0){ + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = (in_addr_t)a; + mreq.imr_interface.s_addr = INADDR_ANY; + if (setsockopt(udp_server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + log_e("could not join igmp: %d", errno); + stop(); + return 0; + } + multicast_ip = a; + } + return 1; + } + return 0; +} + +void WiFiUDP::stop(){ + if(tx_buffer){ + delete[] tx_buffer; + } + tx_buffer_len = 0; + if(rx_buffer){ + cbuf *b = rx_buffer; + rx_buffer = NULL; + delete b; + } + if(udp_server == -1) + return; + if(multicast_ip != 0){ + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = (in_addr_t)multicast_ip; + mreq.imr_interface.s_addr = (in_addr_t)0; + setsockopt(udp_server, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + multicast_ip = IPAddress(INADDR_ANY); + } + close(udp_server); + udp_server = -1; +} + +int WiFiUDP::beginMulticastPacket(){ + if(!server_port || multicast_ip == IPAddress(INADDR_ANY)) + return 0; + remote_ip = server_port; + remote_port = multicast_ip; + return beginPacket(); +} + +int WiFiUDP::beginPacket(){ + if(!remote_port) + return 0; + tx_buffer_len = 0; + return 1; +} + +int WiFiUDP::beginPacket(IPAddress ip, uint16_t port){ + remote_ip = ip; + remote_port = port; + return beginPacket(); +} + +int WiFiUDP::beginPacket(const char *host, uint16_t port){ + struct hostent *server; + server = gethostbyname(host); + if (server == NULL){ + log_e("could not get host from dns: %d", errno); + return 0; + } + return beginPacket(IPAddress((const uint8_t *)(server->h_addr_list[0])), port); +} + +int WiFiUDP::endPacket(){ + struct sockaddr_in recipient; + recipient.sin_addr.s_addr = (uint32_t)remote_ip; + recipient.sin_family = AF_INET; + recipient.sin_port = htons(remote_port); + int sent = sendto(udp_server, tx_buffer, tx_buffer_len, 0, (struct sockaddr*) &recipient, sizeof(recipient)); + if(sent < 0){ + log_e("could not send data: %d", errno); + } + return sent; +} + +size_t WiFiUDP::write(uint8_t data){ + if(tx_buffer_len == 1460){ + endPacket(); + tx_buffer_len = 0; + } + tx_buffer[tx_buffer_len++] = data; + return 1; +} + +size_t WiFiUDP::write(const uint8_t *buffer, size_t size){ + size_t i; + for(i=0;iwrite(buf, len); + delete[] buf; + return len; +} + +int WiFiUDP::available(){ + if(!rx_buffer) return 0; + return rx_buffer->size(); +} + +int WiFiUDP::read(){ + if(!rx_buffer) return -1; + int out = rx_buffer->read(); + if(!rx_buffer->size()){ + cbuf *b = rx_buffer; + rx_buffer = 0; + delete b; + } + return out; +} + +int WiFiUDP::read(unsigned char* buffer, size_t len){ + return read((char *)buffer, len); +} + +int WiFiUDP::read(char* buffer, size_t len){ + if(!rx_buffer) return 0; + int out = rx_buffer->read(buffer, len); + if(!rx_buffer->size()){ + cbuf *b = rx_buffer; + rx_buffer = 0; + delete b; + } + return out; +} + +int WiFiUDP::peek(){ + if(!rx_buffer) return -1; + return rx_buffer->peek(); +} + +void WiFiUDP::flush(){ + cbuf *b = rx_buffer; + rx_buffer = 0; + delete b; +} + +IPAddress WiFiUDP::remoteIP(){ + return remote_ip; +} + +uint16_t WiFiUDP::remotePort(){ + return remote_port; +} diff --git a/libraries/WiFi/src/WiFiUdp.h b/libraries/WiFi/src/WiFiUdp.h new file mode 100644 index 00000000..b543d5f9 --- /dev/null +++ b/libraries/WiFi/src/WiFiUdp.h @@ -0,0 +1,77 @@ +/* + * Udp.cpp: Library to send/receive UDP packets. + * + * NOTE: UDP is fast, but has some important limitations (thanks to Warren Gray for mentioning these) + * 1) UDP does not guarantee the order in which assembled UDP packets are received. This + * might not happen often in practice, but in larger network topologies, a UDP + * packet can be received out of sequence. + * 2) UDP does not guard against lost packets - so packets *can* disappear without the sender being + * aware of it. Again, this may not be a concern in practice on small local networks. + * For more information, see http://www.cafeaulait.org/course/week12/35.html + * + * MIT License: + * Copyright (c) 2008 Bjoern Hartmann + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * bjoern@cs.stanford.edu 12/30/2008 + */ + +#ifndef _WIFIUDP_H_ +#define _WIFIUDP_H_ + +#include +#include +#include + +class WiFiUDP : public UDP { +private: + int udp_server; + IPAddress multicast_ip; + IPAddress remote_ip; + uint16_t server_port; + uint16_t remote_port; + char * tx_buffer; + size_t tx_buffer_len; + cbuf * rx_buffer; +public: + WiFiUDP(); + ~WiFiUDP(); + uint8_t begin(IPAddress a, uint16_t p); + uint8_t begin(uint16_t p); + uint8_t beginMulticast(IPAddress a, uint16_t p); + void stop(); + int beginMulticastPacket(); + int beginPacket(); + int beginPacket(IPAddress ip, uint16_t port); + int beginPacket(const char *host, uint16_t port); + int endPacket(); + size_t write(uint8_t); + size_t write(const uint8_t *buffer, size_t size); + int parsePacket(); + int available(); + int read(); + int read(unsigned char* buffer, size_t len); + int read(char* buffer, size_t len); + int peek(); + void flush(); + IPAddress remoteIP(); + uint16_t remotePort(); +}; + +#endif /* _WIFIUDP_H_ */ diff --git a/tools/sdk/bin/bootloader.bin b/tools/sdk/bin/bootloader.bin index 52ceab2a4704181db2ae11f9bc061b85ecabcc0e..570b509cf5f53c8e3f759e1af0db6372b7dd84bd 100644 GIT binary patch delta 2403 zcmY*a4Nz3a9pCrfap3uYi{hye!rO~yP9n*9#4j4X#|0?hSy57&vFiAc%ZKp;fvLu7 z@Ai@)PH^IJ8ZBb_-qD*_>NRpEH8Dd9G($30t)X_xq@hD@hNfv_<}fvJkhk022kEpo zv-jWq{eSZk8ObRR^TK zKk+y1QW~43`n`KwcI{Of{7q7Gi?ZYSX?2mbL)r6Ci^Mb*5S9xr7V$q8xGj;RX$4E$ zga7tartWyi?+5`_0Go+f_E)0mv}0Kfr?f)K?>0RWLN(_8@nzy(5x=PPc??Du@b-bVY zh#G#};G!XAPX@(*N%eoDYbZ>4w$lMXljj%0RH5gDY05M5h*{<(08~2vn2x{FL6ZWR zX;Yr%bZn+07>Sv^KYD|VWdOCiUtFL%U(8Pdu%Z-f>?Wn%q$VcGZ%MIQpq5C-EQ9KH zO(Tb9e;OI@0DYE$vqU>XM$>MY&{^i8NMN?!1>lXGOjoxw9$A~-92nry&zTaPX8#El z4616JL$9$FY$5JnlYR+Q@~+3MRIcXXKC*1z|3l+#)aI_Xi->u-SH~65-s5qcANRHP0NM&}wZD&FD&}^hZOma*RTINjy6!=njlg`& z1MJWh7Z&rpc?m7Zxme(b76T`&OIj0ZznBvja^DkuUd8295M(w9Rb@z2St*+0f>&{( zJj1o}XxxNK+4LNzo-ajDkN};pu^6ko9^_#vSC91lTz+U4#a-OmNx-0XzZ8`cDtBcdRzq{{vcz_%7RSRh30RJfe^*HqmJP!4zF8fL z5A`yz1N!2#9P>b4aR>L=sczo<4hg~4q9sHLF-D+w%8>ioFzmz!%cQ5R0qKWUS-vA$ zu9-*EbW@)tsmdtI$kTv0+m|KgqO%O~!Ya`sJ!bX7%4>qOh|20w{$NXE;~hP{&*IBM zCk#7blxJ3YI<) zUXvv_=(nOZxWfrf*=kvHDgYJEBse1nN^qZL)J#vZ06ol9ZCuynuQ-1p=yO;G3h@Z1 z6=QTyXH{F!14aTi0G`W0d2EgzjtpfMIJ4P2o`$9+-zQu%A;_>I3q>t#_Kkbivb;!U zmb<`;yXHD1b}&|~W#Q2@G|b$`P2%kP+fgdAEo()lt|tXfo6=d;??<#OyXR~D)a8N@ z1n`z_dzN#|H!A;9*S^w6Z0I-40sOoE%F9R7Pun`5$}3*EXs+${q&Pcn7Te!VM;}J` z><*E}C;j*Hk^pWsjQC z79+rk$fMSUfz!tPq%IXkrAJDQb_}1;Y0I|F3I-R|Rg!S{e^=98qJWrA=*Kh6jKw*1 zou)R<9tD!qx`Z3QN6--+zRawS^2c~8oEddZ#6L+;NDqe?elFv9r$lAPv-5x!&=Zi} b3Bwir?nh{di(^(3XPtZ3h{jro{SyBH-CNA( delta 2314 zcmY*ae{2)i9e;N|UyO4kmy#L?gxr~!nKV#yVS$AoKASX3QYV-n%}_cNoX*<`vCNdVi=dI07dGVhJN(5j#nDH)QXS{6_(_R1=&`$KYoA*R;3825T-!L=Y zFWv&G`!G-gmw>uf;nmB-K)qVdtHBk#dL054kf5c6q>&5&6wqQy)oUa6Q)J;ch(1b& zV4?o~-lvIFxf$=+N?v6p00Lxwn2g_&L9@TW&DgzP5rwzTc+2BVj{nDJ@J$=g`i6uB zn)~(QEC7{0uqBCoNgPZ{>K~=>HlS5WZ`y~oU3worl>5(kvJ<53nfI{%E*`hu=HPpj zVTFap`WJxT7tZzcNy&Im&P$OH5bUoMw1xnTP)tGt@HINw_bV*e+ny8K6j45iNBOd9 zRh=5Q7+l&O$b$zdWR$16kQ>Moir{`~Ia1Kgl)2k>i;^_e_DfN)6G)D9SJEy@6PgNl zQ;fk1xzub%{nT7kZozw=8j}S18mB*nM;-@??00IcXZ;WFZ?nlh>`|gl3uzXtqzkOth%+1UmsR-AllZh{XpouE z0c@lmf>`-|NpcGR3pbT9$^DdC!I>o(N)%C?O~?_>@#_3R+3M81=aqtF8{M|~STDn4 zTgvnJelWQbXk}7U5i|J$b%|Kd!(+Z%TWb2iR&7Yhh|QbQY`Ku)6isU-)jvszt11VF zC1SL|C#afY!xg|OuTEK)vFyk)uvIQ!W@S4{fjiU`VG9ID%9`ecKx=5VTZi`$iAY<7 zKq9nG;8WJtP&xdT+7h9P6b~PmQ<>8~^ufm|`A++3A5j+d!vd-X`=y^1u_HEc+Lz|a z1vr-)8wkd8PDs7dXYVm-+ah>(_K5&sE>|EA0x5@^qe+Ucn4bL{A65}M}B zi2VuIWgNTWl*<))wY2o55suu=gjv1T7Y&bKM?FKzQ{*z|F!nenMCVy|g3qHE%}>Hl zxh~;YGYRjwk3qPZ?!t9c^E*sfoDrFCx=Wg4yV^OqgL?jg_-;I`FDA{D64Ic?Tp4jy zgAMlHqRA$@180%`XEe^w&LXAqOK?wzRTJ~BFTO$6@X1;2cC_ABxcl8D2YD$Ar(N&@N>s!+gldC{)Rd_R{ z>_qwpXxsujDPM86Q?8AjNqjydaO7R6ji*^LHHq8tDC`#MIj#|~w>(9p?f z*C%z3=Q&cTe~d}U^@F9>Nh{E9NKJ1rlTQJ1vom{;{wBI6*WcO>e=(a{qLdphwWjA@ z3kBgZYPu!|Q*&LpU{OUUbEm71t@#W)&oJRK+PEi>JjUp& z5kv;9+73B00S5rPZIGdJSRP-NSK^+c{*)EoGbNk0jB?OP{VVLmnx)qJ%A8e;<9qXz zk^v)tNYx@ATd?x-OQq%Rv6d zaQvKc%{ME*ZRp<`V-7e#3FQA6$9{7(r`yr9V^PJL(s_=%(?V{F7h)H3;MI6*{-v@3 zL;uKt2Pv3$fPH6;pmXR4=NCFY<6Jz==ZvBM!62Ie_r`