From e1a8158cfa30b3be829b159b0aac799460880652 Mon Sep 17 00:00:00 2001 From: mjs513 Date: Tue, 19 Sep 2017 22:03:54 -0400 Subject: [PATCH 1/3] Part of BoardPackage not needed --- src/SPI.cpp | 163 ---------------------------------------------------- src/SPI.h | 86 --------------------------- 2 files changed, 249 deletions(-) delete mode 100644 src/SPI.cpp delete mode 100644 src/SPI.h diff --git a/src/SPI.cpp b/src/SPI.cpp deleted file mode 100644 index 9d52aec..0000000 --- a/src/SPI.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2010 by Cristian Maglie - * SPI Master library for arduino. - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of either the GNU General Public License version 2 - * or the GNU Lesser General Public License version 2.1, both as - * published by the Free Software Foundation. - */ - -#include "SPI.h" - -SPIClass::SPIClass(Spi *_spi, uint32_t _id, void(*_initCb)(void)) : - spi(_spi), id(_id), initCb(_initCb), initialized(false) -{ - // Empty -} - -void SPIClass::begin() -{ - init(); - // NPCS control is left to the user - // Default speed set to 4Mhz - setClockDivider(BOARD_SPI_DEFAULT_SS, 21); - setDataMode(BOARD_SPI_DEFAULT_SS, SPI_MODE0); - setBitOrder(BOARD_SPI_DEFAULT_SS, MSBFIRST); -} - -void SPIClass::begin(uint8_t _pin) -{ - init(); - uint32_t spiPin = BOARD_PIN_TO_SPI_PIN(_pin); - PIO_Configure( - g_APinDescription[spiPin].pPort, - g_APinDescription[spiPin].ulPinType, - g_APinDescription[spiPin].ulPin, - g_APinDescription[spiPin].ulPinConfiguration); - // Default speed set to 4Mhz - setClockDivider(_pin, 21); - setDataMode(_pin, SPI_MODE0); - setBitOrder(_pin, MSBFIRST); -} - -void SPIClass::init() -{ - if (initialized) - { - return; - } - - initCb(); - SPI_Configure(spi, id, SPI_MR_MSTR | SPI_MR_PS | SPI_MR_MODFDIS); - SPI_Enable(spi); - initialized = true; -} - -void SPIClass::end(uint8_t _pin) -{ - uint32_t spiPin = BOARD_PIN_TO_SPI_PIN(_pin); - // Setting the pin as INPUT will disconnect it from SPI peripheral - pinMode(spiPin, INPUT); -} - -void SPIClass::end() -{ - SPI_Disable(spi); - initialized = false; -} - -void SPIClass::setBitOrder(uint8_t _pin, BitOrder _bitOrder) -{ - uint32_t ch = BOARD_PIN_TO_SPI_CHANNEL(_pin); - bitOrder[ch] = _bitOrder; -} - -void SPIClass::setDataMode(uint8_t _pin, uint8_t _mode) -{ - uint32_t ch = BOARD_PIN_TO_SPI_CHANNEL(_pin); - mode[ch] = _mode | SPI_CSR_CSAAT; - // SPI_CSR_DLYBCT(1) keeps CS enabled for 32 MCLK after a completed - // transfer. Some device needs that for working properly. //TODO: See if this is needed for Flutter - SPI_ConfigureNPCS(spi, ch, mode[ch] | SPI_CSR_SCBR(divider[ch]) | SPI_CSR_DLYBCT(1)); -} - -void SPIClass::setClockDivider(uint8_t _pin, uint8_t _divider) -{ - uint32_t ch = BOARD_PIN_TO_SPI_CHANNEL(_pin); - divider[ch] = _divider; - // SPI_CSR_DLYBCT(1) keeps CS enabled for 32 MCLK after a completed - // transfer. Some device needs that for working properly. //TODO: See if this is needed for Flutter - SPI_ConfigureNPCS(spi, ch, mode[ch] | SPI_CSR_SCBR(divider[ch]) | SPI_CSR_DLYBCT(1)); -} - -byte SPIClass::transfer(byte _pin, uint8_t _data, SPITransferMode _mode) -{ - uint32_t ch = BOARD_PIN_TO_SPI_CHANNEL(_pin); - - // Reverse bit order - if (bitOrder[ch] == LSBFIRST) - { - _data = __REV(__RBIT(_data)); - } - - uint32_t d = _data | SPI_PCS(ch); - - if (_mode == SPI_LAST) - { - d |= SPI_TDR_LASTXFER; - } - - // SPI_Write(spi, _channel, _data); - while ((spi->SPI_SR & SPI_SR_TDRE) == 0) - ; - - spi->SPI_TDR = d; - - // return SPI_Read(spi); - while ((spi->SPI_SR & SPI_SR_RDRF) == 0) - ; - - d = spi->SPI_RDR; - - // Reverse bit order - if (bitOrder[ch] == LSBFIRST) - { - d = __REV(__RBIT(d)); - } - - return d & 0xFF; -} - -void SPIClass::attachInterrupt(void) -{ - // Should be enableInterrupt() -} - -void SPIClass::detachInterrupt(void) -{ - // Should be disableInterrupt() -} - -#if SPI_INTERFACES_COUNT > 0 -static void SPI_0_Init(void) -{ - PIO_Configure( - g_APinDescription[PIN_SPI_MOSI].pPort, - g_APinDescription[PIN_SPI_MOSI].ulPinType, - g_APinDescription[PIN_SPI_MOSI].ulPin, - g_APinDescription[PIN_SPI_MOSI].ulPinConfiguration); - PIO_Configure( - g_APinDescription[PIN_SPI_MISO].pPort, - g_APinDescription[PIN_SPI_MISO].ulPinType, - g_APinDescription[PIN_SPI_MISO].ulPin, - g_APinDescription[PIN_SPI_MISO].ulPinConfiguration); - PIO_Configure( - g_APinDescription[PIN_SPI_SCK].pPort, - g_APinDescription[PIN_SPI_SCK].ulPinType, - g_APinDescription[PIN_SPI_SCK].ulPin, - g_APinDescription[PIN_SPI_SCK].ulPinConfiguration); -} - -SPIClass SPI0(SPI_INTERFACE, SPI_INTERFACE_ID, SPI_0_Init); -#endif diff --git a/src/SPI.h b/src/SPI.h deleted file mode 100644 index fcf65cb..0000000 --- a/src/SPI.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2010 by Cristian Maglie - * SPI Master library for arduino. - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of either the GNU General Public License version 2 - * or the GNU Lesser General Public License version 2.1, both as - * published by the Free Software Foundation. - */ - -#ifndef _SPI_H_INCLUDED -#define _SPI_H_INCLUDED - -#include "variant.h" -#include - -#define SPI_MODE0 0x02 -#define SPI_MODE1 0x00 -#define SPI_MODE2 0x03 -#define SPI_MODE3 0x01 - -enum SPITransferMode -{ - SPI_CONTINUE, - SPI_LAST -}; - -class SPIClass -{ -public: - SPIClass(Spi *_spi, uint32_t _id, void(*_initCb)(void)); - - byte transfer(uint8_t _data, SPITransferMode _mode = SPI_LAST) - { - return transfer(BOARD_SPI_DEFAULT_SS, _data, _mode); - } - byte transfer(byte _channel, uint8_t _data, SPITransferMode _mode = SPI_LAST); - - // SPI Configuration methods - - void attachInterrupt(void); - void detachInterrupt(void); - - void begin(void); - void end(void); - - // Attach/Detach pin to/from SPI controller - void begin(uint8_t _pin); - void end(uint8_t _pin); - - // These methods sets a parameter on a single pin - void setBitOrder(uint8_t _pin, BitOrder); - void setDataMode(uint8_t _pin, uint8_t); - void setClockDivider(uint8_t _pin, uint8_t); - - // These methods sets the same parameters but on default pin BOARD_SPI_DEFAULT_SS - void setBitOrder(BitOrder _order) - { - setBitOrder(BOARD_SPI_DEFAULT_SS, _order); - }; - void setDataMode(uint8_t _mode) - { - setDataMode(BOARD_SPI_DEFAULT_SS, _mode); - }; - void setClockDivider(uint8_t _div) - { - setClockDivider(BOARD_SPI_DEFAULT_SS, _div); - }; - -private: - void init(); - - Spi *spi; - uint32_t id; - BitOrder bitOrder[SPI_CHANNELS_NUM]; - uint32_t divider[SPI_CHANNELS_NUM]; - uint32_t mode[SPI_CHANNELS_NUM]; - void (*initCb)(void); - bool initialized; -}; - -#if SPI_INTERFACES_COUNT > 0 -extern SPIClass SPI0; -#endif - -#endif From d641750db8589c0990f62d789514c089242f5747 Mon Sep 17 00:00:00 2001 From: mjs513 Date: Wed, 20 Sep 2017 20:43:02 -0400 Subject: [PATCH 2/3] Added BME280 Sensor Example Using TinyPacks --- .../BME280SensorTest/BME280SensorTest.ino | 397 ++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 examples/BME280SensorTest/BME280SensorTest.ino diff --git a/examples/BME280SensorTest/BME280SensorTest.ino b/examples/BME280SensorTest/BME280SensorTest.ino new file mode 100644 index 0000000..d88fd0a --- /dev/null +++ b/examples/BME280SensorTest/BME280SensorTest.ino @@ -0,0 +1,397 @@ +/* +This example code for Flutter is +Copyright 2015, Taylor Alexander and Flutter Wireless, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include "Flutter.h" + +// +// TinyPacks is data serialization format for constrained environments +// like 8-bit and 16-bit microcontrollers. It can be downloaded to your +// Arduino Libraries folder from https://github.com/francc/tinypacks +// +#include "TinyPacks.h" + +boolean _running = false; +Flutter flutter; +unsigned long delayTime; + +byte mydata = 0; + +float Temp = 0; +float Pressure = 0; +float Altitude = 0; +float Humidity = 0; + +// To test this program, flash one board as a transmitter and then +// comment out this line (with //) to flash another board as a receiver +#define TRANSMITTER + + +#ifdef TRANSMITTER + + //Setup Adafruit BME280 Pressure Sensor on I2C + #include + #include + #include + #include + + #define SEALEVELPRESSURE_HPA (1013.25) + Adafruit_BME280 bme; // I2C + + //initiate TinyPacks as a writer inorder to transmit data + PackWriter writer; +#else + //initiate TinyPacks as a writer to unpack data + PackReader reader; +#endif + +//TinyPacks Constants +#define MAX_PACKED_DATA 256 +unsigned char packed_data[MAX_PACKED_DATA]; +int packed_data_length; + +void setup() +{ + //Starts Serial on pins D2 and D3 + Serial.begin(115200); + + /********************************************************************/ + * Using SerialUSB has it quirks so becareful in using it + * 1. It appears that the radio is somehow linked to the operation + * of the USB so you can not use it unless you actually have it + * operational either as a transmitter or as a receiver. + * 2. When you first start the radio as in the initRadio function + * you can not use SerialUSB, if you try it will hang the sketch + * at the initializing the radio. + * 3. After loading a sketch and you restart the Flutter (may restart + * automatically but more often than not you have to unplug it + * and plug it back in) it will appear as a Arduino Due programmers + * port. Not to worry it still works. + * 4. If you open the serialUSB port while the sketch is running and + * then close it, the sketch will stop, period and you have to + * disconnect from the computer and then reconnect it. This goes + * for both the transmit and receive sketches + * 5. Bi-directional communction is not implemented. + * + *********************************************************************/ + SerialUSB.begin(115200); //Starts Serial output on USB Connector + + // I put a delay of 5 seconds here only to give me time to open SerialUSB + // in case i want to see the data while debugging + delay(5000); + + // Initialized the radio and network + initRadio(); + + // Since the sketch works for both transmit and recieve this starts the + // transmitter portion of the sketch + #ifdef TRANSMITTER + + // Initializing the BME280 + SerialUSB.println(F("BME280 Initializing...")); + bool status1; + + // default settings for the BME280 + status1 = bme.begin(); + if (!status1) { + SerialUSB.println("Could not find a valid BME280 sensor, check wiring!"); + while (1); + } + #endif + + delayTime = 1000; + SerialUSB.println(); + delay(100); // let sensor boot up +} + + +void loop() +{ + // Checks status of the flutter radio + status(); + + #ifdef TRANSMITTER + mydata++; + //Get BME280 Sensor Data + Temp = bme.readTemperature(); + Pressure = bme.readPressure() / 100.0F; + Altitude = bme.readAltitude(SEALEVELPRESSURE_HPA); + Humidity = bme.readHumidity(); + + //Pack Sensor Data + writer.setBuffer(packed_data, MAX_PACKED_DATA); + writer.openMap(); + writer.putString("Temp"); + writer.putReal( Temp); + writer.putString("Pressure"); + writer.putReal( Pressure); + writer.putString("Altitude"); + writer.putReal( Altitude); + writer.putString("Humidity"); + writer.putReal( Humidity); + writer.close(); + packed_data_length = writer.getOffset(); + + // Prints the packed data from TinyPacks as a debugging aid + SerialUSB.println(packed_data_length); + SerialUSB.println("PACKED DATA"); + for(int i = 0; i < packed_data_length; i++) + SerialUSB.print(packed_data[i]); + SerialUSB.println(); + + + // send a byte array over the radio + flutter.sendData(packed_data, packed_data_length, 2); //legth is 3, 2 is car's address + + switch ((mydata - 1) % 3) + { + case 0: + flutter.setLED(RED); + break; + + case 1: + flutter.setLED(BLUE); + break; + + case 2: + flutter.setLED(GREEN); + break; + + default: + break; + } + + delay(40); //spend some time smelling the roses + #else + + if (flutter.dataAvailable() > 0) + { + int packetSize = flutter.nextPacketLength(); + unsigned char array[packetSize]; + flutter.readBytes(array, packetSize); + + SerialUSB.print("Packet Size: "); + SerialUSB.println(packetSize); + + for (int i = 0; i < packetSize; i++) + { + SerialUSB.print("["); + SerialUSB.print(array[i]); + SerialUSB.print("]"); + } + + // Have to start at index 5 since 0-4 are used as a header + // by the radio + for (int i = 5; i < packetSize; i++){ + packed_data[i-5] = array[i]; + } + + // Unpack + reader.setBuffer(packed_data, packetSize-5); + reader.next(); + if(reader.openMap()) { + while(reader.next()) { + if (reader.match("Temp")) Temp = reader.getReal(); + else if(reader.match("Pressure")) Pressure = reader.getReal(); + else if(reader.match("Altitude")) Altitude = reader.getReal(); + else if(reader.match("Humidity")) Humidity = reader.getReal(); + else reader.next(); + } + reader.close(); + } + + // Print unpacked data + SerialUSB.println(); + SerialUSB.println("Map contents:"); + SerialUSB.print(" Temp: "); + SerialUSB.print(Temp); + SerialUSB.println(" Press: "); + SerialUSB.print(Pressure); + SerialUSB.println(" Altitude: "); + SerialUSB.print(Altitude); + SerialUSB.println(" Humidity: "); + SerialUSB.print(Humidity); + SerialUSB.println(); + + SerialUSB.println(); + + mydata = array[6]; + int rssi = flutter.packetRSSI(array, packetSize); + SerialUSB.print("RSSI: "); + SerialUSB.print(rssi); + SerialUSB.println(" dBm"); + + for (int j = 0; j > rssi; j--) + { + SerialUSB.print("="); + } + + SerialUSB.println(""); + + switch (mydata % 3) + { + case 0: + flutter.setLED(RED); + break; + + case 1: + flutter.setLED(BLUE); + break; + + case 2: + flutter.setLED(GREEN); + break; + + default: + break; + } + flutter.nextPacket(); + } + #endif +} + +void initRadio() +{ + flutter.band = BAND; + flutter.setNetworkName("Range Test"); + Serial.println("Initializing..."); + + if (flutter.init() == true) + { + Serial.println("Init success."); + flutter.ledLightShow(); + delay(500); + //analogWrite(LED_R, 128); + } + else + { + flutter.setLED(RED); + Serial.println("Init failed."); + + while (true); + } + + //flutter.setAddress(1); + #ifdef TRANSMITTER + flutter.setAddress(1); + #else + flutter.setAddress(2); + #endif + flutter.connect(1); //form a network with this and one other device +} + +void status() +{ + if(flutter.getState()!=NORMAL_OPERATION) //if we aren't synchronized with another radio, just loop and blink lights. + { + if(millis()%400<200) + { + flutter.setLED(RED); + } + + else + { + flutter.setLED(BLUE); + } + + } +} + + + +void button1() +{ + interrupts(); + int val = digitalRead(BUTTON1); //top button + + if (val == HIGH) + { + // _button1=255; + } + else + { + // _button1=0; + } + +// buttonsChanged=true; +} + +void button2() +{ + interrupts(); + int val = digitalRead(BUTTON2); +#ifdef FLUTTER_R2 + + if (val == HIGH) +#else + if (val == LOW) +#endif + { + //_button2=255; + } + else + { + //_button2=0; + } + +// buttonsChanged=true; +} + +void systemReset() +{ + flutter.setLED(0, 0, 255); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 0, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 255, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 0, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(255, 0, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 0, 0); + initiateReset(1); + tickReset(); +} + + + +void radioInterrupt() +{ + flutter.interrupt(); +} + +void softInt() +{ + flutter.processSoftInt(); +} + +extern boolean tickInterrupt() +{ + if (!flutter.initialized) + { + return true; + } + + return flutter.tickInt(); +} + + + From 094ec173b7dfc81a4b409dc980ab2d1a9ed417cc Mon Sep 17 00:00:00 2001 From: mjs513 Date: Fri, 29 Sep 2017 10:49:30 -0400 Subject: [PATCH 3/3] Added Actual Pin Map & logger/mpu9250/bme28 example --- docs/AcutalPinMapping.xlsx | Bin 0 -> 39876 bytes .../RangeTestSensorv2/RangeTestSensorv2.ino | 728 ++++++++++++++++++ 2 files changed, 728 insertions(+) create mode 100644 docs/AcutalPinMapping.xlsx create mode 100644 examples/RangeTestSensorv2/RangeTestSensorv2.ino diff --git a/docs/AcutalPinMapping.xlsx b/docs/AcutalPinMapping.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..408937b9b633b64cef0788cf6cb959689fe6f2ee GIT binary patch literal 39876 zcmeFXWmH^Uvo4AULI@5C5VUc3_r~2_gS)$X0s)t$KYXWLtFp{LzM>w1L^Ssdh#C=8;U&5f3 z%1^?e3}+Z_nz@6;vNn!dPMDEh-M{2@!jT>clFIA!0JB|odiF;n6pHDQ8=%c_WrFlq zv5}V7Ex$*}`otRWJdg@2tCx>yjSOHYMu#nF1Ygg=QV`fOW0HgY<1ubAtU}9f4L!|I zL@FcvMM*lx%zr|yT)JJFT+js;e-``1Ou*NxI!P;?gWJ)e%)+b=h%0vn(_oIhjO|Bi zmcG_`g%|DpP)Q7Sd+IPd#`1+Ku7%5MllEnr_#zMxfhkJz;N+!q=8(Z-6?^(Z2BX@7 z=_GQv8lFU!sanHEXun(m`^oXB2KBUslfjS)FToxASZ(1fMOXD^Ep%m|Rm+Q@s4_jK z%(60i<5-|?i$Hv(!wVM&@&>zgkhJxp8KBod7UqrO4xTOIKsTx}k^O;KsVoGq$PhpqIGNfw zGcx@1`QJePKN!9LVd~Kd)AIdHpN1|aJ4E+iPV6KR^NZVMCEH0<33~t+M2#_p0HKHb z5ZEB;?`yO7N7I5jn-_mrFBtJcx_3qWX5dY6tIx5&zy?EOiE)$7PE<8OHJ344*0{m2^H!27J97^ zKX`xmDe%nqRM*TVXYavy=xdxRA1#!F$vf_LAZmtOHoaDwAu=u%jD4H+3w`v3^gC=0 z#j38t-Fp+4BdH8%MsoX>Z!6MS==3y@A=jepv2%)Vwk%M ztgmB&ooNzJXS8s&&I1&M((cRvN6I~=yDqAg{ zoEovAxLkXU>XEBcfG?PMv1u;5zSK}CNYv54*}q@j`4#x%51=5D96oI2vZ!dzu$tPD z&^BwH;V$F*SG?b@o{K1Bd~(e!uL}XjKd57qgB&V2h~~W_|(6}Q{W{7Fiv;o7T6rcg(>i&Jsu+db*I{bbU8KyOMqmko)1>W#9k+uq}Mg1U8 zZS1gchA_X6pJM(byzOP&6DS}tUVNt>c3%z8+AzLG{@+;;C`Mw(2x1iu5I>6wF%5_X z{YTr0R-TpbW5V*~ye7)%lF|AAhlmeSB4Zo^h8(qR)`nH-t?q7LcAml*-}RPwD_pMp zZY|NFi)*CF-R`KPC>%AEL`v(EHg)^zYaqBGM|~~)g}Q+~eiqZCZNlCjr_%KW)%pPr zK6=^$TZGko0vCI}57h=1fwI`O5fO1C_Gu_Ez6%Di(aeV{t45nj)H}5GB`%C!My)59 z{xf^pOcd7)X`Zh^+0ZW^B^s1WKQhvcL$Ueems0B4zHo^$n33pM^i>=`f)-I{e?`2K zdY3fi6oV8WSD*;>XPyej&U&81L5VB{9y8oQapBW$!4qO`yJT?FBo&cSLkX)idV^z~ z8jETvo6OL0nV>g9W4VZUi4V)gz)G^$1gbqKIjv<~C)F|tbzuSfd3bXYo5PMgQGN$E z)1bDtyXGnP>@G6qE%ckHu@V72wg_&Tszw25)}wFl{Oq-U|9_9?7vo~vBnX8q!T*QG z{%bW{EKF@p8UOXk{12MXv=r>uIIy}=m)(h7>}@HV@ISalF4tyO%Ha>#g%et)r)a7a z2+c|P;;-%nK=-#9r9!u#GuhD2z_h#MQs{(N!aif)OG?*$8JC$uz+YSl2a@{xyl?H1 zx&B!Vj>s1!1spi8mFrb`MGOcK{o=Clb`N`#!6!`kIQ)IWLw0pDOY(Ac@#Ru)Fp~y= zE?gs!lG$<|Dovi59C>H-Wqh2Egn2@)C88!s16_3ph6s8iLT&Nqf*E-q+r45m8L_uF zOTK`xsNCGzSj5AZjv?t2=v+`Bg~$yG&kT^-`H@pDyWWxH&x|^vttVEuFf0H)iEP z8*Y7&tE3al`14o1GI5qJ;B1fO+^$invi%X}?GJgnPPTl>th8w@5M@MQG4%?6!OYit zvKK#okTd1#&y-v9n#&b}$@L_;&%1QFmisjV77Lz2>db2Xo1Zj)xGdKieW*Q@PhmzM zE>%PrUmIH#QryB%&8RD1hG(c5p)l+ia0aDGkZ;5;L#%j^@g$TzhHhG|XqPVDTu1yJ zyqxOV3sY}AIwY47dh;EI*x=p1)GfdJqa~t3 z!5qKan`_(y53Pas=YGoW*H=$&G!_ez*(k=`*ZZ^4sA_??xAtN3E89Q`vB!9EV1dp) z;vAQ?=s8-b1Dhgn`DJJPHBD5k4GbLArRMWT}# zTxTTP@ZR-S z%&<XOS$SE&W?Iu+WbpSP%xoPKQO9jHNZ--QTes4*X{VW7_ZmtGKxHt2V-^SbXlU zWNL;}4POgg<+dv(7ORVZ!p%!Y>c2#?({&~z%pwc=={2-6m$Sxu_gk%)(sVF+@ZsLj zG+U8|{U-6Ic+BxprDMJRa&!VU&JH4ah$uSa8L~!rh=}P z*^o8H>G4gh4ncWQNn9~`>dUN@W#&Y(a(FR1?<%TM`G7&P@Hr(P1%)*^3@VD9lJ;F2 zcEU^PW0GoNy;N-%bnjKQ6ttU*o(u)%Zh1jHGTExzJv)7|&yR>NA$w$O#;lG3y>C3K z!nw@J4tqv4L4JX!+_tu?2RKJ;zavwP5zL2~Wkj-4bg~PyD0Et=C0jooRuU5)tJAbA zUA+EGOX?<8i!^!_R1v$RbbzlFSR}mKanm#`Vl6&*%oq8rMuk+ig*(HQwhBE9qtU^H zA`I_7f7neN{@gvuEz@7bo&{KdMv2d>Au3Zut3x#%1#VFfSpD8Km8wgM2bBi3u+$pI zjgT0L>cM7u**_>E=G*<0(nk1nVe;B~X^9X6v$2nLJ6UG(v}x_UoqOS9!xF4)_l*NZ z&g@-xpo3rY?5A6&R?c~+nAqJw!Q@YKE$E4lI>NMaFH2ZESvr-KnlgCR>?}JyQR8_1 zhMQ>Q4&abGqUHC}tMs?+e9n0i!GP7s?Uzsr1S41_-%$=iNnA-@uArgO&#Q)WMHQC6 z7NvCP*D4rWeoa2*Fg2x|(ft)Dsl7d%Fg@De6~p6gtod%u+cY=7y^=`%Q8e+4_&dS( zt|V+hv+G%3hr7={9BItta!kD?G=cfyglT*M`^^NCbCunY5cQu)DI?wONEhS|075Is z{w;`^I2pNH+L=55J3IaN=D*_Ezu0L=oUj!v^Cz)~(9K>81M44Aienyi)PXiKPEbbH zd_TUgEXzyU8HjSCV71M^zdp<~bPvkuwa&*6^pt*3q~mBM-t2jD@$iII!v_`8BjL9= zhrYeOviaVKSH_*-IjLonF7&Kw`$<%|9Z0H`Ow47HCrei54t}S25zD!D{oC z6a3H9=VopMuR*3g^S`G552JFnFg11g9}4(a<9|)R0zCN-DGj&`?h2mmberq{QYg>( zedctPKfs(8q(zD^qqEeU>rO>iTr+O>ESxKle|}+|Gf#;V3!-aigAUzSiD1+8;1{SS z5~}6;XeS+lGc{6ks_wpqo0T^?6X3*$6%$Y`9 zLFyW&OwG2d?+v5m35MKxlF7TF3u@E42bF$NcVw10{>NSaTiy8eMjjYK?zlkM=9hmn zjsKvVe`$r~ACeQD(EE=m$xHi7h%x1d`*+o2<+iLaWW#Z9h5-Q7*~xKlpyO-c0HBadJC42~Y$f1__~ zazX{7_dZ8+lDL4-^w|xI;%Z=Jl0RmH1C=$9x47dEm`t>5Blakbikf@?fD0T1^`;d61;^t+-V#wBap-ZzD_?BaLx&6s1JsY*oFr_9Gia z8C7E4g!86{rLASccRc5$4jY`8)9XjpJy37lr9T1c=0n?Q=6%CW@M!ksLdnk!j*`^m z_dnyq?lN1a;WrKRsOM$+PlBhL{_l4$_Hp*M50zU>f3^cdRl08;`W^hzopKVQ{)r{5n{F*N(kTr;6uUf}%tzLMo*w3FXHDBixtu9n@ja9S&X(kH1w zGzR^8`7qg3W?YV~3LS#C!N8--EkR@5v{?^&PRGE}`k^ z!|-cuSzD96dq3QSTxj7%#%0NaO3mHf2)EBk4&n5H%v&pASB{|TX4TiOhTJ@8;~j-u z!V>95Tf<&DX?=}L^}pedMwmmgec1l&e{}3$FHsI|kDe7ZL_1A?@ZnS)HN6dmz_V>I zqqYR3)0O8x=~tij8y%F8jJ)_i(w+Gqz~j^KvmCvQh<=)~Kh=Bk`tsLi>!!_f1(oTV zAfDL?buHI&Lj#Q^%?o2Hec>m6!1&b?F9+QNl;@Ku*P2cE+Zd{OB5|8ez-qbvOFsW! z`qfqiE8U2_?WeH4D%7+cD39HM7pa!454Q%BSUW-rG5M_83=p9qp7W7PKTmtQv* zj?dp%+C`*xrFXq`>Toa+OPes36Yhcl4dg4;7{5GlIRD>uI_u%(P9dLg+Zey zZ|6+u8MazY*cAN4 zyRr!JV`N{L*co-K3b?ftccqOTWxcjZq=$EucGL!ojc_1;n^hjSm0O>8o$6kU?{ z;pK{8jSE9Yhw@CEf)bLl0S@d8()qQD3Gqc1l>2II!~`tusgLn8M22{c_AnSRFk(we zA0_1Eiu7ug`m7*QH_514)qGir(ULG4n3%HG^EGL-q9i+DEA(lZ>`xt4X3H_9qn}y{ zYK5bwDM$Aw$)Fr<@R-wK%59Fd~ljcD96*h4cN!C`Y0|cwF*N26NPoZTM8HPqt zS@P8lIW5G6VOX0^s^KG+o;fuiJ3H8I-VCXvj+rh!t7e`$j!rLTT-m}V1wcjpA_A8v zm10678$cY2~)alhG0M~m76QL`-6UVl;NUnWzHR(U_AzD=|cp!2-tw+j?FT zfD{W^3#mTtdM_z+20f;?Pm;p)|0eh+BSW%!5F&U^Cb{blU#Q#;(2J2#wxCM@kXjN$ z2=~fs`hpCP^O*@H;U965g$AJ*q)~WkT_$$$=LnR9>?4T)tYQCO!h5b^p>qo@#Gykl zQtBB9%KgG8BGcxh75a1O-%H>V2Fm`Y9P9ZHcou>O+wk#x>5Lyos}01SQu}#Hq#oQq zi_(8vILnmbIHtADA*Z=<{+={tv{7J(Eh7q@7doVl3BFCR__1ev8$y5&PJ8_z)8{Nq z1;7<+>vO=drmpkA06+R-{fK(pNg&*3wGG~D>Lxg>TiTb3v?5_&@?;U;4yDfcJ+jPa zR2{Q3x^K9Oc$Ba&hS8Rx+5Hs%W2VO-8Hcq%YF56~Pk5|`97n=E619ZgHbGiD4;nK% zGo~-LEd4jr@|9s`Y~b)d6zY_w)dF(e37~^G5^1*)tM{v@RtCD#PqC9>78?wxQI3(~ zO*ly!i$FZgSY*YDp7{wEVw-@oW4{+`iX=+k);u_!37=@twDo-2c;p|u9uX?K%vNz7 zC+<}4uSWV*sIIy=Jf4rb_{I6%^mSW1!!z;bu5zLcX`hEO_%8VJYb}noqu7eXwWnIF z9nK5xjbrH-+mrU40`W#ZBo`y^PK#R-;GIG7lXC8R>O5zK6bu#!8`p}*NPQQ-p;W`O zvs~D>{haPMUq9*q$uX{G7Kdw zMrge7R&-xXSZifu4{xH$v#IcRr&QLjYT9Q7$1aMw|7_-O3Wr^ zhj`n4XrWBXy?5Bq?XMizfYV)WJSP_H(e*fe_PT%mX~Nlo{AH@z;NXCq;AFKbkXFX8 zwc}>mmea+Q^>LGM_1!jqFFpsK>9QXGE+3lr#`$5!M#E3T=4oN1iFRvSWJ9IWCrS=d z{rlw)A+!HE6*zT&8_tB3=2;KZx2(7H^q}2YG*BWt8)12SbbknhMAvqn5fIP+wv^Z2Tf$GVqL(@!~dWH4iObb2T?)ES?Yc{ zw@OHFblav?jG>hkw7RX<-=-z7DVl5f3nWYn0h6H2ak0*6?)I=kO~hIJ@E~J0SEj2J zstuwhX`Z}7{LsOw!%g19M(_zu;-%CmA>Ij+$wBuB;RU-BGWWnj^z8Ooj`1i*bmlGo4yC6g#7ZTUhp~D{jc6#=p z2FtKH&6_^qI!&djtKYJrW#6-cV={(X4U1=GMOc?7m-!tj9B^h-L^eJin>N37DR5!g zXdDABYdxs42cU00J5oLkwY)b~vSa9`I8tBEwD?DMZpFKDz|UGYDqhf^Q5ib!N9bWT zDX-c1%)89E53k~F3|kc@-X+M@8f>?g#;dNags3I2Px*v-H2rxKET(GQMS(71Ry6-b zPWye3vgRxI(uc_1+CyL%EZRo}=FHuvSg)^=Uxg0uaSek?-5j1H5=3eE>d>a6gU8=f z(e6H9odw**qW%6I9nyNnJ7Dg8#TK%G;j`ZS=g3L+5^DN0Dgmj2!+=!9jB#IL2^n^7 z3fbh!?C%N>fuhkwzbBNIdiMbH5**cfg`!3K?Y>Ruobx^d?0xgIgvNF)V7Jr-%?7wbEaVR#@`L1*29$p(l z=!*B)rHHVMmXeJw+}V95)eT8Y|1*j$NSu)`Ac@m11j+xNwEoX}#(%72WU2JptucM- zL|qm{Y^!$DZI=sO7MscI4=_fKJcrdxj}2W-ASsi2eke4dv~$DrDts(`Af&c(^*`fn zk~mG8B{$G5&dN{uq3)$|BiK#UP0#I2=z@|xt9j$@^4j(MZ0cH3$<-2L3e3yS`#!33 z`2h@&Z(0s+XMwd6!T;`k<X(j`;J)+x1ah;Fq+`aBtN1ws|*T@0SeMKgu-%vuzHv|Il+sAn2X5S|{Xu$^<7mBOvvKD4>Z=dsaFczMt-_;9qLfjX)Gp-R3jXhaz7MN`bwP$#B+o6j~HjozfK7pL* z;)ao1Q{_?Oeio7hBVOOFzc=#zuuK|=0o(Ii3EIUEe&#!`vR>Hcs)u>OEiyZcY7k_Cc7k$yCm>J2$Yxx z^lE9Zi)&)&>oVT7lg6l|gAh(%-P!n&p;rK$4Y9t$XQ7~Aa1kNrhw6n5=2vpS+!EZyes%jp^bZT2keMUYo>!S`e0_MmgFJ= znLfAgnc*g~APi5p>GVt$g-3~&YIMsjP zx&5ipNh29wJ8G?eo1yto)w281dCj&t9arCjWMRT?1`otxze&KEfxj)j`S-p#Q^EiF z*WnPpJzKeYWh6Oqo=jfw@z(*cux`ZQWydBwUZTZx4u9~FvFlLpYd`=^59{->gdoq% zY}1E3aX`?y9y=Xzo-NZha{6O)Wp5V_=+UWG0F9d1FeAX!c(r#s4tW_XSqd&N?Q$l9 z0}l<238|rMI9U=45sOL2LME3Mj3~&0MrD{{hzZ&{&0_;0sW44@MqLv%Y&r*6+bH)>=z8Q2e*?Vkym{kx3XwXoj)3cg(h-hG1zh^h61 zWk~y0ZQGnA47e@z@xicjLKq}y-S3RRR6 zZ(7KJp%q_~2Q{StRRr%=#4T5XTj{#D!>3I})#@K`E>2B$186=r1f-nf~azw9HcqGboucrpWVI676W9=xKT^#%O0g*zxnn3Tk4cP=g*H% zgrvTB`BD#&1!$O|5Tpbj%+`S*v1u-C9K}Qnm3CRW+z${MzX=K8_uUenO_O`v3^jw#!JAN+tc%)al`K@0QKFMmG{c`zI$I(a3aDc}S*tlx zc%3?)4yx=hLk6~Arkk@OcQi#D1PfIdtoZMBijaDLCb!*VZ58*E&_qQ}v%3oeLLCf~ z^N^7v_PwJoorAl-gQ3XUIu1n+6{uR!qWuLM%x<^&TjG7U=eDB9KM*sR{Du2!bCBGc zYStN`iB>BRsk*A8iaxSNE{KrX4me6;BSo)i+W7yB|$3gqr|g?haO3_G(sTp7nNC8 z(xE3?1Dm|2?Uo_1;{!S^Sqf6Ya;x7f?b2U+@E~#;|6MFl^Q)G{o4?;R@YxEdgW==& zpL=hIS)1jm+=*PcjxZrOlpFUK-xiA-&n70uAR7~f<%^7sCW_xu&~IXwk|ExJ$luPs zHHxo#INIo*aWWn6?nhm{Te`I5)i3Mf=e|CvEeUAhO+p0vONLu<_KJuQRmTE>J&If; z5eU4i@caCT-N-eFZauw?a5rs-FvgA{bX7Xkm!gq|&2ond(Dk94U`bN57FjpoL|Epf z*)$lD9r=ujZMPnX2O_R%F~Wex%JQ9Ns<|@9Cme6F*A#%kn7c1PH0-%6bO(s!i?#a3 zcgp~#avqp^%|8d1A57`3k7Re~vr#(Lf7B!{24;7Uy3g2E3rFAU)L;j8n+7_LMv)RX6pEB+i^A;~31d&V! z`g6D1Sl7SPgi~}k2opFEn@LiTESgk-fq9P52Sr&lR|4IC_x-Pvga0?3 zO-Z@j98XW$yXjlUR=&F*4ldXejDX~bm>K!6v02~e{@#rR1dQ(9jp9zyPOlru+qAM3 zme+QPS6_2hInI6s5r;2+k<;INsl3yJhED7qHTUOZnXz_y%}3s~wsjFHP@!x}wTh=l zlmG5XP2fMK+jZdS40yYwI-Izte8?#Fjy=@HoFO7$eSf{eG+S8zefQ2FpSd?p$Ry)V zz*gS5l=J~d{MRVo4u8<1R0#Y}X=3#CF7zw`Bfl=^lGb~PhfEDdgX9r@oe6f{xU};* z|KW!;+LUTe-1E0a?)0vtL1yRSu@4jF8W*f<=kMc3gLSJ}%|_K)ZfB}KVnP~Rrk@Gc z&AaN|vKJmsCqs2|o1Sxy>Q_6A-U4@6rwlg<)?1mqwkYcF9y|W{zj#)$M5)_x%Ah{G zkP3f;0U?65DV4NvrzpRwa6eQ$$}d23FI1E**f%-`-o#s6 zy>r)ppRNiirRB)5&dH84K(WAA60~14oYM5`_Uv!d3qE@49Cptr_iJ0A`*zp<$!XS= z2J8b!c>i8@UjmFl7`B}*Ba}>pCfe!rP^e6ZAP%}W9Z3}Qs(u=^^~V^`AbPFkuDhu` zaed5UmY4X}N<*snwU{s-(Q`)E@9N+VHHZ|WIPE356`FLTzMsyzR}QhpaqKJ(>n)m~ zm{3G=;5Is<;dhO*FFFX0F%t!U$=juGAdp_}!|bGg=aZB3TWS?^?)&S}x)q<|Oj<>s;5`~`)zB8=9C_kfcM@ZcTaN8fT z9+sRI&YFjKkV|337$$dd=JDxo+E?kIw`!h#&Jg`A<2Q2aImg%GUheshcs-UtB{KD3 zrH%-*(QwBoafkghg`yFwJDs;h19}vCkRkkggj$XwLOd5p)a|^_@BASpJ-1TlyT0#{ zTm8guOl(+;Z*d+DeB^UvW|l+4459^ymL=V8d-bziI5VG39JFz@LNw- z{N|s%mDBz65x~-|8ZG-_GqGsY>d;(bEdP-OI`U>oRq;TF;NtV2eT&b}Lel()wF$W_` zDw5`GsS+!hfU5KyySc5`X4gyQg|H(M1hHCI-0oQQydrXctH`@d`3hhp8T#ES!($Yu zjLeb~f!Mw^iv3zrmnwQn`)7!>x0}t&v}_&!ixv}f7HGKgem0E2B*`ds0jnw!Lq$na zN8a`DZ!=i8(-$m66D=mhzMrpe8*+0mp8!DZhFzoEbavpTbo>Y_)w87PKKITA z_5LXxD7=0O1~>~-BvnxJ8Kpm40bpQBJ*C)mDYY9|?2GyGkPxPKa_A3l?@2ANF#@3> z@Tpq_Ii*PYSZ8!?2XSn_Ki8J8k)w}Qx4NK>qTtUd+E!9!Hujv{U)VGSjYQHPrlhKv z#vMs}PttRrO7Zo?RXR$V_!5?{86a5-XMg{@Ts`r?vhtDizD@+hRv95FsNcRo0{-Jk z%aJyYJQsB3q5Fmz$mF3k5jdkZ2}9BnTA6@ou_zhhz!7sz-!S&^ii-G`N-*|CHExj; z+#S*F@aIf>YvBUsLa8tmb;JgF(rq1=NlZlGnE1~l*Hv0^~K4)Qr8c@k${^ufbLUSYjKjkPBH|dxk!^juhjW)k9v`!L9(*A z9$I5N#RxkY0i+z*);49#ADr(f8}{)UlD3N0?*W&K$fUZ}lzG-j>fa{hf^Itpemm2dR5DIKuJyZBjhoI=-|At2WFG-cC?ON68cxT@WC~X^W>iQ zg#X5MQLE{>S4oHu8@|}&Qq+t|kAw$f?ko+6IN;tCaphxDXyUenbSR^BAQX^Iph#<7 z-AVzyC>R!Gh9gDc@$~aM!h)c|j#(_K@V?wHVHZsPNC@UWOkZL`9djU#)>;@pP`nu<;s=O!+E54I^g zK>-X)9xIungPn_TGKfo{7AyE-StIY|TV6U%@M#608vYC1f=lGMiDNph` zeJCtQC>sk$gBV+?zmrUrjkC+XzrSD7hHmPjbc#Jn{esE=Xg0m0t4~x!0zC++Ys2da zvdC}_*bR)Lnz(%|mPWR4FuReTpwqcTzSoft{0iy#TTO-*IXxkeibS@l66}&wf|t8H z3+NAffDjirpQtLaT^V*%s<@5{@Q*&{^XhgDRM&L_pt7G3B>+Y(0vHSvxGK6KTv5RU z^DxH34hJh*d~WMnNdLptX{IgI~t|O|jg@ zwK+%Mo?$(uo3HPU{VSa|B|1*2bog4|2q!T8#=-iVC9wj#J}=@-V7Z3+$`_zc?k$db zllb$tD)8O8sE9Z1d9%udaW_b10p$Pig_wfA7}aksQOPl%@H zvP3?=zK{xiLf}1LY26JA$}DK1V&tEpRhcO3ObPHVGY-`4)x7!&amt+XFDxNgM9P$s zSGVUiaaT2k*ZC%<<(Ik{p;13`px#hwbp^W-<7=YFV-wWhY=|PIlFV$(e`>w)^!Zxb zijVzx`{!>)I7O6Dj&XRwGDozix&BW=#7c68rVRr0Wbtw4nu}gS%XPhl>Jh4%D>T8? z*$zL6d_Fbwn$e8+Hv&|BE}af`wA^E@QyH$lYut!0(FBFkoZcj-r`$Vq+Epk=c zd8n(^X^mQx!P(BPYL#Fa?;h-E^P@+iq454_;q%!fSZ-a=ZE>~d8nu!rD5Y zrch3i)yyCzZN#^sQd*%&k$Gyb&W((`F{|cVAN+kZ`EU`#!)_j)8XEAU_nX|~1St$t z8}4M{qj%<&M$_15#pk>eGVqVWCW~jD&VwBq5jil_c);#jtFvY@OD|Nep=Bh_5Q+Az z`%INKmEH=?px0miBOgGh#hDhKEsQlx{x+E%->Ka=eGl4=2G^@OW827l3DDi)SlcWV>*sYy}!IQ`cXQ{j{(WMsTlZMAq3< zksT?2Y|33q4$`Ae1Fc9MtUitOoO;4qpIhk31W2kK23O&)A%Wmr{FL@QT09Fih1m0I zOtFHjBkGdRUs%YRS~&51NKC;}r%-R|6tyYV>%9dk7)V9y9Qq{@Az(`R^D-_{1gzjd z8Sh8WK33{IBrGi~0OMNeqT(R6-=!cK+?Cs|@>@%V?mBX8MOnoxn0wiw|OUDqlzIDe@vd zy~bFxSy+Qe6SMGz(MAdy$8oA0N9U11W6|nWLM;Jq@wh0ow0wd@=t8pu)NyE@&P`Sf zUF|u?Nm021MvMGaj18+)tXuV=*0$v;Bri3=WoKzC0 zAlYPiw5_t=NV=$f^sV!DDEmtRGDBgAp zN15<$ZB^}|ilkWc?1kBS@kt?{E)F(yWzuHQj{#=Ht`_`0y-Nu%*%@E5^I7~Pb0w|X zjerG18;?(SWdfM5r4!gi|3eEbbGo(yXlowql#y#UK=J~PmMU%4YKAYuN?)W*oz}J} z^L-J||4hoU?W6ZW6haqeFK~b?jy)YUmJ+3riMdl{=|xk3ebmT9Imc5p)#ZFJ)^94G zBu`-=V8mfVTT7-0yM6LV_}qYa{t#NOw?7%@lQQ@~NOXO3s2qAuM~C}?2NbZlSVn{T za2}B1;x>BY;LY;&U2iS3>RVE}i$atGktxN6KwZb$(-?t3htx66qMyA%e7jGQ5aD?g z2GzjJzhj6z(2xQFW@zw&FHqlVoGog4unx@0c{5qtV2!?YTl{VHPnV}^rr`{Ow#8z| z!$`mu#G8C0{gW)@Np?yn2_8?vrMpV#yFK$Qx&mA17Ne_yOfV5$L`b2t)#tf*Y8ozi zp&OLcXUwTFrSDt$)TDM^7`FI+Mi<*4e|j~|VP9&ob0`78_>hp{7|54I%s8$*DF`YJ z&nskN^&U+z29XyHqkLHU(P;fAsR)F9-}^xEsVD zi{b)9yAmT>KfbNcAJCD46MH*_iQ17R*@W!T(vic4ip;Q zygYKHLUxgMh0D!y$;1F6Mhy=uwl%YV|CrKd&qndXOWc_&B@2;q{8M@P)sR+X^pwrP zc!W0i?LabW_$M0)dUiC%_S_my+S9pwnUB)N4P)WO|Hj$TB`a?N z%P>Xn-GvHpszDkj_r$^9Th{b`;6h)~_K?i#C zXWgif0?XqJU?R}3xZdQknl+Wkhul|gJ84GqN{hsRqB*5^(!4>M^qj=lw*i5~WZViE zv~eJhst}K59Fv2#f#oVS&#~TXNs>Ty&(V=QuHB3?_Mn!W^s-_rEkXI3CP!$3PpKq}XtD6I*5+jJC#G~OA3cfV`;Z|G8 zzU9}Eldzq=bM+eUXRUTs`MgO1I`K=PK#l{JMA2rG2W%tLaaF(M4DUlo8b}-L3Aowm zGPipdOycEV#|2v=1p@g4(em@=R!|(Li3CmlTKh~M=Y(`g)-je67r^5R)a^IVDbS0P zfEtcV-Q1Hmu~-^0Sw#G z?+T0JCx?#@#n3ZUwIKpd@Mog#X|jJyR0hFPtl7~1L4m&wUGlQ1c@TVvuFLO%^HC0!>K?X;7xIz zECl;+eX|o9?Q z`P9RXnY0C*(80g%YqUvwru+m+y#HWuHa`a)Mz+jsa5f zMf%?lkqx=_9mWxHY#DS5kPmw~Z!~q9tyFCsws}|dZ}E}#$dzN99Ih-^=}`iHzA(Lr z7fFFjAvG0}TcORIsN?E93eg&(xR1A5Qc4U#Kb)A- z(~)&5*o+5y2#Yx@%6Y_jC{`v$e5mlX{921YB$v9HW?Yl*37{upU!efiO!??nh4ch{ z%m$X}S1eoDkw+ceDLK=IcWtz8*kpwR%O_tr2|0!pa+zp}bnT}!8>i0%`EffRdV9Tv zH`wJu{vtrK1Q-hg3Xv3%O|yGMrS?*ZF)h=SD)7sT0P@`?&$7lLkpjH7uaCFR8qF&NE%dVl zF>-50-hI_eQxA63>Wsk}Ij!vipaOZ_lu57Zn13YWfh@Q@UDH0wk`2Etoh_I+rK@#X z8E-QdanZye1BE(99ii;T8d<`2}^Z>6b zqdxnae+FI8I!;G++pUfUKxje6M56=!z({2l%X3&05mC{c}!GNBjvw@2|v+K8q>eSl=@gtud z1uZt;x1pU!kQ}Xjl~f6<&gB<8J-G~ivLjq4hv)KKdt4JDgUQ?6LU}((H&16c$|TXj zMjIQ&kM#XE$c$;vJ_~^cr9D(|?+x=0@`091x@f#RevOs$-fE|s$oDI zOc9_YhSF9f+F#Rn=8@lPLV?RRYhqfJpC7!N(x!!x)LIy!}#LADUm0e<|mZO z3R=DdL|o|$6uM$u=c`GGps)AR^${WqP>-?B>ssg1sbWAYS2hSxlg-&zCL*d%MvD3P z{RJp#T!%mTi|!Yv&7+G88-TxD*?uV;6`?>cy|p}zBIMJPMH z!z6V^daJ2F%1)9#9sUr|ItIYpX{DQ}9u{6~FMI{TMUS=M__R`C#F$=$z$}j~a?X>Z z_jNjLeFEsAjo?&tsT!)+3Xui^MVaz`g}W6cZh1~FDP$)i_UJD9d5nS%yF1WuBGHEkonJ_p;+vmf9v{=n{$qMMA~;yvOowH!wS)z&}aEe(4rL5z6Hkv?O#y>&&2meE?mh*{gian0B)1}f5|-y#?7zz?iANG7rCuEC(JxkvyZj?y z>v}u5U5=4lr{Q~8tLg#YG0Pw%_~Y#uB97&wkW5Ordvu$%{x8xy&#nAKss-d`KA%A{ zv#(kV&JvS#e^AANYvyj^JgojU2D;j;9tMD(&%~bZSugj@;W=J}XO?asuQo_xAgSQ~KqB`iJ=c5lm9|mB&Pq|#@N4Gh zPrfN#?M@H}(0_A9Ig#p?9kIzw*AYd|Fch`krkBZbd8MDFLc^PWpR+*3pK*O{(~tNn`c{pBzYvX zZT}kMu6>B^QaDZI37>0}CVl7!SB>>2dXYZlt&Q+7!-$c^36lIEJVB6orunr!gfymI z(88yC2y_(MvAh*UVv?NA`6Ezi5INT0_}pAZkE%eYtrTSg{qD&(<^PMaw~TA@egB80L6jJ!AT>s( zNJ!U!(IuTqcO#Q7VIVQOyOfkJQ9|iPknWNWY4Cr|&-eGbAKwod+qJ!}?L5xoUB}6H zx7{Xclp$z?BrvQ)7<@cALUXzC>H)Nk6TDix!Ce^I0)rA5-vI92LXXFl*?Q5yK1*pf zCXR4|rj1Bm4CKc_+xgNyi6|zs6ULhIiM$o3eEW)SbWa-g%0Nfdq0-PW^I>R`*B`MND0Zk)kqEns4^(J7D5A7GW%yAX6wHlY&R z1+ZD$c081h`>uz%O@0&)@qqlJGRu>iow26CKuqHVT%H{61cyQX170a>2n;M-Kb}<$#irI~+IsP-U zaC&1!=KWSm_z10tL;KM6Sj?Br0w zxGew~1Caj@prh^8<&a(3_~>PBLbczB?MGW5@{xs;eS1Shqcm;`qzap7M;)H5h*g6E z_-=Jo9*_032NAttP#s^5Jd1DOCq zl~z!~nAY5U)RXNc*5EUl! z&=sK%MhOeE>yOZ{YTne#SRfNd#MMKf@f-V7B#+(qURsJ4;;PU-RItdQX}^_n@1mc- zDXj6-H^?m6KQGu>>>lXi*R9;XY;W0EdCm(_RO_3FNl(=JA;LnHLz*lvkrU(3<>Fr% z;qMgXZxZ2eg=NX9A$r*SwsGcZvsVw{K5T&QKrJSVPiMoP4N%$eKZ%OISIIZZUng>i zeDJ(7*58EWuI*DdVdyN$ow7*(5CRlmI^BJ+ujA?8nxynV{cez!kLzMEs)R6`1U5P|HLISd;TJki|{QvzQThJ3cYPFjD*TS zteU>jbsSw6N+cr^t6o$zzW2{p6*b~Vo!$Li&w~sK z>T=Pe*^Ftj=`}E!yTxeJjxcb%QU`RQPRrLATDo`r&q_aHxdrIRN=#tWYs{@GsW3>F z038Uu(|;Q{0VuTN)%X*zS2~t-e@eK#ivnp^p9dY?@g?#j2QBlp6-jP1?WXp``%2iW zS7FgHU`jYqU$rfuV)b`1M!&)B6TO{CaLAD^Ib7lCf$2Av-{pAPSMSaIit0cY(IBMHH#|2c8t zF@&HyT5*}p6n}PTD`%v1irV1DYy1ufg}SbEyhvalVPXWQ^HUB+eM$`PNfiU-ojJ$) zJMd0k1&$AYgtm7DGs1{8DLiBbf(-0`mGR8d-vy^+EUY#! zp;woN>-GMc)y$_cI~_x#FEXf|{|;2uw7pc!iNfnlhBO3L8PLF9O8Vbzz0}F!4?6jD zKHK%i_Xg|GWIIvq;oHyU53Q78lV3ja_{#B6p^&p~Q+BIP!Jy#e2ul)7)nr42h5wy! ztWBRj<}qqv4(yhLR{wGGM@8z@N)PWL!F^|(W{lreJqNld$PwfZfqfBgJe>gi=zhC2 z!t1e4#E4B6)bo>pY;XL~o0a5noJV712+1U2nIWU@OY0}*IlV3)!?vFzJ!UyXO z-}X@J%etWH(xzppEKu-zXH(*(N8)3m zI{>C2{i1R{chQ%u*={1{j0B^YYz>TjkRO*7UD@uoZ8~YELzNW|>+kZj0(rF-Ym8d; zyzWCUruf$}mvOH;Q|1XX5U;aP6@@4_Sav?9`m&?4dG6QxkYdy~*b+Zg^+O0$`_t#< zDLDXUlVpXs@JhT9f8jPksyDPt7OAiAe7|-bF7H;{d290y*suHaB9=vhmy%!TI0^jK zP=JzMP6?c1mO+5qgsNkMVv1na>V7px`Ik|fLK}tKlKDHbz{)+CH;WD1d!bf2W9=1> zT{uY=BzzMT5g}`r1-0y&6{kn%Rm|tB7n62Uos5(ptT8|R?YbKToxsn6o(%f=Z~&0Y zcU^aL)VBO5+Ntb!(DScj9_r85xyj@kx%-u3onQlT4j0J=ovhh%Xm`E~~eaZs=KRnb*pNzG3RXsKJB;%>9M zV^a9id9T5)m6q(4f*{3X?0CxXAVmy=Et-JGSAp}6+%TvvVJC^Xv zIOtAkFgvrqIj-Wgal@J!a>h_?UanVS&@pHeV43Z+`Tv8^@ z0Zn7o9yTdr%Im+%kgmyJ1AYE_Gm98PpWNgz!yjltHB-r{K^4g~&J6y%ywG#g&xma8 z2^Nu%UJaW1$&dwYziN%pfz5srDZwJALjZh(qw3k0A;KQPv?jNLjKl9$Wg0C93a`Q> zM{d`ZYu93?%K}JNY9mi@d&0mJPKOK5>ZTV8hR|_^^*M8?{JJqxvk`(*iZZX%9G7Tb z=J9L>q|m=DAMk&@a2Lx7I!KYFM{M)m;KzL$6DrpSt?C#FA-(H_EYA<$b?ck$CPTGx zG2vY8+tjXK=Y39XpgrWR0+yPi*;$_L^c3_ldfc4MKbo}{*NPrG8CqAW6PqV042)f2%;M@r1+bC zH9KCkSxLq|C=@lqD;Th2^v3|HU|7(0MSXGF@V!cK>B{*pXFIdMsh|D*-LG-J2r$IX zaNE7&3$Ab!00s1zIqOh@Lqf!u!A%Wsun5{UEWbvh$vI%>HkGgO@4(m8@cc0VX9W0; zg%6pUymQ)HwLLUHN+mtXp1JsX9dqe_e|-4Y-$mkf*#7U)Pr;9eu0MDxXBP$#0?q`u$xAl-hjIckI_ZV_LB z84Gu>A^d8DkyePgofaGI!ryy8nDH>`aK^8SN#&qQ351qon8MtEzX$-4vU$q|T6EC4 zwDA(`6>0yE7NcvV;P;0(`bJ^3vU0A8BMpkaJ%!6muiw0Ok+1rz_E+Hx4)H;f50?^z z!-U#H1+4xZjKBf`#rMJU6P+*gu{!IM%pfw$A{k7kIodDV`O@eJmxYqb7PjotIj*rn zx=J?k6AY!ry?XKVSM-_-5BguiFkOUf3j{7tDFY_v$5j<9)+Q0u&+~{BolZxAOBMA8 z9QQXS)kvoEIJex^L@|69W6o~-dL&&7H#9E4DY?if;}%=G?TEkV?!c(1fNLhhJS+B_ zU>0lZ?q%43f(qZB2fe;7o&jxOk*cS+VAf#q-tAbXq`nvtow)sz^3_4W`N01aj_b4g z_N)D(-~H9t{N~a$IF?fyg3fMWikR#Ou7@^BS#CjD?2a=__2J&epsRzG7-x2x7Qqm#JwnC%mxoi z$-z*UTQprs6!NzThkw0kg#C^$?bT(1F37pOP7m|3_U{$d*kvF-=KBOj?LAZYNesx5 z=nzo@c#|9x>XNql^S+H z$1M*RmUx9hK??*OK;fOi?D!>K?eo&an|9^yS8nLC>H;F@hFD=hkJ=Ii^EfJ0lh5s}7>=u@XYZ#9(nG@3(0Ira9$rB$n?1{$3I2UEJ$+1Ph8xLPiB0lq2w zv-14f)EMA~g@Tuo*ph8f{+MNH!2i@lVj9U5t5!0{P%pt;qhW!(AFy5`i5*Q}9g25} zoust&qZ`K#3jBvCA=}-^ckcO%$>FUl(!@g#$4VjH#tovrO~wu;kYY{{+9s_150BiY zexsZoJ-N1D2WIx`1fjSnpwN5wca7*ZFK~#Q?ZT*5quMeAuSM}WpI`^o8T%L#x?-p_ z(}6;XrwI}iWEOUb{65z=*C~L}u7W)BeMSaJtl&bvF+E2R!0R}T`=={WKu&_%>KPAxay|u4aOvr}9l@-qDS$54CjPw3 z2UtVso*?z7eUKsFSp`g%=c(U48BW&3FFOc}-wM%qD1g;TdB#I8aD`El5|NC3Ap3?Q zeR^M$0@Qev1N%v@YSZ2M+(hd$LZF8&eS_>q#{)XXz$b&Qz80N+Ar4$tLC{GwjUTzJ zt91`6pN+uh$(q*ah|SpIt&{!{vWZZE)WFC6U4%tPPU{a^#O?k1ClY*!XDJaivQsG6#N+&K4g$#TDBvBL2KRsMz6SkclAp2?Hil_| zP55rI*0nH6$&5DDNlFB-GkI#D>(mRMaV&9?R1Tb-=AbwOOCJZ-G86Aa7-)62Kk;-O4#RUmgb4$Dqp0$(}-T(8MO|zIX zy=C86soX&Pm6#n%0FU)ZoPYkjhdWsxhwnY1B3Sg6AF$%tERS2aCE^S{4tMzjEDO%K zZ_5YlOY~b4U$Rl;lL234si;vc>?c6-iv4g}YN8+Bj!{$CEzK?^P;b+R$!*Y7e!agw ztNalq4T??-mlyuL5ATA*Zvw9B;Lu}bA^;~Y1#+YCsOi(t2%Ys;t)lbp0?Z)Tr1CqJ z{7H}k+s#UZtxAS>iY1&yD3J!bk)_F8{h1CWXl)a5W z=|qL1M)nb$dzGv0CGX&uN7ZxDRJ6*b1B!GSFLpQ)nH{GelI8}d_s~Vva`9aM5CAv1 zXTg1Gf(Up&l7W!r3SVs$g&e_H@C3;e$eYp1gilt?%76*{+bt|=%2qlgRMJcb1|=8Vz)06r zE%@LQ0+LFBpvUD!%B~vF69ME<;JRr0s65lWU@tI#6b9w9PgadGlUy!rcP+37;iWFE zQ=5ESssp3iq7ZKD_RzAPLxZqf;G!PKJrXo(z5HRBa`e&{NmUktJ5O+a$b!*yu&Jqm z-^ACs=+%}-4(Ugr!DU8VY^R>kA*m#v09Xe31`H*aLoZIoq^(zCm4@`z+3LXbr%Obwb`i;zz}5 zHkfmsQ*c~ed^#D5jQdi3Ib@xN5$g+gS@I@24~A#3fRP3?+3i2b?IaLo*%iB5W98~5 zooA0y!qwhGS)w;vO#F7PUO0w9tK~8uZZAp*KlpeurTqyYrWiI>#=f=w3545RXf+$! zH9N}xPCOWtgkO3qgl-%U-K=A}s_a;)0I$Q{77=>JyRz6ARFe#o5EIQ(V`td-hsmWTQFm?!=sq8k z!(+ptvUxWdUp)?#88ecT@RQp{=8vPl+vP;?F=}!l9-dE?+94uEO8|E~zhDp7kBLG! z@Tp*lDSkmpA@(n#cBN&d*+v1BWwN4is}f8gjPTu9`6u}BaErfX@%kI^gf>t#hbBJj zCtgLCth;yq;DOKRT@4F_m?7o{>lLm}w3qMAXX7!IWDY`F3`8nR>pm&5pc8p5Nx$OL z#u&5)t{O;9Zc=#FFEgiKX*8|e)300nKsFY zJj{mPpNzX(pXl{{I_aVC`*k~Hy_9#3ge^#h00n{7U>YhvHrM7@_0LBR&}M$}X$&mv zigU~$+bM0#A}M&pE8EEWxKQzz*2gohp_;qwp`P8s+mX<3*3tbDg&*rx!RpL;gy@K+ zd1e44U|9-TAM4}0(X5lGe$7?$g|0rG1`5ENQ7%JAv#2bd1)xJ)6zwFNBHcq+=fDcF zltLwIJ!tF=rE}4YkBAjWs~)yMh$9f|`Zp7RFmnSGj0bPl)0E_x96ejrAZ}YSL6YC4 zf4q-{d2Y%YC%^)CD=ily%>3$H{7L{?3nWa~NvG+yE0Y6T_k!4iEf|&EXhQM(B4UZi zAi_+Ye>a>H-1tv7<^7nDfgQ9p5$z|fIzgPtM z^AsW4{9RTh(CB~BT5spgLbUF;C)&Mrlu5^svxJprF%%*0cJugSh-+L#sarNXbN&XV zmo#`VPb7r}ghzp%1kFA^DFrC6ho657a>P($WU4mzEu0&S7P|pgOZ_t5=P?VLoB@~ z6<^?Q42%9R=cq6Lgi&L7XXSIb;Pl;UwNN6L<_PWIq~@`mx)#W)e_aFE-dU``H4g2B z=Usw8_f;j!Wm8~M{=*A3@smpL?CI_r#aF{AKcg?_^S~Agg@tz(iqzvq%`Y=v=jb=w zz{)8MJlRWS=!j>k3q3*Fcw}}cFa=1CT=L4j|_qAhaP`#=*6{;Qj3aFgAQghmsyboeG&Bn7^FvP(Z zlWd41vU+YASVr8qi`UGBCD*chOdLy>% zf|cOAGUI^Oyi$sk^&P=SGUY^p5&p~kd5Rc~4 zD7)qL5?5eARXuUXmo;&^2>{>u9SX<63^CDx8t$|%Py-{R;R(-g9zT{5nS2MXSO8?f zaX7r`W+h^Z$dEE6+#a`aVyS~B+>K3w5QaYrc{>f@by9ZM}UDj<5%mdgWaLwU-zb3$%OPr3dYU4nBN&dc+Lzurf>iMw}V zBbN>Z`D(}_I#*<+SUj|AC2>P+$^eYM%L*uJADr{Fbpivb598iVs8}pjF_Z}~jec%W zCUNy$Zf?5x$>{3fAq$QZJR-w8!~*O+lYc(Kd4e0~OPr-m6N zk+5&e&bxNyRa39JP(7bYSA8ufh&?M>3vqJaWN z^HSUE1z_!bE)3*06Zp>xw45AHs74T=$%)q}t6tLbQiL=7wsT z^zQ3~?)PZi_hS!Y-Pd#yvUo1o-7)^gMtGup*9CaduP#0sk)Ep=&{8D~7RulrVTs z^A9?h76*iz}16&6u)IMwcIRUdRGi?D^cJ7M}TZ72$$SdoaaHo2> z8Z@;#G>Xx{noHtR@m3L-VG#jUkUpt~v~d8*IBD*IuFt{-Aw+-%52~2Jzz4E!e2rQM zROZ7D0leT}*#*x(4(^z5#%PaOXl^Q_y$F&936+EcZ8Lz?8fYUpXWQwrQo?WaIP;5A z%)#N1H}?r~RzwIb44jg|``#}i=2sy|vUfs$oMz;J90A}#63`Gpn#V$)qmtgnE5mRg z-j72YW%T7{h)RH3wOsStAV3)bwu!}1RHk5j;N<8NL~&E{Uh|T+p-|t+?}f#vY%O;hdD!!XQh4q1Lm(NbH_mF-^Pf+z7q zdWRI=PM(KlQ61IxAT(^5Vlez-XsJn#BQ6(65vdEbKa+xArVMB6oCGpqf?hw^p*mrj zvn<-bp~OL85`{`(Qg4V8`Hy7Sd8mTtN`5@b_r6tKEB+Z~A0i1B&24`msAX?XZJPjF zgAk7-2Z>MVDxGZl0}(>G)9(g2bHS~GDrjMBl?2uozB~Q3_-g9gUC$6(+UxKBcIhr9 z`Y@HTI}+PIg9OvX$+i+2{%1SxyndBdN?$--$=M*s%R(ptOl|PBE5|R>HlkJyU1o__ zGxE|uK%U_eT8)1L+c}b~I21K<_`Cb6{S#g&Rc7hp<7_NG^9R3s=dD0|WK`x&Ze;JH z0jMj)6>LG!nuPsC=;Db>hQ&h3WuoT_9e&};Gg_h3M48Voarh6_IdaN)xGoQ(Z&u8# z4#F|on@Vb|NfD4D&OiTZNrI4=D49URlKUjYw%^@qE1NoPXpeZbJ1oh1N*M73UdPSB zE;Iduk+^!G3qE!iAjq=zn=UStWSuTLGoHEgwdt&W1$YL{qV0$kR7thI<-fPav{5Iz zzpb3Z=c?}s!qb-<=)z%MFB0bMv z0wh2@^!QN`A|j2bF)de9``SW+h4?cW4!*iTE~-g0?LKfqfkIIiQ&e!tC!_cvAw9`jE3M6rtw(4DzOs z)om?QayF=^k|#3i zBcX+hOBgAG7R{m<;z`lMIsG6NE_i+D=_g#p%aWYSQ1}{Ef%wh*)|-ukhd|3O5OBA_ zopI52vtZSCvq-TdV|?-}IKzN+xJRrq(#<`8IyRfaBxCv_R`2EPRBy|@l-AG>~J8w)_)BZmkd6^{k++6g?@uLqU zXVyJ?d+II04vu(Bf4{RY6`!ZTP8`aD&=R+D;(CJt#uWKyv&DGo&2A;<=H1j1eet;i zJK)LspWy4e3pI$ycyC94&_bM98Q0kO5_a*bQAG3FPCFCV-k?Y4_+)aH16NIo1A*FOE>`BBPKj^-c6 zyMVzOT{x~86e@KTO|Q4qXWk`DO(|M(PyRk_bstsX83R=^AWHY5kI zUth0OedpVawfKZCjeZ^m+{u^^Sv+a8%Wtt{FbG2=)4gPUhVYb?b<(#hwjs^XTMb%GHfG-zbL*}w5OKM_Twera zT;@1c?ZV)evYqF^??_Poh8LOt0V|Z4Gbk2HY_dpey~btmcJ!H|>2Ipa_Uqrj_QyzF z)5Gd{6!~Yv&)X*UAFS6;jAR@x_yp{1`JXIv56%dMn{8PZ#BIv_5MY&9AIxi<_4Ee+ zi-?svdAe`eWO~&-j8~p_gIV~S`!_fZMc5vY^p;xDYif1dk}LAHeDtK00355mDSZ`L z+=G)vfC%YM|D>EB_n7(wtMwF!u6tjkLYqX+`Z$_Bn%oj~JTTyk`nu7ByLlqa;fBmy zvpgB5@-zrvlZkG%es_sy9jm}Vm*bT1lxnD$gx%i|+FP^C%#W<=d=F=SH+@`?%xjX{}?jJeXnGt7F8x&mhs08AqGuxRP3_Vmrs?#5r&jvU! zDSE$M9)(d9zx|0NCr0R_m7ee@ABR>+;Bfv-`FG8W>H~okdGg)I83eH$?ORA(n=xSO zwabvbotW^Jx!|5B)%qNoXOk))Rny+Mk~J7B;z1uusX41``No34*0HJ{SG`-AqkQLzPxYc;>xRt1Fg=Lt{wOMGTGy_M>1!`rjwi)Lxi&Zby!hM zqwQ{JxB>&~#K3FPbV>7Cs>-P$mq8f+eC;gAEV(AFsDJ9a{D44o_MAm}^>hIE+=n+6HIpMy$4cw>gv zr7{W{AFW3F;%&J>R>Dti>$@1HIvpSZPMwDuCo|9$o*?LmMRoR7bRPRIpLL@S53;IW zt&_Kh;3=oB$%8YN4fy~VObn!ece33*Up2UNzm3MvUkdR09yPxnqSbMe&<};@GnvY= zOOMi{7I$&3=(D)vXCUPRBfm zq|_Ad=uHm}47rYfbNrvDy!pE-pQ@6$EXY1l1`GtGwj%oRfxFQKj z?5TwqBDm@~#epBIA0t`^x9(w_NpiSW zcOk!AApnUW^sX+?HgZ)>GYVq}Dg3u(UJ$<`qJOmnBwKFXeTH%z$Ii7}T*vpkZ(UK- zcd$E(Rx)96*hJX+OG5Q>wLthv`yqL}jYT)&c0Gv@Iz3CG)}4dB#Lv1P!_ z-!dumQ+*SUHE9GJo$1TP6!}J%PFyVWbu55N)pSTd@w%>{+!MHo-T|;~Rf*arID^Q& z3ora8j>P~^2?Eu?1Lq`oGqmGYa>pB$h~6?N`Lq9@ ztkg7imOC*F);hIEA7#!opK7>IjWlFb?fo+MuU?~=;S5;|`}L)7(an7%V{ZE1x0<@z zflySKS&=>?rBtJh9)ObnUk+8LRtaYw^@c|7FNd!Vc3pTm02ed)@t0OjZz&qYElR;wAaWST?*K#%rR5f{+k# zN;yaj5qPSvQI2nagZRU!!{u;91?w^gjCls%(fpuPU#FUZ^z{ML#!6cX$^`3rVmtII z5|Ya!wrsM%OBkb__B}9|{TaV4ZB^%Tny&kVZrtDEoi|T+INlOW(t{#jaxUM~wPiH* zx+XXq-d?8}P@UjOB@l85F}wc-7dp-85BuRn-DtHYV5fVW;m4 zXP%c!zUS00#3 z7opRMP{6yi_*@I_W*H{)6{WlU6emt&0{4khP=Du4s(9ob{nZwgS}9_=mcrS!5Jus} z{+Uc}&f)kM!abP6+#}ZQS4vyy67AbvpW=sFs>y#StiJU%|2!)Q9{NHs^*G?86>S~b zlIS$~{SSVwG{9?tinPDCkzR`*bfi)^@30pXw|{S`vxC0Z|HbNNQGO`jCqO7(4|Xt# zQ=Ui{W8CcLqJD6<`FJ>)EKk->gJVUkA3;6NbDPj*30OtomtN=tf^+xw5mHBN2XSKeZz5by#$W;vJ&y# zlN_s8>S2;kre{&9+D^-f_BL1zw4Y&2+?`I9%R_{tZqnY>8ZB?6Wud;mil&SIqd!jXuyq3 zUy>4m@VTQZcUWrI2Zt^F9^iw`%+?p+mAH2PrsO9k<7OGTz6`yH!1*bR;CVZnl1$wC zS{5=}>G=y7H}m!oImW8;in{Q-NH6Qt1}Sx@J$rwR`j(V9JukdZ*7*Wtt5=^1mI22l z^L|L=Dy-_6vU};$V$$<%wftAQ>rMQilNeU%K{`|L=LZ$>r4c_ZSZ!og8Xhs{$wCxG z2HkezRaHcI8eX+q$bG5hKU!W8$cL~DNI$erA=`X!+IHf|;Kvl-fr}9RBQdt@z59hQ zwYK5ldE-&lqs)_QGrFX8t()eXh_u0(dI4}@A<{S`&h`Z$@GX@B&RBT0`&MuApQ3vR zNh25}+uP*Px76L{UjF0ookF~C19#W-``vVG^bPzV?1=94v;}8y8Xd?zpO#l`fvj~x z;q{`;-Nw5zwh-uX--#Yy=S&bb$C!Pe~S?p0^D6NGBRZ97-1x2?Z2L(9Mgj4 zZ!`4=zzUS%^{tf@(ogjV3)2(tEl02)uC^aXJ9qyq+G|@Q@uFN6rEhp{g`u!GvULPE zj*nCim=|pPjbB|UBA80@ePqmYqprE^CS7!(VsqDis{8uH_xBX1!A+|EZ`Tc9RQs6B z3QS`&sws4@A&0=D`E!-6ix?8Quao1-y)j^%)q%^WYd4K55Yo&m@|zdx)cC}7vRdl- zXve>TwimW?oELA41-~zKQN~Wi2!l`Q$#d$(8MYIpn5uIhJFq^TZ!RO@Q_N;O$ZbpB zR;>{zB`o~Hh!oH$Go_)Sr_QoiO}p%>TrHWhEv&VmMv;+Q_^e1()bzWQ>v<6R(wk(( zg}xndIJV%tiA>u(dJ~i(JFreC$kSF6-0ji3`AHeuxmz zpsK`9*sSd_8Za^cz&5siGD7g-Ku~Tg$1`?HUjdKT@cCorD)O1lY{9>j0QR|C*g0$a zJ|zJ59MlI&Q$zFh_-X6hgTe5kpyS>z;X=C1MVK9%@P4yjc&ikLb#mi$pVcH60SaexGI@&r(N==<&HZKV z=J48#wMf8TseVo^Z8-Py-2V7Ss9q@0nV`K*}v8|4NkIN`nvX7V1^|m{~fzsmU@v?}6+dxWsw-sDz(xIz9<+#Kh(VH{MDe05rhd$bcxq! z{_%%Wm;o864&IzMsk2xIBHGd?N5zjVBcMo1KWS{K?12epUFMPc*AH?hgPJp)>3;$^ zKJM{#V8dMK>RXdjGIB3f@B{bH0!@#=d^VH-Cm{nNB`jNKP^Ts1JZx`@ZM!3d76U4o zD2Ab~x!=$+xiBNKX?fAmFX`|`nPT(J99sX*@{{*`6XPf3jr3M@rBqN`r7_&0xm_c{ zzb89q+PCty1u;f}g@l~zRb<0%EFvJ=?Z)+9}m^i3<0m_3I$nZ%KpKf85 zZ|uG3Yv#-6$&@;)t3T*EW-A+_nxZ{Qq)@T~=#MJu9_ggrMMZ1Ml}epvU7FAl&eD)F zf&Je3E+B$a5@7q@J4T)7!E9%J(Uw<3QyEd`*HeGT zw*-3&%KwT!?H(8mhyE{_MpjfK8-VL>)zk2J^MiLlDC7?3YH>|uNLyk-Sb65D#~Y6< zMF=4Rn~?rGTdXx6XIIR#Gqz)14gQ2ET-;VV8+8~!S?mWV@ZZHGDvlz3OYu4@_v;jT zZ`#8ujNVt?rj~seo7wzJeb2g+MMnZ=uS)c)5W@k1Z zSZ^19{)k|U?K#X+m3$f$76nVpUxw>^Zm9e(4lB7J6pYI-_rMmk0wb?_xYNx%)aBF2 zL=|Tdp&Jhkd1S_U?0Wkx7#9z^=(ZHH*?6eR_R_C&sd?D%OQoz`Oyr&L%cI?=rxSl> z(_S$uB%XeOyG!ue_0xOqd;kv*zTYbYSom zKN~l@PT7!Td~-km+w$U|DN7@jUq~UksTO-wUka{hOA%55-el z*82LY4|GK^n1zWt{z3Yp0_H8@Uf4&!@7&nVEKbag$7NBoZ45%gQ|vy?cnHzVW_C&~ zd)xaVDO{c}GOBLg9BX1YGO6pp!jzfm|7PTd_6`gqe2X^La3F398uCm^`E_nT#j1r# zB&*v9qM>w*Grr!NaJAv+5aYTMpf0+I}lrPA-I|(O$)~<-$Vgcf=;^hH4o7z zJHWN&h;oEab48~IDnGh;*${qXaZKnS560oB2L0Gv5{q{)1#%YoNT#p%;*Xy{Mq{7}G|7|PY z7QZf?!-JF&N0RAms-&##LMsO#CC^GEvi#pX2VUN86}KHk>(}ZvCCy~`8ck;0Gxh5_ z*26kcq2E8mx6}| zyzLlr8sV_EE!4>?Rq!GEaW&R(X%Iq5elW9qlef<=PNi%N_ep-Qm^n2bX$A8N6ZSUU zdIc7x(F$cHR+Z1f?jV`25%FIWU{W&r0nRCq@ox+w`k?M5U@szxK`zIDwOV|#uAd+H z#_V-(c&!+#f6D$N%so#o?1G1iNLaZB)r~w3qr`Z77c7qq&B5i1TMMJT7B*p2Kjc=UFQsBJ`uLzT}Iz{sJW0@qUTqx=Jfe znsRaHp5%N}%S~|#5Tndm8~B~t7Ra&ZN1A$C-TN>I!nPGWeI+tI<6el+ZBP{om4uK_ z$})n`uEr$`H_gP?YDHz2{emQiMw7X8nTVGMT=!AO)4cWHRF#yZpkv76G*^#;2J*S0 zKaN9SeF_Y;eV_%x4mcw>AH}vyz$J1Zl9MTIpd=$26`&&|w(>{{m^;g*M8YIPc%t}- z-k*wuKCCqPR`~6KT3xhMHkwKA#CHR0b_i(H#Ym)cna=>~1ct>`V(AXuNkG{Qiwtl8 zA{Og$_!UtU(VMKW^1#3vA~Bh*mg<+{9*V#*Jz;_WKh8VAG`|mME%E9z$4n3aP9=Jv zAgc^Ee|);N#Ljx z@O+bBJJ0eEz5l8<8}QO{yzXN#k5rVUj@6Hk)nl!fI`hhUWE$UUM|To{X~4$N zcMC(N#o5csf%m-BZjyIvBPTFK)b4+@oL%B1j0`(?Y5D;VtJH^5VZ| z<@(nXj;hi&kIGC?Q?05T)#>! z(awW&?KBPR%S*1@I>R~h&6$g_tKS7k=)Ey9jWc~Xd-vkr!(vM2W|5*ci;uoswPUHB zM1A~ZyXE#0vu<^9S0rEG`DNDa9OLVMoF%Z|fj!CM2Y)mp1H&RX^R)s|@j2a6VfJ#O-=$UlU?~s8=+k4-d_NCvWcQKaA! zG7oAkUg*GHkP#La|8&o?I}s64!OdTVVnlpy1;&YIJg@aNEURJf+NmO7#bXY)4GZ2R|f)hT^oio(2huyU|E(~4_rh5m=Lr4)$Z zdYMXvUY4_r3!4)2L`I_pTOC(loUt}CTWTq1=+l+J(^IFh$Zy;6JMMJ z3(0KWTbnyM({|av#OuqWXWjFXDJoalZgb$XeCy|+_Y!4F5TF$^LE>WzI~=ua$A&gnD%DZ0xx>RjF0mUf3|c3egOrZbu?$7Z`&PEGO z?ASDp$OXi_+ANdh|qI~e7EFB zbEu?O{tG_Tr!Xa8p85O6B#)RaU+!Koy4A-&|Jv`i#;v=uq~5Plv|IY`ZBCbj_Nlyb zu|1QL0}bzTy@@W`a((Bd$6;&Nd@MM7aF>ysRS!8;0rbExwn{CV1*EUc2QtY!nf>}W8WLx5m>B;YRl!;f}lip!rTE@uC z!M(gA+ghZoz-oP}J3Zk9!fu&1cNqk6UL27Xffr|@djB}E zj0HNPBqKF96+#+f^S-{$=M(#YwyFSKpp0UBk096p=ykBgQ13vwV6(7#$I@`?#M?lF zE?^kg4ZJcM=pvAJiZfDEOJGKVIk*k1JJ|I!0ccnWFnmCF)gr)hppQ`v1RG_9&7ZhU zu@%K?iZM1*&OMy6yO*7Tp)?yFr9rZlL4O+mlt72O%=T_J=46MG>(K<^l#n}FIlMd)W>aGb!vfYDS% z*N)zjKp1d&64(L6v@6gJMXe4IMlvuYPeF1Mv~mpaW(8(EP}5qFp^F8WgQkOc0O(uL A0{{R3 literal 0 HcmV?d00001 diff --git a/examples/RangeTestSensorv2/RangeTestSensorv2.ino b/examples/RangeTestSensorv2/RangeTestSensorv2.ino new file mode 100644 index 0000000..7e02c08 --- /dev/null +++ b/examples/RangeTestSensorv2/RangeTestSensorv2.ino @@ -0,0 +1,728 @@ +/* +This example code for Flutter is +Copyright 2015, Taylor Alexander and Flutter Wireless, Inc. + +Code for using BME280 adafruit libray, MPU9250 library and SDCard +using SDfat libraries implemented for Flutter developed by +MJS. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +#include +#include +#include "SdFat.h" + +#include +elapsedMillis timeElapsed; +#define sensorUpdate 100 + +//Set to 0 to not print to USB +#define DEBUG_PRINT 0 + +// Set to 0 to not log data +//#define LOGGER + + + +/********************************************************** + * Setup Flutter instance + **********************************************************/ +#include "Flutter.h" +boolean _running = false; + +Flutter flutter; +unsigned long delayTime; + +byte mydata = 0; + +/********************************************************** + * TinyPacks is data serialization format for constrained environments + * like 8-bit and 16-bit microcontrollers. It can be downloaded to your + * Arduino Libraries folder from https://github.com/francc/tinypacks + **********************************************************/ +#include "TinyPacks.h" + +/********************************************************** + * variables for tinypacks and output from BME280 + **********************************************************/ +float Temp = 0; +float Pressure = 0; +float Altitude = 0; +float Humidity = 0; +float accel[3] = {0, 0, 0}; +float gyro[3] = {0, 0, 0}; +float mag[3] = {0, 0, 0}; +int32_t timeStamp; + +/********************************************************** + * To test this program, flash one board as a transmitter and then + * comment out this line (with //) to flash another board as a receiver + **********************************************************/ +#define TRANSMITTER + +#ifdef TRANSMITTER + /********************************************************** + * Setup Adafruit BME280 Pressure Sensor on I2C + * Only needed for the transmitter side + **********************************************************/ + #include + #include + + #define SEALEVELPRESSURE_HPA (1013.25) + + #define BME_SCK 10 //d10 + #define BME_MISO 8 //d9 + #define BME_MOSI 9 //d8 + #define BME_CS 7 //d7, + Adafruit_BME280 bme; // I2C + //Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); + //Adafruit_BME280 bme(BME_CS); + + /********************************************************** + * DigitalIO provides Fast Digital I/O, Software I2C, and Software SPI + * developed by Bill Greiman and can be downloaded from + * https://github.com/greiman/DigitalIO. Version used here is modified + * to include the flutter Wireless board. It provided in the Flutter + * Wireless core-libs Library folder. + * + * Pin numbers do not correspond to whats on the pin map pdf + * what is used are the actual pin numbers + ***********************************************************/ + #include "DigitalIO.h" + const uint8_t SOFT_SPI_MISO_PIN = 8; + const uint8_t SOFT_SPI_MOSI_PIN = 9; + const uint8_t SOFT_SPI_SCK_PIN = 10; + const uint8_t SPI_MODE = 0; + SoftSPI spi; + + /********************************************************** + * MPU9250 driver library based on MBED implementation + * uses Software SPI + **********************************************************/ + #include + mpu9250_spi imu(BME_CS); + + //#define PACKET_SIZE 18 + //uint8_t data[PACKET_SIZE]; // data array to hold accelerometer and gyro x, y, z, data + //uint16_t ii, packet_count, fifo_count; + + /********************************************************** + * initiate TinyPacks as a writer inorder to transmit data + **********************************************************/ + PackWriter writer; + + #ifdef LOGGER + /********************************************************** + * Chip select may be constant or RAM variable for software + * SPI for the SD Card - Datalogging + **********************************************************/ + const uint8_t SD_SOFT_SPI_MISO_PIN = 24; + const uint8_t SD_SOFT_SPI_MOSI_PIN = 25; + const uint8_t SD_SOFT_SPI_SCK_PIN = 26; + const uint8_t SD_CHIP_SELECT_PIN = 6; + // SdFat software SPI template + SdFatSoftSpi sd; + + // Setup instance of SdFile + SdFile file; + + // Log file base name. Must be six characters or less. + #define FILE_BASE_NAME "Data" + + // Interval between data records in milliseconds. + // The interval must be greater than the maximum SD write latency plus the + // time to acquire and write data to the SD to avoid overrun errors. + // Run the bench example to check the quality of your SD card. + const uint32_t SAMPLE_INTERVAL_MS = sensorUpdate; + #endif + + /********************************************************** + * simple StopWatch class to measure elapsed time for + * logger + **********************************************************/ + #include + StopWatch logTime; + + + /********************************************************** + * Finish Transmitter + **********************************************************/ + +#else + /**********************************************************/ + //initiate TinyPacks as a writer to unpack data + PackReader reader; + + /**********************************************************/ + /*********************** FINISH RECEIVER *****************/ + /**********************************************************/ +#endif + +//TinyPacks Constants, need for both pack and unpack +#define MAX_PACKED_DATA 256 +unsigned char packed_data[MAX_PACKED_DATA]; +int packed_data_length; + +/**********************************************************/ + +void setup() +{ + //Starts Serial on pins D2 and D3 + Serial.begin(115200); + spi.begin(); + + /******************************************************************** + * Using SerialUSB has it quirks so becareful in using it + * 1. It appears that the radio is somehow linked to the operation + * of the USB so you can not use it unless you actually have it + * operational either as a transmitter or as a receiver. + * 2. When you first start the radio as in the initRadio function + * you can not use SerialUSB, if you try it will hang the sketch + * at the initializing the radio. + * 3. After loading a sketch and you restart the Flutter (may restart + * automatically but more often than not you have to unplug it + * and plug it back in) it will appear as a Arduino Due programmers + * port. Not to worry it still works. + * 4. If you open the serialUSB port while the sketch is running and + * then close it, the sketch will stop, period and you have to + * disconnect from the computer and then reconnect it. This goes + * for both the transmit and receive sketches + * 5. Bi-directional communction is not implemented. + * + *********************************************************************/ + SerialUSB.begin(115200); //Starts Serial output on USB Connector + + // I put a delay of 5 seconds here only to give me time to open SerialUSB + // in case i want to see the data while debugging + delay(5000); + + // Initialized the radio and network + initRadio(); + + // Since the sketch works for both transmit and recieve this starts the + // transmitter portion of the sketch + #ifdef TRANSMITTER + + // Initializing the BME280 + SerialUSB.println(F("BME280 Initializing...")); + bool status1; + + pinMode(BME_CS, OUTPUT); //for spi only + // default settings for the BME280 + status1 = bme.begin(); + if (!status1) { + SerialUSB.println("Could not find a valid BME280 sensor, check wiring!"); + while (1); + } + + delayTime = 1000; + SerialUSB.println(); + + if(imu.init(1,BITS_DLPF_CFG_188HZ)) + { + SerialUSB.println(F("Couldn't initialize MPU9250 via SPI!")); + } + + SerialUSB.print(F("WHOAMI = ")); SerialUSB.println(imu.whoami()); //output the I2C address to know if SPI is working, it should be 104 + delay(1); + SerialUSB.print("Gyro_scale = "); SerialUSB.println(imu.set_gyro_scale(BITS_FS_2000DPS)); //Set full scale range for gyros + delay(1); + SerialUSB.print(F("Acc_scale = ")); SerialUSB.println(imu.set_acc_scale(BITS_FS_16G)); //Set full scale range for accs + delay(100); + SerialUSB.print(F("AK8963 WHIAM = ")); SerialUSB.println(imu.AK8963_whoami()); + delay(1); + imu.AK8963_calib_Magnetometer(); + + delay(100); + + #ifdef LOGGER + /******************************************************************** + * Initialize SD Card and set up file name + ********************************************************************/ + const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1; + char fileName[13] = FILE_BASE_NAME "00.csv"; + + // Initialize sd. + if (!sd.begin(SD_CHIP_SELECT_PIN)) { + sd.initErrorHalt(); + } + + // Find an unused file name. + if (BASE_NAME_SIZE > 6) { + SerialUSB.println("FILE_BASE_NAME too long"); + } + + while (sd.exists(fileName)) { + if (fileName[BASE_NAME_SIZE + 1] != '9') { + fileName[BASE_NAME_SIZE + 1]++; + } else if (fileName[BASE_NAME_SIZE] != '9') { + fileName[BASE_NAME_SIZE + 1] = '0'; + fileName[BASE_NAME_SIZE]++; + } else { + SerialUSB.println("Can't create file name"); + } + } + if (!file.open(fileName, O_CREAT | O_WRITE | O_EXCL)) { + SerialUSB.println("file.open"); + } + + SerialUSB.print(F("Logging to: ")); + SerialUSB.println(fileName); + + SerialUSB.println(F("Done.")); + + /*********************************************************** + * Write Header to file + */ + writeHeader(); + #endif + + #endif +} + + +void loop() +{ + // Checks status of the flutter radio + status(); + + // Time for next record. + logTime.start(); + + #ifdef TRANSMITTER + if (timeElapsed > sensorUpdate) { + mydata++; + + //Get BME280 Sensor Data + Temp = bme.readTemperature(); + Pressure = bme.readPressure() / 100.0F; + Altitude = bme.readAltitude(SEALEVELPRESSURE_HPA); + Humidity = bme.readHumidity(); + + accel[0] = imu.accelerometer_data[0]; + accel[1] = imu.accelerometer_data[1]; + accel[2] = imu.accelerometer_data[2]; + gyro[0] = imu.gyroscope_data[0]; + gyro[1] = imu.gyroscope_data[1]; + gyro[2] = imu.gyroscope_data[2]; + mag[0] = imu.Magnetometer[0]; + mag[1] = imu.Magnetometer[1]; + mag[2] = imu.Magnetometer[2]; + + + /**********************************************************/ + imu.read_all(); + if (DEBUG_PRINT == 1){ + SerialUSB.print(F("Gyro : ")); + SerialUSB.print(gyro[0]); SerialUSB.print(F(",")); + SerialUSB.print(gyro[1]); SerialUSB.print(F(",")); + SerialUSB.print(gyro[2]); SerialUSB.print(F(",")); + SerialUSB.print(F(" Acc : ")); + SerialUSB.print(accel[0]); SerialUSB.print(F(",")); + SerialUSB.print(accel[1]); SerialUSB.print(F(",")); + SerialUSB.print(accel[2]); SerialUSB.print(F(",")); + SerialUSB.print(F(" Mag : ")); + SerialUSB.print(mag[0]); SerialUSB.print(F(",")); + SerialUSB.print(mag[1]); SerialUSB.print(F(",")); + SerialUSB.println(mag[2]); + } + + #ifdef LOGGER + /************************************************************** + * Write data to the SD Card + **************************************************************/ + logData(); + + // Force data to SD and update the directory entry to avoid data loss. + if (!file.sync() || file.getWriteError()) { + SerialUSB.println("write error"); + } + #endif + + /*************************************************************** + * packet data to transmit + ***************************************************************/ + packData(); + + // send a byte array over the radio + flutter.sendData(packed_data, packed_data_length, 2); // 2 is car's address + + switch ((mydata - 1) % 3) + { + case 0: + flutter.setLED(RED); + break; + + case 1: + flutter.setLED(BLUE); + break; + + case 2: + flutter.setLED(GREEN); + break; + + default: + break; + } + + delay(30); //spend some time smelling the roses + } + //************************************************* + // END TRANSMITTER SECTION + //************************************************* + + #else + if (flutter.dataAvailable() > 0) + { + int packetSize = flutter.nextPacketLength(); + unsigned char array[packetSize]; + flutter.readBytes(array, packetSize); + + SerialUSB.print("Packet Size: "); + SerialUSB.println(packetSize); + + for (int i = 0; i < packetSize; i++) + { + SerialUSB.print("["); + SerialUSB.print(array[i]); + SerialUSB.print("]"); + } + + // Have to start at index 5 since 0-4 are used as a header + // by the radio + for (int i = 5; i < packetSize; i++){ + packed_data[i-5] = array[i]; + } + + unpackData(packetSize); + + SerialUSB.println(); + + mydata = array[6]; + int rssi = flutter.packetRSSI(array, packetSize); + SerialUSB.print("RSSI: "); + SerialUSB.print(rssi); + SerialUSB.println(" dBm"); + + for (int j = 0; j > rssi; j--) + { + SerialUSB.print("="); + } + + SerialUSB.println(""); + + switch (mydata % 3) + { + case 0: + flutter.setLED(RED); + break; + + case 1: + flutter.setLED(BLUE); + break; + + case 2: + flutter.setLED(GREEN); + break; + + default: + break; + } + flutter.nextPacket(); + } + #endif +} + +#ifdef LOGGER + //------------------------------------------------------------------------------ + // Write data header. + void writeHeader() { + file.print(F("micros")); + file.print(F("gx")); file.write(','); + file.print(F("gy")); file.write(','); + file.print(F("gz")); file.write(','); + //SerialUSB.print(F(" Acc : ")); + file.print(F("ax")); file.write(','); + file.print(F("ay")); file.write(','); + file.print(F("az")); file.write(','); + //SerialUSB.print(F(" Mag : ")); + file.print(F("mx")); file.write(','); + file.print(F("my")); file.write(','); + file.print(F("mz")); file.write(','); + + file.print(F("Temp")); file.write(','); + file.print(F("Pres")); file.write(','); + file.print(F("Alt")); + file.println(); + file.println(); + } + + //------------------------------------------------------------------------------ + // Log a data record. + void logData() { + + // Write data to file. Start with log time in micros. + file.print(logTime.value()); file.write(','); + + //SerialUSB.print(F("Gyro : ")); + file.print(imu.gyroscope_data[0],6); file.write(','); + file.print(imu.gyroscope_data[1],6); file.write(','); + file.print(imu.gyroscope_data[2],6); file.write(','); + //SerialUSB.print(F(" Acc : ")); + file.print(imu.accelerometer_data[0],6); file.write(','); + file.print(imu.accelerometer_data[1],6); file.write(','); + file.print(imu.accelerometer_data[2],6); file.write(','); + //SerialUSB.print(F(" Mag : ")); + file.print(imu.Magnetometer[0],6); file.write(','); + file.print(imu.Magnetometer[1],6); file.write(','); + file.print(imu.Magnetometer[2],6); file.write(','); + + file.print(Temp); file.write(','); + file.print(Pressure); file.write(','); + file.print(Altitude); + file.println(); + } +#endif + +/********************************************************** + * TinyPacks - Pack and Unpack routines + **********************************************************/ +void packData() +{ + //Pack Sensor Data + writer.setBuffer(packed_data, MAX_PACKED_DATA); + writer.openMap(); + writer.putString("timeStamp"); writer.putInteger(logTime.value()); + writer.putString("Temp"); writer.putReal( Temp); + writer.putString("Pressure"); writer.putReal( Pressure); + writer.putString("Altitude"); writer.putReal( Altitude); + writer.putString("Humidity"); writer.putReal( Humidity); + + writer.putString("ax"); writer.putReal( accel[0]); + writer.putString("ay"); writer.putReal( accel[1]); + writer.putString("az"); writer.putReal( accel[2]); + writer.putString("gx"); writer.putReal( gyro[0]); + writer.putString("gy"); writer.putReal( gyro[1]); + writer.putString("gz"); writer.putReal( gyro[2]); + writer.putString("mx"); writer.putReal( mag[0]); + writer.putString("my"); writer.putReal( mag[1]); + writer.putString("mz"); writer.putReal( mag[2]); + + writer.close(); + packed_data_length = writer.getOffset(); + /**********************************************************/ + + // Prints the packed data from TinyPacks as a debugging aid + if(DEBUG_PRINT == 1){ + SerialUSB.println(packed_data_length); + SerialUSB.println("PACKED DATA"); + for(int i = 0; i < packed_data_length; i++) + SerialUSB.print(packed_data[i]); + SerialUSB.println(); + } + +} + +#ifndef TRANSMITTER +void unpackData(int packetSize) +{ + // Unpack + reader.setBuffer(packed_data, packetSize-5); + reader.next(); + if(reader.openMap()) { + while(reader.next()) { + if(reader.match("timeStamp")) timeStamp = reader.getInteger(); + else if(reader.match("Temp")) Temp = reader.getReal(); + else if(reader.match("Pressure")) Pressure = reader.getReal(); + else if(reader.match("Altitude")) Altitude = reader.getReal(); + else if(reader.match("Humidity")) Humidity = reader.getReal(); + else if(reader.match("ax")) accel[0] = reader.getReal(); + else if(reader.match("ay")) accel[1] = reader.getReal(); + else if(reader.match("az")) accel[2] = reader.getReal(); + else if(reader.match("gx")) gyro[0] = reader.getReal(); + else if(reader.match("gy")) gyro[1] = reader.getReal(); + else if(reader.match("gz")) gyro[2] = reader.getReal(); + else if(reader.match("mx")) mag[0] = reader.getReal(); + else if(reader.match("my")) mag[1] = reader.getReal(); + else if(reader.match("mz")) mag[2] = reader.getReal(); + else reader.next(); + } + reader.close(); + + // Print unpacked data + SerialUSB.println(); + SerialUSB.println("Map contents:"); + SerialUSB.print(" Temp: "); + SerialUSB.print(Temp); + SerialUSB.println(" Press: "); + SerialUSB.print(Pressure); + SerialUSB.println(" Altitude: "); + SerialUSB.print(Altitude); + SerialUSB.println(" Humidity: "); + SerialUSB.print(Humidity); + SerialUSB.println(); + + SerialUSB.print(F("Gyro : ")); + SerialUSB.print(gyro[0]); SerialUSB.print(F(",")); + SerialUSB.print(gyro[1]); SerialUSB.print(F(",")); + SerialUSB.print(gyro[2]); SerialUSB.print(F(",")); + SerialUSB.print(F(" Acc : ")); + SerialUSB.print(accel[0]); SerialUSB.print(F(",")); + SerialUSB.print(accel[1]); SerialUSB.print(F(",")); + SerialUSB.print(accel[2]); SerialUSB.print(F(",")); + SerialUSB.print(F(" Mag : ")); + SerialUSB.print(mag[0]); SerialUSB.print(F(",")); + SerialUSB.print(mag[1]); SerialUSB.print(F(",")); + SerialUSB.println(mag[2]); + SerialUSB.println(); + /**********************************************************/ + } +} +#endif + +/********************************************************** + * Radio routines + **********************************************************/ +void initRadio() +{ + flutter.band = BAND; + flutter.setNetworkName("Range Test"); + Serial.println("Initializing..."); + + if (flutter.init() == true) + { + Serial.println("Init success."); + flutter.ledLightShow(); + delay(500); + //analogWrite(LED_R, 128); + } + else + { + flutter.setLED(RED); + Serial.println("Init failed."); + + while (true); + } + + //flutter.setAddress(1); + #ifdef TRANSMITTER + flutter.setAddress(1); + #else + flutter.setAddress(2); + #endif + flutter.connect(1); //form a network with this and one other device +} + + void status() +{ + if(flutter.getState()!=NORMAL_OPERATION) //if we aren't synchronized with another radio, just loop and blink lights. + { + if(millis()%400<200) + { + flutter.setLED(RED); + } + + else + { + flutter.setLED(BLUE); + } + + } +} + + + +void button1() +{ + interrupts(); + int val = digitalRead(BUTTON1); //top button + + if (val == HIGH) + { + // _button1=255; + } + else + { + // _button1=0; + } + +// buttonsChanged=true; +} + +void button2() +{ + interrupts(); + int val = digitalRead(BUTTON2); +#ifdef FLUTTER_R2 + + if (val == HIGH) +#else + if (val == LOW) +#endif + { + //_button2=255; + } + else + { + //_button2=0; + } + +// buttonsChanged=true; +} + +void systemReset() +{ + flutter.setLED(0, 0, 255); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 0, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 255, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 0, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(255, 0, 0); + delayMicroseconds(16000); + delayMicroseconds(16000); + flutter.setLED(0, 0, 0); + initiateReset(1); + tickReset(); +} + + + +void radioInterrupt() +{ + flutter.interrupt(); +} + +void softInt() +{ + flutter.processSoftInt(); +} + +extern boolean tickInterrupt() +{ + if (!flutter.initialized) + { + return true; + } + + return flutter.tickInt(); +} + + +