diff --git a/README.md b/README.md index 3539041c9..c72d2af6d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Tulip CC supports: - Can load PNGs from disk to set sprites or background, or generate bitmap data from code - Built in code and text editor - Built in BBS chat room and file transfer area called **TULIP ~ WORLD** -- USB keyboard support +- USB keyboard, MIDI and mouse support, including hubs - Capactive multi-touch support (mouse on Tulip Desktop) - MIDI input and output - I2C / Grove / Mabee connector, compatible with [many I2C devices like joysticks, keyboard, GPIO, DACs, ADCs, hubs](docs/tulip_api.md#i2c--grove--mabee) @@ -96,7 +96,7 @@ edit("game.py") ### Input and user interface -Tulip supports USB keyboard input and touch input. (On Tulip Desktop, mouse clicks act as touch points.) It also comes with UI elements like buttons and sliders to use in your applications, and a way to run mulitple applications as once using callbacks. More in the [full API](docs/tulip_api.md). +Tulip supports USB keyboard and mice input as well as touch input. (On Tulip Desktop, mouse clicks act as touch points.) It also comes with UI elements like buttons and sliders to use in your applications, and a way to run mulitple applications as once using callbacks. More in the [full API](docs/tulip_api.md). ```python (x0, y0, x1, y1, x2, y2) = tulip.touch() diff --git a/docs/getting_started.md b/docs/getting_started.md index 5e50f279d..9f79d2333 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -31,10 +31,11 @@ The Tulip board you got can work on its own. Most people will want to add a coup Tulip is a very command-line centered interface with some touch controls. You'll have a much easier time using Tulip if you connect a USB computer keyboard to it for typing. It turns the Tulip into a portable "deck" for whatever you can imagine. -You can use _almost_ any USB computer keyboard you already have. However, a keyboard including an internal USB hub will not work. You can tell if a keyboard includes a hub if it has one or more subsidiary USB sockets (e.g. for connecting a mouse), or if it includes an integrated pointing device (trackpad, trackball etc). These keyboards are not compatible with Tulip. +You _should_ be able to use any USB computer keyboard you already have. We're always surprised to find weird keyboards that don't follow all the "rules", so if yours doesn't "just work", get in touch and we'll help you debug! [If you want a more compact hardware keyboard, the tiny "cardKB" will work great with Tulip, plugged into the I2C port.](https://shop.m5stack.com/products/cardkb-mini-keyboard-programmable-unit-v1-1-mega8a) + ### DACs or ADCs for modular synths If you use modular synths, Tulip is a powerful and fun control surface and way to "program" your modular rig or older analog synth in Python. You can use the existing MIDI ports to do a lot, but if you have CV gear, we recommend getting a I2C DAC to control it. [You can get a 2 channel DAC with modular ready 3.5mm jacks from Makerfabs](https://www.makerfabs.com/mabee-dac-gp8413.html). You can use up to 8 at once for a total of 16 CV outputs! @@ -43,7 +44,11 @@ If you use modular synths, Tulip is a powerful and fun control surface and way t ### Type-A 3.5mm MIDI adapters to full size -Most modern MIDI equipment now uses 3.5mm jacks for MIDI connectors. If you have the older 5-pin DIN connectors and want to use them on Tulip, you'll want to pick up a couple of [Type-A 3.5mm adapters](https://www.amazon.com/Kurrent-Electric-Type-3-5mm-Adapter/dp/B0C2RLB3SL/). +We support both USB MIDI and "standard" TRS MIDI. Most modern MIDI equipment now uses 3.5mm jacks for MIDI connectors. If you have the older 5-pin DIN connectors and want to use them on Tulip, you'll want to pick up a couple of [Type-A 3.5mm adapters](https://www.amazon.com/Kurrent-Electric-Type-3-5mm-Adapter/dp/B0C2RLB3SL/). + +### A hub for USB MIDI + +If you want to use a USB MIDI device (like a keyboard or a USB-MIDI adapter), we support that on Tulip from the `USB-KB` connector. We also support simple hubs, since you'll want to connect both a USB MIDI plug and a typing keyboard. USB hub support works but is in constant development, so please let us know if your setup doesn't work! ### A small li-po battery @@ -51,6 +56,8 @@ Tulip is a low power device and can run on battery for portable computing. There Larger batteries will work great and last longer, but you'll have to remove the back case to make them fit (or mount the battery some other way.) [This 5200mAh battery pack](https://www.amazon.com/XINLANTECH-Rechargeable-Bluetooth-Electronic-Batteries/dp/B0C2VFTDPY) will last many many hours on Tulip and fit right on the PCB with the back case removed. +**Please make sure you have the polarity right!** The red cable on the battery should be going to the side of the connector with a + sign on the Tulip board. + ### An extra Alles or five diff --git a/docs/music.md b/docs/music.md index a7c3aa50c..fe4ce6f0d 100644 --- a/docs/music.md +++ b/docs/music.md @@ -19,6 +19,7 @@ Tulip is a **music computer** where everything about the underlying synthesis an - Control CV outputs for modular synths and analog synths, with built in waveforms and sample & hold - Sample CV inputs from an ADC to control other events on Tulip - Send MIDI in and out, from code. You can write code to respond to MIDI messages to do whatever you want + - Use USB MIDI devices like keyboards or synthesizers or adapters connected to Tulip's USB-KB port (with a hub to still support typing keyboards) - Share a common sequencer clock across multiple apps, for example, a drum machine and an arpeggiator - Add global EQ, chorus, echo or reverb to the audio output - A scale and chord library to define musical notes in code, e.g. `music.Chord("F:min")` @@ -31,7 +32,7 @@ If you're using [Tulip Desktop](tulip_desktop.md) instead of a real Tulip, thing ## The built-in Tulip synthesizer -When you start up your Tulip, it is configured to receive MIDI messages from the MIDI in port. You can plug in any MIDI device that sends MIDI out, like a MIDI keyboard or your computer running a sequencer. +When you start up your Tulip, it is configured to receive MIDI messages from the MIDI in port (or the USB MIDI port if connected.) You can plug in any MIDI device that sends MIDI out, like a MIDI keyboard or your computer running a sequencer. Try to just play notes once you've turned on Tulip, By default, MIDI channel 1 plays a Juno-6 patch. Notes on channel 10 will play PCM patches, roughly aligned with General MIDI drums. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 8e28972de..1c4f4d19e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -78,7 +78,7 @@ You can see how to use them with Olav's `boot.py`: `world.download('boot.py', 'o ## USB computer keyboard not working -If your USB computer keyboard is not working, the most likely explanation is that it appears as a "hub" instead of a bare keyboard to Tulip. At this time (we're working on it!) Tulip only supports "root" devices. You'll have to try another keyboard. If you think your keyboard should work, please find us on [issues](https://github.com/shorepine/tulipcc/issues) or the [Discord](https://discord.gg/TzBFkUb8pG) and we'll help out! +If your USB computer keyboard is not working, please find us on [issues](https://github.com/shorepine/tulipcc/issues) or the [Discord](https://discord.gg/TzBFkUb8pG) and we'll help out! We have tested many keyboards but since we have to build this support ourselves there are likely edge cases we haven't run into yet. ## The Mabee DAC is not correctly sending CV / Gate values to your modular synthesizer diff --git a/docs/tulip_api.md b/docs/tulip_api.md index 6967858ac..e560485f5 100644 --- a/docs/tulip_api.md +++ b/docs/tulip_api.md @@ -285,7 +285,9 @@ def run(screen): ## Input -Tulip supports USB keyboard input and touch input. It also supports a software on-screen keyboard, and any I2C connected keyboard or joystick on Tulip CC. On Tulip Desktop, mouse clicks act as touch points, and your computers' keyboard works. +Tulip supports USB keyboard input, USB mouse input, and touch input. It also supports a software on-screen keyboard, and any I2C connected keyboard or joystick on Tulip CC. On Tulip Desktop, mouse clicks act as touch points, and your computers' keyboard works. + +If you have a USB mouse connected to Tulip (presumably through a hub) it will, by default, show a mouse pointer and treat clicks as touch downs. ```python # Returns a mask of joystick-like presses from the keyboard, from arrow keys, Z, X, A, S, Q, W, enter and ' @@ -503,6 +505,10 @@ See the example `seq.py` on Tulip World for an example of using the music clock, Tulip supports MIDI in and out to connect to external music hardware. You can set up a python callback to respond immediately to any incoming MIDI message. You can also send messages out to MIDI out. +You can use MIDI over USB as well, using the `USB-KB` connector. Note this is meant as a **host** connector: you can connect USB MIDI keyboards or USB MIDI interfaces to Tulip. You cannot connect Tulip directly to a computer as a "USB MIDI gadget". If you want your Tulip to control your computer, use a MIDI interface on your computer and wire Tulip's MIDI out to it. + +If you have a USB MIDI adapter connected, MIDI out from Tulip will go to USB, not the TRS MIDI connectors. MIDI in can come into either TRS or USB. + By default, Tulip boots into a live MIDI synthesizer mode. Any note-ons, note-offs, program changes or pitch bend messages will be processed automatically with polyphony and voice stealing, and Tulip will play the tones with no other user intervention needed. By default, MIDI notes on channel 1 will map to Juno-6 patch 0. And MIDI notes on channel 10 will play the PCM samples (like a drum machine). diff --git a/docs/tulip_board.md b/docs/tulip_board.md index e1d9e18b4..f331b6f29 100644 --- a/docs/tulip_board.md +++ b/docs/tulip_board.md @@ -11,7 +11,7 @@ If you're comfortable with surface mount soldering, you can put together your ow [The BOM for the latest revision is here.](https://github.com/shorepine/tulipcc/blob/main/docs/pcbs/tulip4_board_v4r9/tulipcc-bom.xlsx) [KiCad files are here.](https://github.com/shorepine/tulipcc/tree/main/docs/pcbs/tulip4_board_v4r9) -Almost_ any USB keyboard should work. Please ensure it's just a keyboard -- if it has a trackpad, or extra USB ports on it, or anything else, it likely [won't work as we only support single root USB devices.](https://github.com/shorepine/tulipcc/issues/40). +_Almost_ any USB keyboard should work. If this looks too hard, you can [instead make a breakout board with just through hole solder](tulip_breakout.md), or [with no soldering and a breadboard.](tulip_breadboard.md) diff --git a/docs/tulip_board_r9.md b/docs/tulip_board_r9.md index e1d9e18b4..f331b6f29 100644 --- a/docs/tulip_board_r9.md +++ b/docs/tulip_board_r9.md @@ -11,7 +11,7 @@ If you're comfortable with surface mount soldering, you can put together your ow [The BOM for the latest revision is here.](https://github.com/shorepine/tulipcc/blob/main/docs/pcbs/tulip4_board_v4r9/tulipcc-bom.xlsx) [KiCad files are here.](https://github.com/shorepine/tulipcc/tree/main/docs/pcbs/tulip4_board_v4r9) -Almost_ any USB keyboard should work. Please ensure it's just a keyboard -- if it has a trackpad, or extra USB ports on it, or anything else, it likely [won't work as we only support single root USB devices.](https://github.com/shorepine/tulipcc/issues/40). +_Almost_ any USB keyboard should work. If this looks too hard, you can [instead make a breakout board with just through hole solder](tulip_breakout.md), or [with no soldering and a breadboard.](tulip_breadboard.md) diff --git a/docs/tulip_breadboard.md b/docs/tulip_breadboard.md index c2a705354..744278bc6 100644 --- a/docs/tulip_breadboard.md +++ b/docs/tulip_breadboard.md @@ -14,7 +14,7 @@ You'll need - [This $58 RGB dot-clock 10.1" display with capacitive touch.](https://www.hotmcu.com/101-inch-1024x600-tft-lcd-display-with-capacitive-touch-panel-p-215.html) Note other RGB dot clock displays of different sizes and resolutions can also work, but the pin numberings will be different and you'll have to update the resolution in our code. - [A 40-pin FPC header for the display.](https://www.adafruit.com/product/4905) - One of two choices for sound: either [this mono I2S speaker amp board](https://www.adafruit.com/product/3006) (you'll also need a 3W speaker) or this stereo line-out / headphone jack [UDA1334 DAC.](https://www.aliexpress.com/item/3256803337983466.html?gatewayAdapt=4itemAdapt) -- _Almost_ any USB keyboard should work. Please ensure it's just a keyboard -- if it has a trackpad, or extra USB ports on it, or anything else, it likely [won't work as we only support single root USB devices.](https://github.com/shorepine/tulipcc/issues/40) If yours doesn't, please file an issue here and I can investigate with you. I can only test the ones I have here! I do recommend the [Keychron series of mechanical keyboards](https://www.keychron.com/products/keychron-k7-ultra-slim-wireless-mechanical-keyboard?variant=39396239048793), they're inspiringly clicky. +- _Almost_ any USB keyboard should work. - If you want to support an optional NES or SNES joytstick, [get the right connector.](https://www.zedlabz.com/collections/retro-nintendo-snes/products/zedlabz-7-pin-90-degree-female-controller-connector-port-for-nintendo-snes-console-2-pack-grey) - Connectors and random parts: - [1 USB female A screw terminal](https://www.amazon.com/Poyiccot-Terminal-Connector-Converter-Breakout/dp/B08Y8NKGHL) diff --git a/docs/tulip_breakout.md b/docs/tulip_breakout.md index 8fd4a9217..ccdb4989f 100644 --- a/docs/tulip_breakout.md +++ b/docs/tulip_breakout.md @@ -22,7 +22,7 @@ You'll need: - [This $58 RGB dot-clock 10.1" display with capacitive touch.](https://www.hotmcu.com/101-inch-1024x600-tft-lcd-display-with-capacitive-touch-panel-p-215.html) Note other RGB dot clock displays of different sizes and resolutions can also work, but the pin numberings will be different and you'll have to update the resolution in our code. - [A 40-pin FPC header for the display.](https://www.adafruit.com/product/4905) - This stereo line-out / headphone jack [UDA1334 DAC.](https://www.aliexpress.com/item/3256803337983466.html?gatewayAdapt=4itemAdapt) -- _Almost_ any USB keyboard should work. Please ensure it's just a keyboard -- if it has a trackpad, or extra USB ports on it, or anything else, it likely [won't work as we only support single root USB devices.](https://github.com/shorepine/tulipcc/issues/40) If yours doesn't, please file an issue here and I can investigate with you. I can only test the ones I have here! I do recommend the [Keychron series of mechanical keyboards](https://www.keychron.com/products/keychron-k7-ultra-slim-wireless-mechanical-keyboard?variant=39396239048793), they're inspiringly clicky. +- _Almost_ any USB keyboard should work. - Connectors and random parts: - [Female headers are recommended, so you don't solder the ESP and audio jack directly to the PCB.](https://www.adafruit.com/product/598) - I'd also get this [2x20 shrouded header](https://www.adafruit.com/product/1993) for the display FPC breakout. diff --git a/docs/tulip_flashing.md b/docs/tulip_flashing.md index a2394395c..0b934c128 100644 --- a/docs/tulip_flashing.md +++ b/docs/tulip_flashing.md @@ -61,7 +61,9 @@ Linux: sudo apt install cmake ninja-build dfu-util virtualenv ``` -For both macOS & Linux, next, download the supported version of ESP-IDF. That is currently 5.2. [You can download it directly here.](https://dl.espressif.com/github_assets/espressif/esp-idf/releases/download/v5.2/esp-idf-v5.2.zip) Unpack it to a folder. I like to keep them in `~/esp/`, as you'll likely want to use different versions eventually. So we'll assume it's in `~/esp/esp-idf-v5.2`. +For both macOS & Linux, next, download the supported version of ESP-IDF. That is currently a pre-release of 5.4. You need to get this with `git`, so install that if you don't already have it. Once Espressif updates the release, we can provide a direct download link that's a bit easier. + +I like to keep them in `~/esp/`, as you'll likely want to use different versions eventually. So we'll assume it's in `~/esp/esp-idf`. Also, clone this Tulip repository. We'll assume it's in `~/tulipcc`. @@ -69,10 +71,12 @@ Also, clone this Tulip repository. We'll assume it's in `~/tulipcc`. cd ~ mkdir esp cd esp -curl -O https://dl.espressif.com/github_assets/espressif/esp-idf/releases/download/v5.2/esp-idf-v5.2.zip -unzip esp-idf-v5.2.zip -esp-idf-v5.2/install.sh esp32s3 -source ~/esp/esp-idf-v5.2/export.sh +git clone https://github.com/espressif/esp-idf.git +cd esp-idf +git checkout 70f222e5d29df8ffe5da25057601708c8097bcd1 +git submodule update --init --recursive +./install.sh esp32s3 +source export.sh cd ~ git clone https://github.com/shorepine/tulipcc.git @@ -111,7 +115,7 @@ To build and flash going forward, without modifying the filesystem: ```bash cd tulip/esp32s3 -source ~/esp/esp-idf-v5.2/export.sh # do this once per terminal window +source ~/esp/esp-idf/export.sh # do this once per terminal window idf.py -DMICROPY_BOARD=[X] flash idf.py monitor # shows stderr and stdin for controlling Tulip, use control-] to quit diff --git a/micropython b/micropython index 2ede7c6c2..82e69df33 160000 --- a/micropython +++ b/micropython @@ -1 +1 @@ -Subproject commit 2ede7c6c21133812af8a0891e6a4f3f31c362b50 +Subproject commit 82e69df33e379bf491bea647e217d6d56c5b8090 diff --git a/tulip/esp32s3/CMakeLists.txt b/tulip/esp32s3/CMakeLists.txt index dd1426744..c0721899d 100644 --- a/tulip/esp32s3/CMakeLists.txt +++ b/tulip/esp32s3/CMakeLists.txt @@ -67,7 +67,7 @@ set(SDKCONFIG_DEFAULTS ${CMAKE_BINARY_DIR}/sdkconfig.combined) include($ENV{IDF_PATH}/tools/cmake/project.cmake) # Set the location of the main component for the project (one per target). -set(EXTRA_COMPONENT_DIRS main components) +#set(EXTRA_COMPONENT_DIRS main components) # Define the project. project(micropython) diff --git a/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.cmake b/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.cmake index 71b697ada..bccb7309e 100644 --- a/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.cmake +++ b/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.cmake @@ -13,8 +13,9 @@ set(SDKCONFIG_DEFAULTS ) -list(APPEND MICROPY_SOURCE_PORT +list(APPEND MICROPY_SOURCE_BOARD gt911_touchscreen.c esp32s3_display.c - usb_keyboard.c + esp_lcd_touch_gt911.c + usb_host.c ) \ No newline at end of file diff --git a/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.h b/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.h index e43850b0f..aad97b588 100644 --- a/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.h +++ b/tulip/esp32s3/boards/MATOUCH7/mpconfigboard.h @@ -7,5 +7,15 @@ // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) + #define MICROPY_HW_I2C0_SCL (I2C_SCL) #define MICROPY_HW_I2C0_SDA (I2C_SDA) + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_BLUETOOTH_NIMBLE (0) +#define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_ENABLE_USBDEV (1) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (0) +#define MICROPY_ENABLE_SCHEDULER (1) diff --git a/tulip/esp32s3/boards/MATOUCH7/pins.csv b/tulip/esp32s3/boards/MATOUCH7/pins.csv new file mode 100644 index 000000000..46d28cf1a --- /dev/null +++ b/tulip/esp32s3/boards/MATOUCH7/pins.csv @@ -0,0 +1,5 @@ +I2C_SCL,GPIO9 +I2C_SDA,GPIO8 +FG_INT,GPIO21 +UART0_TX,GPIO43 +UART0_RX,GPIO44 diff --git a/tulip/esp32s3/boards/N16R8/mpconfigboard.cmake b/tulip/esp32s3/boards/N16R8/mpconfigboard.cmake index b77c056fc..166397c8e 100644 --- a/tulip/esp32s3/boards/N16R8/mpconfigboard.cmake +++ b/tulip/esp32s3/boards/N16R8/mpconfigboard.cmake @@ -12,8 +12,8 @@ set(SDKCONFIG_DEFAULTS boards/N16R8/sdkconfig.board ) -list(APPEND MICROPY_SOURCE_PORT +list(APPEND MICROPY_SOURCE_BOARD esp32s3_display.c - usb_keyboard.c + usb_host.c ft5x06_touchscreen.c ) \ No newline at end of file diff --git a/tulip/esp32s3/boards/N16R8/mpconfigboard.h b/tulip/esp32s3/boards/N16R8/mpconfigboard.h index f7dff2034..aad97b588 100644 --- a/tulip/esp32s3/boards/N16R8/mpconfigboard.h +++ b/tulip/esp32s3/boards/N16R8/mpconfigboard.h @@ -10,3 +10,12 @@ #define MICROPY_HW_I2C0_SCL (I2C_SCL) #define MICROPY_HW_I2C0_SDA (I2C_SDA) + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_BLUETOOTH_NIMBLE (0) +#define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_ENABLE_USBDEV (1) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (0) +#define MICROPY_ENABLE_SCHEDULER (1) diff --git a/tulip/esp32s3/boards/N16R8/pins.csv b/tulip/esp32s3/boards/N16R8/pins.csv new file mode 100644 index 000000000..46d28cf1a --- /dev/null +++ b/tulip/esp32s3/boards/N16R8/pins.csv @@ -0,0 +1,5 @@ +I2C_SCL,GPIO9 +I2C_SDA,GPIO8 +FG_INT,GPIO21 +UART0_TX,GPIO43 +UART0_RX,GPIO44 diff --git a/tulip/esp32s3/boards/N32R8/mpconfigboard.cmake b/tulip/esp32s3/boards/N32R8/mpconfigboard.cmake index 33b8044fb..36b543628 100644 --- a/tulip/esp32s3/boards/N32R8/mpconfigboard.cmake +++ b/tulip/esp32s3/boards/N32R8/mpconfigboard.cmake @@ -11,8 +11,8 @@ set(SDKCONFIG_DEFAULTS boards/N32R8/sdkconfig.board ) -list(APPEND MICROPY_SOURCE_PORT +list(APPEND MICROPY_SOURCE_BOARD esp32s3_display.c ft5x06_touchscreen.c - usb_keyboard.c + usb_host.c ) diff --git a/tulip/esp32s3/boards/N32R8/mpconfigboard.h b/tulip/esp32s3/boards/N32R8/mpconfigboard.h index f7dff2034..aad97b588 100644 --- a/tulip/esp32s3/boards/N32R8/mpconfigboard.h +++ b/tulip/esp32s3/boards/N32R8/mpconfigboard.h @@ -10,3 +10,12 @@ #define MICROPY_HW_I2C0_SCL (I2C_SCL) #define MICROPY_HW_I2C0_SDA (I2C_SDA) + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_BLUETOOTH_NIMBLE (0) +#define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_ENABLE_USBDEV (1) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (0) +#define MICROPY_ENABLE_SCHEDULER (1) diff --git a/tulip/esp32s3/boards/N32R8/pins.csv b/tulip/esp32s3/boards/N32R8/pins.csv new file mode 100644 index 000000000..46d28cf1a --- /dev/null +++ b/tulip/esp32s3/boards/N32R8/pins.csv @@ -0,0 +1,5 @@ +I2C_SCL,GPIO9 +I2C_SDA,GPIO8 +FG_INT,GPIO21 +UART0_TX,GPIO43 +UART0_RX,GPIO44 diff --git a/tulip/esp32s3/boards/TDECK/mpconfigboard.cmake b/tulip/esp32s3/boards/TDECK/mpconfigboard.cmake index 084a32dd9..e4fcda43f 100644 --- a/tulip/esp32s3/boards/TDECK/mpconfigboard.cmake +++ b/tulip/esp32s3/boards/TDECK/mpconfigboard.cmake @@ -13,8 +13,11 @@ set(SDKCONFIG_DEFAULTS boards/TDECK/sdkconfig.board ) -list(APPEND MICROPY_SOURCE_PORT +list(APPEND MICROPY_SOURCE_BOARD tdeck_display.c tdeck_keyboard.c + esp_lcd_touch_gt911.c gt911_touchscreen.c + ../../micropython/ports/esp32/usb_serial_jtag.c ) + diff --git a/tulip/esp32s3/boards/TDECK/mpconfigboard.h b/tulip/esp32s3/boards/TDECK/mpconfigboard.h index f7dff2034..3c57a193b 100644 --- a/tulip/esp32s3/boards/TDECK/mpconfigboard.h +++ b/tulip/esp32s3/boards/TDECK/mpconfigboard.h @@ -7,6 +7,17 @@ // Enable UART REPL for modules that have an external USB-UART and don't use native USB. #define MICROPY_HW_ENABLE_UART_REPL (1) - +#define USB_SERIAL_JTAG_PACKET_SZ_BYTES 64 #define MICROPY_HW_I2C0_SCL (I2C_SCL) #define MICROPY_HW_I2C0_SDA (I2C_SDA) + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_BLUETOOTH_NIMBLE (0) +#define MICROPY_HW_USB_CDC (0) +#define MICROPY_HW_ENABLE_USBDEV (0) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (1) +#define MICROPY_ENABLE_SCHEDULER (1) +#define MICROPY_ESP32_USE_BOOTLOADER_RTC (0) +#define MICROPY_BOARD_ENTER_BOOTLOADER(nargs,args) void \ No newline at end of file diff --git a/tulip/esp32s3/boards/TDECK/pins.csv b/tulip/esp32s3/boards/TDECK/pins.csv new file mode 100644 index 000000000..46d28cf1a --- /dev/null +++ b/tulip/esp32s3/boards/TDECK/pins.csv @@ -0,0 +1,5 @@ +I2C_SCL,GPIO9 +I2C_SDA,GPIO8 +FG_INT,GPIO21 +UART0_TX,GPIO43 +UART0_RX,GPIO44 diff --git a/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.cmake b/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.cmake index 897af27f1..68afc458b 100644 --- a/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.cmake +++ b/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.cmake @@ -14,8 +14,9 @@ set(SDKCONFIG_DEFAULTS boards/TULIP4_R11/sdkconfig.board ) -list(APPEND MICROPY_SOURCE_PORT +list(APPEND MICROPY_SOURCE_BOARD gt911_touchscreen.c + esp_lcd_touch_gt911.c esp32s3_display.c - usb_keyboard.c + usb_host.c ) \ No newline at end of file diff --git a/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.h b/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.h index f7dff2034..3b42f195b 100644 --- a/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.h +++ b/tulip/esp32s3/boards/TULIP4_R11/mpconfigboard.h @@ -10,3 +10,13 @@ #define MICROPY_HW_I2C0_SCL (I2C_SCL) #define MICROPY_HW_I2C0_SDA (I2C_SDA) + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_BLUETOOTH_NIMBLE (0) +#define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_ENABLE_USBDEV (1) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (0) +#define MICROPY_ENABLE_SCHEDULER (1) + diff --git a/tulip/esp32s3/boards/TULIP4_R11/pins.csv b/tulip/esp32s3/boards/TULIP4_R11/pins.csv new file mode 100644 index 000000000..46d28cf1a --- /dev/null +++ b/tulip/esp32s3/boards/TULIP4_R11/pins.csv @@ -0,0 +1,5 @@ +I2C_SCL,GPIO9 +I2C_SDA,GPIO8 +FG_INT,GPIO21 +UART0_TX,GPIO43 +UART0_RX,GPIO44 diff --git a/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.cmake b/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.cmake index e41f4906c..ae5739c01 100644 --- a/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.cmake +++ b/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.cmake @@ -13,8 +13,9 @@ set(SDKCONFIG_DEFAULTS boards/TULIP4_R11_DEBUG/sdkconfig.board ) -list(APPEND MICROPY_SOURCE_PORT +list(APPEND MICROPY_SOURCE_BOARD + esp_lcd_touch_gt911.c gt911_touchscreen.c esp32s3_display.c - usb_keyboard.c + usb_host.c ) \ No newline at end of file diff --git a/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.h b/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.h index f7dff2034..aad97b588 100644 --- a/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.h +++ b/tulip/esp32s3/boards/TULIP4_R11_DEBUG/mpconfigboard.h @@ -10,3 +10,12 @@ #define MICROPY_HW_I2C0_SCL (I2C_SCL) #define MICROPY_HW_I2C0_SDA (I2C_SDA) + +#define MICROPY_HW_ENABLE_SDCARD (0) +#define MICROPY_PY_MACHINE_I2S (0) +#define MICROPY_PY_BLUETOOTH (0) +#define MICROPY_BLUETOOTH_NIMBLE (0) +#define MICROPY_HW_USB_CDC (1) +#define MICROPY_HW_ENABLE_USBDEV (1) +#define MICROPY_HW_ESP_USB_SERIAL_JTAG (0) +#define MICROPY_ENABLE_SCHEDULER (1) diff --git a/tulip/esp32s3/boards/TULIP4_R11_DEBUG/pins.csv b/tulip/esp32s3/boards/TULIP4_R11_DEBUG/pins.csv new file mode 100644 index 000000000..46d28cf1a --- /dev/null +++ b/tulip/esp32s3/boards/TULIP4_R11_DEBUG/pins.csv @@ -0,0 +1,5 @@ +I2C_SCL,GPIO9 +I2C_SDA,GPIO8 +FG_INT,GPIO21 +UART0_TX,GPIO43 +UART0_RX,GPIO44 diff --git a/tulip/esp32s3/boards/sdkconfig.tulip b/tulip/esp32s3/boards/sdkconfig.tulip index 119026a0f..36a2a7b72 100644 --- a/tulip/esp32s3/boards/sdkconfig.tulip +++ b/tulip/esp32s3/boards/sdkconfig.tulip @@ -4,7 +4,7 @@ CONFIG_SPIRAM_IGNORE_NOTFOUND=y #CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_PERF=y #CONFIG_COMPILER_OPTIMIZATION_NONE=y - +CONFIG_USB_HOST_HUBS_SUPPORTED=y #CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=n CONFIG_FREERTOS_HZ=1000 @@ -25,3 +25,5 @@ CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y CONFIG_SPIRAM_RODATA=y CONFIG_LCD_RGB_ISR_IRAM_SAFE=n CONFIG_LCD_RGB_RESTART_IN_VSYNC=y + +CONFIG_LWIP_PPP_SUPPORT=n \ No newline at end of file diff --git a/tulip/esp32s3/components/esp_lcd/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/CMakeLists.txt deleted file mode 100644 index 9e1c10b13..000000000 --- a/tulip/esp32s3/components/esp_lcd/CMakeLists.txt +++ /dev/null @@ -1,31 +0,0 @@ -idf_build_get_property(target IDF_TARGET) - -if(${target} STREQUAL "linux") - return() # This component is not supported by the POSIX/Linux simulator -endif() - -set(srcs "src/esp_lcd_common.c" - "src/esp_lcd_panel_io.c" - "src/esp_lcd_panel_io_i2c_v1.c" - "src/esp_lcd_panel_io_i2c_v2.c" - "src/esp_lcd_panel_io_spi.c" - "src/esp_lcd_panel_nt35510.c" - "src/esp_lcd_panel_ssd1306.c" - "src/esp_lcd_panel_st7789.c" - "src/esp_lcd_panel_ops.c") -set(includes "include" "interface") -set(priv_requires "esp_mm" "esp_psram") - -if(CONFIG_SOC_I2S_LCD_I80_VARIANT) - list(APPEND srcs "src/esp_lcd_panel_io_i2s.c") -endif() - -if(CONFIG_SOC_LCDCAM_SUPPORTED) - list(APPEND srcs "src/esp_lcd_panel_io_i80.c" "src/esp_lcd_panel_rgb.c") -endif() - -idf_component_register(SRCS ${srcs} - INCLUDE_DIRS ${includes} - PRIV_REQUIRES ${priv_requires} - REQUIRES driver - LDFRAGMENTS linker.lf) diff --git a/tulip/esp32s3/components/esp_lcd/Kconfig b/tulip/esp32s3/components/esp_lcd/Kconfig deleted file mode 100644 index 25a77c837..000000000 --- a/tulip/esp32s3/components/esp_lcd/Kconfig +++ /dev/null @@ -1,39 +0,0 @@ -menu "LCD and Touch Panel" - comment "LCD Touch Drivers are maintained in the IDF Component Registry" - - menu "LCD Peripheral Configuration" - config LCD_PANEL_IO_FORMAT_BUF_SIZE - int "LCD panel io format buffer size" - default 32 - help - LCD driver allocates an internal buffer to transform the data into a proper format, because of - the endian order mismatch. This option is to set the size of the buffer, in bytes. - - config LCD_ENABLE_DEBUG_LOG - bool "Enable debug log" - default n - help - Wether to enable the debug log message for LCD driver. - Note that, this option only controls the LCD driver log, won't affect other drivers. - - if SOC_LCD_RGB_SUPPORTED - config LCD_RGB_ISR_IRAM_SAFE - bool "RGB LCD ISR IRAM-Safe" - default n - help - Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be - executable when the cache is disabled (e.g. SPI Flash write). - If you want the LCD driver to keep flushing the screen even when cache ops disabled, - you can enable this option. Note, this will also increase the IRAM usage. - - config LCD_RGB_RESTART_IN_VSYNC - bool "Restart transmission in VSYNC" - default n - select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR - help - Reset the GDMA channel every VBlank to stop permanent desyncs from happening. - Only need to enable it when in your application, the DMA can't deliver data - as fast as the LCD consumes it. - endif # SOC_LCD_RGB_SUPPORTED - endmenu -endmenu diff --git a/tulip/esp32s3/components/esp_lcd/README.md b/tulip/esp32s3/components/esp_lcd/README.md deleted file mode 100644 index add8ed69b..000000000 --- a/tulip/esp32s3/components/esp_lcd/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# esp_lcd Driver Design - -## Class Diagram - -`esp_lcd` driver focuses on two parts: panel driver and IO driver. The panel driver is a bunch of operations on the **frame-buffer**, no matter where the frame-buffer is located. The IO driver is mainly consumed by the controller-based LCD panel drivers (e.g. ST7789). Usually such LCD controller can support various IO interfaces (e.g. I80, SPI, I2C, etc). So we define an abstract interface for the IO driver. - -```mermaid -classDiagram - class esp_lcd_panel_t { - <> - +reset() esp_err_t - +init() esp_err_t - +draw_bitmap(int x_start, int y_start, int x_end, int y_end, const void *color_data) esp_err_t - +mirror(bool x_axis, bool y_axis) esp_err_t - +swap_xy(bool swap_axes) esp_err_t - +set_gap(int x_gap, int y_gap) esp_err_t - +invert_color(bool invert_color_data) esp_err_t - +disp_on_off(bool on_off) esp_err_t - } - - esp_lcd_rgb_panel_t --|> esp_lcd_panel_t : Inheritance - class esp_lcd_rgb_panel_t { - -int panel_id - -size_t data_width - -int disp_gpio - -intr_handle_t intr - -uint8_t* frame_buffer - -gdma_channel_handle_t gdma_channel - -dma_descriptor_t* dma_nodes - -on_vsync(void* user_data) bool - } - - esp_lcd_panel_model_t --|> esp_lcd_panel_t : Inheritance - esp_lcd_panel_model_t "1" --> "1" esp_lcd_panel_io_t : Use - class esp_lcd_panel_model_t { - -esp_lcd_panel_io_t* io - -int reset_gpio_num - } - - class esp_lcd_panel_io_t { - <> - +rx_param(int lcd_cmd, void *param, size_t param_size) - +tx_param(int lcd_cmd, const void *param, size_t param_size) - +tx_color(int lcd_cmd, const void *color, size_t color_size) - } - - esp_lcd_panel_io_i2c_t --|> esp_lcd_panel_io_t : Inheritance - class esp_lcd_panel_io_i2c_t { - -int i2c_bus_id - -int ctrl_phase_cmd - -int ctrl_phase_data - -on_color_trans_done(void* user_data) bool - } - - esp_lcd_panel_io_spi_t --|> esp_lcd_panel_io_t : Inheritance - class esp_lcd_panel_io_spi_t { - -spi_device_handle_t spi_dev - -int dc_gpio_num - -spi_transaction_t trans_worker - -on_color_trans_done(void* user_data) bool - } - - esp_lcd_panel_io_i80_t --|> esp_lcd_panel_io_t : Inheritance - class esp_lcd_panel_io_i80_t { - -esp_lcd_i80_bus_t* bus - -int cs_gpio_num - -int dc_level - -size_t pclk_hz - -QueueHandle_t trans_queue - -QueueHandle_t done_queue - -on_color_trans_done(void* user_data) bool - } - - esp_lcd_i80_bus_t "1" --> "1..*" esp_lcd_panel_io_i80_t : Has - class esp_lcd_i80_bus_t { - -int bus_id - -size_t data_width - -intr_handle_t intr - -gdma_cannel_handle_t dma_chan - -dma_descriptor_t* dma_nodes - -list_t i80_devices - } -``` diff --git a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_commands.h b/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_commands.h deleted file mode 100644 index 5917c3e87..000000000 --- a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_commands.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -/* Common LCD panel commands */ -#define LCD_CMD_NOP 0x00 // This command is empty command -#define LCD_CMD_SWRESET 0x01 // Software reset registers (the built-in frame buffer is not affected) -#define LCD_CMD_RDDID 0x04 // Read 24-bit display ID -#define LCD_CMD_RDDST 0x09 // Read display status -#define LCD_CMD_RDDPM 0x0A // Read display power mode -#define LCD_CMD_RDD_MADCTL 0x0B // Read display MADCTL -#define LCD_CMD_RDD_COLMOD 0x0C // Read display pixel format -#define LCD_CMD_RDDIM 0x0D // Read display image mode -#define LCD_CMD_RDDSM 0x0E // Read display signal mode -#define LCD_CMD_RDDSR 0x0F // Read display self-diagnostic result -#define LCD_CMD_SLPIN 0x10 // Go into sleep mode (DC/DC, oscillator, scanning stopped, but memory keeps content) -#define LCD_CMD_SLPOUT 0x11 // Exit sleep mode -#define LCD_CMD_PTLON 0x12 // Turns on partial display mode -#define LCD_CMD_NORON 0x13 // Turns on normal display mode -#define LCD_CMD_INVOFF 0x20 // Recover from display inversion mode -#define LCD_CMD_INVON 0x21 // Go into display inversion mode -#define LCD_CMD_GAMSET 0x26 // Select Gamma curve for current display -#define LCD_CMD_DISPOFF 0x28 // Display off (disable frame buffer output) -#define LCD_CMD_DISPON 0x29 // Display on (enable frame buffer output) -#define LCD_CMD_CASET 0x2A // Set column address -#define LCD_CMD_RASET 0x2B // Set row address -#define LCD_CMD_RAMWR 0x2C // Write frame memory -#define LCD_CMD_RAMRD 0x2E // Read frame memory -#define LCD_CMD_PTLAR 0x30 // Define the partial area -#define LCD_CMD_VSCRDEF 0x33 // Vertical scrolling definition -#define LCD_CMD_TEOFF 0x34 // Turns off tearing effect -#define LCD_CMD_TEON 0x35 // Turns on tearing effect - -#define LCD_CMD_MADCTL 0x36 // Memory data access control -#define LCD_CMD_MH_BIT (1 << 2) // Display data latch order, 0: refresh left to right, 1: refresh right to left -#define LCD_CMD_BGR_BIT (1 << 3) // RGB/BGR order, 0: RGB, 1: BGR -#define LCD_CMD_ML_BIT (1 << 4) // Line address order, 0: refresh top to bottom, 1: refresh bottom to top -#define LCD_CMD_MV_BIT (1 << 5) // Row/Column order, 0: normal mode, 1: reverse mode -#define LCD_CMD_MX_BIT (1 << 6) // Column address order, 0: left to right, 1: right to left -#define LCD_CMD_MY_BIT (1 << 7) // Row address order, 0: top to bottom, 1: bottom to top - -#define LCD_CMD_VSCSAD 0x37 // Vertical scroll start address -#define LCD_CMD_IDMOFF 0x38 // Recover from IDLE mode -#define LCD_CMD_IDMON 0x39 // Fall into IDLE mode (8 color depth is displayed) -#define LCD_CMD_COLMOD 0x3A // Defines the format of RGB picture data -#define LCD_CMD_RAMWRC 0x3C // Memory write continue -#define LCD_CMD_RAMRDC 0x3E // Memory read continue -#define LCD_CMD_STE 0x44 // Set tear scan line, tearing effect output signal when display module reaches line N -#define LCD_CMD_GDCAN 0x45 // Get scan line -#define LCD_CMD_WRDISBV 0x51 // Write display brightness -#define LCD_CMD_RDDISBV 0x52 // Read display brightness value diff --git a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_io.h b/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_io.h deleted file mode 100644 index 7fe5bc974..000000000 --- a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_io.h +++ /dev/null @@ -1,309 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_err.h" -#include "esp_lcd_types.h" -#include "soc/soc_caps.h" -#include "hal/lcd_types.h" -#include "hal/i2c_types.h" -#include "driver/i2c_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef void *esp_lcd_spi_bus_handle_t; /*!< Type of LCD SPI bus handle */ -typedef uint32_t esp_lcd_i2c_bus_handle_t; /*!< Type of LCD I2C bus handle */ -typedef struct esp_lcd_i80_bus_t *esp_lcd_i80_bus_handle_t; /*!< Type of LCD intel 8080 bus handle */ - -/** - * @brief Type of LCD panel IO event data - */ -typedef struct { -} esp_lcd_panel_io_event_data_t; - -/** - * @brief Declare the prototype of the function that will be invoked when panel IO finishes transferring color data - * - * @param[in] panel_io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] edata Panel IO event data, fed by driver - * @param[in] user_ctx User data, passed from `esp_lcd_panel_io_xxx_config_t` - * @return Whether a high priority task has been waken up by this function - */ -typedef bool (*esp_lcd_panel_io_color_trans_done_cb_t)(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); - -/** - * @brief Type of LCD panel IO callbacks - */ -typedef struct { - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */ -} esp_lcd_panel_io_callbacks_t; - -/** - * @brief Transmit LCD command and receive corresponding parameters - * - * @note Commands sent by this function are short, so they are sent using polling transactions. - * The function does not return before the command transfer is completed. - * If any queued transactions sent by `esp_lcd_panel_io_tx_color()` are still pending when this function is called, - * this function will wait until they are finished and the queue is empty before sending the command(s). - * - * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed - * @param[out] param Buffer for the command data - * @param[in] param_size Size of `param` buffer - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NOT_SUPPORTED if read is not supported by transport - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_io_rx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, void *param, size_t param_size); - -/** - * @brief Transmit LCD command and corresponding parameters - * - * @note Commands sent by this function are short, so they are sent using polling transactions. - * The function does not return before the command transfer is completed. - * If any queued transactions sent by `esp_lcd_panel_io_tx_color()` are still pending when this function is called, - * this function will wait until they are finished and the queue is empty before sending the command(s). - * - * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed - * @param[in] param Buffer that holds the command specific parameters, set to NULL if no parameter is needed for the command - * @param[in] param_size Size of `param` in memory, in bytes, set to zero if no parameter is needed for the command - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_io_tx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size); - -/** - * @brief Transmit LCD RGB data - * - * @note This function will package the command and RGB data into a transaction, and push into a queue. - * The real transmission is performed in the background (DMA+interrupt). - * The caller should take care of the lifecycle of the `color` buffer. - * Recycling of color buffer should be done in the callback `on_color_trans_done()`. - * - * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed - * @param[in] color Buffer that holds the RGB color data - * @param[in] color_size Size of `color` in memory, in bytes - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_io_tx_color(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *color, size_t color_size); - -/** - * @brief Destroy LCD panel IO handle (deinitialize panel and free all corresponding resource) - * - * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()` - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io); - -/** - * @brief Register LCD panel IO callbacks - * - * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] cbs structure with all LCD panel IO callbacks - * @param[in] user_ctx User private data, passed directly to callback's user_ctx - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_io_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); - -/** - * @brief Panel IO configuration structure, for SPI interface - */ -typedef struct { - int cs_gpio_num; /*!< GPIO used for CS line */ - int dc_gpio_num; /*!< GPIO used to select the D/C line, set this to -1 if the D/C line is not used */ - int spi_mode; /*!< Traditional SPI mode (0~3) */ - unsigned int pclk_hz; /*!< Frequency of pixel clock */ - size_t trans_queue_depth; /*!< Size of internal transaction queue */ - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */ - void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */ - int lcd_cmd_bits; /*!< Bit-width of LCD command */ - int lcd_param_bits; /*!< Bit-width of LCD parameter */ - struct { - unsigned int dc_low_on_data: 1; /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */ - unsigned int octal_mode: 1; /*!< transmit with octal mode (8 data lines), this mode is used to simulate Intel 8080 timing */ - unsigned int quad_mode: 1; /*!< transmit with quad mode (4 data lines), this mode is useful when transmitting LCD parameters (Only use one line for command) */ - unsigned int sio_mode: 1; /*!< Read and write through a single data line (MOSI) */ - unsigned int lsb_first: 1; /*!< transmit LSB bit first */ - unsigned int cs_high_active: 1; /*!< CS line is high active */ - } flags; /*!< Extra flags to fine-tune the SPI device */ -} esp_lcd_panel_io_spi_config_t; - -/** - * @brief Create LCD panel IO handle, for SPI interface - * - * @param[in] bus SPI bus handle - * @param[in] io_config IO configuration, for SPI interface - * @param[out] ret_io Returned IO handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io); - -/** - * @brief Panel IO configuration structure, for I2C interface - * - */ -typedef struct { - uint32_t dev_addr; /*!< I2C device address */ - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */ - void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */ - size_t control_phase_bytes; /*!< I2C LCD panel will encode control information (e.g. D/C selection) into control phase, in several bytes */ - unsigned int dc_bit_offset; /*!< Offset of the D/C selection bit in control phase */ - int lcd_cmd_bits; /*!< Bit-width of LCD command */ - int lcd_param_bits; /*!< Bit-width of LCD parameter */ - struct { - unsigned int dc_low_on_data: 1; /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */ - unsigned int disable_control_phase: 1; /*!< If this flag is enabled, the control phase isn't used */ - } flags; /*!< Extra flags to fine-tune the I2C device */ - uint32_t scl_speed_hz; /*!< I2C LCD SCL frequency (hz) */ -} esp_lcd_panel_io_i2c_config_t; - -/** - * @brief Create LCD panel IO handle, for I2C interface in legacy implementation - * - * @param[in] bus I2C bus handle, (in uint32_t) - * @param[in] io_config IO configuration, for I2C interface - * @param[out] ret_io Returned IO handle - * - * @note Please don't call this function in your project directly. Please call `esp_lcd_new_panel_to_i2c` instead. - * - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_io_i2c_v1(uint32_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io); - -/** - * @brief Create LCD panel IO handle, for I2C interface in new implementation - * - * @param[in] bus I2C bus handle, (in i2c_master_dev_handle_t) - * @param[in] io_config IO configuration, for I2C interface - * @param[out] ret_io Returned IO handle - * - * @note Please don't call this function in your project directly. Please call `esp_lcd_new_panel_to_i2c` instead. - * - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io); - -/** - * @brief Create LCD panel IO handle - * - * @param[in] bus I2C bus handle - * @param[in] io_config IO configuration, for I2C interface - * @param[out] ret_io Returned IO handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -#define esp_lcd_new_panel_io_i2c(bus, io_config, ret_io) _Generic((bus), \ - i2c_master_bus_handle_t : esp_lcd_new_panel_io_i2c_v2, \ - default : esp_lcd_new_panel_io_i2c_v1) (bus, io_config, ret_io) \ - -#if SOC_LCD_I80_SUPPORTED -/** - * @brief LCD Intel 8080 bus configuration structure - */ -typedef struct { - int dc_gpio_num; /*!< GPIO used for D/C line */ - int wr_gpio_num; /*!< GPIO used for WR line */ - lcd_clock_source_t clk_src; /*!< Clock source for the I80 LCD peripheral */ - int data_gpio_nums[SOC_LCD_I80_BUS_WIDTH]; /*!< GPIOs used for data lines */ - size_t bus_width; /*!< Number of data lines, 8 or 16 */ - size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */ - size_t psram_trans_align; /*!< DMA transfer alignment for data allocated from PSRAM */ - size_t sram_trans_align; /*!< DMA transfer alignment for data allocated from SRAM */ -} esp_lcd_i80_bus_config_t; - -/** - * @brief Create Intel 8080 bus handle - * - * @param[in] bus_config Bus configuration - * @param[out] ret_bus Returned bus handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_ERR_NOT_FOUND if no free bus is available - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus); - -/** - * @brief Destroy Intel 8080 bus handle - * - * @param[in] bus Intel 8080 bus handle, created by `esp_lcd_new_i80_bus()` - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_INVALID_STATE if there still be some device attached to the bus - * - ESP_OK on success - */ -esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus); - -/** - * @brief Panel IO configuration structure, for intel 8080 interface - */ -typedef struct { - int cs_gpio_num; /*!< GPIO used for CS line, set to -1 will declaim exclusively use of I80 bus */ - uint32_t pclk_hz; /*!< Frequency of pixel clock */ - size_t trans_queue_depth; /*!< Transaction queue size, larger queue, higher throughput */ - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data was transferred done */ - void *user_ctx; /*!< User private data, passed directly to on_color_trans_done's user_ctx */ - int lcd_cmd_bits; /*!< Bit-width of LCD command */ - int lcd_param_bits; /*!< Bit-width of LCD parameter */ - struct { - unsigned int dc_idle_level: 1; /*!< Level of DC line in IDLE phase */ - unsigned int dc_cmd_level: 1; /*!< Level of DC line in CMD phase */ - unsigned int dc_dummy_level: 1; /*!< Level of DC line in DUMMY phase */ - unsigned int dc_data_level: 1; /*!< Level of DC line in DATA phase */ - } dc_levels; /*!< Each i80 device might have its own D/C control logic */ - struct { - unsigned int cs_active_high: 1; /*!< If set, a high level of CS line will select the device, otherwise, CS line is low level active */ - unsigned int reverse_color_bits: 1; /*!< Reverse the data bits, D[N:0] -> D[0:N] */ - unsigned int swap_color_bytes: 1; /*!< Swap adjacent two color bytes */ - unsigned int pclk_active_neg: 1; /*!< The display will write data lines when there's a falling edge on WR signal (a.k.a the PCLK) */ - unsigned int pclk_idle_low: 1; /*!< The WR signal (a.k.a the PCLK) stays at low level in IDLE phase */ - } flags; /*!< Panel IO config flags */ -} esp_lcd_panel_io_i80_config_t; - -/** - * @brief Create LCD panel IO, for Intel 8080 interface - * - * @param[in] bus Intel 8080 bus handle, created by `esp_lcd_new_i80_bus()` - * @param[in] io_config IO configuration, for i80 interface - * @param[out] ret_io Returned panel IO handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NOT_SUPPORTED if some configuration can't be satisfied, e.g. pixel clock out of the range - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io); - -#endif // SOC_LCD_I80_SUPPORTED - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_ops.h b/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_ops.h deleted file mode 100644 index 56b21a1ad..000000000 --- a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_ops.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_err.h" -#include "esp_lcd_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Reset LCD panel - * - * @note Panel reset must be called before attempting to initialize the panel using `esp_lcd_panel_init()`. - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @return - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel); - -/** - * @brief Initialize LCD panel - * - * @note Before calling this function, make sure the LCD panel has finished the `reset` stage by `esp_lcd_panel_reset()`. - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @return - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel); - -/** - * @brief Deinitialize the LCD panel - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @return - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel); - -/** - * @brief Draw bitmap on LCD panel - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_start Start index on x-axis (x_start included) - * @param[in] y_start Start index on y-axis (y_start included) - * @param[in] x_end End index on x-axis (x_end not included) - * @param[in] y_end End index on y-axis (y_end not included) - * @param[in] color_data RGB color data that will be dumped to the specific window range - * @return - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); - -/** - * @brief Mirror the LCD panel on specific axis - * - * @note Combined with `esp_lcd_panel_swap_xy()`, one can realize screen rotation - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] mirror_x Whether the panel will be mirrored about the x axis - * @param[in] mirror_y Whether the panel will be mirrored about the y axis - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ -esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y); - -/** - * @brief Swap/Exchange x and y axis - * - * @note Combined with `esp_lcd_panel_mirror()`, one can realize screen rotation - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] swap_axes Whether to swap the x and y axis - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ -esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes); - -/** - * @brief Set extra gap in x and y axis - * - * The gap is the space (in pixels) between the left/top sides of the LCD panel and the first row/column respectively of the actual contents displayed. - * - * @note Setting a gap is useful when positioning or centering a frame that is smaller than the LCD. - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_gap Extra gap on x axis, in pixels - * @param[in] y_gap Extra gap on y axis, in pixels - * @return - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap); - -/** - * @brief Invert the color (bit-wise invert the color data line) - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] invert_color_data Whether to invert the color data - * @return - * - ESP_OK on success - */ -esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data); - -/** - * @brief Turn on or off the display - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] on_off True to turns on display, False to turns off display - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ -esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off); - -/** - * @brief Turn off the display - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] off Whether to turn off the screen - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ -esp_err_t esp_lcd_panel_disp_off(esp_lcd_panel_handle_t panel, bool off) -__attribute__((deprecated("use esp_lcd_panel_disp_on_off instead"))); - -/** - * @brief Enter or exit sleep mode - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] sleep True to enter sleep mode, False to wake up - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ -esp_err_t esp_lcd_panel_disp_sleep(esp_lcd_panel_handle_t panel, bool sleep); - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_rgb.h b/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_rgb.h deleted file mode 100644 index a58cdd624..000000000 --- a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_rgb.h +++ /dev/null @@ -1,280 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include -#include "esp_err.h" -#include "esp_lcd_types.h" -#include "soc/soc_caps.h" -#include "hal/lcd_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#if SOC_LCD_RGB_SUPPORTED -/** - * @brief LCD RGB timing structure - * @verbatim - * Total Width - * <---------------------------------------------------> - * HSYNC width HBP Active Width HFP - * <---><--><--------------------------------------><---> - * ____ ____|_______________________________________|____| - * |___| | | | - * | | | - * __| | | | - * /|\ /|\ | | | | - * | VSYNC| | | | | - * |Width\|/ |__ | | | - * | /|\ | | | | - * | VBP | | | | | - * | \|/_____|_________|_______________________________________| | - * | /|\ | | / / / / / / / / / / / / / / / / / / / | | - * | | | |/ / / / / / / / / / / / / / / / / / / /| | - * Total | | | |/ / / / / / / / / / / / / / / / / / / /| | - * Height | | | |/ / / / / / / / / / / / / / / / / / / /| | - * |Active| | |/ / / / / / / / / / / / / / / / / / / /| | - * |Heigh | | |/ / / / / / Active Display Area / / / /| | - * | | | |/ / / / / / / / / / / / / / / / / / / /| | - * | | | |/ / / / / / / / / / / / / / / / / / / /| | - * | | | |/ / / / / / / / / / / / / / / / / / / /| | - * | | | |/ / / / / / / / / / / / / / / / / / / /| | - * | | | |/ / / / / / / / / / / / / / / / / / / /| | - * | \|/_____|_________|_______________________________________| | - * | /|\ | | - * | VFP | | | - * \|/ \|/_____|______________________________________________________| - * @endverbatim - */ -typedef struct { - uint32_t pclk_hz; /*!< Frequency of pixel clock */ - uint32_t h_res; /*!< Horizontal resolution, i.e. the number of pixels in a line */ - uint32_t v_res; /*!< Vertical resolution, i.e. the number of lines in the frame */ - uint32_t hsync_pulse_width; /*!< Horizontal sync width, unit: PCLK period */ - uint32_t hsync_back_porch; /*!< Horizontal back porch, number of PCLK between hsync and start of line active data */ - uint32_t hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */ - uint32_t vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */ - uint32_t vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */ - uint32_t vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */ - struct { - uint32_t hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */ - uint32_t vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */ - uint32_t de_idle_high: 1; /*!< The de signal is high in IDLE state */ - uint32_t pclk_active_neg: 1; /*!< Whether the display data is clocked out on the falling edge of PCLK */ - uint32_t pclk_idle_high: 1; /*!< The PCLK stays at high level in IDLE phase */ - } flags; /*!< LCD RGB timing flags */ -} esp_lcd_rgb_timing_t; - -/** - * @brief Type of RGB LCD panel event data - */ -typedef struct { -} esp_lcd_rgb_panel_event_data_t; - -/** - * @brief RGB LCD VSYNC event callback prototype - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] edata Panel event data, fed by driver - * @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_register_event_callbacks()` - * @return Whether a high priority task has been waken up by this function - */ -typedef bool (*esp_lcd_rgb_panel_vsync_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx); - -/** - * @brief Prototype for function to re-fill a bounce buffer, rather than copying from the frame buffer - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] bounce_buf Bounce buffer to write data into - * @param[in] pos_px How many pixels already were sent to the display in this frame, in other words, - * at what pixel the routine should start putting data into bounce_buf - * @param[in] len_bytes Length, in bytes, of the bounce buffer. Routine should fill this length fully. - * @param[in] user_ctx Opaque pointer that was passed from `esp_lcd_rgb_panel_register_event_callbacks()` - * @return Whether a high priority task has been waken up by this function - */ -typedef bool (*esp_lcd_rgb_panel_bounce_buf_fill_cb_t)(esp_lcd_panel_handle_t panel, void *bounce_buf, int pos_px, int len_bytes, void *user_ctx); - -/** - * @brief Prototype for the function to be called when the bounce buffer finish copying the entire frame. - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] edata Panel event data, fed by driver - * @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_register_event_callbacks()` - * @return Whether a high priority task has been waken up by this function - */ -typedef bool (*esp_lcd_rgb_panel_bounce_buf_finish_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx); - -/** - * @brief Group of supported RGB LCD panel callbacks - * @note The callbacks are all running under ISR environment - * @note When CONFIG_LCD_RGB_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. - */ -typedef struct { - esp_lcd_rgb_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */ - esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */ - esp_lcd_rgb_panel_bounce_buf_finish_cb_t on_bounce_frame_finish; /*!< Bounce buffer finish callback. */ -} esp_lcd_rgb_panel_event_callbacks_t; - -/** - * @brief LCD RGB panel configuration structure - */ -typedef struct { - lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */ - esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters, including the screen resolution */ - size_t data_width; /*!< Number of data lines */ - size_t bits_per_pixel; /*!< Frame buffer color depth, in bpp, specially, if set to zero, it will default to `data_width`. - When using a Serial RGB interface, this value could be different from `data_width` */ - size_t num_fbs; /*!< Number of screen-sized frame buffers that allocated by the driver. By default (set to either 0 or 1) only one frame buffer will be used. Maximum number of buffers are 3 */ - size_t bounce_buffer_size_px; /*!< If it's non-zero, the driver allocates two DRAM bounce buffers for DMA use. - DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */ - size_t sram_trans_align; /*!< Alignment of buffers (frame buffer or bounce buffer) that allocated in SRAM */ - size_t psram_trans_align; /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */ - int hsync_gpio_num; /*!< GPIO used for HSYNC signal */ - int vsync_gpio_num; /*!< GPIO used for VSYNC signal */ - int de_gpio_num; /*!< GPIO used for DE signal, set to -1 if it's not used */ - int pclk_gpio_num; /*!< GPIO used for PCLK signal, set to -1 if it's not used */ - int disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */ - int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */ - struct { - uint32_t disp_active_low: 1; /*!< If this flag is enabled, a low level of display control signal can turn the screen on; vice versa */ - uint32_t refresh_on_demand: 1; /*!< If this flag is enabled, the host only refresh the frame buffer when `esp_lcd_panel_draw_bitmap` is called. - This is useful when the LCD screen has a GRAM and can refresh the LCD by itself. */ - uint32_t fb_in_psram: 1; /*!< If this flag is enabled, the frame buffer will be allocated from PSRAM, preferentially */ - uint32_t double_fb: 1; /*!< If this flag is enabled, the driver will allocate two screen sized frame buffer, same as num_fbs=2 */ - uint32_t no_fb: 1; /*!< If this flag is enabled, the driver won't allocate frame buffer. - Instead, user should fill in the bounce buffer manually in the `on_bounce_empty` callback */ - uint32_t bb_invalidate_cache: 1; /*!< If this flag is enabled, in bounce back mode we'll do a cache invalidate on the read data, freeing the cache. - Can be dangerous if data is written from other core(s). */ - } flags; /*!< LCD RGB panel configuration flags */ -} esp_lcd_rgb_panel_config_t; - -/** - * @brief Create RGB LCD panel - * - * @param[in] rgb_panel_config RGB panel configuration - * @param[out] ret_panel Returned LCD panel handle - * @return - * - ESP_ERR_INVALID_ARG: Create RGB LCD panel failed because of invalid argument - * - ESP_ERR_NO_MEM: Create RGB LCD panel failed because of out of memory - * - ESP_ERR_NOT_FOUND: Create RGB LCD panel failed because some mandatory hardware resources are not found - * - ESP_OK: Create RGB LCD panel successfully - */ -esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel); - -/** - * @brief Register LCD RGB panel event callbacks - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] callbacks Group of callback functions - * @param[in] user_ctx User data, which will be passed to the callback functions directly - * @return - * - ESP_OK: Set event callbacks successfully - * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument - * - ESP_FAIL: Set event callbacks failed because of other error - */ -esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_callbacks_t *callbacks, void *user_ctx); - -/** - * @brief Set frequency of PCLK for RGB LCD panel - * - * @note The PCLK frequency is set in the `esp_lcd_rgb_timing_t` and gets configured during LCD panel initialization. - * Usually you don't need to call this function to set the PCLK again, but in some cases, you might want to change the PCLK frequency. - * e.g. slow down the PCLK frequency to reduce power consumption or to reduce the memory throughput during OTA. - * @note This function doesn't cause the hardware to update the PCLK immediately but to record the new frequency and set a flag internally. - * Only in the next VSYNC event handler, will the driver attempt to update the PCLK frequency. - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] freq_hz Frequency of pixel clock, in Hz - * @return - * - ESP_ERR_INVALID_ARG: Set PCLK frequency failed because of invalid argument - * - ESP_OK: Set PCLK frequency successfully - */ -esp_err_t esp_lcd_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz); - -/** - * @brief Restart the LCD transmission - * - * @note This function can be useful when the LCD controller is out of sync with the DMA because of insufficient bandwidth. - * To save the screen from a permanent shift, you can call this function to restart the LCD DMA. - * @note This function doesn't restart the DMA immediately but to set a flag internally. - * Only in the next VSYNC event handler, will the driver attempt to do the restart job. - * @note If CONFIG_LCD_RGB_RESTART_IN_VSYNC is enabled, you don't need to call this function manually, - * because the restart job will be done automatically in the VSYNC event handler. - * - * @param[in] panel panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @return - * - ESP_ERR_INVALID_ARG: Restart the LCD failed because of invalid argument - * - ESP_ERR_INVALID_STATE: Restart the LCD failed because the LCD diver is working in refresh-on-demand mode - * - ESP_OK: Restart the LCD successfully - */ -esp_err_t esp_lcd_rgb_panel_restart(esp_lcd_panel_handle_t panel); - -/** - * @brief Get the address of the frame buffer(s) that allocated by the driver - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] fb_num Number of frame buffer(s) to get. This value must be the same as the number of the following parameters. - * @param[out] fb0 Returned address of the frame buffer 0 - * @param[out] ... List of other frame buffer addresses - * @return - * - ESP_ERR_INVALID_ARG: Get frame buffer address failed because of invalid argument - * - ESP_OK: Get frame buffer address successfully - */ -esp_err_t esp_lcd_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...); - -/** - * @brief Manually trigger once transmission of the frame buffer to the LCD panel - * - * @note This function should only be called when the RGB panel is working under the `refresh_on_demand` mode. - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @return - * - ESP_ERR_INVALID_ARG: Start a refresh failed because of invalid argument - * - ESP_ERR_INVALID_STATE: Start a refresh failed because the LCD panel is not created with the `refresh_on_demand` flag enabled. - * - ESP_OK: Start a refresh successfully - */ -esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel); - -/** - * @brief LCD color conversion profile - */ -typedef struct { - lcd_color_space_t color_space; /*!< Color space of the image */ - lcd_color_range_t color_range; /*!< Color range of the image */ - lcd_yuv_sample_t yuv_sample; /*!< YUV sample format of the image */ -} esp_lcd_color_conv_profile_t; - -/** - * @brief Configuration of YUG-RGB conversion - */ -typedef struct { - lcd_yuv_conv_std_t std; /*!< YUV conversion standard: BT601, BT709 */ - esp_lcd_color_conv_profile_t src; /*!< Color conversion profile of the input image */ - esp_lcd_color_conv_profile_t dst; /*!< Color conversion profile of the output image */ -} esp_lcd_yuv_conv_config_t; - -/** - * @brief Configure how to convert the color format between RGB and YUV - * - * @note Pass in `config` as NULL will disable the RGB-YUV converter. - * @note The hardware converter can only parse a "packed" storage format, while "planar" and "semi-planar" format is not supported. - * - * @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel` - * @param[in] config Configuration of RGB-YUV conversion - * @return - * - ESP_ERR_INVALID_ARG: Configure RGB-YUV conversion failed because of invalid argument - * - ESP_ERR_NOT_SUPPORTED: Configure RGB-YUV conversion failed because the conversion mode is not supported by the hardware - * - ESP_OK: Configure RGB-YUV conversion successfully - */ -esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config); - -#endif // SOC_LCD_RGB_SUPPORTED - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_vendor.h b/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_vendor.h deleted file mode 100644 index 2f5c8fd26..000000000 --- a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_panel_vendor.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_err.h" -#include "esp_lcd_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Configuration structure for panel device - */ -typedef struct { - int reset_gpio_num; /*!< GPIO used to reset the LCD panel, set to -1 if it's not used */ - union { - lcd_rgb_element_order_t color_space; /*!< @deprecated Set RGB color space, please use rgb_ele_order instead */ - lcd_rgb_element_order_t rgb_endian; /*!< @deprecated Set RGB data endian, please use rgb_ele_order instead */ - lcd_rgb_element_order_t rgb_ele_order; /*!< Set RGB element order, RGB or BGR */ - }; - lcd_rgb_data_endian_t data_endian; /*!< Set the data endian for color data larger than 1 byte */ - unsigned int bits_per_pixel; /*!< Color depth, in bpp */ - struct { - unsigned int reset_active_high: 1; /*!< Setting this if the panel reset is high level active */ - } flags; /*!< LCD panel config flags */ - void *vendor_config; /*!< vendor specific configuration, optional, left as NULL if not used */ -} esp_lcd_panel_dev_config_t; - -/** - * @brief Create LCD panel for model ST7789 - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config general panel device configuration - * @param[out] ret_panel Returned LCD panel handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); - -/** - * @brief Create LCD panel for model NT35510 - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config general panel device configuration - * @param[out] ret_panel Returned LCD panel handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_nt35510(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); - -/** - * @brief Create LCD panel for model SSD1306 - * - * @param[in] io LCD panel IO handle - * @param[in] panel_dev_config general panel device configuration - * @param[out] ret_panel Returned LCD panel handle - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NO_MEM if out of memory - * - ESP_OK on success - */ -esp_err_t esp_lcd_new_panel_ssd1306(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel); - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_types.h b/tulip/esp32s3/components/esp_lcd/include/esp_lcd_types.h deleted file mode 100644 index 6f5928061..000000000 --- a/tulip/esp32s3/components/esp_lcd/include/esp_lcd_types.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include "hal/lcd_types.h" -#include "esp_assert.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct esp_lcd_panel_io_t *esp_lcd_panel_io_handle_t; /*!< Type of LCD panel IO handle */ -typedef struct esp_lcd_panel_t *esp_lcd_panel_handle_t; /*!< Type of LCD panel handle */ - -/** @cond */ -/** - * @brief LCD color space type definition (WRONG!) - * @deprecated RGB and BGR should belong to the same color space, but this enum take them both as two different color spaces. - * If you want to use a enum to describe a color space, please use lcd_color_space_t instead. - */ -typedef enum { - ESP_LCD_COLOR_SPACE_RGB, /*!< Color space: RGB */ - ESP_LCD_COLOR_SPACE_BGR, /*!< Color space: BGR */ - ESP_LCD_COLOR_SPACE_MONOCHROME, /*!< Color space: monochrome */ -} esp_lcd_color_space_t __attribute__((deprecated)); - -// Ensure binary compatibility with lcd_color_rgb_endian_t -ESP_STATIC_ASSERT((lcd_rgb_element_order_t)ESP_LCD_COLOR_SPACE_RGB == LCD_RGB_ELEMENT_ORDER_RGB, "ESP_LCD_COLOR_SPACE_RGB is not compatible with LCD_RGB_ORDER_RGB"); -ESP_STATIC_ASSERT((lcd_rgb_element_order_t)ESP_LCD_COLOR_SPACE_BGR == LCD_RGB_ELEMENT_ORDER_BGR, "ESP_LCD_COLOR_SPACE_BGR is not compatible with LCD_RGB_ORDER_BGR"); -/** @endcond */ - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/interface/esp_lcd_panel_interface.h b/tulip/esp32s3/components/esp_lcd/interface/esp_lcd_panel_interface.h deleted file mode 100644 index ff5d289d7..000000000 --- a/tulip/esp32s3/components/esp_lcd/interface/esp_lcd_panel_interface.h +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct esp_lcd_panel_t esp_lcd_panel_t; /*!< Type of LCD panel */ - -/** - * @brief LCD panel interface - */ -struct esp_lcd_panel_t { - /** - * @brief Reset LCD panel - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @return - * - ESP_OK on success - */ - esp_err_t (*reset)(esp_lcd_panel_t *panel); - - /** - * @brief Initialize LCD panel - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @return - * - ESP_OK on success - */ - esp_err_t (*init)(esp_lcd_panel_t *panel); - - /** - * @brief Destory LCD panel - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @return - * - ESP_OK on success - */ - esp_err_t (*del)(esp_lcd_panel_t *panel); - - /** - * @brief Draw bitmap on LCD panel - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_start Start index on x-axis (x_start included) - * @param[in] y_start Start index on y-axis (y_start included) - * @param[in] x_end End index on x-axis (x_end not included) - * @param[in] y_end End index on y-axis (y_end not included) - * @param[in] color_data RGB color data that will be dumped to the specific window range - * @return - * - ESP_OK on success - */ - esp_err_t (*draw_bitmap)(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); - - /** - * @brief Mirror the LCD panel on specific axis - * - * @note Combine this function with `swap_xy`, one can realize screen rotatation - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_axis Whether the panel will be mirrored about the x_axis - * @param[in] y_axis Whether the panel will be mirrored about the y_axis - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ - esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis); - - /** - * @brief Swap/Exchange x and y axis - * - * @note Combine this function with `mirror`, one can realize screen rotatation - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] swap_axes Whether to swap the x and y axis - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ - esp_err_t (*swap_xy)(esp_lcd_panel_t *panel, bool swap_axes); - - /** - * @brief Set extra gap in x and y axis - * - * @note The gap is only used for calculating the real coordinates. - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_gap Extra gap on x axis, in pixels - * @param[in] y_gap Extra gap on y axis, in pixels - * @return - * - ESP_OK on success - */ - esp_err_t (*set_gap)(esp_lcd_panel_t *panel, int x_gap, int y_gap); - - /** - * @brief Invert the color (bit 1 -> 0 for color data line, and vice versa) - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] invert_color_data Whether to invert the color data - * @return - * - ESP_OK on success - */ - esp_err_t (*invert_color)(esp_lcd_panel_t *panel, bool invert_color_data); - - /** - * @brief Turn on or off the display - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] on_off True to turns on display, False to turns off display - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ - esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off); - - /** - * @brief Enter or exit sleep mode - * - * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] sleep True to enter sleep mode, False to wake up - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel - */ - esp_err_t (*disp_sleep)(esp_lcd_panel_t *panel, bool sleep); - - void *user_data; /*!< User data, used to store externally customized data */ -}; - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/interface/esp_lcd_panel_io_interface.h b/tulip/esp32s3/components/esp_lcd/interface/esp_lcd_panel_io_interface.h deleted file mode 100644 index 88bf9db0d..000000000 --- a/tulip/esp32s3/components/esp_lcd/interface/esp_lcd_panel_io_interface.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "esp_err.h" -#include "esp_lcd_panel_io.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct esp_lcd_panel_io_t esp_lcd_panel_io_t; /*!< Type of LCD panel IO */ - -/** - * @brief LCD panel IO interface - */ -struct esp_lcd_panel_io_t { - /** - * @brief Transmit LCD command and receive corresponding parameters - * - * @note This is the panel-specific interface called by function `esp_lcd_panel_io_rx_param()`. - * - * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] lcd_cmd The specific LCD command, set to -1 if no command needed - * @param[out] param Buffer for the command data - * @param[in] param_size Size of `param` buffer - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_ERR_NOT_SUPPORTED if read is not supported by transport - * - ESP_OK on success - */ - esp_err_t (*rx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size); - - /** - * @brief Transmit LCD command and corresponding parameters - * - * @note This is the panel-specific interface called by function `esp_lcd_panel_io_tx_param()`. - * - * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] lcd_cmd The specific LCD command - * @param[in] param Buffer that holds the command specific parameters, set to NULL if no parameter is needed for the command - * @param[in] param_size Size of `param` in memory, in bytes, set to zero if no parameter is needed for the command - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ - esp_err_t (*tx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); - - /** - * @brief Transmit LCD RGB data - * - * @note This is the panel-specific interface called by function `esp_lcd_panel_io_tx_color()`. - * - * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] lcd_cmd The specific LCD command - * @param[in] color Buffer that holds the RGB color data - * @param[in] color_size Size of `color` in memory, in bytes - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ - esp_err_t (*tx_color)(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); - - /** - * @brief Destory LCD panel IO handle (deinitialize all and free resource) - * - * @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()` - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ - esp_err_t (*del)(esp_lcd_panel_io_t *io); - - /** - * @brief Register LCD panel IO callbacks - * - * @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()` - * @param[in] cbs structure with all LCD panel IO callbacks - * @param[in] user_ctx User private data, passed directly to callback's user_ctx - * @return - * - ESP_ERR_INVALID_ARG if parameter is invalid - * - ESP_OK on success - */ - esp_err_t (*register_event_callbacks)(esp_lcd_panel_io_t *io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); -}; - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/linker.lf b/tulip/esp32s3/components/esp_lcd/linker.lf deleted file mode 100644 index 6e3aa0d3c..000000000 --- a/tulip/esp32s3/components/esp_lcd/linker.lf +++ /dev/null @@ -1,12 +0,0 @@ -[mapping:esp_lcd_driver] -archive: libesp_lcd.a -entries: - if LCD_RGB_ISR_IRAM_SAFE = y: - esp_lcd_common: lcd_com_mount_dma_data (noflash) - -[mapping:esp_lcd_hal] -archive: libhal.a -entries: - if LCD_RGB_ISR_IRAM_SAFE = y: - lcd_hal: lcd_hal_cal_pclk_freq (noflash) - hal_utils: hal_utils_calc_clk_div_frac_fast (noflash) diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_common.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_common.c deleted file mode 100644 index f9716b785..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_common.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "freertos/FreeRTOS.h" -#include "soc/soc_caps.h" -#include "esp_lcd_common.h" -#if SOC_LCDCAM_SUPPORTED -#include "hal/lcd_ll.h" -#include "hal/lcd_hal.h" - -typedef struct esp_lcd_platform_t { - portMUX_TYPE spinlock; // spinlock used to protect platform level resources - union { - void *panels[SOC_LCD_RGB_PANELS]; // array of RGB LCD panel instances - void *buses[SOC_LCD_I80_BUSES]; // array of i80 bus instances - }; // LCD peripheral can only work under either RGB mode or intel 8080 mode -} esp_lcd_platform_t; - -esp_lcd_platform_t s_lcd_platform = { - .spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED, - .buses = {} // initially the bus slots and panel slots are empty -}; - -int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj) -{ - int member_id = -1; - switch (device_type) { - case LCD_COM_DEVICE_TYPE_I80: - // search for a bus slot then register to platform - for (int i = 0; (i < SOC_LCD_I80_BUSES) && (member_id == -1); i++) { - portENTER_CRITICAL(&s_lcd_platform.spinlock); - if (!s_lcd_platform.buses[i]) { - s_lcd_platform.buses[i] = device_obj; - member_id = i; - } - portEXIT_CRITICAL(&s_lcd_platform.spinlock); - } - break; - case LCD_COM_DEVICE_TYPE_RGB: - // search for a panel slot then register to platform - for (int i = 0; (i < SOC_LCD_RGB_PANELS) && (member_id == -1); i++) { - portENTER_CRITICAL(&s_lcd_platform.spinlock); - if (!s_lcd_platform.panels[i]) { - s_lcd_platform.panels[i] = device_obj; - member_id = i; - } - portEXIT_CRITICAL(&s_lcd_platform.spinlock); - } - break; - default: - break; - } - return member_id; -} - -void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id) -{ - switch (device_type) { - case LCD_COM_DEVICE_TYPE_I80: - portENTER_CRITICAL(&s_lcd_platform.spinlock); - if (s_lcd_platform.buses[member_id]) { - s_lcd_platform.buses[member_id] = NULL; - } - portEXIT_CRITICAL(&s_lcd_platform.spinlock); - break; - case LCD_COM_DEVICE_TYPE_RGB: - portENTER_CRITICAL(&s_lcd_platform.spinlock); - if (s_lcd_platform.panels[member_id]) { - s_lcd_platform.panels[member_id] = NULL; - } - portEXIT_CRITICAL(&s_lcd_platform.spinlock); - break; - default: - break; - } -} -#endif // SOC_LCDCAM_SUPPORTED - -void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len) -{ - size_t prepared_length = 0; - uint8_t *data = (uint8_t *)buffer; - dma_descriptor_t *desc = desc_head; - while (len > DMA_DESCRIPTOR_BUFFER_MAX_SIZE) { - desc->dw0.suc_eof = 0; // not the end of the transaction - desc->dw0.size = DMA_DESCRIPTOR_BUFFER_MAX_SIZE; - desc->dw0.length = DMA_DESCRIPTOR_BUFFER_MAX_SIZE; - desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; - desc->buffer = &data[prepared_length]; - desc = desc->next; // move to next descriptor - prepared_length += DMA_DESCRIPTOR_BUFFER_MAX_SIZE; - len -= DMA_DESCRIPTOR_BUFFER_MAX_SIZE; - } - if (len) { - desc->dw0.suc_eof = 1; // end of the transaction - desc->dw0.size = len; - desc->dw0.length = len; - desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; - desc->buffer = &data[prepared_length]; - desc = desc->next; // move to next descriptor - prepared_length += len; - } -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_common.h b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_common.h deleted file mode 100644 index f036bc5fa..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_common.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include -#include "sdkconfig.h" -#include "soc/soc_caps.h" -#include "hal/dma_types.h" -#include "esp_intr_alloc.h" -#include "esp_heap_caps.h" -#if SOC_LCDCAM_SUPPORTED -#include "hal/lcd_hal.h" -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define LCD_I80_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED -#define LCD_I80_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT - -#define LCD_PERIPH_CLOCK_PRE_SCALE (2) // This is the minimum divider that can be applied to LCD peripheral - -#if SOC_LCDCAM_SUPPORTED - -typedef enum { - LCD_COM_DEVICE_TYPE_I80, - LCD_COM_DEVICE_TYPE_RGB -} lcd_com_device_type_t; - -/** - * @brief Register a LCD device to platform - * - * @param device_type Device type, refer to lcd_com_device_type_t - * @param device_obj Device object - * @return >=0: member_id, <0: no free lcd bus/panel slots - */ -int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj); - -/** - * @brief Remove a device from platform - * - * @param device_type Device type, refer to lcd_com_device_type_t - * @param member_id member ID - */ -void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id); -#endif // SOC_LCDCAM_SUPPORTED - -/** - * @brief Mount data to DMA descriptors - * - * @param desc_head Point to the head of DMA descriptor chain - * @param buffer Data buffer - * @param len Size of the data buffer, in bytes - */ -void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len); - -/** - * @brief Reverse the bytes in the buffer - * - * @note LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first - * However, the low level peripheral (like i80, i2s) will send 0x34 first. - * This helper function is used to reverse the bytes order - * - * @param buf buffer address - * @param start start index of the buffer - * @param end end index of the buffer - */ -static inline void lcd_com_reverse_buffer_bytes(uint8_t *buf, int start, int end) -{ - uint8_t temp = 0; - while (start < end) { - temp = buf[start]; - buf[start] = buf[end]; - buf[end] = temp; - start++; - end--; - } -} - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io.c deleted file mode 100644 index 917ca6d4c..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io.c +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "esp_check.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_io_interface.h" - -static const char *TAG = "lcd_panel.io"; - -esp_err_t esp_lcd_panel_io_rx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, void *param, size_t param_size) -{ - ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle"); - ESP_RETURN_ON_FALSE(io->rx_param, ESP_ERR_NOT_SUPPORTED, TAG, "rx_param is not supported yet"); - return io->rx_param(io, lcd_cmd, param, param_size); -} - -esp_err_t esp_lcd_panel_io_tx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) -{ - ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle"); - return io->tx_param(io, lcd_cmd, param, param_size); -} - -esp_err_t esp_lcd_panel_io_tx_color(esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *color, size_t color_size) -{ - ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle"); - return io->tx_color(io, lcd_cmd, color, color_size); -} - -esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io) -{ - ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle"); - return io->del(io); -} - -esp_err_t esp_lcd_panel_io_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx) -{ - ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle"); - ESP_RETURN_ON_FALSE(cbs, ESP_ERR_INVALID_ARG, TAG, "invalid callbacks structure"); - return io->register_event_callbacks(io, cbs, user_ctx); -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2c_v1.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2c_v1.c deleted file mode 100644 index ba7731b2d..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2c_v1.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "esp_lcd_panel_io_interface.h" -#include "esp_lcd_panel_io.h" -#include "driver/i2c.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" - -static const char *TAG = "lcd_panel.io.i2c"; - -#define CMD_HANDLER_BUFFER_SIZE I2C_LINK_RECOMMENDED_SIZE(2) // only 2 operations will be queued in the handler ATTOW -#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF) - -static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io); -static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size); -static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); -static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); -static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); - -typedef struct { - esp_lcd_panel_io_t base; // Base class of generic lcd panel io - uint32_t i2c_bus_id; // I2C bus id, indicating which I2C port - uint32_t dev_addr; // Device address - int lcd_cmd_bits; // Bit width of LCD command - int lcd_param_bits; // Bit width of LCD parameter - bool control_phase_enabled; // Is control phase enabled - uint32_t control_phase_cmd; // control byte when transferring command - uint32_t control_phase_data; // control byte when transferring data - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done - void *user_ctx; // User's private data, passed directly to callback on_color_trans_done() - uint8_t cmdlink_buffer[]; // pre-alloc I2C command link buffer, to be reused in all transactions -} lcd_panel_io_i2c_t; - -esp_err_t esp_lcd_new_panel_io_i2c_v1(uint32_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = NULL; - ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes"); - ESP_GOTO_ON_FALSE(io_config->scl_speed_hz == 0, ESP_ERR_INVALID_ARG, err, TAG, "scl_speed_hz is not need to set in legacy i2c_lcd driver"); - i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t) + CMD_HANDLER_BUFFER_SIZE); // expand zero-length array cmdlink_buffer - ESP_GOTO_ON_FALSE(i2c_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c panel io"); - - i2c_panel_io->i2c_bus_id = bus; - i2c_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits; - i2c_panel_io->lcd_param_bits = io_config->lcd_param_bits; - i2c_panel_io->on_color_trans_done = io_config->on_color_trans_done; - i2c_panel_io->user_ctx = io_config->user_ctx; - i2c_panel_io->control_phase_enabled = (!io_config->flags.disable_control_phase); - i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset); - i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset); - i2c_panel_io->dev_addr = io_config->dev_addr; - i2c_panel_io->base.del = panel_io_i2c_del; - i2c_panel_io->base.rx_param = panel_io_i2c_rx_param; - i2c_panel_io->base.tx_param = panel_io_i2c_tx_param; - i2c_panel_io->base.tx_color = panel_io_i2c_tx_color; - i2c_panel_io->base.register_event_callbacks = panel_io_i2c_register_event_callbacks; - *ret_io = &(i2c_panel_io->base); - ESP_LOGD(TAG, "new i2c lcd panel io @%p", i2c_panel_io); - - return ESP_OK; -err: - return ret; -} - -static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - - ESP_LOGD(TAG, "del lcd panel i2c @%p", i2c_panel_io); - free(i2c_panel_io); - return ret; -} - -static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx) -{ - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - - if (i2c_panel_io->on_color_trans_done != NULL) { - ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was owerwritten!"); - } - - i2c_panel_io->on_color_trans_done = cbs->on_color_trans_done; - i2c_panel_io->user_ctx = user_ctx; - - return ESP_OK; -} - -static esp_err_t panel_io_i2c_rx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, void *buffer, size_t buffer_size) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - bool send_param = (lcd_cmd >= 0); - - i2c_cmd_handle_t cmd_link = i2c_cmd_link_create_static(i2c_panel_io->cmdlink_buffer, CMD_HANDLER_BUFFER_SIZE); - ESP_GOTO_ON_FALSE(cmd_link, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c cmd link"); - ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase - ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_WRITE, true), err, TAG, "write address failed"); // address phase - if (send_param) { - if (i2c_panel_io->control_phase_enabled) { - ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, i2c_panel_io->control_phase_cmd, true), - err, TAG, "write control phase failed"); // control phase - } - uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)}; - size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8; - if (cmds_size > 0 && cmds_size <= sizeof(cmds)) { - ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, cmds + (sizeof(cmds) - cmds_size), cmds_size, true), err, TAG, "write LCD cmd failed"); - } - } - - if (buffer) { - ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase - ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_READ, true), err, TAG, "write address failed"); // address phase - ESP_GOTO_ON_ERROR(i2c_master_read(cmd_link, buffer, buffer_size, I2C_MASTER_LAST_NACK), err, TAG, "read data failed"); - } - - ESP_GOTO_ON_ERROR(i2c_master_stop(cmd_link), err, TAG, "issue stop failed"); // stop phase - ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(i2c_panel_io->i2c_bus_id, cmd_link, portMAX_DELAY), err, TAG, "i2c transaction failed"); - - i2c_cmd_link_delete_static(cmd_link); - - return ESP_OK; -err: - if (cmd_link) { - i2c_cmd_link_delete_static(cmd_link); - } - return ret; -} - -static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, const void *buffer, size_t buffer_size, bool is_param) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - bool send_param = (lcd_cmd >= 0); - - i2c_cmd_handle_t cmd_link = i2c_cmd_link_create_static(i2c_panel_io->cmdlink_buffer, CMD_HANDLER_BUFFER_SIZE); - ESP_GOTO_ON_FALSE(cmd_link, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c cmd link"); - ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase - ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_WRITE, true), err, TAG, "write address failed"); // address phase - if (i2c_panel_io->control_phase_enabled) { - ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, is_param ? i2c_panel_io->control_phase_cmd : i2c_panel_io->control_phase_data, true), - err, TAG, "write control phase failed"); // control phase - } - - // some displays don't want any additional commands on data transfers - if (send_param) { - uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)}; - size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8; - if (cmds_size > 0 && cmds_size <= sizeof(cmds)) { - ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, cmds + (sizeof(cmds) - cmds_size), cmds_size, true), err, TAG, "write LCD cmd failed"); - } - } - - if (buffer) { - ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, buffer, buffer_size, true), err, TAG, "write data failed"); - } - - ESP_GOTO_ON_ERROR(i2c_master_stop(cmd_link), err, TAG, "issue stop failed"); // stop phase - ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(i2c_panel_io->i2c_bus_id, cmd_link, portMAX_DELAY), err, TAG, "i2c transaction failed"); - i2c_cmd_link_delete_static(cmd_link); - - if (!is_param) { - // trans done callback - if (i2c_panel_io->on_color_trans_done) { - i2c_panel_io->on_color_trans_done(&(i2c_panel_io->base), NULL, i2c_panel_io->user_ctx); - } - } - - return ESP_OK; -err: - if (cmd_link) { - i2c_cmd_link_delete_static(cmd_link); - } - return ret; -} - -static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size) -{ - return panel_io_i2c_rx_buffer(io, lcd_cmd, param, param_size); -} - -static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size) -{ - return panel_io_i2c_tx_buffer(io, lcd_cmd, param, param_size, true); -} - -static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size) -{ - return panel_io_i2c_tx_buffer(io, lcd_cmd, color, color_size, false); -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2c_v2.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2c_v2.c deleted file mode 100644 index fa2c834ad..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2c_v2.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "esp_lcd_panel_io_interface.h" -#include "esp_lcd_panel_io.h" -#include "driver/i2c_master.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" -#include "freertos/FreeRTOS.h" -#include "esp_heap_caps.h" -static const char *TAG = "lcd_panel.io.i2c"; - -#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF) -#define CONTROL_PHASE_LENGTH (1) -#define CMD_LENGTH (4) - -static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io); -static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size); -static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); -static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); -static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); - -typedef struct { - esp_lcd_panel_io_t base; // Base class of generic lcd panel io - i2c_master_dev_handle_t i2c_handle; // I2C master driver handle. - uint32_t dev_addr; // Device address - int lcd_cmd_bits; // Bit width of LCD command - int lcd_param_bits; // Bit width of LCD parameter - bool control_phase_enabled; // Is control phase enabled - uint32_t control_phase_cmd; // control byte when transferring command - uint32_t control_phase_data; // control byte when transferring data - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done - void *user_ctx; // User's private data, passed directly to callback on_color_trans_done() -} lcd_panel_io_i2c_t; - -esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = NULL; - i2c_master_dev_handle_t i2c_handle = NULL; - ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes"); - i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t)); - ESP_GOTO_ON_FALSE(i2c_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c panel io"); - - i2c_device_config_t i2c_lcd_cfg = { - .device_address = io_config->dev_addr, - .scl_speed_hz = io_config->scl_speed_hz, - }; - ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(bus, &i2c_lcd_cfg, &i2c_handle), err, TAG, "i2c add device fail"); - - i2c_panel_io->i2c_handle = i2c_handle; - i2c_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits; - i2c_panel_io->lcd_param_bits = io_config->lcd_param_bits; - i2c_panel_io->on_color_trans_done = io_config->on_color_trans_done; - i2c_panel_io->user_ctx = io_config->user_ctx; - i2c_panel_io->control_phase_enabled = (!io_config->flags.disable_control_phase); - i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset); - i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset); - i2c_panel_io->dev_addr = io_config->dev_addr; - i2c_panel_io->base.del = panel_io_i2c_del; - i2c_panel_io->base.rx_param = panel_io_i2c_rx_param; - i2c_panel_io->base.tx_param = panel_io_i2c_tx_param; - i2c_panel_io->base.tx_color = panel_io_i2c_tx_color; - i2c_panel_io->base.register_event_callbacks = panel_io_i2c_register_event_callbacks; - *ret_io = &(i2c_panel_io->base); - ESP_LOGD(TAG, "new i2c lcd panel io @%p", i2c_panel_io); - - return ESP_OK; -err: - if (i2c_panel_io) { - free(i2c_panel_io); - } - return ret; -} - -static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - - ESP_LOGD(TAG, "del lcd panel io i2c @%p", i2c_panel_io); - ESP_ERROR_CHECK(i2c_master_bus_rm_device(i2c_panel_io->i2c_handle)); - free(i2c_panel_io); - return ret; -} - -static esp_err_t panel_io_i2c_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx) -{ - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - - if (i2c_panel_io->on_color_trans_done != NULL) { - ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was owerwritten!"); - } - - i2c_panel_io->on_color_trans_done = cbs->on_color_trans_done; - i2c_panel_io->user_ctx = user_ctx; - - return ESP_OK; -} - -static esp_err_t panel_io_i2c_rx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, void *buffer, size_t buffer_size) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - bool send_param = (lcd_cmd >= 0); - - int write_size = 0; - uint8_t write_buffer[CONTROL_PHASE_LENGTH + CMD_LENGTH] = {0}; - - if (send_param) { - if (i2c_panel_io->control_phase_enabled) { - write_buffer[0] = i2c_panel_io->control_phase_cmd; - write_size += 1; - - } - uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)}; - size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8; - if (cmds_size > 0 && cmds_size <= sizeof(cmds)) { - memcpy(write_buffer + write_size, cmds + (sizeof(cmds) - cmds_size), cmds_size); - write_size += cmds_size; - } - } - - ESP_GOTO_ON_ERROR(i2c_master_transmit_receive(i2c_panel_io->i2c_handle, write_buffer, write_size, buffer, buffer_size, -1), err, TAG, "i2c transaction failed"); - return ESP_OK; -err: - return ret; -} - -static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, const void *buffer, size_t buffer_size, bool is_param) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base); - bool send_param = (lcd_cmd >= 0); - int write_size = 0; - uint8_t *write_buffer = (uint8_t*)heap_caps_malloc(CONTROL_PHASE_LENGTH + CMD_LENGTH + buffer_size, MALLOC_CAP_8BIT); - ESP_GOTO_ON_FALSE(write_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for write buffer"); - - if (i2c_panel_io->control_phase_enabled) { - write_buffer[0] = is_param ? i2c_panel_io->control_phase_cmd : i2c_panel_io->control_phase_data; - write_size += 1; - } - - // some displays don't want any additional commands on data transfers - if (send_param) { - uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)}; - size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8; - if (cmds_size > 0 && cmds_size <= sizeof(cmds)) { - memcpy(write_buffer + write_size, cmds + (sizeof(cmds) - cmds_size), cmds_size); - write_size += cmds_size; - } - } - - if (buffer) { - memcpy(write_buffer + write_size, buffer, buffer_size); - write_size += buffer_size; - } - - ESP_GOTO_ON_ERROR(i2c_master_transmit(i2c_panel_io->i2c_handle, write_buffer, write_size, -1), err, TAG, "i2c transaction failed"); - free(write_buffer); - if (!is_param) { - // trans done callback - if (i2c_panel_io->on_color_trans_done) { - i2c_panel_io->on_color_trans_done(&(i2c_panel_io->base), NULL, i2c_panel_io->user_ctx); - } - } - - return ESP_OK; -err: - if (write_buffer) { - free(write_buffer); - } - return ret; -} - -static esp_err_t panel_io_i2c_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size) -{ - return panel_io_i2c_rx_buffer(io, lcd_cmd, param, param_size); -} - -static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size) -{ - return panel_io_i2c_tx_buffer(io, lcd_cmd, param, param_size, true); -} - -static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size) -{ - return panel_io_i2c_tx_buffer(io, lcd_cmd, color, color_size, false); -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2s.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2s.c deleted file mode 100644 index a835f2655..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i2s.c +++ /dev/null @@ -1,787 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Although we're manipulating I2S peripheral (on esp32/s2 target), it has nothing to do with the AUDIO BUS. -// In fact, we're simulating the Intel 8080 bus with I2S peripheral, in a special parallel mode. -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_attr.h" -#include "esp_check.h" -#include "esp_intr_alloc.h" -#include "esp_heap_caps.h" -#include "esp_pm.h" -#include "esp_lcd_panel_io_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_common.h" -#include "esp_rom_gpio.h" -#include "soc/soc_caps.h" -#include "hal/dma_types.h" -#include "hal/gpio_hal.h" -#include "driver/gpio.h" -#include "esp_clk_tree.h" -#include "esp_private/periph_ctrl.h" -#include "esp_private/i2s_platform.h" -#include "soc/lcd_periph.h" -#include "hal/i2s_hal.h" -#include "hal/i2s_ll.h" -#include "hal/i2s_types.h" - -static const char *TAG = "lcd_panel.io.i80"; - -typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t; -typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t; -typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t; - -static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); -static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); -static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io); -static esp_err_t i2s_lcd_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t src); -static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus); -static esp_err_t i2s_lcd_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config); -static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus); -static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device); -static void lcd_default_isr_handler(void *args); -static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); - -struct esp_lcd_i80_bus_t { - int bus_id; // Bus ID, index from 0 - portMUX_TYPE spinlock; // spinlock used to protect i80 bus members(hal, device_list, cur_trans) - i2s_hal_context_t hal; // Hal object - size_t bus_width; // Number of data lines - int dc_gpio_num; // GPIO used for DC line - int wr_gpio_num; // GPIO used for WR line - intr_handle_t intr; // LCD peripheral interrupt handle - esp_pm_lock_handle_t pm_lock; // lock APB frequency when necessary - size_t num_dma_nodes; // Number of DMA descriptors - uint8_t *format_buffer;// The driver allocates an internal buffer for DMA to do data format transformer - unsigned long resolution_hz; // LCD_CLK resolution, determined by selected clock source - lcd_i80_trans_descriptor_t *cur_trans; // Current transaction - lcd_panel_io_i80_t *cur_device; // Current working device - LIST_HEAD(i80_device_list, lcd_panel_io_i80_t) device_list; // Head of i80 device list - struct { - unsigned int exclusive: 1; // Indicate whether the I80 bus is owned by one device (whose CS GPIO is not assigned) exclusively - } flags; - dma_descriptor_t dma_nodes[]; // DMA descriptor pool, the descriptors are shared by all i80 devices -}; - -struct lcd_i80_trans_descriptor_t { - lcd_panel_io_i80_t *i80_device; // i80 device issuing this transaction - const void *data; // Data buffer - uint32_t data_length; // Data buffer size - esp_lcd_panel_io_color_trans_done_cb_t trans_done_cb; // transaction done callback - void *user_ctx; // private data used by trans_done_cb - struct { - unsigned int dc_level: 1; // Level of DC line for this transaction - } flags; -}; - -struct lcd_panel_io_i80_t { - esp_lcd_panel_io_t base; // Base class of generic lcd panel io - esp_lcd_i80_bus_t *bus; // Which bus the device is attached to - int cs_gpio_num; // GPIO used for CS line - uint32_t pclk_hz; // PCLK clock frequency - size_t clock_prescale; // Prescaler coefficient, determined by user's configured PCLK frequency - QueueHandle_t trans_queue; // Transaction queue, transactions in this queue are pending for scheduler to dispatch - QueueHandle_t done_queue; // Transaction done queue, transactions in this queue are finished but not recycled by the caller - size_t queue_size; // Size of transaction queue - size_t num_trans_inflight; // Number of transactions that are undergoing (the descriptor not recycled yet) - int lcd_cmd_bits; // Bit width of LCD command - int lcd_param_bits; // Bit width of LCD parameter - void *user_ctx; // private data used when transfer color data - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback - LIST_ENTRY(lcd_panel_io_i80_t) device_list_entry; // Entry of i80 device list - struct { - unsigned int dc_cmd_level: 1; // Level of DC line in CMD phase - unsigned int dc_data_level: 1; // Level of DC line in DATA phase - } dc_levels; - struct { - unsigned int cs_active_high: 1; // Whether the CS line is active on high level - unsigned int swap_color_bytes: 1; // Swap adjacent two data bytes before sending out - unsigned int pclk_idle_low: 1; // The WR line keeps at low level in IDLE phase - } flags; - lcd_i80_trans_descriptor_t trans_pool[]; // Transaction pool -}; - -esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - esp_lcd_i80_bus_t *bus = NULL; - ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - // although I2S bus supports up to 24 parallel data lines, we restrict users to only use 8 or 16 bit width, due to limited GPIO numbers - ESP_GOTO_ON_FALSE(bus_config->bus_width == 8 || bus_config->bus_width == 16, ESP_ERR_INVALID_ARG, err, - TAG, "invalid bus width:%d", bus_config->bus_width); - size_t max_transfer_bytes = (bus_config->max_transfer_bytes + 3) & ~0x03; // align up to 4 bytes -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - // double the size of the internal DMA buffer if bus_width is 8, - // because one I2S FIFO (4 bytes) will only contain two bytes of valid data - max_transfer_bytes = max_transfer_bytes * 16 / bus_config->bus_width + 4; -#endif - size_t num_dma_nodes = max_transfer_bytes / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1; - // DMA descriptors must be placed in internal SRAM - bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus"); - bus->num_dma_nodes = num_dma_nodes; - bus->bus_id = -1; -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - // transform format for LCD commands, parameters and color data, so we need a big buffer - bus->format_buffer = heap_caps_calloc(1, max_transfer_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); -#else - // only transform format for LCD parameters, buffer size depends on specific LCD, set at compile time - bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); -#endif // SOC_I2S_TRANS_SIZE_ALIGN_WORD - ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer"); - // LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform - int bus_id = -1; - for (int i = 0; i < SOC_LCD_I80_BUSES; i++) { - if (i2s_platform_acquire_occupation(i, "esp_lcd_panel_io_i2s") == ESP_OK) { - bus_id = i; - break; - } - } - ESP_GOTO_ON_FALSE(bus_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free i80 bus slot"); - bus->bus_id = bus_id; - // initialize HAL layer - i2s_hal_init(&bus->hal, bus->bus_id); - // set peripheral clock resolution - ret = i2s_lcd_select_periph_clock(bus, bus_config->clk_src); - ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed"); - // reset peripheral, DMA channel and FIFO - i2s_ll_tx_reset(bus->hal.dev); - i2s_ll_tx_reset_dma(bus->hal.dev); - i2s_ll_tx_reset_fifo(bus->hal.dev); - // install interrupt service, (I2S LCD mode only uses the "TX Unit", which leaves "RX Unit" for other purpose) - // So the interrupt should also be able to share with other functionality - int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED; - ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus->bus_id].irq_id, isr_flags, - (uint32_t)i2s_ll_get_intr_status_reg(bus->hal.dev), - I2S_LL_EVENT_TX_EOF, lcd_default_isr_handler, bus, &bus->intr); - ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed"); - i2s_ll_enable_intr(bus->hal.dev, I2S_LL_EVENT_TX_EOF, false); // disable interrupt temporarily - i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); // clear pending interrupt - // initialize DMA link - i2s_lcd_init_dma_link(bus); - // enable I2S LCD master mode (refer to I2S TRM) - i2s_ll_enable_lcd(bus->hal.dev, true); - i2s_ll_tx_stop_on_fifo_empty(bus->hal.dev, true); - i2s_ll_tx_bypass_pcm(bus->hal.dev, true); - i2s_ll_tx_set_slave_mod(bus->hal.dev, false); - i2s_ll_tx_set_bits_mod(bus->hal.dev, bus_config->bus_width); - i2s_ll_tx_select_std_slot(bus->hal.dev, I2S_STD_SLOT_BOTH, true); // copy mono - bus->bus_width = bus_config->bus_width; - i2s_ll_tx_enable_right_first(bus->hal.dev, true); -#if SOC_I2S_SUPPORTS_DMA_EQUAL - i2s_ll_tx_enable_dma_equal(bus->hal.dev, true); -#endif - // enable trans done interrupt - i2s_ll_enable_intr(bus->hal.dev, I2S_LL_EVENT_TX_EOF, true); - // trigger a quick "trans done" event, and wait for the interrupt line goes active - // this could ensure we go into ISR handler next time we call `esp_intr_enable` - i2s_lcd_trigger_quick_trans_done_event(bus); - // configure GPIO - ret = i2s_lcd_configure_gpio(bus, bus_config); - ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed"); - // fill other i80 bus runtime parameters - LIST_INIT(&bus->device_list); // initialize device list head - bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; - bus->dc_gpio_num = bus_config->dc_gpio_num; - bus->wr_gpio_num = bus_config->wr_gpio_num; - *ret_bus = bus; - ESP_LOGD(TAG, "new i80 bus(%d) @%p, %zu dma nodes, resolution %luHz", bus->bus_id, bus, bus->num_dma_nodes, bus->resolution_hz); - return ESP_OK; - -err: - if (bus) { - if (bus->intr) { - esp_intr_free(bus->intr); - } - if (bus->bus_id >= 0) { - i2s_platform_release_occupation(bus->bus_id); - } - if (bus->format_buffer) { - free(bus->format_buffer); - } - if (bus->pm_lock) { - esp_pm_lock_delete(bus->pm_lock); - } - free(bus); - } - return ret; -} - -esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus) -{ - esp_err_t ret = ESP_OK; - ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty"); - int bus_id = bus->bus_id; - i2s_platform_release_occupation(bus_id); - esp_intr_free(bus->intr); - if (bus->pm_lock) { - esp_pm_lock_delete(bus->pm_lock); - } - free(bus->format_buffer); - free(bus); - ESP_LOGD(TAG, "del i80 bus(%d)", bus_id); -err: - return ret; -} - -esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i80_t *i80_device = NULL; - bool bus_exclusive = false; - ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - // check if the bus has been configured as exclusive - portENTER_CRITICAL(&bus->spinlock); - if (!bus->flags.exclusive) { - bus->flags.exclusive = io_config->cs_gpio_num < 0; - } else { - bus_exclusive = true; - } - portEXIT_CRITICAL(&bus->spinlock); - ESP_GOTO_ON_FALSE(!bus_exclusive, ESP_ERR_INVALID_STATE, err, TAG, "bus has been exclusively owned by device"); - // because we set the I2S's left channel data same to right channel, so f_pclk = f_i2s/pclk_div/2 - uint32_t pclk_prescale = bus->resolution_hz / 2 / io_config->pclk_hz; - ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= I2S_LL_BCK_MAX_PRESCALE, ESP_ERR_NOT_SUPPORTED, err, TAG, - "prescaler can't satisfy PCLK clock %"PRIu32"Hz", io_config->pclk_hz); - i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io"); - // create two queues for i80 device - i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *)); - ESP_GOTO_ON_FALSE(i80_device->trans_queue, ESP_ERR_NO_MEM, err, TAG, "create trans queue failed"); - i80_device->done_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *)); - ESP_GOTO_ON_FALSE(i80_device->done_queue, ESP_ERR_NO_MEM, err, TAG, "create done queue failed"); - // adding device to list - portENTER_CRITICAL(&bus->spinlock); - LIST_INSERT_HEAD(&bus->device_list, i80_device, device_list_entry); - portEXIT_CRITICAL(&bus->spinlock); - // we don't initialize the i80 bus at the memont, but initialize the bus when start a transaction for a new device - // so save these as i80 device runtime parameters - i80_device->bus = bus; - i80_device->queue_size = io_config->trans_queue_depth; - i80_device->clock_prescale = pclk_prescale; - i80_device->lcd_cmd_bits = io_config->lcd_cmd_bits; - i80_device->lcd_param_bits = io_config->lcd_param_bits; - i80_device->pclk_hz = bus->resolution_hz / pclk_prescale / 2; - i80_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level; - i80_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level; - i80_device->cs_gpio_num = io_config->cs_gpio_num; - i80_device->on_color_trans_done = io_config->on_color_trans_done; - i80_device->user_ctx = io_config->user_ctx; - i80_device->flags.cs_active_high = io_config->flags.cs_active_high; - i80_device->flags.swap_color_bytes = io_config->flags.swap_color_bytes; - i80_device->flags.pclk_idle_low = io_config->flags.pclk_idle_low; - // fill panel io function table - i80_device->base.del = panel_io_i80_del; - i80_device->base.tx_param = panel_io_i80_tx_param; - i80_device->base.tx_color = panel_io_i80_tx_color; - i80_device->base.register_event_callbacks = panel_io_i80_register_event_callbacks; - if (io_config->cs_gpio_num >= 0) { - // CS signal is controlled by software - gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high); // de-assert by default - gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO); - } - *ret_io = &(i80_device->base); - ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d), pclk=%"PRIu32"Hz", i80_device, bus->bus_id, i80_device->pclk_hz); - return ESP_OK; - -err: - if (i80_device) { - if (i80_device->trans_queue) { - vQueueDelete(i80_device->trans_queue); - } - if (i80_device->done_queue) { - vQueueDelete(i80_device->done_queue); - } - free(i80_device); - } - return ret; -} - -static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io) -{ - lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base); - esp_lcd_i80_bus_t *bus = i80_device->bus; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - size_t num_trans_inflight = i80_device->num_trans_inflight; - // wait all pending transaction to finish - for (size_t i = 0; i < num_trans_inflight; i++) { - ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE, - ESP_FAIL, TAG, "recycle inflight transactions failed"); - i80_device->num_trans_inflight--; - } - // remove from device list - portENTER_CRITICAL(&bus->spinlock); - LIST_REMOVE(i80_device, device_list_entry); - portEXIT_CRITICAL(&bus->spinlock); - - // reset CS GPIO - if (i80_device->cs_gpio_num >= 0) { - gpio_reset_pin(i80_device->cs_gpio_num); - } - - ESP_LOGD(TAG, "del i80 lcd panel io @%p", i80_device); - vQueueDelete(i80_device->trans_queue); - vQueueDelete(i80_device->done_queue); - free(i80_device); - return ESP_OK; -} - -static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx) -{ - lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base); - - if (i80_device->on_color_trans_done != NULL) { - ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was owerwritten!"); - } - - i80_device->on_color_trans_done = cbs->on_color_trans_done; - i80_device->user_ctx = user_ctx; - - return ESP_OK; -} - -static void i2s_lcd_prepare_cmd_buffer(lcd_i80_trans_descriptor_t *trans_desc, const void *cmd) -{ - lcd_panel_io_i80_t *i80_device = trans_desc->i80_device; - esp_lcd_i80_bus_t *bus = i80_device->bus; - uint8_t *from = (uint8_t *)cmd; - // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the data bus first - // However, the I2S peripheral will send 0x34 first, so we reversed the order below - if (bus->bus_width < i80_device->lcd_cmd_bits) { - int start = 0; - int end = i80_device->lcd_cmd_bits / 8 - 1; - lcd_com_reverse_buffer_bytes(from, start, end); - } -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - uint8_t *to = bus->format_buffer; - int cmd_cycle = i80_device->lcd_cmd_bits / bus->bus_width; - if (cmd_cycle * bus->bus_width < i80_device->lcd_cmd_bits) { - cmd_cycle++; - } - int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_cmd_bits) / 8; - int cnt_from = 0; - // format command buffer - for (int i = 0; i < cmd_cycle; i++) { - for (int j = 0; j < bytes_to_copy; j++) { - to[2 + j] = from[cnt_from++]; - } - to += 4; - } - trans_desc->data = bus->format_buffer; - trans_desc->data_length = cmd_cycle * 4; -#else - trans_desc->data = cmd; - trans_desc->data_length = MAX(i80_device->lcd_cmd_bits, bus->bus_width) / 8; -#endif -} - -static void i2s_lcd_prepare_param_buffer(lcd_i80_trans_descriptor_t *trans_desc, const void *param, size_t param_num) -{ - lcd_panel_io_i80_t *i80_device = trans_desc->i80_device; - esp_lcd_i80_bus_t *bus = i80_device->bus; - uint8_t *from = (uint8_t *)param; - int param_size = i80_device->lcd_param_bits / 8; - // LCD is big-endian, e.g. to send param 0x1234, byte 0x12 should appear on the data bus first - // However, the I2S peripheral will send 0x34 first, so we reversed the order below - if (bus->bus_width < i80_device->lcd_param_bits) { - for (size_t i = 0; i < param_num; i++) { - int start = i * param_size; - int end = start + param_size - 1; - lcd_com_reverse_buffer_bytes(from, start, end); - } - } -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - uint8_t *to = bus->format_buffer; - int param_cycle = i80_device->lcd_param_bits / bus->bus_width; - if (param_cycle * bus->bus_width < i80_device->lcd_param_bits) { - param_cycle++; - } - int ele_cycles = param_cycle * param_num; - int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_param_bits) / 8; - int cnt_from = 0; - // format parameter buffer - for (int i = 0; i < ele_cycles; i++) { - for (int j = 0; j < bytes_to_copy; j++) { - to[2 + j] = from[cnt_from++]; - } - to += 4; - } - trans_desc->data = bus->format_buffer; - trans_desc->data_length = ele_cycles * 4; -#else - uint8_t *to = bus->format_buffer; - uint8_t step = bus->bus_width / 8; - int param_cycle = i80_device->lcd_param_bits / bus->bus_width; - if (param_cycle * bus->bus_width < i80_device->lcd_param_bits) { - param_cycle++; - } - int ele_cycles = param_cycle * param_num; - int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_param_bits) / 8; - int cnt_from = 0; - // format parameter buffer - for (int i = 0; i < ele_cycles; i++) { - for (int j = 0; j < bytes_to_copy; j++) { - to[j] = from[cnt_from++]; - } - to += step; - } - trans_desc->data = bus->format_buffer; - trans_desc->data_length = to - bus->format_buffer; -#endif -} - -static void i2s_lcd_prepare_color_buffer(lcd_i80_trans_descriptor_t *trans_desc, const void *color, size_t color_size) -{ -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - lcd_panel_io_i80_t *i80_device = trans_desc->i80_device; - esp_lcd_i80_bus_t *bus = i80_device->bus; - uint8_t *from = (uint8_t *)color; - uint8_t *to = bus->format_buffer; - int bytes_to_copy = bus->bus_width / 8; - int cnt_from = 0; - int first_half = i80_device->flags.swap_color_bytes ? 0 : 2; - int second_half = i80_device->flags.swap_color_bytes ? 2 : 0; - // format color buffer - while (cnt_from < color_size) { - for (int i = 0; i < bytes_to_copy; i++) { - to[first_half + i] = from[cnt_from++]; - } - for (int i = 0; i < bytes_to_copy; i++) { - to[second_half + i] = from[cnt_from++]; - } - to += 4; - } - trans_desc->data = bus->format_buffer; - trans_desc->data_length = to - bus->format_buffer; -#else - trans_desc->data = color; - trans_desc->data_length = color_size; -#endif -} - -static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size) -{ - lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base); - esp_lcd_i80_bus_t *bus = next_device->bus; - lcd_panel_io_i80_t *cur_device = bus->cur_device; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - assert(param_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "parameter bytes too long, enlarge max_transfer_bytes"); - assert(param_size <= CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE && "format buffer too small, increase CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE"); - size_t num_trans_inflight = next_device->num_trans_inflight; - // before issue a polling transaction, need to wait queued transactions finished - for (size_t i = 0; i < num_trans_inflight; i++) { - ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE, - ESP_FAIL, TAG, "recycle inflight transactions failed"); - next_device->num_trans_inflight--; - } - - i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); - // switch devices if necessary - lcd_i80_switch_devices(cur_device, next_device); - trans_desc = &next_device->trans_pool[0]; - trans_desc->i80_device = next_device; - trans_desc->trans_done_cb = NULL; // no callback for command transfer - bus->cur_trans = trans_desc; -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - // switch to I2S 32bits mode, one WS cycle <=> one I2S FIFO - i2s_ll_tx_set_bits_mod(bus->hal.dev, 32); -#endif - i2s_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd); - lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length); - gpio_set_level(bus->dc_gpio_num, next_device->dc_levels.dc_cmd_level); - i2s_ll_tx_stop(bus->hal.dev); - i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first - i2s_ll_start_out_link(bus->hal.dev); - // delay a while, wait for DMA data beeing feed to I2S FIFO - // in fact, this is only needed when LCD pixel clock is set too high - esp_rom_delay_us(1); - // increase the pm lock reference count before starting a new transaction - if (bus->pm_lock) { - esp_pm_lock_acquire(bus->pm_lock); - } - i2s_ll_tx_start(bus->hal.dev); - // polling the trans done event - while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {} - - // parameter is usually short, using polling mode - if (param && param_size) { - i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); - i2s_lcd_prepare_param_buffer(trans_desc, param, param_size * 8 / next_device->lcd_param_bits); - lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length); - gpio_set_level(bus->dc_gpio_num, next_device->dc_levels.dc_data_level); - i2s_ll_tx_stop(bus->hal.dev); - i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first - i2s_ll_start_out_link(bus->hal.dev); - esp_rom_delay_us(1); - i2s_ll_tx_start(bus->hal.dev); - // polling the trans done event, but don't clear the event status - while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {} - } - // decrease pm lock reference count - if (bus->pm_lock) { - esp_pm_lock_release(bus->pm_lock); - } - bus->cur_trans = NULL; - return ESP_OK; -} - -static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size) -{ - lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base); - esp_lcd_i80_bus_t *bus = next_device->bus; - lcd_panel_io_i80_t *cur_device = bus->cur_device; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - assert(color_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "color bytes too long, enlarge max_transfer_bytes"); - size_t num_trans_inflight = next_device->num_trans_inflight; - // before issue a polling transaction, need to wait queued transactions finished - for (size_t i = 0; i < num_trans_inflight; i++) { - ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE, - ESP_FAIL, TAG, "recycle inflight transactions failed"); - next_device->num_trans_inflight--; - } - - i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); - // switch devices if necessary - lcd_i80_switch_devices(cur_device, next_device); - trans_desc = &next_device->trans_pool[0]; - trans_desc->i80_device = next_device; - trans_desc->trans_done_cb = NULL; // no callback for command transfer - bus->cur_trans = trans_desc; -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - // switch to I2S 32bits mode, one WS cycle <=> one I2S FIFO - i2s_ll_tx_set_bits_mod(bus->hal.dev, 32); -#endif - i2s_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd); - lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length); - gpio_set_level(bus->dc_gpio_num, next_device->dc_levels.dc_cmd_level); - i2s_ll_tx_stop(bus->hal.dev); - i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first - i2s_ll_start_out_link(bus->hal.dev); - esp_rom_delay_us(1); - // increase the pm lock reference count before starting a new transaction - if (bus->pm_lock) { - esp_pm_lock_acquire(bus->pm_lock); - } - i2s_ll_tx_start(bus->hal.dev); - // polling the trans done event - while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {} - // decrease pm lock reference count - if (bus->pm_lock) { - esp_pm_lock_release(bus->pm_lock); - } - bus->cur_trans = NULL; - - // sending LCD color data to queue - trans_desc->trans_done_cb = next_device->on_color_trans_done; - trans_desc->user_ctx = next_device->user_ctx; - trans_desc->flags.dc_level = next_device->dc_levels.dc_data_level; // DC level for data transaction - i2s_lcd_prepare_color_buffer(trans_desc, color, color_size); - // send transaction to trans_queue - xQueueSend(next_device->trans_queue, &trans_desc, portMAX_DELAY); - next_device->num_trans_inflight++; - // enable interrupt and go into isr handler, where we fetch the transactions from trans_queue and start it - // we will go into `lcd_default_isr_handler` almost at once, because the "trans done" event is active at the moment - esp_intr_enable(bus->intr); - return ESP_OK; -} - -static esp_err_t i2s_lcd_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t src) -{ - // get clock source frequency - uint32_t src_clk_hz = 0; - ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz), - TAG, "get clock source frequency failed"); - - // I2S clock source is binary compatible with lcd_clock_source_t - i2s_ll_tx_clk_set_src(bus->hal.dev, (i2s_clock_src_t)src); - i2s_ll_set_raw_mclk_div(bus->hal.dev, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0); - // save the resolution of the i80 bus - bus->resolution_hz = src_clk_hz / LCD_PERIPH_CLOCK_PRE_SCALE; - - // create pm lock based on different clock source - // clock sources like PLL and XTAL will be turned off in light sleep -#if CONFIG_PM_ENABLE - ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "i80_bus_lcd", &bus->pm_lock), TAG, "create pm lock failed"); -#endif - return ESP_OK; -} - -static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus) -{ - for (int i = 0; i < bus->num_dma_nodes; i++) { - bus->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; - bus->dma_nodes[i].next = &bus->dma_nodes[i + 1]; - } - bus->dma_nodes[bus->num_dma_nodes - 1].next = NULL; // one-off DMA chain - i2s_ll_dma_enable_eof_on_fifo_empty(bus->hal.dev, true); - i2s_ll_dma_enable_owner_check(bus->hal.dev, true); - i2s_ll_dma_enable_auto_write_back(bus->hal.dev, true); - i2s_ll_set_out_link_addr(bus->hal.dev, (uint32_t)bus->dma_nodes); - i2s_ll_enable_dma(bus->hal.dev, true); - return ESP_OK; -} - -static esp_err_t i2s_lcd_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config) -{ - int bus_id = bus->bus_id; - // check validation of GPIO number - bool valid_gpio = (bus_config->wr_gpio_num >= 0) && (bus_config->dc_gpio_num >= 0); - for (size_t i = 0; i < bus_config->bus_width; i++) { - valid_gpio = valid_gpio && (bus_config->data_gpio_nums[i] >= 0); - } - if (!valid_gpio) { - return ESP_ERR_INVALID_ARG; - } - // connect peripheral signals via GPIO matrix - // data line - for (size_t i = 0; i < bus_config->bus_width; i++) { - gpio_set_direction(bus_config->data_gpio_nums[i], GPIO_MODE_OUTPUT); -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_signals.buses[bus_id].data_sigs[i + 8], false, false); -#else - esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_signals.buses[bus_id].data_sigs[i + SOC_LCD_I80_BUS_WIDTH - bus_config->bus_width], false, false); -#endif - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->data_gpio_nums[i]], PIN_FUNC_GPIO); - } - // WR signal (pclk) - gpio_set_direction(bus_config->wr_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(bus_config->wr_gpio_num, lcd_periph_signals.buses[bus_id].wr_sig, true, false); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->wr_gpio_num], PIN_FUNC_GPIO); - // DC signal is controlled by software, set as general purpose IO - gpio_set_direction(bus_config->dc_gpio_num, GPIO_MODE_OUTPUT); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->dc_gpio_num], PIN_FUNC_GPIO); - return ESP_OK; -} - -static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus) -{ - // trigger a quick interrupt event by a dummy transaction, wait the LCD interrupt line goes active - // next time when esp_intr_enable is invoked, we can go into interrupt handler immediately - // where we dispatch transactions for i80 devices - static uint32_t fake_trigger = 0; - lcd_com_mount_dma_data(bus->dma_nodes, &fake_trigger, 4); - i2s_ll_start_out_link(bus->hal.dev); - i2s_ll_tx_start(bus->hal.dev); - while (!(i2s_ll_get_intr_status(bus->hal.dev) & I2S_LL_EVENT_TX_EOF)) {} -} - -static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device) -{ - // the caller should make sure the next_device and cur_device are attached to the same bus - esp_lcd_i80_bus_t *bus = next_device->bus; - if (next_device != cur_device) { - // reconfigure PCLK for the new device - i2s_ll_tx_set_bck_div_num(bus->hal.dev, next_device->clock_prescale); - if (cur_device && cur_device->cs_gpio_num >= 0) { // de-assert current device - gpio_set_level(cur_device->cs_gpio_num, !cur_device->flags.cs_active_high); - } - if (next_device->cs_gpio_num >= 0) { - gpio_set_level(next_device->cs_gpio_num, next_device->flags.cs_active_high); // select the next device - } - // the WR signal (a.k.a the PCLK) generated by I2S is low level in idle stage - // but most of 8080 LCDs require the WR line to be in high level during idle stage - esp_rom_gpio_connect_out_signal(bus->wr_gpio_num, lcd_periph_signals.buses[bus->bus_id].wr_sig, !next_device->flags.pclk_idle_low, false); - } - bus->cur_device = next_device; -} - -static IRAM_ATTR void lcd_default_isr_handler(void *args) -{ - esp_lcd_i80_bus_t *bus = (esp_lcd_i80_bus_t *)args; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - lcd_panel_io_i80_t *cur_device = NULL; - lcd_panel_io_i80_t *next_device = NULL; - BaseType_t high_task_woken = pdFALSE; - bool need_yield = false; - uint32_t intr_status = i2s_ll_get_intr_status(bus->hal.dev); - if (intr_status & I2S_LL_EVENT_TX_EOF) { // trans done event - // disable interrupt temporarily, only re-enable when there be remained transaction in the queue - esp_intr_disable(bus->intr); - trans_desc = bus->cur_trans; // the finished transaction - cur_device = bus->cur_device;// the working device - // process finished transaction - if (trans_desc) { - assert(trans_desc->i80_device == cur_device && "transaction device mismatch"); - // decrease pm lock reference count - if (bus->pm_lock) { - esp_pm_lock_release(bus->pm_lock); - } - // device callback - if (trans_desc->trans_done_cb) { - if (trans_desc->trans_done_cb(&cur_device->base, NULL, trans_desc->user_ctx)) { - need_yield = true; - } - } - // move transaction to done_queue - high_task_woken = pdFALSE; - xQueueSendFromISR(cur_device->done_queue, &trans_desc, &high_task_woken); - if (high_task_woken == pdTRUE) { - need_yield = true; - } - bus->cur_trans = NULL; - } - // fetch transactions from devices' trans_queue - // Note: the first registered device will have the highest priority to be scheduled - LIST_FOREACH(next_device, &bus->device_list, device_list_entry) { - high_task_woken = pdFALSE; - if (xQueueReceiveFromISR(next_device->trans_queue, &trans_desc, &high_task_woken) == pdTRUE) { - if (high_task_woken == pdTRUE) { - need_yield = true; - } - // sanity check - assert(trans_desc); - // only clear the interrupt status when we're sure there still remains transaction to handle - i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF); - // switch devices if necessary - lcd_i80_switch_devices(cur_device, next_device); - bus->cur_trans = trans_desc; - gpio_set_level(bus->dc_gpio_num, trans_desc->flags.dc_level); - // mount data to DMA links - lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length); -#if SOC_I2S_TRANS_SIZE_ALIGN_WORD - // switch to I2S 16bits mode, two WS cycle <=> one I2S FIFO - i2s_ll_tx_set_bits_mod(bus->hal.dev, 16); -#endif - // enable interrupt again, because the new transaction can trigger new trans done event - esp_intr_enable(bus->intr); - i2s_ll_tx_stop(bus->hal.dev); - i2s_ll_tx_reset(bus->hal.dev); // reset TX engine first - i2s_ll_start_out_link(bus->hal.dev); - esp_rom_delay_us(1); - // increase the pm lock reference count before starting a new transaction - if (bus->pm_lock) { - esp_pm_lock_acquire(bus->pm_lock); - } - i2s_ll_tx_start(bus->hal.dev); - break; // exit for-each loop - } - } - } - if (need_yield) { - portYIELD_FROM_ISR(); - } -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i80.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i80.c deleted file mode 100644 index 816445541..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_i80.c +++ /dev/null @@ -1,724 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_attr.h" -#include "esp_check.h" -#include "esp_pm.h" -#include "esp_lcd_panel_io_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_rom_gpio.h" -#include "soc/soc_caps.h" -#include "esp_clk_tree.h" -#include "esp_memory_utils.h" -#include "hal/dma_types.h" -#include "hal/gpio_hal.h" -#include "hal/cache_hal.h" -#include "hal/cache_ll.h" -#include "esp_private/gdma.h" -#include "driver/gpio.h" -#include "esp_private/periph_ctrl.h" -#include "esp_lcd_common.h" -#include "soc/lcd_periph.h" -#include "hal/lcd_ll.h" -#include "hal/lcd_hal.h" -#include "esp_cache.h" - -#define ALIGN_UP(size, align) (((size) + (align) - 1) & ~((align) - 1)) -#define ALIGN_DOWN(size, align) ((size) & ~((align) - 1)) - -static const char *TAG = "lcd_panel.io.i80"; - -typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t; -typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t; -typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t; - -static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); -static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); -static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io); -static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus); -static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus); -static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src); -static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config); -static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device); -static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc); -static void lcd_default_isr_handler(void *args); -static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); - -struct esp_lcd_i80_bus_t { - int bus_id; // Bus ID, index from 0 - portMUX_TYPE spinlock; // spinlock used to protect i80 bus members(hal, device_list, cur_trans) - lcd_hal_context_t hal; // Hal object - size_t bus_width; // Number of data lines - intr_handle_t intr; // LCD peripheral interrupt handle - esp_pm_lock_handle_t pm_lock; // Power management lock - size_t num_dma_nodes; // Number of DMA descriptors - uint8_t *format_buffer; // The driver allocates an internal buffer for DMA to do data format transformer - size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source - gdma_channel_handle_t dma_chan; // DMA channel handle - size_t psram_trans_align; // DMA transfer alignment for data allocated from PSRAM - size_t sram_trans_align; // DMA transfer alignment for data allocated from SRAM - lcd_i80_trans_descriptor_t *cur_trans; // Current transaction - lcd_panel_io_i80_t *cur_device; // Current working device - LIST_HEAD(i80_device_list, lcd_panel_io_i80_t) device_list; // Head of i80 device list - struct { - unsigned int exclusive: 1; // Indicate whether the I80 bus is owned by one device (whose CS GPIO is not assigned) exclusively - } flags; - dma_descriptor_t dma_nodes[]; // DMA descriptor pool, the descriptors are shared by all i80 devices -}; - -struct lcd_i80_trans_descriptor_t { - lcd_panel_io_i80_t *i80_device; // i80 device issuing this transaction - int cmd_value; // Command value - uint32_t cmd_cycles; // Command cycles - const void *data; // Data buffer - uint32_t data_length; // Data buffer size - void *user_ctx; // private data used by trans_done_cb - esp_lcd_panel_io_color_trans_done_cb_t trans_done_cb; // transaction done callback -}; - -struct lcd_panel_io_i80_t { - esp_lcd_panel_io_t base; // Base class of generic lcd panel io - esp_lcd_i80_bus_t *bus; // Which bus the device is attached to - int cs_gpio_num; // GPIO used for CS line - unsigned int pclk_hz; // PCLK clock frequency - size_t clock_prescale; // Prescaler coefficient, determined by user's configured PCLK frequency - QueueHandle_t trans_queue; // Transaction queue, transactions in this queue are pending for scheduler to dispatch - QueueHandle_t done_queue; // Transaction done queue, transactions in this queue are finished but not recycled by the caller - size_t queue_size; // Size of transaction queue - size_t num_trans_inflight; // Number of transactions that are undergoing (the descriptor not recycled yet) - int lcd_cmd_bits; // Bit width of LCD command - int lcd_param_bits; // Bit width of LCD parameter - void *user_ctx; // private data used when transfer color data - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback - LIST_ENTRY(lcd_panel_io_i80_t) device_list_entry; // Entry of i80 device list - struct { - unsigned int dc_idle_level: 1; // Level of DC line in IDLE phase - unsigned int dc_cmd_level: 1; // Level of DC line in CMD phase - unsigned int dc_dummy_level: 1; // Level of DC line in DUMMY phase - unsigned int dc_data_level: 1; // Level of DC line in DATA phase - } dc_levels; - struct { - unsigned int cs_active_high: 1; // Whether the CS line is active on high level - unsigned int reverse_color_bits: 1; // Reverse the data bits, D[N:0] -> D[0:N] - unsigned int swap_color_bytes: 1; // Swap adjacent two data bytes before sending out - unsigned int pclk_active_neg: 1; // The display will write data lines when there's a falling edge on WR line - unsigned int pclk_idle_low: 1; // The WR line keeps at low level in IDLE phase - } flags; - lcd_i80_trans_descriptor_t trans_pool[]; // Transaction pool -}; - -esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - esp_lcd_i80_bus_t *bus = NULL; - ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - size_t num_dma_nodes = bus_config->max_transfer_bytes / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1; - // DMA descriptors must be placed in internal SRAM - bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus"); - bus->num_dma_nodes = num_dma_nodes; - bus->bus_id = -1; - bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer"); - // register to platform - int bus_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_I80, bus); - ESP_GOTO_ON_FALSE(bus_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free i80 bus slot"); - bus->bus_id = bus_id; - // enable APB to access LCD registers - PERIPH_RCC_ACQUIRE_ATOMIC(lcd_periph_signals.panels[bus_id].module, ref_count) { - if (ref_count == 0) { - lcd_ll_enable_bus_clock(bus_id, true); - lcd_ll_reset_register(bus_id); - } - } - // initialize HAL layer, so we can call LL APIs later - lcd_hal_init(&bus->hal, bus_id); - // reset peripheral and FIFO - lcd_ll_reset(bus->hal.dev); - lcd_ll_fifo_reset(bus->hal.dev); - lcd_ll_enable_clock(bus->hal.dev, true); - // set peripheral clock resolution - ret = lcd_i80_select_periph_clock(bus, bus_config->clk_src); - ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock %d failed", bus_config->clk_src); - // install interrupt service, (LCD peripheral shares the same interrupt source with Camera peripheral with different mask) - // interrupt is disabled by default - int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED; - ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus_id].irq_id, isr_flags, - (uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev), - LCD_LL_EVENT_TRANS_DONE, lcd_default_isr_handler, bus, &bus->intr); - ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed"); - lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, false); // disable all interrupts - lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt - // install DMA service - bus->psram_trans_align = bus_config->psram_trans_align; - bus->sram_trans_align = bus_config->sram_trans_align; - ret = lcd_i80_init_dma_link(bus); - ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed"); - // enable 8080 mode and set bus width - lcd_ll_enable_rgb_mode(bus->hal.dev, false); - lcd_ll_set_data_width(bus->hal.dev, bus_config->bus_width); - bus->bus_width = bus_config->bus_width; - // number of data cycles is controlled by DMA buffer size - lcd_ll_enable_output_always_on(bus->hal.dev, true); - // enable trans done interrupt - lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, true); - // trigger a quick "trans done" event, and wait for the interrupt line goes active - // this could ensure we go into ISR handler next time we call `esp_intr_enable` - lcd_periph_trigger_quick_trans_done_event(bus); - // configure GPIO - ret = lcd_i80_bus_configure_gpio(bus, bus_config); - ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed"); - // fill other i80 bus runtime parameters - LIST_INIT(&bus->device_list); // initialize device list head - bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; - *ret_bus = bus; - ESP_LOGD(TAG, "new i80 bus(%d) @%p, %zu dma nodes", bus_id, bus, bus->num_dma_nodes); - return ESP_OK; - -err: - if (bus) { - if (bus->intr) { - esp_intr_free(bus->intr); - } - if (bus->dma_chan) { - gdma_disconnect(bus->dma_chan); - gdma_del_channel(bus->dma_chan); - } - if (bus->bus_id >= 0) { - PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_signals.panels[bus->bus_id].module, ref_count) { - if (ref_count == 0) { - lcd_ll_enable_bus_clock(bus->bus_id, false); - } - } - lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus->bus_id); - } - if (bus->format_buffer) { - free(bus->format_buffer); - } - if (bus->pm_lock) { - esp_pm_lock_delete(bus->pm_lock); - } - free(bus); - } - return ret; -} - -esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus) -{ - esp_err_t ret = ESP_OK; - ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty"); - int bus_id = bus->bus_id; - lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus_id); - PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_signals.panels[bus_id].module, ref_count) { - if (ref_count == 0) { - lcd_ll_enable_bus_clock(bus_id, false); - } - } - gdma_disconnect(bus->dma_chan); - gdma_del_channel(bus->dma_chan); - esp_intr_free(bus->intr); - free(bus->format_buffer); - if (bus->pm_lock) { - esp_pm_lock_delete(bus->pm_lock); - } - free(bus); - ESP_LOGD(TAG, "del i80 bus(%d)", bus_id); -err: - return ret; -} - -esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io) -{ - esp_err_t ret = ESP_OK; - lcd_panel_io_i80_t *i80_device = NULL; - bool bus_exclusive = false; - ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - // check if the bus has been configured as exclusive - portENTER_CRITICAL(&bus->spinlock); - if (!bus->flags.exclusive) { - bus->flags.exclusive = io_config->cs_gpio_num < 0; - } else { - bus_exclusive = true; - } - portEXIT_CRITICAL(&bus->spinlock); - ESP_GOTO_ON_FALSE(!bus_exclusive, ESP_ERR_INVALID_STATE, err, TAG, "bus has been exclusively owned by device"); - // check if pixel clock setting is valid - uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz; - ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= LCD_LL_PCLK_DIV_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG, - "prescaler can't satisfy PCLK clock %"PRIu32"Hz", io_config->pclk_hz); - i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS); - ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io"); - // create two queues for i80 device - i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *)); - ESP_GOTO_ON_FALSE(i80_device->trans_queue, ESP_ERR_NO_MEM, err, TAG, "create trans queue failed"); - i80_device->done_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *)); - ESP_GOTO_ON_FALSE(i80_device->done_queue, ESP_ERR_NO_MEM, err, TAG, "create done queue failed"); - // adding device to list - portENTER_CRITICAL(&bus->spinlock); - LIST_INSERT_HEAD(&bus->device_list, i80_device, device_list_entry); - portEXIT_CRITICAL(&bus->spinlock); - // we don't initialize the i80 bus at the memont, but initialize the bus when start a transaction for a new device - // so save these as i80 device runtime parameters - i80_device->bus = bus; - i80_device->lcd_cmd_bits = io_config->lcd_cmd_bits; - i80_device->lcd_param_bits = io_config->lcd_param_bits; - i80_device->queue_size = io_config->trans_queue_depth; - i80_device->clock_prescale = pclk_prescale; - i80_device->pclk_hz = bus->resolution_hz / pclk_prescale; - i80_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level; - i80_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level; - i80_device->dc_levels.dc_dummy_level = io_config->dc_levels.dc_dummy_level; - i80_device->dc_levels.dc_idle_level = io_config->dc_levels.dc_idle_level; - i80_device->cs_gpio_num = io_config->cs_gpio_num; - i80_device->flags.reverse_color_bits = io_config->flags.reverse_color_bits; - i80_device->flags.swap_color_bytes = io_config->flags.swap_color_bytes; - i80_device->flags.cs_active_high = io_config->flags.cs_active_high; - i80_device->flags.pclk_idle_low = io_config->flags.pclk_idle_low; - i80_device->flags.pclk_active_neg = io_config->flags.pclk_active_neg; - i80_device->on_color_trans_done = io_config->on_color_trans_done; - i80_device->user_ctx = io_config->user_ctx; - // fill panel io function table - i80_device->base.del = panel_io_i80_del; - i80_device->base.tx_param = panel_io_i80_tx_param; - i80_device->base.tx_color = panel_io_i80_tx_color; - i80_device->base.register_event_callbacks = panel_io_i80_register_event_callbacks; - // we only configure the CS GPIO as output, don't connect to the peripheral signal at the moment - // we will connect the CS GPIO to peripheral signal when switching devices in lcd_i80_switch_devices() - if (io_config->cs_gpio_num >= 0) { - gpio_set_level(io_config->cs_gpio_num, !io_config->flags.cs_active_high); - gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO); - } - *ret_io = &(i80_device->base); - ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d)", i80_device, bus->bus_id); - return ESP_OK; - -err: - if (i80_device) { - if (i80_device->trans_queue) { - vQueueDelete(i80_device->trans_queue); - } - if (i80_device->done_queue) { - vQueueDelete(i80_device->done_queue); - } - free(i80_device); - } - return ret; -} - -static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io) -{ - lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base); - esp_lcd_i80_bus_t *bus = i80_device->bus; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - // wait all pending transaction to finish - size_t num_trans_inflight = i80_device->num_trans_inflight; - for (size_t i = 0; i < num_trans_inflight; i++) { - ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE, - ESP_FAIL, TAG, "recycle inflight transactions failed"); - i80_device->num_trans_inflight--; - } - // remove from device list - portENTER_CRITICAL(&bus->spinlock); - LIST_REMOVE(i80_device, device_list_entry); - portEXIT_CRITICAL(&bus->spinlock); - - // reset CS to normal GPIO - if (i80_device->cs_gpio_num >= 0) { - gpio_reset_pin(i80_device->cs_gpio_num); - } - - ESP_LOGD(TAG, "del i80 lcd panel io @%p", i80_device); - vQueueDelete(i80_device->trans_queue); - vQueueDelete(i80_device->done_queue); - free(i80_device); - return ESP_OK; -} - -static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx) -{ - lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base); - - if (i80_device->on_color_trans_done != NULL) { - ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!"); - } - - i80_device->on_color_trans_done = cbs->on_color_trans_done; - i80_device->user_ctx = user_ctx; - - return ESP_OK; -} - -static void i80_lcd_prepare_cmd_buffer(esp_lcd_i80_bus_t *bus, lcd_panel_io_i80_t *i80_device, void *lcd_cmd) -{ - uint8_t *from = (uint8_t *)lcd_cmd; - if (bus->bus_width < i80_device->lcd_cmd_bits) { - // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first - // However, the i80 peripheral will send 0x34 first, so we reversed the order below - int start = 0; - int end = i80_device->lcd_cmd_bits / 8 - 1; - lcd_com_reverse_buffer_bytes(from, start, end); - } -} - -static uint32_t i80_lcd_prepare_param_buffer(esp_lcd_i80_bus_t *bus, lcd_panel_io_i80_t *i80_device, const void *lcd_param, size_t param_size) -{ - int param_per_size = i80_device->lcd_param_bits / 8; - int param_num = param_size / param_per_size; - const uint8_t *from = (const uint8_t *)lcd_param; - uint8_t *to = bus->format_buffer; - uint8_t step = bus->bus_width / 8; - int param_cycle = i80_device->lcd_param_bits / bus->bus_width; - // in case bus_width=16 and param_bits=8, we still need 1 param_cycle - if (param_cycle * bus->bus_width < i80_device->lcd_param_bits) { - param_cycle++; - } - int ele_cycles = param_cycle * param_num; - int bytes_to_copy = MIN(bus->bus_width, i80_device->lcd_param_bits) / 8; - int cnt_from = 0; - // expand the width of parameters when necessary - for (int i = 0; i < ele_cycles; i++) { - for (int j = 0; j < bytes_to_copy; j++) { - to[j] = from[cnt_from++]; - } - to += step; - } - return to - bus->format_buffer; -} - -static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size) -{ - lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base); - esp_lcd_i80_bus_t *bus = next_device->bus; - lcd_panel_io_i80_t *cur_device = bus->cur_device; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - assert(param_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "parameter bytes too long, enlarge max_transfer_bytes"); - assert(param_size <= CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE && "format buffer too small, increase CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE"); - uint32_t cmd_cycles = next_device->lcd_cmd_bits / bus->bus_width; - // in case bus_width=16 and cmd_bits=8, we still need 1 cmd_cycle - if (cmd_cycles * bus->bus_width < next_device->lcd_cmd_bits) { - cmd_cycles++; - } - i80_lcd_prepare_cmd_buffer(bus, next_device, &lcd_cmd); - uint32_t param_len = i80_lcd_prepare_param_buffer(bus, next_device, param, param_size); - // wait all pending transaction in the queue to finish - size_t num_trans_inflight = next_device->num_trans_inflight; - for (size_t i = 0; i < num_trans_inflight; i++) { - ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE, - ESP_FAIL, TAG, "recycle inflight transactions failed"); - next_device->num_trans_inflight--; - } - - uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev); - lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status); - // switch devices if necessary - lcd_i80_switch_devices(cur_device, next_device); - // set data format - lcd_ll_reverse_bit_order(bus->hal.dev, false); - lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->lcd_param_bits > bus->bus_width); - bus->cur_trans = NULL; - bus->cur_device = next_device; - // package a transaction - trans_desc = &next_device->trans_pool[0]; - trans_desc->i80_device = next_device; - trans_desc->cmd_cycles = cmd_cycles; - trans_desc->cmd_value = lcd_cmd; - // either the param is NULL or the param_size is zero, means there isn't a data phase in this transaction - trans_desc->data = (param && param_len) ? bus->format_buffer : NULL; - trans_desc->data_length = trans_desc->data ? param_len : 0; - trans_desc->trans_done_cb = NULL; // no callback for parameter transaction - // mount data to DMA links - lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length); - // increase the pm lock reference count before starting a new transaction - if (bus->pm_lock) { - esp_pm_lock_acquire(bus->pm_lock); - } - lcd_start_transaction(bus, trans_desc); - // polling the trans done event, but don't clear the event status - while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE)) {} - // decrease pm lock reference count - if (bus->pm_lock) { - esp_pm_lock_release(bus->pm_lock); - } - return ESP_OK; -} - -static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size) -{ - lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base); - esp_lcd_i80_bus_t *bus = i80_device->bus; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - assert(color_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "color bytes too long, enlarge max_transfer_bytes"); - // in case bus_width=16 and cmd_bits=8, we still need 1 cmd_cycle - uint32_t cmd_cycles = i80_device->lcd_cmd_bits / bus->bus_width; - if (cmd_cycles * bus->bus_width < i80_device->lcd_cmd_bits) { - cmd_cycles++; - } - i80_lcd_prepare_cmd_buffer(bus, i80_device, &lcd_cmd); - if (i80_device->num_trans_inflight < i80_device->queue_size) { - trans_desc = &i80_device->trans_pool[i80_device->num_trans_inflight]; - } else { - // transaction pool has used up, recycle one from done_queue - ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE, - ESP_FAIL, TAG, "recycle inflight transactions failed"); - i80_device->num_trans_inflight--; - } - trans_desc->i80_device = i80_device; - trans_desc->cmd_cycles = cmd_cycles; - trans_desc->cmd_value = lcd_cmd; - trans_desc->data = color; - trans_desc->data_length = color_size; - trans_desc->trans_done_cb = i80_device->on_color_trans_done; - trans_desc->user_ctx = i80_device->user_ctx; - - if (esp_ptr_external_ram(color)) { - uint32_t dcache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA); - // flush frame buffer from cache to the physical PSRAM - // note the esp_cache_msync function will check the alignment of the address and size, make sure they're aligned to current cache line size - esp_cache_msync((void *)ALIGN_DOWN((intptr_t)color, dcache_line_size), ALIGN_UP(color_size, dcache_line_size), 0); - } - - // send transaction to trans_queue - xQueueSend(i80_device->trans_queue, &trans_desc, portMAX_DELAY); - i80_device->num_trans_inflight++; - // enable interrupt and go into isr handler, where we fetch the transactions from trans_queue and start it - // we will go into `lcd_default_isr_handler` almost at once, because the "trans done" event is active at the moment - esp_intr_enable(bus->intr); - return ESP_OK; -} - -static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src) -{ - // get clock source frequency - uint32_t src_clk_hz = 0; - ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz), - TAG, "get clock source frequency failed"); - - // force to use integer division, as fractional division might lead to clock jitter - lcd_ll_select_clk_src(bus->hal.dev, clk_src); - lcd_ll_set_group_clock_coeff(bus->hal.dev, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0); - - // save the resolution of the i80 bus - bus->resolution_hz = src_clk_hz / LCD_PERIPH_CLOCK_PRE_SCALE; - // create pm lock based on different clock source - // clock sources like PLL and XTAL will be turned off in light sleep -#if CONFIG_PM_ENABLE - ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "i80_bus_lcd", &bus->pm_lock), TAG, "create pm lock failed"); -#endif - return ESP_OK; -} - -static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus) -{ - esp_err_t ret = ESP_OK; - // chain DMA descriptors - for (int i = 0; i < bus->num_dma_nodes; i++) { - bus->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; - bus->dma_nodes[i].next = &bus->dma_nodes[i + 1]; - } - bus->dma_nodes[bus->num_dma_nodes - 1].next = NULL; // one-off DMA chain - // alloc DMA channel and connect to LCD peripheral - gdma_channel_alloc_config_t dma_chan_config = { - .direction = GDMA_CHANNEL_DIRECTION_TX, - }; -#if SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AHB - ret = gdma_new_ahb_channel(&dma_chan_config, &bus->dma_chan); -#elif SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AXI - ret = gdma_new_axi_channel(&dma_chan_config, &bus->dma_chan); -#endif - ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc DMA channel failed"); - gdma_connect(bus->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); - gdma_strategy_config_t strategy_config = { - .auto_update_desc = true, - .owner_check = true - }; - gdma_apply_strategy(bus->dma_chan, &strategy_config); - // set DMA transfer ability - gdma_transfer_ability_t ability = { - .psram_trans_align = bus->psram_trans_align, - .sram_trans_align = bus->sram_trans_align, - }; - gdma_set_transfer_ability(bus->dma_chan, &ability); - return ESP_OK; -err: - if (bus->dma_chan) { - gdma_del_channel(bus->dma_chan); - } - return ret; -} - -static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config) -{ - int bus_id = bus->bus_id; - // check validation of GPIO number - bool valid_gpio = (bus_config->wr_gpio_num >= 0) && (bus_config->dc_gpio_num >= 0); - for (size_t i = 0; i < bus_config->bus_width; i++) { - valid_gpio = valid_gpio && (bus_config->data_gpio_nums[i] >= 0); - } - if (!valid_gpio) { - return ESP_ERR_INVALID_ARG; - } - // connect peripheral signals via GPIO matrix - for (size_t i = 0; i < bus_config->bus_width; i++) { - gpio_set_direction(bus_config->data_gpio_nums[i], GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_signals.buses[bus_id].data_sigs[i], false, false); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->data_gpio_nums[i]], PIN_FUNC_GPIO); - } - gpio_set_direction(bus_config->dc_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(bus_config->dc_gpio_num, lcd_periph_signals.buses[bus_id].dc_sig, false, false); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->dc_gpio_num], PIN_FUNC_GPIO); - gpio_set_direction(bus_config->wr_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(bus_config->wr_gpio_num, lcd_periph_signals.buses[bus_id].wr_sig, false, false); - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->wr_gpio_num], PIN_FUNC_GPIO); - return ESP_OK; -} - -static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus) -{ - // trigger a quick interrupt event by a dummy transaction, wait the LCD interrupt line goes active - // next time when esp_intr_enable is invoked, we can go into interrupt handler immediately - // where we dispatch transactions for i80 devices - lcd_ll_set_phase_cycles(bus->hal.dev, 0, 1, 0); - lcd_ll_start(bus->hal.dev); - while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE)) {} -} - -static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc) -{ - // by default, the dummy phase is disabled because it's not common for most LCDs - uint32_t dummy_cycles = 0; - uint32_t cmd_cycles = trans_desc->cmd_value >= 0 ? trans_desc->cmd_cycles : 0; - // Number of data phase cycles are controlled by DMA buffer length, we only need to enable/disable the phase here - uint32_t data_cycles = trans_desc->data ? 1 : 0; - if (trans_desc->cmd_value >= 0) { - lcd_ll_set_command(bus->hal.dev, bus->bus_width, trans_desc->cmd_value); - } - lcd_ll_set_phase_cycles(bus->hal.dev, cmd_cycles, dummy_cycles, data_cycles); - lcd_ll_set_blank_cycles(bus->hal.dev, 1, 1); - - if (trans_desc->data) { // some specific LCD commands can have no parameters - gdma_start(bus->dma_chan, (intptr_t)(bus->dma_nodes)); - // delay 1us is sufficient for DMA to pass data to LCD FIFO - // in fact, this is only needed when LCD pixel clock is set too high - esp_rom_delay_us(1); - } - lcd_ll_start(bus->hal.dev); -} - -static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device) -{ - // we assume the next_device and cur_device are attached to the same bus - esp_lcd_i80_bus_t *bus = next_device->bus; - if (next_device != cur_device) { - // reconfigure PCLK for the new device - lcd_ll_set_pixel_clock_prescale(bus->hal.dev, next_device->clock_prescale); - lcd_ll_set_clock_idle_level(bus->hal.dev, !next_device->flags.pclk_idle_low); - lcd_ll_set_pixel_clock_edge(bus->hal.dev, next_device->flags.pclk_active_neg); - // configure DC line level for the new device - lcd_ll_set_dc_level(bus->hal.dev, next_device->dc_levels.dc_idle_level, next_device->dc_levels.dc_cmd_level, - next_device->dc_levels.dc_dummy_level, next_device->dc_levels.dc_data_level); - if (cur_device && cur_device->cs_gpio_num >= 0) { - // disconnect current CS GPIO from peripheral signal - esp_rom_gpio_connect_out_signal(cur_device->cs_gpio_num, SIG_GPIO_OUT_IDX, false, false); - } - if (next_device->cs_gpio_num >= 0) { - // connect CS signal to the new device - esp_rom_gpio_connect_out_signal(next_device->cs_gpio_num, lcd_periph_signals.buses[bus->bus_id].cs_sig, - next_device->flags.cs_active_high, false); - } - } -} - -IRAM_ATTR static void lcd_default_isr_handler(void *args) -{ - esp_lcd_i80_bus_t *bus = (esp_lcd_i80_bus_t *)args; - lcd_i80_trans_descriptor_t *trans_desc = NULL; - lcd_panel_io_i80_t *cur_device = NULL; - lcd_panel_io_i80_t *next_device = NULL; - BaseType_t high_task_woken = pdFALSE; - bool need_yield = false; - uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev); - if (intr_status & LCD_LL_EVENT_TRANS_DONE) { - // disable interrupt temporarily, only re-enable when there be remained transaction in the queue - esp_intr_disable(bus->intr); - trans_desc = bus->cur_trans; // the finished transaction - cur_device = bus->cur_device;// the working device - // process finished transaction - if (trans_desc) { - assert(trans_desc->i80_device == cur_device && "transaction device mismatch"); - // decrease pm lock reference count - if (bus->pm_lock) { - esp_pm_lock_release(bus->pm_lock); - } - // device callback - if (trans_desc->trans_done_cb) { - if (trans_desc->trans_done_cb(&cur_device->base, NULL, trans_desc->user_ctx)) { - need_yield = true; - } - } - // move transaction to done_queue - // there won't be case that will overflow the queue, so skip checking the return value - high_task_woken = pdFALSE; - xQueueSendFromISR(cur_device->done_queue, &trans_desc, &high_task_woken); - if (high_task_woken == pdTRUE) { - need_yield = true; - } - bus->cur_trans = NULL; - } - // fetch transactions from devices' trans_queue - // Note: the first registered device will have the highest priority to be scheduled - LIST_FOREACH(next_device, &bus->device_list, device_list_entry) { - high_task_woken = pdFALSE; - if (xQueueReceiveFromISR(next_device->trans_queue, &trans_desc, &high_task_woken) == pdTRUE) { - if (high_task_woken == pdTRUE) { - need_yield = true; - } - // sanity check - assert(trans_desc); - // only clear the interrupt status when we're sure there still remains transaction to handle - lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status); - // switch devices if necessary - lcd_i80_switch_devices(cur_device, next_device); - // only reverse data bit/bytes for color data - lcd_ll_reverse_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits); - lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->flags.swap_color_bytes); - bus->cur_trans = trans_desc; - bus->cur_device = next_device; - // mount data to DMA links - lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length); - // enable interrupt again, because the new transaction can trigger new trans done event - esp_intr_enable(bus->intr); - // increase the pm lock reference count before starting a new transaction - if (bus->pm_lock) { - esp_pm_lock_acquire(bus->pm_lock); - } - lcd_start_transaction(bus, trans_desc); - break; // exit for-each loop - } - } - } - if (need_yield) { - portYIELD_FROM_ISR(); - } -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_spi.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_spi.c deleted file mode 100644 index 663e3f2a6..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_io_spi.c +++ /dev/null @@ -1,417 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "esp_lcd_panel_io_interface.h" -#include "esp_lcd_panel_io.h" -#include "driver/spi_master.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" -#include "esp_lcd_common.h" - -static const char *TAG = "lcd_panel.io.spi"; - -static esp_err_t panel_io_spi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size); -static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size); -static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size); -static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io); -static void lcd_spi_pre_trans_cb(spi_transaction_t *trans); -static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans); -static esp_err_t panel_io_spi_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx); - -typedef struct { - spi_transaction_t base; - struct { - unsigned int dc_gpio_level: 1; - unsigned int en_trans_done_cb: 1; - } flags; -} lcd_spi_trans_descriptor_t; - -typedef struct { - esp_lcd_panel_io_t base; // Base class of generic lcd panel io - spi_device_handle_t spi_dev; // SPI device handle - size_t spi_trans_max_bytes; // Maximum bytes that can be transmitted in one spi transaction - int dc_gpio_num; // D/C line GPIO number - esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done - void *user_ctx; // User's private data, passed directly to callback on_color_trans_done - size_t queue_size; // Size of transaction queue - size_t num_trans_inflight; // Number of transactions that are undergoing (the descriptor not recycled yet) - int lcd_cmd_bits; // Bit width of LCD command - int lcd_param_bits; // Bit width of LCD parameter - struct { - unsigned int dc_data_level: 1; // Indicates the level of DC line when tranfering data - unsigned int octal_mode: 1; // Indicates whether the transmitting is enabled with octal mode (8 data lines) - unsigned int quad_mode: 1; // Indicates whether the transmitting is enabled with quad mode (4 data lines) - } flags; - lcd_spi_trans_descriptor_t trans_pool[]; // Transaction pool -} esp_lcd_panel_io_spi_t; - -esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - esp_lcd_panel_io_spi_t *spi_panel_io = NULL; - ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - spi_panel_io = calloc(1, sizeof(esp_lcd_panel_io_spi_t) + sizeof(lcd_spi_trans_descriptor_t) * io_config->trans_queue_depth); - ESP_GOTO_ON_FALSE(spi_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for spi panel io"); - - spi_device_interface_config_t devcfg = { - .flags = SPI_DEVICE_HALFDUPLEX | - (io_config->flags.lsb_first ? SPI_DEVICE_TXBIT_LSBFIRST : 0) | - (io_config->flags.sio_mode ? SPI_DEVICE_3WIRE : 0) | - (io_config->flags.cs_high_active ? SPI_DEVICE_POSITIVE_CS : 0), - .clock_speed_hz = io_config->pclk_hz, - .mode = io_config->spi_mode, - .spics_io_num = io_config->cs_gpio_num, - .queue_size = io_config->trans_queue_depth, - .pre_cb = lcd_spi_pre_trans_cb, // pre-transaction callback, mainly control DC gpio level - .post_cb = lcd_spi_post_trans_color_cb, // post-transaction, where we invoke user registered "on_color_trans_done()" - }; - ret = spi_bus_add_device((spi_host_device_t)bus, &devcfg, &spi_panel_io->spi_dev); - ESP_GOTO_ON_ERROR(ret, err, TAG, "adding spi device to bus failed"); - - // if the DC line is not encoded into any spi transaction phase or it's not controlled by SPI peripheral - if (io_config->dc_gpio_num >= 0) { - gpio_config_t io_conf = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << io_config->dc_gpio_num, - }; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for D/C line failed"); - } - - spi_panel_io->flags.dc_data_level = !io_config->flags.dc_low_on_data; - spi_panel_io->flags.octal_mode = io_config->flags.octal_mode; - spi_panel_io->flags.quad_mode = io_config->flags.quad_mode; - spi_panel_io->on_color_trans_done = io_config->on_color_trans_done; - spi_panel_io->user_ctx = io_config->user_ctx; - spi_panel_io->lcd_cmd_bits = io_config->lcd_cmd_bits; - spi_panel_io->lcd_param_bits = io_config->lcd_param_bits; - spi_panel_io->dc_gpio_num = io_config->dc_gpio_num; - spi_panel_io->queue_size = io_config->trans_queue_depth; - spi_panel_io->base.rx_param = panel_io_spi_rx_param; - spi_panel_io->base.tx_param = panel_io_spi_tx_param; - spi_panel_io->base.tx_color = panel_io_spi_tx_color; - spi_panel_io->base.del = panel_io_spi_del; - spi_panel_io->base.register_event_callbacks = panel_io_spi_register_event_callbacks; - - size_t max_trans_bytes = 0; - ESP_GOTO_ON_ERROR(spi_bus_get_max_transaction_len((spi_host_device_t)bus, &max_trans_bytes), err, TAG, "get spi max transaction len failed"); - spi_panel_io->spi_trans_max_bytes = max_trans_bytes; - - *ret_io = &(spi_panel_io->base); - ESP_LOGD(TAG, "new spi lcd panel io @%p, max_trans_bytes: %d", spi_panel_io, (int)max_trans_bytes); - - return ESP_OK; - -err: - if (spi_panel_io) { - if (io_config->dc_gpio_num >= 0) { - gpio_reset_pin(io_config->dc_gpio_num); - } - free(spi_panel_io); - } - return ret; -} - -static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io) -{ - esp_err_t ret = ESP_OK; - spi_transaction_t *spi_trans = NULL; - esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base); - - // wait all pending transaction to finish - size_t num_trans_inflight = spi_panel_io->num_trans_inflight; - for (size_t i = 0; i < num_trans_inflight; i++) { - ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY); - ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed"); - spi_panel_io->num_trans_inflight--; - } - spi_bus_remove_device(spi_panel_io->spi_dev); - if (spi_panel_io->dc_gpio_num >= 0) { - gpio_reset_pin(spi_panel_io->dc_gpio_num); - } - ESP_LOGD(TAG, "del lcd panel io spi @%p", spi_panel_io); - free(spi_panel_io); - -err: - return ret; -} - -static esp_err_t panel_io_spi_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx) -{ - esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base); - - if (spi_panel_io->on_color_trans_done != NULL) { - ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!"); - } - - spi_panel_io->on_color_trans_done = cbs->on_color_trans_done; - spi_panel_io->user_ctx = user_ctx; - - return ESP_OK; -} - -static void spi_lcd_prepare_cmd_buffer(esp_lcd_panel_io_spi_t *panel_io, const void *cmd) -{ - uint8_t *from = (uint8_t *)cmd; - // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first - // However, the SPI peripheral will send 0x34 first, so we reversed the order below - if (panel_io->lcd_cmd_bits > 8) { - int start = 0; - int end = panel_io->lcd_cmd_bits / 8 - 1; - lcd_com_reverse_buffer_bytes(from, start, end); - } -} - -static void spi_lcd_prepare_param_buffer(esp_lcd_panel_io_spi_t *panel_io, const void *param, size_t param_size) -{ - uint8_t *from = (uint8_t *)param; - int param_width = panel_io->lcd_param_bits / 8; - size_t param_num = param_size / param_width; - // LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the bus first - // However, the SPI peripheral will send 0x34 first, so we reversed the order below - if (panel_io->lcd_param_bits > 8) { - for (size_t i = 0; i < param_num; i++) { - int start = i * param_width; - int end = start + param_width - 1; - lcd_com_reverse_buffer_bytes(from, start, end); - } - } -} - -static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size) -{ - esp_err_t ret = ESP_OK; - spi_transaction_t *spi_trans = NULL; - lcd_spi_trans_descriptor_t *lcd_trans = NULL; - esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base); - bool send_cmd = (lcd_cmd >= 0); - - ESP_RETURN_ON_ERROR(spi_device_acquire_bus(spi_panel_io->spi_dev, portMAX_DELAY), TAG, "acquire spi bus failed"); - - // before issue a polling transaction, need to wait queued transactions finished - size_t num_trans_inflight = spi_panel_io->num_trans_inflight; - for (size_t i = 0; i < num_trans_inflight; i++) { - ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY); - ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed"); - spi_panel_io->num_trans_inflight--; - } - lcd_trans = &spi_panel_io->trans_pool[0]; - memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t)); - - lcd_trans->base.user = spi_panel_io; - if (param && param_size) { - lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE; - } - if (spi_panel_io->flags.octal_mode) { - // use 8 lines for transmitting command, address and data - lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); - } - - if (send_cmd) { - spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd); - lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode - lcd_trans->base.length = spi_panel_io->lcd_cmd_bits; - lcd_trans->base.tx_buffer = &lcd_cmd; - // command is short, using polling mode - ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base); - ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed"); - } - - if (param && param_size) { - spi_lcd_prepare_param_buffer(spi_panel_io, param, param_size); - lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode - lcd_trans->base.length = param_size * 8; // transaction length is in bits - lcd_trans->base.tx_buffer = param; - lcd_trans->base.flags &= ~SPI_TRANS_CS_KEEP_ACTIVE; - // parameter is usually short, using polling mode - ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base); - ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) param failed"); - } - -err: - spi_device_release_bus(spi_panel_io->spi_dev); - - return ret; -} - -static esp_err_t panel_io_spi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size) -{ - esp_err_t ret = ESP_OK; - spi_transaction_t *spi_trans = NULL; - lcd_spi_trans_descriptor_t *lcd_trans = NULL; - esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base); - bool send_cmd = (lcd_cmd >= 0); - - ESP_RETURN_ON_ERROR(spi_device_acquire_bus(spi_panel_io->spi_dev, portMAX_DELAY), TAG, "acquire spi bus failed"); - - // before issue a polling transaction, need to wait queued transactions finished - size_t num_trans_inflight = spi_panel_io->num_trans_inflight; - for (size_t i = 0; i < num_trans_inflight; i++) { - ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY); - ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed"); - spi_panel_io->num_trans_inflight--; - } - lcd_trans = &spi_panel_io->trans_pool[0]; - memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t)); - - lcd_trans->base.user = spi_panel_io; - lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE; - if (spi_panel_io->flags.octal_mode) { - // use 8 lines for transmitting command, address and data - lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); - } - - if (send_cmd) { - spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd); - lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode - lcd_trans->base.length = spi_panel_io->lcd_cmd_bits; - lcd_trans->base.tx_buffer = &lcd_cmd; - // command is short, using polling mode - ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base); - ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed"); - } - - if (param && param_size) { - lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode - lcd_trans->base.length = 0; - lcd_trans->base.tx_buffer = NULL; - lcd_trans->base.rxlength = param_size * 8; // Read length in bits - lcd_trans->base.rx_buffer = param; - lcd_trans->base.flags &= ~SPI_TRANS_CS_KEEP_ACTIVE; - // parameter is usually short, using polling mode - ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base); - ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) param failed"); - } - -err: - spi_device_release_bus(spi_panel_io->spi_dev); - - return ret; -} - -static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size) -{ - esp_err_t ret = ESP_OK; - spi_transaction_t *spi_trans = NULL; - lcd_spi_trans_descriptor_t *lcd_trans = NULL; - esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base); - - ESP_RETURN_ON_ERROR(spi_device_acquire_bus(spi_panel_io->spi_dev, portMAX_DELAY), TAG, "acquire spi bus failed"); - - bool send_cmd = (lcd_cmd >= 0); - if (send_cmd) { - // before issue a polling transaction, need to wait queued transactions finished - size_t num_trans_inflight = spi_panel_io->num_trans_inflight; - for (size_t i = 0; i < num_trans_inflight; i++) { - ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY); - ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed"); - spi_panel_io->num_trans_inflight--; - } - lcd_trans = &spi_panel_io->trans_pool[0]; - memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t)); - - spi_lcd_prepare_cmd_buffer(spi_panel_io, &lcd_cmd); - lcd_trans->base.user = spi_panel_io; - lcd_trans->flags.dc_gpio_level = !spi_panel_io->flags.dc_data_level; // set D/C line to command mode - lcd_trans->base.length = spi_panel_io->lcd_cmd_bits; - lcd_trans->base.tx_buffer = &lcd_cmd; - if (color && color_size) { - lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE; - } - if (spi_panel_io->flags.octal_mode) { - // use 8 lines for transmitting command, address and data - lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); - } - // command is short, using polling mode - ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base); - ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed"); - } - - // if the color buffer is big, we want to split it into chunks, and queue the chunks one by one - do { - size_t chunk_size = color_size; - - if (spi_panel_io->num_trans_inflight < spi_panel_io->queue_size) { - // get the next available transaction - lcd_trans = &spi_panel_io->trans_pool[spi_panel_io->num_trans_inflight]; - } else { - // transaction pool has used up, recycle one transaction - ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY); - ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed"); - lcd_trans = __containerof(spi_trans, lcd_spi_trans_descriptor_t, base); - spi_panel_io->num_trans_inflight--; - } - memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t)); - - // SPI per-transfer size has its limitation, if the color buffer is too big, we need to split it into multiple chunks - if (chunk_size > spi_panel_io->spi_trans_max_bytes) { - // cap the transfer size to the maximum supported by the bus - chunk_size = spi_panel_io->spi_trans_max_bytes; - lcd_trans->base.flags |= SPI_TRANS_CS_KEEP_ACTIVE; - } else { - // mark en_trans_done_cb only at the last round to avoid premature completion callback - lcd_trans->flags.en_trans_done_cb = 1; - lcd_trans->base.flags &= ~SPI_TRANS_CS_KEEP_ACTIVE; - } - - lcd_trans->base.user = spi_panel_io; - lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode - lcd_trans->base.length = chunk_size * 8; // transaction length is in bits - lcd_trans->base.tx_buffer = color; - if (spi_panel_io->flags.octal_mode) { - // use 8 lines for transmitting command, address and data - lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT); - } else if (spi_panel_io->flags.quad_mode) { - // use 4 lines only for transmitting data - lcd_trans->base.flags |= SPI_TRANS_MODE_QIO; - } - - // color data is usually large, using queue+blocking mode - ret = spi_device_queue_trans(spi_panel_io->spi_dev, &lcd_trans->base, portMAX_DELAY); - ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (queue) color failed"); - spi_panel_io->num_trans_inflight++; - - // move on to the next chunk - color = (const uint8_t *)color + chunk_size; - color_size -= chunk_size; - } while (color_size > 0); // continue while we have remaining data to transmit - -err: - spi_device_release_bus(spi_panel_io->spi_dev); - return ret; -} - -static void lcd_spi_pre_trans_cb(spi_transaction_t *trans) -{ - esp_lcd_panel_io_spi_t *spi_panel_io = trans->user; - lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base); - if (spi_panel_io->dc_gpio_num >= 0) { // set D/C line level if necessary - gpio_set_level(spi_panel_io->dc_gpio_num, lcd_trans->flags.dc_gpio_level); - } -} - -static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans) -{ - esp_lcd_panel_io_spi_t *spi_panel_io = trans->user; - lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base); - if (lcd_trans->flags.en_trans_done_cb) { - if (spi_panel_io->on_color_trans_done) { - spi_panel_io->on_color_trans_done(&spi_panel_io->base, NULL, spi_panel_io->user_ctx); - } - } -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_nt35510.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_nt35510.c deleted file mode 100644 index 7fbe7ab37..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_nt35510.c +++ /dev/null @@ -1,319 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include "sdkconfig.h" - -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" - -static const char *TAG = "lcd_panel.nt35510"; - -static esp_err_t panel_nt35510_del(esp_lcd_panel_t *panel); -static esp_err_t panel_nt35510_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_nt35510_init(esp_lcd_panel_t *panel); -static esp_err_t panel_nt35510_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, - const void *color_data); -static esp_err_t panel_nt35510_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_nt35510_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_nt35510_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_nt35510_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_nt35510_disp_on_off(esp_lcd_panel_t *panel, bool off); -static esp_err_t panel_nt35510_sleep(esp_lcd_panel_t *panel, bool sleep); - -typedef struct { - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - bool reset_level; - int x_gap; - int y_gap; - uint8_t fb_bits_per_pixel; - uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register - uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register -} nt35510_panel_t; - -esp_err_t -esp_lcd_new_panel_nt35510(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, - esp_lcd_panel_handle_t *ret_panel) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - nt35510_panel_t *nt35510 = NULL; - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - nt35510 = calloc(1, sizeof(nt35510_panel_t)); - ESP_GOTO_ON_FALSE(nt35510, ESP_ERR_NO_MEM, err, TAG, "no mem for nt35510 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_config_t io_conf = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, - }; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - switch (panel_dev_config->rgb_ele_order) { - case LCD_RGB_ELEMENT_ORDER_RGB: - nt35510->madctl_val = 0; - break; - case LCD_RGB_ELEMENT_ORDER_BGR: - nt35510->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); - break; - } - - uint8_t fb_bits_per_pixel = 0; - switch (panel_dev_config->bits_per_pixel) { - case 16: // RGB565 - nt35510->colmod_val = 0x55; - fb_bits_per_pixel = 16; - break; - case 18: // RGB666 - nt35510->colmod_val = 0x66; - // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel - fb_bits_per_pixel = 24; - break; - case 24: // RGB888 - nt35510->colmod_val = 0x77; - fb_bits_per_pixel = 24; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - nt35510->io = io; - nt35510->fb_bits_per_pixel = fb_bits_per_pixel; - nt35510->reset_gpio_num = panel_dev_config->reset_gpio_num; - nt35510->reset_level = panel_dev_config->flags.reset_active_high; - nt35510->base.del = panel_nt35510_del; - nt35510->base.reset = panel_nt35510_reset; - nt35510->base.init = panel_nt35510_init; - nt35510->base.draw_bitmap = panel_nt35510_draw_bitmap; - nt35510->base.invert_color = panel_nt35510_invert_color; - nt35510->base.set_gap = panel_nt35510_set_gap; - nt35510->base.mirror = panel_nt35510_mirror; - nt35510->base.swap_xy = panel_nt35510_swap_xy; - nt35510->base.disp_on_off = panel_nt35510_disp_on_off; - nt35510->base.disp_sleep = panel_nt35510_sleep; - *ret_panel = &(nt35510->base); - ESP_LOGD(TAG, "new nt35510 panel @%p", nt35510); - - return ESP_OK; - -err: - if (nt35510) { - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(nt35510); - } - return ret; -} - -static esp_err_t panel_nt35510_del(esp_lcd_panel_t *panel) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - - if (nt35510->reset_gpio_num >= 0) { - gpio_reset_pin(nt35510->reset_gpio_num); - } - ESP_LOGD(TAG, "del nt35510 panel @%p", nt35510); - free(nt35510); - return ESP_OK; -} - -static esp_err_t panel_nt35510_reset(esp_lcd_panel_t *panel) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - - // perform hardware reset - if (nt35510->reset_gpio_num >= 0) { - gpio_set_level(nt35510->reset_gpio_num, nt35510->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(nt35510->reset_gpio_num, !nt35510->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - } else { - // perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET << 8, NULL, 0), TAG, - "io tx param failed"); - vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5m before sending new command - } - - return ESP_OK; -} - -static esp_err_t panel_nt35510_init(esp_lcd_panel_t *panel) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT << 8, NULL, 0), TAG, - "io tx param failed");; - vTaskDelay(pdMS_TO_TICKS(100)); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL << 8, (uint16_t[]) { - nt35510->madctl_val, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD << 8, (uint16_t[]) { - nt35510->colmod_val, - }, 2), TAG, "io tx param failed"); - - return ESP_OK; -} - -static esp_err_t panel_nt35510_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, - const void *color_data) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - esp_lcd_panel_io_handle_t io = nt35510->io; - - x_start += nt35510->x_gap; - x_end += nt35510->x_gap; - y_start += nt35510->y_gap; - y_end += nt35510->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 0, (uint16_t[]) { - (x_start >> 8) & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 1, (uint16_t[]) { - x_start & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 2, (uint16_t[]) { - ((x_end - 1) >> 8) & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_CASET << 8) + 3, (uint16_t[]) { - (x_end - 1) & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 0, (uint16_t[]) { - (y_start >> 8) & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 1, (uint16_t[]) { - y_start & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 2, (uint16_t[]) { - ((y_end - 1) >> 8) & 0xFF, - }, 2), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, (LCD_CMD_RASET << 8) + 3, (uint16_t[]) { - (y_end - 1) & 0xFF, - }, 2), TAG, "io tx param failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * nt35510->fb_bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR << 8, color_data, len), TAG, "io tx color failed"); - - return ESP_OK; -} - -static esp_err_t panel_nt35510_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - int command = 0; - if (invert_color_data) { - command = LCD_CMD_INVON; - } else { - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command << 8, NULL, 0), TAG, - "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_nt35510_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - if (mirror_x) { - nt35510->madctl_val |= LCD_CMD_MX_BIT; - } else { - nt35510->madctl_val &= ~LCD_CMD_MX_BIT; - } - if (mirror_y) { - nt35510->madctl_val |= LCD_CMD_MY_BIT; - } else { - nt35510->madctl_val &= ~LCD_CMD_MY_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL << 8, (uint16_t[]) { - nt35510->madctl_val - }, 2), TAG, "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_nt35510_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - if (swap_axes) { - nt35510->madctl_val |= LCD_CMD_MV_BIT; - } else { - nt35510->madctl_val &= ~LCD_CMD_MV_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL << 8, (uint16_t[]) { - nt35510->madctl_val - }, 2), TAG, "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_nt35510_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - nt35510->x_gap = x_gap; - nt35510->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_nt35510_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - int command = 0; - if (on_off) { - command = LCD_CMD_DISPON; - } else { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command << 8, NULL, 0), TAG, - "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_nt35510_sleep(esp_lcd_panel_t *panel, bool sleep) -{ - nt35510_panel_t *nt35510 = __containerof(panel, nt35510_panel_t, base); - esp_lcd_panel_io_handle_t io = nt35510->io; - int command = 0; - if (sleep) { - command = LCD_CMD_SLPIN; - } else { - command = LCD_CMD_SLPOUT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param failed"); - vTaskDelay(pdMS_TO_TICKS(100)); - - return ESP_OK; -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_ops.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_ops.c deleted file mode 100644 index 1e03be92d..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_ops.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "esp_check.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_interface.h" - -static const char *TAG = "lcd_panel"; - -esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->reset(panel); -} - -esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->init(panel); -} - -esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->del(panel); -} - -esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data); -} - -esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->mirror(panel, mirror_x, mirror_y); -} - -esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->swap_xy(panel, swap_axes); -} - -esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->set_gap(panel, x_gap, y_gap); -} - -esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->invert_color(panel, invert_color_data); -} - -esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - return panel->disp_on_off(panel, on_off); -} - -esp_err_t esp_lcd_panel_disp_off(esp_lcd_panel_handle_t panel, bool off) -{ - return esp_lcd_panel_disp_on_off(panel, !off); -} - -esp_err_t esp_lcd_panel_disp_sleep(esp_lcd_panel_handle_t panel, bool sleep) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); - ESP_RETURN_ON_FALSE(panel->disp_sleep, ESP_ERR_NOT_SUPPORTED, TAG, "sleep is not supported by this panel"); - return panel->disp_sleep(panel, sleep); -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_rgb.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_rgb.c deleted file mode 100644 index d1cec8340..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_rgb.c +++ /dev/null @@ -1,1142 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "esp_attr.h" -#include "esp_check.h" -#include "esp_pm.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_rgb.h" -#include "esp_lcd_panel_ops.h" -#include "esp_rom_gpio.h" -#include "soc/soc_caps.h" -#include "esp_clk_tree.h" -#include "hal/dma_types.h" -#include "hal/gpio_hal.h" -#include "esp_private/gdma.h" -#include "driver/gpio.h" -#include "esp_bit_defs.h" -#include "esp_private/periph_ctrl.h" -#include "esp_psram.h" -#include "esp_lcd_common.h" -#include "soc/lcd_periph.h" -#include "hal/lcd_hal.h" -#include "hal/lcd_ll.h" -#include "hal/gdma_ll.h" -#include "rom/cache.h" -#include "esp_cache.h" - - -#if CONFIG_LCD_RGB_ISR_IRAM_SAFE -#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED) -#else -#define LCD_RGB_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED -#endif - -#define RGB_LCD_PANEL_MAX_FB_NUM 3 // maximum supported frame buffer number -#define RGB_LCD_PANEL_BOUNCE_BUF_NUM 2 // bounce buffer number -#define RGB_LCD_PANEL_DMA_LINKS_REPLICA MAX(RGB_LCD_PANEL_MAX_FB_NUM, RGB_LCD_PANEL_BOUNCE_BUF_NUM) - -#define RGB_PANEL_SWAP_XY 0 -#define RGB_PANEL_MIRROR_Y 1 -#define RGB_PANEL_MIRROR_X 2 - -typedef enum { - ROTATE_MASK_SWAP_XY = BIT(RGB_PANEL_SWAP_XY), - ROTATE_MASK_MIRROR_Y = BIT(RGB_PANEL_MIRROR_Y), - ROTATE_MASK_MIRROR_X = BIT(RGB_PANEL_MIRROR_X), -} panel_rotate_mask_t; - -static const char *TAG = "lcd_panel.rgb"; - -typedef struct esp_rgb_panel_t esp_rgb_panel_t; - -static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel); -static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel); -static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel); -static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); -static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool off); -static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src); -static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel); -static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config); -static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel); -static void lcd_default_isr_handler(void *args); - -struct esp_rgb_panel_t { - esp_lcd_panel_t base; // Base class of generic lcd panel - int panel_id; // LCD panel ID - lcd_hal_context_t hal; // Hal layer object - size_t data_width; // Number of data lines - size_t fb_bits_per_pixel; // Frame buffer color depth, in bpp - size_t num_fbs; // Number of frame buffers - size_t output_bits_per_pixel; // Color depth seen from the output data line. Default to fb_bits_per_pixel, but can be changed by YUV-RGB conversion - size_t sram_trans_align; // Alignment for framebuffer that allocated in SRAM - size_t psram_trans_align; // Alignment for framebuffer that allocated in PSRAM - int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off" - intr_handle_t intr; // LCD peripheral interrupt handle - esp_pm_lock_handle_t pm_lock; // Power management lock - size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer - uint8_t *fbs[RGB_LCD_PANEL_MAX_FB_NUM]; // Frame buffers - uint8_t cur_fb_index; // Current frame buffer index - uint8_t bb_fb_index; // Current frame buffer index which used by bounce buffer - size_t fb_size; // Size of frame buffer - int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color" - uint32_t src_clk_hz; // Peripheral source clock resolution - esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width) - size_t bb_size; // If not-zero, the driver uses two bounce buffers allocated from internal memory - int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels - uint8_t *bounce_buffer[RGB_LCD_PANEL_BOUNCE_BUF_NUM]; // Pointer to the bounce buffers - size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR - size_t expect_eof_count; // record the number of DMA EOF event we expected to receive - gdma_channel_handle_t dma_chan; // DMA channel handle - esp_lcd_rgb_panel_vsync_cb_t on_vsync; // VSYNC event callback - esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // callback used to fill a bounce buffer rather than copying from the frame buffer - esp_lcd_rgb_panel_bounce_buf_finish_cb_t on_bounce_frame_finish; // callback used to notify when the bounce buffer finish copying the entire frame - void *user_ctx; // Reserved user's data of callback functions - int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window - int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window - portMUX_TYPE spinlock; // to protect panel specific resource from concurrent access (e.g. between task and ISR) - int lcd_clk_flags; // LCD clock calculation flags - int rotate_mask; // panel rotate_mask mask, Or'ed of `panel_rotate_mask_t` - struct { - uint32_t disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num` - uint32_t stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done - uint32_t fb_in_psram: 1; // Whether the frame buffer is in PSRAM - uint32_t need_update_pclk: 1; // Whether to update the PCLK before start a new transaction - uint32_t need_restart: 1; // Whether to restart the LCD controller and the DMA - uint32_t bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode - } flags; - dma_descriptor_t *dma_links[RGB_LCD_PANEL_DMA_LINKS_REPLICA]; // fbs[0] <-> dma_links[0], fbs[1] <-> dma_links[1], etc - dma_descriptor_t dma_restart_node; // DMA descriptor used to restart the transfer - dma_descriptor_t dma_nodes[]; // DMA descriptors pool -}; - -static esp_err_t lcd_rgb_panel_alloc_frame_buffers(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_rgb_panel_t *rgb_panel) -{ - bool fb_in_psram = false; - size_t psram_trans_align = rgb_panel_config->psram_trans_align ? rgb_panel_config->psram_trans_align : 64; - size_t sram_trans_align = rgb_panel_config->sram_trans_align ? rgb_panel_config->sram_trans_align : 4; - rgb_panel->psram_trans_align = psram_trans_align; - rgb_panel->sram_trans_align = sram_trans_align; - - // alloc frame buffer - if (rgb_panel->num_fbs > 0) { - // fb_in_psram is only an option, if there's no PSRAM on board, we fallback to alloc from SRAM - if (rgb_panel_config->flags.fb_in_psram) { -#if CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC - if (esp_psram_is_initialized()) { - fb_in_psram = true; - } -#endif - } - for (int i = 0; i < rgb_panel->num_fbs; i++) { - if (fb_in_psram) { - // the low level malloc function will help check the validation of alignment - rgb_panel->fbs[i] = heap_caps_aligned_calloc(psram_trans_align, 1, rgb_panel->fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - rgb_panel->fbs[i] = heap_caps_aligned_calloc(sram_trans_align, 1, rgb_panel->fb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - } - ESP_RETURN_ON_FALSE(rgb_panel->fbs[i], ESP_ERR_NO_MEM, TAG, "no mem for frame buffer"); - } - } - - // alloc bounce buffer - if (rgb_panel->bb_size) { - for (int i = 0; i < RGB_LCD_PANEL_BOUNCE_BUF_NUM; i++) { - // bounce buffer must come from SRAM - rgb_panel->bounce_buffer[i] = heap_caps_aligned_calloc(sram_trans_align, 1, rgb_panel->bb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); - ESP_RETURN_ON_FALSE(rgb_panel->bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "no mem for bounce buffer"); - } - } - rgb_panel->cur_fb_index = 0; - rgb_panel->bb_fb_index = 0; - rgb_panel->flags.fb_in_psram = fb_in_psram; - - return ESP_OK; -} - -static esp_err_t lcd_rgb_panel_destory(esp_rgb_panel_t *rgb_panel) -{ - lcd_ll_enable_clock(rgb_panel->hal.dev, false); - if (rgb_panel->panel_id >= 0) { - PERIPH_RCC_RELEASE_ATOMIC(lcd_periph_signals.panels[rgb_panel->panel_id].module, ref_count) { - if (ref_count == 0) { - lcd_ll_enable_bus_clock(rgb_panel->panel_id, false); - } - } - lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id); - } - for (size_t i = 0; i < rgb_panel->num_fbs; i++) { - if (rgb_panel->fbs[i]) { - free(rgb_panel->fbs[i]); - } - } - if (rgb_panel->bounce_buffer[0]) { - free(rgb_panel->bounce_buffer[0]); - } - if (rgb_panel->bounce_buffer[1]) { - free(rgb_panel->bounce_buffer[1]); - } - if (rgb_panel->dma_chan) { - gdma_disconnect(rgb_panel->dma_chan); - gdma_del_channel(rgb_panel->dma_chan); - } - if (rgb_panel->intr) { - esp_intr_free(rgb_panel->intr); - } - if (rgb_panel->pm_lock) { - esp_pm_lock_release(rgb_panel->pm_lock); - esp_pm_lock_delete(rgb_panel->pm_lock); - } - free(rgb_panel); - return ESP_OK; -} - -esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel) -{ - fprintf(stderr, "this is the TULIP SPECIAL esp_lcd\n"); - -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - esp_rgb_panel_t *rgb_panel = NULL; - ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter"); - ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16 || rgb_panel_config->data_width == 8, - ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported data width %d", rgb_panel_config->data_width); - ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb), - ESP_ERR_INVALID_ARG, err, TAG, "double_fb conflicts with no_fb"); - ESP_GOTO_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->num_fbs != 2 && rgb_panel_config->flags.double_fb), - ESP_ERR_INVALID_ARG, err, TAG, "num_fbs conflicts with double_fb"); - ESP_GOTO_ON_FALSE(!(rgb_panel_config->num_fbs > 0 && rgb_panel_config->flags.no_fb), - ESP_ERR_INVALID_ARG, err, TAG, "num_fbs conflicts with no_fb"); - ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0), - ESP_ERR_INVALID_ARG, err, TAG, "must set bounce buffer if there's no frame buffer"); - ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px), - ESP_ERR_INVALID_ARG, err, TAG, "refresh on demand is not supported under bounce buffer mode"); - - // determine number of framebuffers - size_t num_fbs = 1; - if (rgb_panel_config->flags.no_fb) { - num_fbs = 0; - } else if (rgb_panel_config->flags.double_fb) { - num_fbs = 2; - } else if (rgb_panel_config->num_fbs > 0) { - num_fbs = rgb_panel_config->num_fbs; - } - ESP_GOTO_ON_FALSE(num_fbs <= RGB_LCD_PANEL_MAX_FB_NUM, ESP_ERR_INVALID_ARG, err, TAG, "too many frame buffers"); - - // bpp defaults to the number of data lines, but for serial RGB interface, they're not equal - // e.g. for serial RGB 8-bit interface, data lines are 8, whereas the bpp is 24 (RGB888) - size_t fb_bits_per_pixel = rgb_panel_config->data_width; - if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set - fb_bits_per_pixel = rgb_panel_config->bits_per_pixel; - } - // calculate buffer size - size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * fb_bits_per_pixel / 8; - size_t bb_size = rgb_panel_config->bounce_buffer_size_px * fb_bits_per_pixel / 8; - size_t expect_bb_eof_count = 0; - if (bb_size) { - // we want the bounce can always end in the second buffer - ESP_GOTO_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, err, TAG, - "fb size must be even multiple of bounce buffer size"); - expect_bb_eof_count = fb_size / bb_size; - } - // calculate the number of DMA descriptors - size_t num_dma_nodes = 0; - if (bb_size) { - // in bounce buffer mode, DMA is used to convey the bounce buffer, not the frame buffer. - // frame buffer is copied to bounce buffer by CPU - num_dma_nodes = (bb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE; - } else { - // Not bounce buffer mode, DMA descriptors need to fit the entire frame buffer - num_dma_nodes = (fb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE; - } - - // DMA descriptors must be placed in internal SRAM (requested by DMA) - rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t) * RGB_LCD_PANEL_DMA_LINKS_REPLICA, - MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); - ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel"); - rgb_panel->num_dma_nodes = num_dma_nodes; - rgb_panel->num_fbs = num_fbs; - rgb_panel->fb_size = fb_size; - rgb_panel->bb_size = bb_size; - rgb_panel->expect_eof_count = expect_bb_eof_count; - rgb_panel->panel_id = -1; - // register to platform - int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel); - ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free rgb panel slot"); - rgb_panel->panel_id = panel_id; - - // enable APB to access LCD registers - PERIPH_RCC_ACQUIRE_ATOMIC(lcd_periph_signals.panels[panel_id].module, ref_count) { - if (ref_count == 0) { - lcd_ll_enable_bus_clock(panel_id, true); - lcd_ll_reset_register(panel_id); - } - } - - // allocate frame buffers + bounce buffers - ESP_GOTO_ON_ERROR(lcd_rgb_panel_alloc_frame_buffers(rgb_panel_config, rgb_panel), err, TAG, "alloc frame buffers failed"); - - // initialize HAL layer, so we can call LL APIs later - lcd_hal_init(&rgb_panel->hal, panel_id); - // enable clock gating - lcd_ll_enable_clock(rgb_panel->hal.dev, true); - // set clock source - ret = lcd_rgb_panel_select_clock_src(rgb_panel, rgb_panel_config->clk_src); - ESP_GOTO_ON_ERROR(ret, err, TAG, "set source clock failed"); - // set minimal PCLK divider - // A limitation in the hardware, if the LCD_PCLK == LCD_CLK, then the PCLK polarity can't be adjustable - if (!(rgb_panel_config->timings.flags.pclk_active_neg || rgb_panel_config->timings.flags.pclk_idle_high)) { - rgb_panel->lcd_clk_flags |= LCD_HAL_PCLK_FLAG_ALLOW_EQUAL_SYSCLK; - } - // install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask) - int isr_flags = LCD_RGB_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED; - ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags, - (uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev), - LCD_LL_EVENT_VSYNC_END, lcd_default_isr_handler, rgb_panel, &rgb_panel->intr); - ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed"); - lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, false); // disable all interrupts - lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt - - // install DMA service - rgb_panel->flags.stream_mode = !rgb_panel_config->flags.refresh_on_demand; - rgb_panel->fb_bits_per_pixel = fb_bits_per_pixel; - - ret = lcd_rgb_panel_create_trans_link(rgb_panel); - ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed"); - // configure GPIO - ret = lcd_rgb_panel_configure_gpio(rgb_panel, rgb_panel_config); - ESP_GOTO_ON_ERROR(ret, err, TAG, "configure GPIO failed"); - // fill other rgb panel runtime parameters - memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, SOC_LCD_RGB_DATA_WIDTH); - rgb_panel->timings = rgb_panel_config->timings; - rgb_panel->data_width = rgb_panel_config->data_width; - rgb_panel->output_bits_per_pixel = fb_bits_per_pixel; // by default, the output bpp is the same as the frame buffer bpp - rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num; - rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low; - rgb_panel->flags.bb_invalidate_cache = rgb_panel_config->flags.bb_invalidate_cache; - rgb_panel->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; - // fill function table - rgb_panel->base.del = rgb_panel_del; - rgb_panel->base.reset = rgb_panel_reset; - rgb_panel->base.init = rgb_panel_init; - rgb_panel->base.draw_bitmap = rgb_panel_draw_bitmap; - rgb_panel->base.disp_on_off = rgb_panel_disp_on_off; - rgb_panel->base.invert_color = rgb_panel_invert_color; - rgb_panel->base.mirror = rgb_panel_mirror; - rgb_panel->base.swap_xy = rgb_panel_swap_xy; - rgb_panel->base.set_gap = rgb_panel_set_gap; - // return base class - *ret_panel = &(rgb_panel->base); - ESP_LOGD(TAG, "new rgb panel(%d) @%p, num_fbs=%zu, fb_size=%zu, bb0 @%p, bb1 @%p, bb_size=%zu", - rgb_panel->panel_id, rgb_panel, rgb_panel->num_fbs, rgb_panel->fb_size, - rgb_panel->bounce_buffer[0], rgb_panel->bounce_buffer[1], rgb_panel->bb_size); - for (size_t i = 0; i < rgb_panel->num_fbs; i++) { - ESP_LOGD(TAG, "fb[%zu] @%p", i, rgb_panel->fbs[i]); - } - return ESP_OK; - -err: - if (rgb_panel) { - lcd_rgb_panel_destory(rgb_panel); - } - return ret; -} - -esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_callbacks_t *callbacks, void *user_ctx) -{ - ESP_RETURN_ON_FALSE(panel && callbacks, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); -#if CONFIG_LCD_RGB_ISR_IRAM_SAFE - if (callbacks->on_vsync) { - ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM"); - } - if (callbacks->on_bounce_empty) { - ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_empty), ESP_ERR_INVALID_ARG, TAG, "on_bounce_empty callback not in IRAM"); - } - if (callbacks->on_bounce_frame_finish) { - ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_frame_finish), ESP_ERR_INVALID_ARG, TAG, "on_bounce_frame_finish callback not in IRAM"); - } - if (user_ctx) { - ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); - } -#endif // CONFIG_LCD_RGB_ISR_IRAM_SAFE - rgb_panel->on_vsync = callbacks->on_vsync; - rgb_panel->on_bounce_empty = callbacks->on_bounce_empty; - rgb_panel->on_bounce_frame_finish = callbacks->on_bounce_frame_finish; - rgb_panel->user_ctx = user_ctx; - return ESP_OK; -} - -esp_err_t esp_lcd_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - // the pclk frequency will be updated in the `LCD_LL_EVENT_VSYNC_END` event handler - portENTER_CRITICAL(&rgb_panel->spinlock); - rgb_panel->flags.need_update_pclk = true; - rgb_panel->timings.pclk_hz = freq_hz; - portEXIT_CRITICAL(&rgb_panel->spinlock); - return ESP_OK; -} - -esp_err_t esp_lcd_rgb_panel_restart(esp_lcd_panel_handle_t panel) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - ESP_RETURN_ON_FALSE(rgb_panel->flags.stream_mode, ESP_ERR_INVALID_STATE, TAG, "not in stream mode"); - - // the underlying restart job will be done in the `LCD_LL_EVENT_VSYNC_END` event handler - portENTER_CRITICAL(&rgb_panel->spinlock); - rgb_panel->flags.need_restart = true; - portEXIT_CRITICAL(&rgb_panel->spinlock); - return ESP_OK; -} - -esp_err_t esp_lcd_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - ESP_RETURN_ON_FALSE(fb_num && fb_num <= rgb_panel->num_fbs, ESP_ERR_INVALID_ARG, TAG, "invalid frame buffer number"); - void **fb_itor = fb0; - va_list args; - va_start(args, fb0); - for (int i = 0; i < fb_num; i++) { - if (fb_itor) { - *fb_itor = rgb_panel->fbs[i]; - fb_itor = va_arg(args, void **); - } - } - va_end(args); - return ESP_OK; -} - -esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - ESP_RETURN_ON_FALSE(!rgb_panel->flags.stream_mode, ESP_ERR_INVALID_STATE, TAG, "refresh on demand is not enabled"); - lcd_rgb_panel_start_transmission(rgb_panel); - return ESP_OK; -} - -esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_yuv_conv_config_t *config) -{ - ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - lcd_hal_context_t *hal = &rgb_panel->hal; - bool en_conversion = config != NULL; - - // bits per pixel for different YUV sample - const uint8_t bpp_yuv[] = { - [LCD_YUV_SAMPLE_422] = 16, - [LCD_YUV_SAMPLE_420] = 12, - [LCD_YUV_SAMPLE_411] = 12, - }; - - if (en_conversion) { - if (memcmp(&config->src, &config->dst, sizeof(config->src)) == 0) { - ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "conversion source and destination are the same"); - } - - if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_RGB) { // YUV->RGB - lcd_ll_set_convert_mode_yuv_to_rgb(hal->dev, config->src.yuv_sample); - // Note, the RGB->YUV conversion only support RGB565 - rgb_panel->output_bits_per_pixel = 16; - } else if (config->src.color_space == LCD_COLOR_SPACE_RGB && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // RGB->YUV - lcd_ll_set_convert_mode_rgb_to_yuv(hal->dev, config->dst.yuv_sample); - rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample]; - } else if (config->src.color_space == LCD_COLOR_SPACE_YUV && config->dst.color_space == LCD_COLOR_SPACE_YUV) { // YUV->YUV - lcd_ll_set_convert_mode_yuv_to_yuv(hal->dev, config->src.yuv_sample, config->dst.yuv_sample); - rgb_panel->output_bits_per_pixel = bpp_yuv[config->dst.yuv_sample]; - } else { - ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "unsupported conversion mode"); - } - - // set conversion standard - lcd_ll_set_yuv_convert_std(hal->dev, config->std); - // set conversion data width - lcd_ll_set_convert_data_width(hal->dev, rgb_panel->data_width); - // set color range - lcd_ll_set_input_color_range(hal->dev, config->src.color_range); - lcd_ll_set_output_color_range(hal->dev, config->dst.color_range); - } else { - // output bpp equals to frame buffer bpp - rgb_panel->output_bits_per_pixel = rgb_panel->fb_bits_per_pixel; - } - - // enable or disable RGB-YUV conversion - lcd_ll_enable_rgb_yuv_convert(hal->dev, en_conversion); - - return ESP_OK; -} - -static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - int panel_id = rgb_panel->panel_id; - ESP_RETURN_ON_ERROR(lcd_rgb_panel_destory(rgb_panel), TAG, "destroy rgb panel(%d) failed", panel_id); - ESP_LOGD(TAG, "del rgb panel(%d)", panel_id); - return ESP_OK; -} - -static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - lcd_ll_fifo_reset(rgb_panel->hal.dev); - lcd_ll_reset(rgb_panel->hal.dev); - return ESP_OK; -} - -static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) -{ - esp_err_t ret = ESP_OK; - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - // set pixel clock frequency - rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz, rgb_panel->lcd_clk_flags); - // pixel clock phase and polarity - lcd_ll_set_clock_idle_level(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_idle_high); - lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg); - // enable RGB mode and set data width - lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true); - lcd_ll_set_data_width(rgb_panel->hal.dev, rgb_panel->data_width); - lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1); // enable data phase only - // number of data cycles is controlled by DMA buffer size - lcd_ll_enable_output_always_on(rgb_panel->hal.dev, true); - // configure HSYNC, VSYNC, DE signal idle state level - lcd_ll_set_idle_level(rgb_panel->hal.dev, !rgb_panel->timings.flags.hsync_idle_low, - !rgb_panel->timings.flags.vsync_idle_low, rgb_panel->timings.flags.de_idle_high); - // configure blank region timing - lcd_ll_set_blank_cycles(rgb_panel->hal.dev, 1, 1); // RGB panel always has a front and back blank (porch region) - lcd_ll_set_horizontal_timing(rgb_panel->hal.dev, rgb_panel->timings.hsync_pulse_width, - rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->output_bits_per_pixel / rgb_panel->data_width, - rgb_panel->timings.hsync_front_porch); - lcd_ll_set_vertical_timing(rgb_panel->hal.dev, rgb_panel->timings.vsync_pulse_width, - rgb_panel->timings.vsync_back_porch, rgb_panel->timings.v_res, - rgb_panel->timings.vsync_front_porch); - // output hsync even in porch region - lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true); - // generate the hsync at the very beginning of line - lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0); - // send next frame automatically in stream mode - lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode); - // trigger interrupt on the end of frame - lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true); - // enable intr - esp_intr_enable(rgb_panel->intr); - // start transmission - if (rgb_panel->flags.stream_mode) { - lcd_rgb_panel_start_transmission(rgb_panel); - } - ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%"PRIu32"Hz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz); - return ret; -} - -__attribute__((always_inline)) -static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from) -{ - *to++ = *from++; -} - -__attribute__((always_inline)) -static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from) -{ - *to++ = *from++; - *to++ = *from++; -} - -__attribute__((always_inline)) -static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from) -{ - *to++ = *from++; - *to++ = *from++; - *to++ = *from++; -} - -#define COPY_PIXEL_CODE_BLOCK(_bpp) \ - switch (rgb_panel->rotate_mask) \ - { \ - case 0: \ - { \ - uint8_t *to = fb + (y_start * h_res + x_start) * bytes_per_pixel; \ - for (int y = y_start; y < y_end; y++) \ - { \ - memcpy(to, from, copy_bytes_per_line); \ - to += bytes_per_line; \ - from += copy_bytes_per_line; \ - } \ - bytes_to_flush = (y_end - y_start) * bytes_per_line; \ - flush_ptr = fb + y_start * bytes_per_line; \ - } \ - break; \ - case ROTATE_MASK_MIRROR_X: \ - for (int y = y_start; y < y_end; y++) \ - { \ - uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \ - for (size_t x = x_start; x < x_end; x++) \ - { \ - copy_pixel_##_bpp##bpp(to + index, from); \ - index -= bytes_per_pixel; \ - from += bytes_per_pixel; \ - } \ - } \ - bytes_to_flush = (y_end - y_start) * bytes_per_line; \ - flush_ptr = fb + y_start * bytes_per_line; \ - break; \ - case ROTATE_MASK_MIRROR_Y: \ - { \ - uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + x_start) * bytes_per_pixel; \ - for (int y = y_start; y < y_end; y++) \ - { \ - memcpy(to, from, copy_bytes_per_line); \ - to -= bytes_per_line; \ - from += copy_bytes_per_line; \ - } \ - bytes_to_flush = (y_end - y_start) * bytes_per_line; \ - flush_ptr = fb + (v_res - y_end) * bytes_per_line; \ - } \ - break; \ - case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \ - for (int y = y_start; y < y_end; y++) \ - { \ - uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \ - for (size_t x = x_start; x < x_end; x++) \ - { \ - copy_pixel_##_bpp##bpp(to + index, from); \ - index -= bytes_per_pixel; \ - from += bytes_per_pixel; \ - } \ - } \ - bytes_to_flush = (y_end - y_start) * bytes_per_line; \ - flush_ptr = fb + (v_res - y_end) * bytes_per_line; \ - break; \ - case ROTATE_MASK_SWAP_XY: \ - for (int y = y_start; y < y_end; y++) \ - { \ - for (int x = x_start; x < x_end; x++) \ - { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ - uint32_t i = (x * h_res + y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ - } \ - } \ - bytes_to_flush = (x_end - x_start) * bytes_per_line; \ - flush_ptr = fb + x_start * bytes_per_line; \ - break; \ - case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X: \ - for (int y = y_start; y < y_end; y++) \ - { \ - for (int x = x_start; x < x_end; x++) \ - { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ - uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ - } \ - } \ - bytes_to_flush = (x_end - x_start) * bytes_per_line; \ - flush_ptr = fb + x_start * bytes_per_line; \ - break; \ - case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_Y: \ - for (int y = y_start; y < y_end; y++) \ - { \ - for (int x = x_start; x < x_end; x++) \ - { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ - uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ - } \ - } \ - bytes_to_flush = (x_end - x_start) * bytes_per_line; \ - flush_ptr = fb + (v_res - x_end) * bytes_per_line; \ - break; \ - case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \ - for (int y = y_start; y < y_end; y++) \ - { \ - for (int x = x_start; x < x_end; x++) \ - { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ - uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ - } \ - } \ - bytes_to_flush = (x_end - x_start) * bytes_per_line; \ - flush_ptr = fb + (v_res - x_end) * bytes_per_line; \ - break; \ - default: \ - break; \ - } - -static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - ESP_RETURN_ON_FALSE(rgb_panel->num_fbs > 0, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed"); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - - // check if we need to copy the draw buffer (pointed by the color_data) to the driver's frame buffer - bool do_copy = false; - if (color_data == rgb_panel->fbs[0]) { - rgb_panel->cur_fb_index = 0; - } else if (color_data == rgb_panel->fbs[1]) { - rgb_panel->cur_fb_index = 1; - } else if (color_data == rgb_panel->fbs[2]) { - rgb_panel->cur_fb_index = 2; - } else { - // we do the copy only if the color_data is different from either frame buffer - do_copy = true; - } - - // adjust the flush window by adding extra gap - x_start += rgb_panel->x_gap; - y_start += rgb_panel->y_gap; - x_end += rgb_panel->x_gap; - y_end += rgb_panel->y_gap; - // round the boundary - int h_res = rgb_panel->timings.h_res; - int v_res = rgb_panel->timings.v_res; - if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) { - x_start = MIN(x_start, v_res); - x_end = MIN(x_end, v_res); - y_start = MIN(y_start, h_res); - y_end = MIN(y_end, h_res); - } else { - x_start = MIN(x_start, h_res); - x_end = MIN(x_end, h_res); - y_start = MIN(y_start, v_res); - y_end = MIN(y_end, v_res); - } - - int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8; - int pixels_per_line = rgb_panel->timings.h_res; - uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line; - uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index]; - size_t bytes_to_flush = v_res * h_res * bytes_per_pixel; - uint8_t *flush_ptr = fb; - - if (do_copy) { - // copy the UI draw buffer into internal frame buffer - const uint8_t *from = (const uint8_t *)color_data; - uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel; - size_t offset = y_start * copy_bytes_per_line + x_start * bytes_per_pixel; - uint8_t *to = fb; - if (1 == bytes_per_pixel) { - COPY_PIXEL_CODE_BLOCK(8) - } else if (2 == bytes_per_pixel) { - COPY_PIXEL_CODE_BLOCK(16) - } else if (3 == bytes_per_pixel) { - COPY_PIXEL_CODE_BLOCK(24) - } - } - - // Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back - if (rgb_panel->flags.fb_in_psram && !rgb_panel->bb_size) { - // CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back - ESP_RETURN_ON_ERROR(esp_cache_msync(flush_ptr, bytes_to_flush, 0), TAG, "flush cache buffer failed"); - } - - if (!rgb_panel->bb_size) { - if (rgb_panel->flags.stream_mode) { - // the DMA will convey the new frame buffer next time - for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { - rgb_panel->dma_nodes[rgb_panel->num_dma_nodes * (i + 1) - 1].next = rgb_panel->dma_links[rgb_panel->cur_fb_index]; - } - } - } - - return ESP_OK; -} - -static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - int panel_id = rgb_panel->panel_id; - // inverting the data line by GPIO matrix - for (int i = 0; i < rgb_panel->data_width; i++) { - esp_rom_gpio_connect_out_signal(rgb_panel->data_gpio_nums[i], lcd_periph_signals.panels[panel_id].data_sigs[i], - invert_color_data, false); - } - return ESP_OK; -} - -static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - rgb_panel->rotate_mask &= ~(ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y); - rgb_panel->rotate_mask |= (mirror_x << RGB_PANEL_MIRROR_X | mirror_y << RGB_PANEL_MIRROR_Y); - return ESP_OK; -} - -static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - rgb_panel->rotate_mask &= ~(ROTATE_MASK_SWAP_XY); - rgb_panel->rotate_mask |= swap_axes << RGB_PANEL_SWAP_XY; - return ESP_OK; -} - -static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - rgb_panel->x_gap = x_gap; - rgb_panel->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); - if (rgb_panel->disp_gpio_num < 0) { - return ESP_ERR_NOT_SUPPORTED; - } - if (!on_off) { // turn off screen - gpio_set_level(rgb_panel->disp_gpio_num, !rgb_panel->flags.disp_en_level); - } else { // turn on screen - gpio_set_level(rgb_panel->disp_gpio_num, rgb_panel->flags.disp_en_level); - } - return ESP_OK; -} - -static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config) -{ - int panel_id = panel->panel_id; - // check validation of GPIO number - bool valid_gpio = true; - if (panel_config->de_gpio_num < 0) { - // Hsync and Vsync are required in HV mode - valid_gpio = valid_gpio && (panel_config->hsync_gpio_num >= 0) && (panel_config->vsync_gpio_num >= 0); - } - for (size_t i = 0; i < panel_config->data_width; i++) { - valid_gpio = valid_gpio && (panel_config->data_gpio_nums[i] >= 0); - } - if (!valid_gpio) { - return ESP_ERR_INVALID_ARG; - } - // connect peripheral signals via GPIO matrix - for (size_t i = 0; i < panel_config->data_width; i++) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->data_gpio_nums[i]], PIN_FUNC_GPIO); - gpio_set_direction(panel_config->data_gpio_nums[i], GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(panel_config->data_gpio_nums[i], - lcd_periph_signals.panels[panel_id].data_sigs[i], false, false); - } - if (panel_config->hsync_gpio_num >= 0) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->hsync_gpio_num], PIN_FUNC_GPIO); - gpio_set_direction(panel_config->hsync_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(panel_config->hsync_gpio_num, - lcd_periph_signals.panels[panel_id].hsync_sig, false, false); - } - if (panel_config->vsync_gpio_num >= 0) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->vsync_gpio_num], PIN_FUNC_GPIO); - gpio_set_direction(panel_config->vsync_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(panel_config->vsync_gpio_num, - lcd_periph_signals.panels[panel_id].vsync_sig, false, false); - } - // PCLK may not be necessary in some cases (i.e. VGA output) - if (panel_config->pclk_gpio_num >= 0) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->pclk_gpio_num], PIN_FUNC_GPIO); - gpio_set_direction(panel_config->pclk_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(panel_config->pclk_gpio_num, - lcd_periph_signals.panels[panel_id].pclk_sig, false, false); - } - // DE signal might not be necessary for some RGB LCD - if (panel_config->de_gpio_num >= 0) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->de_gpio_num], PIN_FUNC_GPIO); - gpio_set_direction(panel_config->de_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(panel_config->de_gpio_num, - lcd_periph_signals.panels[panel_id].de_sig, false, false); - } - // disp enable GPIO is optional - if (panel_config->disp_gpio_num >= 0) { - gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->disp_gpio_num], PIN_FUNC_GPIO); - gpio_set_direction(panel_config->disp_gpio_num, GPIO_MODE_OUTPUT); - esp_rom_gpio_connect_out_signal(panel_config->disp_gpio_num, SIG_GPIO_OUT_IDX, false, false); - } - return ESP_OK; -} - -static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src) -{ - // get clock source frequency - uint32_t src_clk_hz = 0; - ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz), - TAG, "get clock source frequency failed"); - panel->src_clk_hz = src_clk_hz; - lcd_ll_select_clk_src(panel->hal.dev, clk_src); - - // create pm lock based on different clock source - // clock sources like PLL and XTAL will be turned off in light sleep -#if CONFIG_PM_ENABLE - ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "rgb_panel", &panel->pm_lock), TAG, "create pm lock failed"); - // hold the lock during the whole lifecycle of RGB panel - esp_pm_lock_acquire(panel->pm_lock); - ESP_LOGD(TAG, "installed pm lock and hold the lock during the whole panel lifecycle"); -#endif - - return ESP_OK; -} - -static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, uint8_t *buffer) -{ - bool need_yield = false; - int bytes_per_pixel = panel->fb_bits_per_pixel / 8; - if (panel->num_fbs == 0) { - if (panel->on_bounce_empty) { - // We don't have a frame buffer here; we need to call a callback to refill the bounce buffer - if (panel->on_bounce_empty(&panel->base, buffer, panel->bounce_pos_px, panel->bb_size, panel->user_ctx)) { - need_yield = true; - } - } - } else { - // We do have frame buffer; copy from there. - // Note: if the cache is diabled, and accessing the PSRAM by DCACHE will crash. - memcpy(buffer, &panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size); - if (panel->flags.bb_invalidate_cache) { - // We don't need the bytes we copied from the psram anymore - // Make sure that if anything happened to have changed (because the line already was in cache) we write the data back. - esp_cache_msync(&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], (size_t)panel->bb_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE); - } - } - panel->bounce_pos_px += panel->bb_size / bytes_per_pixel; - // If the bounce pos is larger than the frame buffer size, wrap around so the next isr starts pre-loading the next frame. - if (panel->bounce_pos_px >= panel->fb_size / bytes_per_pixel) { - panel->bounce_pos_px = 0; - panel->bb_fb_index = panel->cur_fb_index; - if (panel->on_bounce_frame_finish) { - if (panel->on_bounce_frame_finish(&panel->base, NULL, panel->user_ctx)) { - need_yield = true; - } - } - } - if (panel->num_fbs > 0) { - // Preload the next bit of buffer from psram - Cache_Start_DCache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], - panel->bb_size, 0); - } - return need_yield; -} - -// This is called in bounce buffer mode, when one bounce buffer has been fully sent to the LCD peripheral. -static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) -{ - esp_rgb_panel_t *panel = (esp_rgb_panel_t *)user_data; - dma_descriptor_t *desc = (dma_descriptor_t *)event_data->tx_eof_desc_addr; - // Figure out which bounce buffer to write to. - // Note: what we receive is the *last* descriptor of this bounce buffer. - int bb = (desc == &panel->dma_nodes[panel->num_dma_nodes - 1]) ? 0 : 1; - portENTER_CRITICAL_ISR(&panel->spinlock); - panel->bb_eof_count++; - portEXIT_CRITICAL_ISR(&panel->spinlock); - return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]); -} - -// If we restart GDMA, many pixels already have been transferred to the LCD peripheral. -// Looks like that has 16 pixels of FIFO plus one holding register. -#define LCD_FIFO_PRESERVE_SIZE_PX (GDMA_LL_L2FIFO_BASE_SIZE + 1) - -static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel) -{ - for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { - panel->dma_links[i] = &panel->dma_nodes[panel->num_dma_nodes * i]; - } - // chain DMA descriptors - for (int i = 0; i < panel->num_dma_nodes * RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { - panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; - panel->dma_nodes[i].next = &panel->dma_nodes[i + 1]; - } - - if (panel->bb_size) { - // loop end back to start - panel->dma_nodes[panel->num_dma_nodes * RGB_LCD_PANEL_BOUNCE_BUF_NUM - 1].next = &panel->dma_nodes[0]; - // mount the bounce buffers to the DMA descriptors - lcd_com_mount_dma_data(panel->dma_links[0], panel->bounce_buffer[0], panel->bb_size); - lcd_com_mount_dma_data(panel->dma_links[1], panel->bounce_buffer[1], panel->bb_size); - } else { - if (panel->flags.stream_mode) { - // circle DMA descriptors chain for each frame buffer - for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { - panel->dma_nodes[panel->num_dma_nodes * (i + 1) - 1].next = &panel->dma_nodes[panel->num_dma_nodes * i]; - } - } else { - // one-off DMA descriptors chain - for (int i = 0; i < RGB_LCD_PANEL_DMA_LINKS_REPLICA; i++) { - panel->dma_nodes[panel->num_dma_nodes * (i + 1) - 1].next = NULL; - } - } - // mount the frame buffer to the DMA descriptors - for (size_t i = 0; i < panel->num_fbs; i++) { - lcd_com_mount_dma_data(panel->dma_links[i], panel->fbs[i], panel->fb_size); - } - } - - // On restart, the data sent to the LCD peripheral needs to start LCD_FIFO_PRESERVE_SIZE_PX pixels after the FB start - // so we use a dedicated DMA node to restart the DMA transaction - // see also `lcd_rgb_panel_try_restart_transmission` - memcpy(&panel->dma_restart_node, &panel->dma_nodes[0], sizeof(panel->dma_restart_node)); - int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * (panel->fb_bits_per_pixel/8); - uint8_t *p = (uint8_t *)panel->dma_restart_node.buffer; - panel->dma_restart_node.buffer = &p[restart_skip_bytes]; - panel->dma_restart_node.dw0.length -= restart_skip_bytes; - panel->dma_restart_node.dw0.size -= restart_skip_bytes; - - // alloc DMA channel and connect to LCD peripheral - gdma_channel_alloc_config_t dma_chan_config = { - .direction = GDMA_CHANNEL_DIRECTION_TX, - }; -#if SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AHB - ESP_RETURN_ON_ERROR(gdma_new_ahb_channel(&dma_chan_config, &panel->dma_chan), TAG, "alloc DMA channel failed"); -#elif SOC_GDMA_TRIG_PERIPH_LCD0_BUS == SOC_GDMA_BUS_AXI - ESP_RETURN_ON_ERROR(gdma_new_axi_channel(&dma_chan_config, &panel->dma_chan), TAG, "alloc DMA channel failed"); -#endif - gdma_connect(panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0)); - gdma_transfer_ability_t ability = { - .psram_trans_align = panel->psram_trans_align, - .sram_trans_align = panel->sram_trans_align, - }; - gdma_set_transfer_ability(panel->dma_chan, &ability); - - // we need to refill the bounce buffer in the DMA EOF interrupt, so only register the callback for bounce buffer mode - if (panel->bb_size) { - gdma_tx_event_callbacks_t cbs = { - .on_trans_eof = lcd_rgb_panel_eof_handler, - }; - gdma_register_tx_event_callbacks(panel->dma_chan, &cbs, panel); - } - - return ESP_OK; -} - -// reset the GDMA channel every VBlank to stop permanent desyncs from happening. -// Note that this fix can lead to single-frame desyncs itself, as in: if this interrupt -// is late enough, the display will shift as the LCD controller already read out the -// first data bytes, and resetting DMA will re-send those. However, the single-frame -// desync this leads to is preferable to the permanent desync that could otherwise -// happen. It's also not super-likely as this interrupt has the entirety of the VBlank -// time to reset DMA. -static IRAM_ATTR void lcd_rgb_panel_try_restart_transmission(esp_rgb_panel_t *panel) -{ - int bb_size_px = panel->bb_size / (panel->fb_bits_per_pixel / 8); - bool do_restart = false; -#if CONFIG_LCD_RGB_RESTART_IN_VSYNC - do_restart = true; -#else - portENTER_CRITICAL_ISR(&panel->spinlock); - if (panel->flags.need_restart) { - panel->flags.need_restart = false; - do_restart = true; - } - if (panel->bb_eof_count < panel->expect_eof_count) { - do_restart = true; - } - panel->bb_eof_count = 0; - portEXIT_CRITICAL_ISR(&panel->spinlock); -#endif // CONFIG_LCD_RGB_RESTART_IN_VSYNC - - if (!do_restart) { - return; - } - - if (panel->bb_size) { - // Catch de-synced frame buffer and reset if needed. - if (panel->bounce_pos_px > bb_size_px*2) { - panel->bounce_pos_px = 0; - } - // Pre-fill bounce buffer 0, if the EOF ISR didn't do that already - if (panel->bounce_pos_px < bb_size_px) { - lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[0]); - } - } - - gdma_reset(panel->dma_chan); - // restart the DMA by a special DMA node - gdma_start(panel->dma_chan, (intptr_t)&panel->dma_restart_node); - - if (panel->bb_size) { - // Fill 2nd bounce buffer while 1st is being sent out, if needed. - if (panel->bounce_pos_px < bb_size_px*2) { - lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[1]); - } - } - -} - -static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel) -{ - // reset FIFO of DMA and LCD, incase there remains old frame data - gdma_reset(rgb_panel->dma_chan); - lcd_ll_stop(rgb_panel->hal.dev); - lcd_ll_fifo_reset(rgb_panel->hal.dev); - - // pre-fill bounce buffers if needed - if (rgb_panel->bb_size) { - rgb_panel->bounce_pos_px = 0; - lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[0]); - lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[1]); - } - - // the start of DMA should be prior to the start of LCD engine - gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_links[rgb_panel->cur_fb_index]); - // delay 1us is sufficient for DMA to pass data to LCD FIFO - // in fact, this is only needed when LCD pixel clock is set too high - esp_rom_delay_us(1); - // start LCD engine - lcd_ll_start(rgb_panel->hal.dev); -} - -IRAM_ATTR static void lcd_rgb_panel_try_update_pclk(esp_rgb_panel_t *rgb_panel) -{ - portENTER_CRITICAL_ISR(&rgb_panel->spinlock); - if (unlikely(rgb_panel->flags.need_update_pclk)) { - rgb_panel->flags.need_update_pclk = false; - rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz, rgb_panel->lcd_clk_flags); - } - portEXIT_CRITICAL_ISR(&rgb_panel->spinlock); -} - -IRAM_ATTR static void lcd_default_isr_handler(void *args) -{ - esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args; - bool need_yield = false; - - uint32_t intr_status = lcd_ll_get_interrupt_status(rgb_panel->hal.dev); - lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status); - if (intr_status & LCD_LL_EVENT_VSYNC_END) { - // call user registered callback - if (rgb_panel->on_vsync) { - if (rgb_panel->on_vsync(&rgb_panel->base, NULL, rgb_panel->user_ctx)) { - need_yield = true; - } - } - - // check whether to update the PCLK frequency, it should be safe to update the PCLK frequency in the VSYNC interrupt - lcd_rgb_panel_try_update_pclk(rgb_panel); - - if (rgb_panel->flags.stream_mode) { - // check whether to restart the transmission - lcd_rgb_panel_try_restart_transmission(rgb_panel); - } - - } - if (need_yield) { - portYIELD_FROM_ISR(); - } -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_ssd1306.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_ssd1306.c deleted file mode 100644 index d2a94aefb..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_ssd1306.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include -#include "sdkconfig.h" -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" - -static const char *TAG = "lcd_panel.ssd1306"; - -// SSD1306 commands -#define SSD1306_CMD_SET_MEMORY_ADDR_MODE 0x20 -#define SSD1306_CMD_SET_COLUMN_RANGE 0x21 -#define SSD1306_CMD_SET_PAGE_RANGE 0x22 -#define SSD1306_CMD_SET_CHARGE_PUMP 0x8D -#define SSD1306_CMD_MIRROR_X_OFF 0xA0 -#define SSD1306_CMD_MIRROR_X_ON 0xA1 -#define SSD1306_CMD_INVERT_OFF 0xA6 -#define SSD1306_CMD_INVERT_ON 0xA7 -#define SSD1306_CMD_DISP_OFF 0xAE -#define SSD1306_CMD_DISP_ON 0xAF -#define SSD1306_CMD_MIRROR_Y_OFF 0xC0 -#define SSD1306_CMD_MIRROR_Y_ON 0xC8 - -static esp_err_t panel_ssd1306_del(esp_lcd_panel_t *panel); -static esp_err_t panel_ssd1306_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_ssd1306_init(esp_lcd_panel_t *panel); -static esp_err_t panel_ssd1306_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); -static esp_err_t panel_ssd1306_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_ssd1306_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_ssd1306_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_ssd1306_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_ssd1306_disp_on_off(esp_lcd_panel_t *panel, bool off); - -typedef struct { - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - bool reset_level; - int x_gap; - int y_gap; - unsigned int bits_per_pixel; - bool swap_axes; -} ssd1306_panel_t; - -esp_err_t esp_lcd_new_panel_ssd1306(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - ssd1306_panel_t *ssd1306 = NULL; - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(panel_dev_config->bits_per_pixel == 1, ESP_ERR_INVALID_ARG, err, TAG, "bpp must be 1"); - ssd1306 = calloc(1, sizeof(ssd1306_panel_t)); - ESP_GOTO_ON_FALSE(ssd1306, ESP_ERR_NO_MEM, err, TAG, "no mem for ssd1306 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_config_t io_conf = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, - }; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - ssd1306->io = io; - ssd1306->bits_per_pixel = panel_dev_config->bits_per_pixel; - ssd1306->reset_gpio_num = panel_dev_config->reset_gpio_num; - ssd1306->reset_level = panel_dev_config->flags.reset_active_high; - ssd1306->base.del = panel_ssd1306_del; - ssd1306->base.reset = panel_ssd1306_reset; - ssd1306->base.init = panel_ssd1306_init; - ssd1306->base.draw_bitmap = panel_ssd1306_draw_bitmap; - ssd1306->base.invert_color = panel_ssd1306_invert_color; - ssd1306->base.set_gap = panel_ssd1306_set_gap; - ssd1306->base.mirror = panel_ssd1306_mirror; - ssd1306->base.swap_xy = panel_ssd1306_swap_xy; - ssd1306->base.disp_on_off = panel_ssd1306_disp_on_off; - *ret_panel = &(ssd1306->base); - ESP_LOGD(TAG, "new ssd1306 panel @%p", ssd1306); - - return ESP_OK; - -err: - if (ssd1306) { - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(ssd1306); - } - return ret; -} - -static esp_err_t panel_ssd1306_del(esp_lcd_panel_t *panel) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - if (ssd1306->reset_gpio_num >= 0) { - gpio_reset_pin(ssd1306->reset_gpio_num); - } - ESP_LOGD(TAG, "del ssd1306 panel @%p", ssd1306); - free(ssd1306); - return ESP_OK; -} - -static esp_err_t panel_ssd1306_reset(esp_lcd_panel_t *panel) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - - // perform hardware reset - if (ssd1306->reset_gpio_num >= 0) { - gpio_set_level(ssd1306->reset_gpio_num, ssd1306->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(ssd1306->reset_gpio_num, !ssd1306->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - } - - return ESP_OK; -} - -static esp_err_t panel_ssd1306_init(esp_lcd_panel_t *panel) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - esp_lcd_panel_io_handle_t io = ssd1306->io; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_DISP_OFF, NULL, 0), TAG, - "io tx param SSD1306_CMD_DISP_OFF failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_MEMORY_ADDR_MODE, (uint8_t[]) { - 0x00 // horizontal addressing mode - }, 1), TAG, "io tx param SSD1306_CMD_SET_MEMORY_ADDR_MODE failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_CHARGE_PUMP, (uint8_t[]) { - 0x14 // enable charge pump - }, 1), TAG, "io tx param SSD1306_CMD_SET_CHARGE_PUMP failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_MIRROR_X_OFF, NULL, 0), TAG, - "io tx param SSD1306_CMD_MIRROR_X_OFF failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_MIRROR_Y_OFF, NULL, 0), TAG, - "io tx param SSD1306_CMD_MIRROR_Y_OFF failed"); - return ESP_OK; -} - -static esp_err_t panel_ssd1306_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - esp_lcd_panel_io_handle_t io = ssd1306->io; - - // adding extra gap - x_start += ssd1306->x_gap; - x_end += ssd1306->x_gap; - y_start += ssd1306->y_gap; - y_end += ssd1306->y_gap; - - if (ssd1306->swap_axes) { - int x = x_start; - x_start = y_start; - y_start = x; - x = x_end; - x_end = y_end; - y_end = x; - } - - // one page contains 8 rows (COMs) - uint8_t page_start = y_start / 8; - uint8_t page_end = (y_end - 1) / 8; - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_COLUMN_RANGE, (uint8_t[]) { - (x_start & 0x7F), - ((x_end - 1) & 0x7F), - }, 2), TAG, "io tx param SSD1306_CMD_SET_COLUMN_RANGE failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_PAGE_RANGE, (uint8_t[]) { - (page_start & 0x07), - (page_end & 0x07), - }, 2), TAG, "io tx param SSD1306_CMD_SET_PAGE_RANGE failed"); - // transfer frame buffer - size_t len = (y_end - y_start) * (x_end - x_start) * ssd1306->bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, -1, color_data, len), TAG, "io tx color failed"); - - return ESP_OK; -} - -static esp_err_t panel_ssd1306_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - esp_lcd_panel_io_handle_t io = ssd1306->io; - int command = 0; - if (invert_color_data) { - command = SSD1306_CMD_INVERT_ON; - } else { - command = SSD1306_CMD_INVERT_OFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param SSD1306_CMD_INVERT_ON/OFF failed"); - return ESP_OK; -} - -static esp_err_t panel_ssd1306_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - esp_lcd_panel_io_handle_t io = ssd1306->io; - - int command = 0; - if (mirror_x) { - command = SSD1306_CMD_MIRROR_X_ON; - } else { - command = SSD1306_CMD_MIRROR_X_OFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param SSD1306_CMD_MIRROR_X_ON/OFF failed"); - if (mirror_y) { - command = SSD1306_CMD_MIRROR_Y_ON; - } else { - command = SSD1306_CMD_MIRROR_Y_OFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param SSD1306_CMD_MIRROR_Y_ON/OFF failed"); - return ESP_OK; -} - -static esp_err_t panel_ssd1306_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - ssd1306->swap_axes = swap_axes; - - return ESP_OK; -} - -static esp_err_t panel_ssd1306_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - ssd1306->x_gap = x_gap; - ssd1306->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_ssd1306_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base); - esp_lcd_panel_io_handle_t io = ssd1306->io; - int command = 0; - if (on_off) { - command = SSD1306_CMD_DISP_ON; - } else { - command = SSD1306_CMD_DISP_OFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param SSD1306_CMD_DISP_ON/OFF failed"); - // SEG/COM will be ON/OFF after 100ms after sending DISP_ON/OFF command - vTaskDelay(pdMS_TO_TICKS(100)); - return ESP_OK; -} diff --git a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_st7789.c b/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_st7789.c deleted file mode 100644 index 76e26fd6d..000000000 --- a/tulip/esp32s3/components/esp_lcd/src/esp_lcd_panel_st7789.c +++ /dev/null @@ -1,317 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include -#include "sdkconfig.h" - -#if CONFIG_LCD_ENABLE_DEBUG_LOG -// The local log level must be defined before including esp_log.h -// Set the maximum log level for this source file -#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG -#endif - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_lcd_panel_interface.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" - -#define ST7789_CMD_RAMCTRL 0xb0 -#define ST7789_DATA_LITTLE_ENDIAN_BIT (1 << 3) - -static const char *TAG = "lcd_panel.st7789"; - -static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel); -static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel); -static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, - const void *color_data); -static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_st7789_disp_on_off(esp_lcd_panel_t *panel, bool off); -static esp_err_t panel_st7789_sleep(esp_lcd_panel_t *panel, bool sleep); - -typedef struct { - esp_lcd_panel_t base; - esp_lcd_panel_io_handle_t io; - int reset_gpio_num; - bool reset_level; - int x_gap; - int y_gap; - uint8_t fb_bits_per_pixel; - uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register - uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register - uint8_t ramctl_val_1; - uint8_t ramctl_val_2; -} st7789_panel_t; - -esp_err_t -esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, - esp_lcd_panel_handle_t *ret_panel) -{ -#if CONFIG_LCD_ENABLE_DEBUG_LOG - esp_log_level_set(TAG, ESP_LOG_DEBUG); -#endif - esp_err_t ret = ESP_OK; - st7789_panel_t *st7789 = NULL; - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - st7789 = calloc(1, sizeof(st7789_panel_t)); - ESP_GOTO_ON_FALSE(st7789, ESP_ERR_NO_MEM, err, TAG, "no mem for st7789 panel"); - - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_config_t io_conf = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, - }; - ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); - } - - switch (panel_dev_config->rgb_endian) { - case LCD_RGB_ENDIAN_RGB: - st7789->madctl_val = 0; - break; - case LCD_RGB_ENDIAN_BGR: - st7789->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); - break; - } - - uint8_t fb_bits_per_pixel = 0; - switch (panel_dev_config->bits_per_pixel) { - case 16: // RGB565 - st7789->colmod_val = 0x55; - fb_bits_per_pixel = 16; - break; - case 18: // RGB666 - st7789->colmod_val = 0x66; - // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel - fb_bits_per_pixel = 24; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - st7789->ramctl_val_1 = 0x00; - st7789->ramctl_val_2 = 0xf0; // Use big endian by default - if ((panel_dev_config->data_endian) == LCD_RGB_DATA_ENDIAN_LITTLE) { - // Use little endian - st7789->ramctl_val_2 |= ST7789_DATA_LITTLE_ENDIAN_BIT; - } - - st7789->io = io; - st7789->fb_bits_per_pixel = fb_bits_per_pixel; - st7789->reset_gpio_num = panel_dev_config->reset_gpio_num; - st7789->reset_level = panel_dev_config->flags.reset_active_high; - st7789->base.del = panel_st7789_del; - st7789->base.reset = panel_st7789_reset; - st7789->base.init = panel_st7789_init; - st7789->base.draw_bitmap = panel_st7789_draw_bitmap; - st7789->base.invert_color = panel_st7789_invert_color; - st7789->base.set_gap = panel_st7789_set_gap; - st7789->base.mirror = panel_st7789_mirror; - st7789->base.swap_xy = panel_st7789_swap_xy; - st7789->base.disp_on_off = panel_st7789_disp_on_off; - st7789->base.disp_sleep = panel_st7789_sleep; - *ret_panel = &(st7789->base); - ESP_LOGD(TAG, "new st7789 panel @%p", st7789); - - return ESP_OK; - -err: - if (st7789) { - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(st7789); - } - return ret; -} - -static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - - if (st7789->reset_gpio_num >= 0) { - gpio_reset_pin(st7789->reset_gpio_num); - } - ESP_LOGD(TAG, "del st7789 panel @%p", st7789); - free(st7789); - return ESP_OK; -} - -static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - - // perform hardware reset - if (st7789->reset_gpio_num >= 0) { - gpio_set_level(st7789->reset_gpio_num, st7789->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(st7789->reset_gpio_num, !st7789->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - } else { // perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, - "io tx param failed"); - vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5m before sending new command - } - - return ESP_OK; -} - -static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, - "io tx param failed"); - vTaskDelay(pdMS_TO_TICKS(100)); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - st7789->madctl_val, - }, 1), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { - st7789->colmod_val, - }, 1), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7789_CMD_RAMCTRL, (uint8_t[]) { - st7789->ramctl_val_1, st7789->ramctl_val_2 - }, 2), TAG, "io tx param failed"); - - return ESP_OK; -} - -static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, - const void *color_data) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); - esp_lcd_panel_io_handle_t io = st7789->io; - - x_start += st7789->x_gap; - x_end += st7789->x_gap; - y_start += st7789->y_gap; - y_end += st7789->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, 4), TAG, "io tx param failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, 4), TAG, "io tx param failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * st7789->fb_bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "io tx color failed"); - - return ESP_OK; -} - -static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - int command = 0; - if (invert_color_data) { - command = LCD_CMD_INVON; - } else { - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - if (mirror_x) { - st7789->madctl_val |= LCD_CMD_MX_BIT; - } else { - st7789->madctl_val &= ~LCD_CMD_MX_BIT; - } - if (mirror_y) { - st7789->madctl_val |= LCD_CMD_MY_BIT; - } else { - st7789->madctl_val &= ~LCD_CMD_MY_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - st7789->madctl_val - }, 1), TAG, "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - if (swap_axes) { - st7789->madctl_val |= LCD_CMD_MV_BIT; - } else { - st7789->madctl_val &= ~LCD_CMD_MV_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - st7789->madctl_val - }, 1), TAG, "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - st7789->x_gap = x_gap; - st7789->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_st7789_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - int command = 0; - if (on_off) { - command = LCD_CMD_DISPON; - } else { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param failed"); - return ESP_OK; -} - -static esp_err_t panel_st7789_sleep(esp_lcd_panel_t *panel, bool sleep) -{ - st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base); - esp_lcd_panel_io_handle_t io = st7789->io; - int command = 0; - if (sleep) { - command = LCD_CMD_SLPIN; - } else { - command = LCD_CMD_SLPOUT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param failed"); - vTaskDelay(pdMS_TO_TICKS(100)); - - return ESP_OK; -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/.build-test-rules.yml b/tulip/esp32s3/components/esp_lcd/test_apps/.build-test-rules.yml deleted file mode 100644 index 7067d63dd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/.build-test-rules.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps - -components/esp_lcd/test_apps/i2c_lcd: - disable: - - if: SOC_I2C_SUPPORTED != 1 - depends_components: - - esp_lcd - depends_filepatterns: - - components/driver/i2c/**/* - disable_test: - - if: IDF_TARGET not in ["esp32c3"] - temporary: true - reason: insufficient runners - -components/esp_lcd/test_apps/i2c_lcd_legacy: - disable: - - if: SOC_I2C_SUPPORTED != 1 - depends_components: - - esp_lcd - depends_filepatterns: - - components/driver/i2c/**/* - disable_test: - - if: IDF_TARGET not in ["esp32c3"] - temporary: true - reason: insufficient runners - -components/esp_lcd/test_apps/i80_lcd: - depends_components: - - esp_lcd - depends_filepatterns: - - components/driver/i2s/**/* # i80 IO driver relies on I2S on esp32 and esp32s2 - disable: - - if: SOC_LCD_I80_SUPPORTED != 1 - -components/esp_lcd/test_apps/rgb_lcd: - depends_components: - - esp_lcd - disable: - - if: SOC_LCD_RGB_SUPPORTED != 1 - -components/esp_lcd/test_apps/spi_lcd: - depends_components: - - esp_lcd - depends_filepatterns: - - components/driver/spi/**/* - disable: - - if: SOC_GPSPI_SUPPORTED != 1 diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/CMakeLists.txt deleted file mode 100644 index 8dd5f4d48..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# This is the project CMakeLists.txt file for the test subproject -cmake_minimum_required(VERSION 3.16) - -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -set(COMPONENTS main) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(i2c_lcd_panel_test) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/README.md b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/README.md deleted file mode 100644 index 10a58b277..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/README.md +++ /dev/null @@ -1,4 +0,0 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | - -This test app is used to test LCDs with I2C interface. diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/CMakeLists.txt deleted file mode 100644 index aa4f3147a..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(srcs "test_app_main.c" - "test_i2c_lcd_panel.c") - -# In order for the cases defined by `TEST_CASE` to be linked into the final elf, -# the component can be registered as WHOLE_ARCHIVE -idf_component_register(SRCS ${srcs} - PRIV_REQUIRES esp_lcd unity driver - WHOLE_ARCHIVE) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_app_main.c b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_app_main.c deleted file mode 100644 index 2c2aa076d..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_app_main.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "unity.h" -#include "unity_test_runner.h" -#include "esp_heap_caps.h" - -// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -void app_main(void) -{ - // ___ ____ ____ _ ____ ____ _____ _ - // |_ _|___ \ / ___| | | / ___| _ \ |_ _|__ ___| |_ - // | | __) | | | | | | | | | | | |/ _ \/ __| __| - // | | / __/| |___ | |__| |___| |_| | | | __/\__ \ |_ - // |___|_____|\____| |_____\____|____/ |_|\___||___/\__| - printf(" ___ ____ ____ _ ____ ____ _____ _\r\n"); - printf("|_ _|___ \\ / ___| | | / ___| _ \\ |_ _|__ ___| |_\r\n"); - printf(" | | __) | | | | | | | | | | | |/ _ \\/ __| __|\r\n"); - printf(" | | / __/| |___ | |__| |___| |_| | | | __/\\__ \\ |_\r\n"); - printf("|___|_____|\\____| |_____\\____|____/ |_|\\___||___/\\__|\r\n"); - unity_run_menu(); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_board.h b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_board.h deleted file mode 100644 index c8f4150a0..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_board.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define TEST_LCD_H_RES 128 -#define TEST_LCD_V_RES 64 - -#define TEST_I2C_SDA_GPIO 0 -#define TEST_I2C_SCL_GPIO 2 - -#define TEST_I2C_HOST_ID 0 - -#define TEST_I2C_DEV_ADDR 0x3C - -#define TEST_LCD_PIXEL_CLOCK_HZ (400 * 1000) - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c deleted file mode 100644 index 19884dee5..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include "unity.h" -#include "driver/i2c_master.h" -#include "driver/gpio.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_system.h" -#include "test_i2c_board.h" - -TEST_CASE("lcd_panel_with_i2c_interface_(ssd1306)", "[lcd]") -{ - const uint8_t pattern[][16] = {{ - 0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00, - 0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00 - }, - { - 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81, - 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 - } - }; - - i2c_master_bus_config_t i2c_bus_conf = { - .clk_source = I2C_CLK_SRC_DEFAULT, - .sda_io_num = TEST_I2C_SDA_GPIO, - .scl_io_num = TEST_I2C_SCL_GPIO, - .i2c_port = -1, - }; - - i2c_master_bus_handle_t bus_handle; - TEST_ESP_OK(i2c_new_master_bus(&i2c_bus_conf, &bus_handle)); - - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = TEST_I2C_DEV_ADDR, - .scl_speed_hz = TEST_LCD_PIXEL_CLOCK_HZ, - .control_phase_bytes = 1, // According to SSD1306 datasheet - .dc_bit_offset = 6, // According to SSD1306 datasheet - .lcd_cmd_bits = 8, // According to SSD1306 datasheet - .lcd_param_bits = 8, // According to SSD1306 datasheet - }; - - TEST_ESP_OK(esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle)); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .bits_per_pixel = 1, - .reset_gpio_num = -1, - }; - TEST_ESP_OK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); - TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - // turn on display - TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - for (int i = 0; i < TEST_LCD_H_RES / 16; i++) { - for (int j = 0; j < TEST_LCD_V_RES / 8; j++) { - TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, i * 16, j * 8, i * 16 + 16, j * 8 + 8, pattern[i & 0x01])); - } - } - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(i2c_del_master_bus(bus_handle)); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/pytest_i2c_lcd.py b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/pytest_i2c_lcd.py deleted file mode 100644 index 610d15c48..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/pytest_i2c_lcd.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - - -@pytest.mark.esp32c3 -@pytest.mark.i2c_oled -@pytest.mark.parametrize( - 'config', - [ - 'release', - ], - indirect=True, -) -def test_i2c_lcd(dut: Dut) -> None: - dut.run_all_single_board_cases() diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/sdkconfig.ci.release b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/sdkconfig.ci.release deleted file mode 100644 index 3cff15d49..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/sdkconfig.ci.release +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/sdkconfig.defaults b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/sdkconfig.defaults deleted file mode 100644 index ccc43c6fd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd/sdkconfig.defaults +++ /dev/null @@ -1,5 +0,0 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# -# CONFIG_ESP_TASK_WDT_INIT is not set -CONFIG_FREERTOS_HZ=1000 diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/CMakeLists.txt deleted file mode 100644 index 8c099ff31..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -# This is the project CMakeLists.txt file for the test subproject -cmake_minimum_required(VERSION 3.16) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) - -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -set(COMPONENTS main) - -project(legacy_i2c_lcd_panel_test) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/README.md b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/README.md deleted file mode 100644 index 10a58b277..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/README.md +++ /dev/null @@ -1,4 +0,0 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | - -This test app is used to test LCDs with I2C interface. diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/CMakeLists.txt deleted file mode 100644 index a168fe90a..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(srcs "test_app_main.c" - "test_i2c_lcd_legacy_panel.c") - -# In order for the cases defined by `TEST_CASE` to be linked into the final elf, -# the component can be registered as WHOLE_ARCHIVE -idf_component_register(SRCS ${srcs} - PRIV_REQUIRES unity driver esp_lcd - WHOLE_ARCHIVE) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_app_main.c b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_app_main.c deleted file mode 100644 index 826e43ddb..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_app_main.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "unity.h" -#include "unity_test_runner.h" -#include "esp_heap_caps.h" - -// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -void app_main(void) -{ - // ___ ____ ____ _ ____ ____ _____ _ - // |_ _|___ \ / ___| | | / ___| _ \ |_ _|__ ___| |_ - // | | __) | | | | | | | | | | | |/ _ \/ __| __| - // | | / __/| |___ | |__| |___| |_| | | | __/\__ \ |_ - // |___|_____|\____| |_____\____|____/ |_|\___||___/\__| - printf(" ___ ____ ____ _ ____ ____ _____ _\r\n"); - printf("|_ _|___ \\ / ___| | | / ___| _ \\ |_ _|__ ___| |_\r\n"); - printf(" | | __) | | | | | | | | | | | |/ _ \\/ __| __|\r\n"); - printf(" | | / __/| |___ | |__| |___| |_| | | | __/\\__ \\ |_\r\n"); - printf("|___|_____|\\____| |_____\\____|____/ |_|\\___||___/\\__|\r\n"); - unity_run_menu(); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_board.h b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_board.h deleted file mode 100644 index f108031ca..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_board.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define TEST_LCD_H_RES 128 -#define TEST_LCD_V_RES 64 - -#define TEST_I2C_SDA_GPIO 0 -#define TEST_I2C_SCL_GPIO 2 - -#define TEST_I2C_HOST_ID 0 - -#define TEST_I2C_DEV_ADDR 0x3C - -#define TEST_LCD_PIXEL_CLOCK_HZ (400 * 1000) - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c deleted file mode 100644 index 4a55b60b2..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include "unity.h" -#include "driver/i2c.h" -#include "driver/gpio.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_system.h" -#include "test_i2c_board.h" - -TEST_CASE("lcd_panel_with_i2c_interface legacy_(ssd1306)", "[lcd]") -{ - const uint8_t pattern[][16] = {{ - 0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00, - 0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00 - }, - { - 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81, - 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 - } - }; - - i2c_config_t conf = { - .mode = I2C_MODE_MASTER, - .sda_io_num = TEST_I2C_SDA_GPIO, - .scl_io_num = TEST_I2C_SCL_GPIO, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = TEST_LCD_PIXEL_CLOCK_HZ, - }; - TEST_ESP_OK(i2c_param_config(TEST_I2C_HOST_ID, &conf)); - TEST_ESP_OK(i2c_driver_install(TEST_I2C_HOST_ID, I2C_MODE_MASTER, 0, 0, 0)); - - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = TEST_I2C_DEV_ADDR, - .control_phase_bytes = 1, // According to SSD1306 datasheet - .dc_bit_offset = 6, // According to SSD1306 datasheet - .lcd_cmd_bits = 8, // According to SSD1306 datasheet - .lcd_param_bits = 8, // According to SSD1306 datasheet - }; - TEST_ESP_OK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TEST_I2C_HOST_ID, &io_config, &io_handle)); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .bits_per_pixel = 1, - .reset_gpio_num = -1, - }; - TEST_ESP_OK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); - TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - // turn on display - TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true)); - - for (int i = 0; i < TEST_LCD_H_RES / 16; i++) { - for (int j = 0; j < TEST_LCD_V_RES / 8; j++) { - TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, i * 16, j * 8, i * 16 + 16, j * 8 + 8, pattern[i & 0x01])); - } - } - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(i2c_driver_delete(TEST_I2C_HOST_ID)); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/pytest_i2c_lcd_legacy.py b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/pytest_i2c_lcd_legacy.py deleted file mode 100644 index a2e09316a..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/pytest_i2c_lcd_legacy.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - - -@pytest.mark.esp32c3 -@pytest.mark.i2c_oled -@pytest.mark.parametrize( - 'config', - [ - 'release', - ], - indirect=True, -) -def test_i2c_lcd_legacy(dut: Dut) -> None: - dut.run_all_single_board_cases() diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/sdkconfig.ci.release b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/sdkconfig.ci.release deleted file mode 100644 index 3cff15d49..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/sdkconfig.ci.release +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/sdkconfig.defaults b/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/sdkconfig.defaults deleted file mode 100644 index ccc43c6fd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i2c_lcd_legacy/sdkconfig.defaults +++ /dev/null @@ -1,5 +0,0 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# -# CONFIG_ESP_TASK_WDT_INIT is not set -CONFIG_FREERTOS_HZ=1000 diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/CMakeLists.txt deleted file mode 100644 index b3e6f9cf7..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# This is the project CMakeLists.txt file for the test subproject -cmake_minimum_required(VERSION 3.16) - -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -set(COMPONENTS main) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(i80_lcd_panel_test) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/README.md b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/README.md deleted file mode 100644 index c6425abb4..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/README.md +++ /dev/null @@ -1,4 +0,0 @@ -| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | - -This test app is used to test LCDs with intel 8080 interface. diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt deleted file mode 100644 index e6bea3170..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(srcs "test_app_main.c" - "test_i80_lcd_panel.c") - -# In order for the cases defined by `TEST_CASE` to be linked into the final elf, -# the component can be registered as WHOLE_ARCHIVE -idf_component_register(SRCS ${srcs} - PRIV_REQUIRES esp_lcd unity driver - WHOLE_ARCHIVE) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_app_main.c b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_app_main.c deleted file mode 100644 index c4d721ce3..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_app_main.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "unity.h" -#include "unity_test_runner.h" -#include "esp_heap_caps.h" - -// Some resources are lazy allocated in LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -void app_main(void) -{ - // _ ___ ___ _ ____ ____ _____ _ - // (_)( _ ) / _ \ | | / ___| _ \ |_ _|__ ___| |_ - // | |/ _ \| | | | | | | | | | | | | |/ _ \/ __| __| - // | | (_) | |_| | | |__| |___| |_| | | | __/\__ \ |_ - // |_|\___/ \___/ |_____\____|____/ |_|\___||___/\__| - printf(" _ ___ ___ _ ____ ____ _____ _\r\n"); - printf("(_)( _ ) / _ \\ | | / ___| _ \\ |_ _|__ ___| |_r\n"); - printf("| |/ _ \\| | | | | | | | | | | | | |/ _ \\/ __| __|\r\n"); - printf("| | (_) | |_| | | |__| |___| |_| | | | __/\\__ \\ |_\r\n"); - printf("|_|\\___/ \\___/ |_____\\____|____/ |_|\\___||___/\\__|\r\n"); - unity_run_menu(); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_i80_board.h b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_i80_board.h deleted file mode 100644 index 0e44ac9a1..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_i80_board.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include "sdkconfig.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define TEST_LCD_H_RES (240) -#define TEST_LCD_V_RES (280) - -#if CONFIG_IDF_TARGET_ESP32S3 -#define TEST_LCD_BK_LIGHT_GPIO (1) -#define TEST_LCD_RST_GPIO (2) -#define TEST_LCD_CS_GPIO (3) -#define TEST_LCD_DC_GPIO (4) -#define TEST_LCD_PCLK_GPIO (5) -#define TEST_LCD_DATA0_GPIO (6) -#define TEST_LCD_DATA1_GPIO (7) -#define TEST_LCD_DATA2_GPIO (8) -#define TEST_LCD_DATA3_GPIO (9) -#define TEST_LCD_DATA4_GPIO (10) -#define TEST_LCD_DATA5_GPIO (11) -#define TEST_LCD_DATA6_GPIO (12) -#define TEST_LCD_DATA7_GPIO (13) -#define TEST_LCD_DATA8_GPIO (14) -#define TEST_LCD_DATA9_GPIO (15) -#define TEST_LCD_DATA10_GPIO (16) -#define TEST_LCD_DATA11_GPIO (17) -#define TEST_LCD_DATA12_GPIO (18) -#define TEST_LCD_DATA13_GPIO (19) -#define TEST_LCD_DATA14_GPIO (20) -#define TEST_LCD_DATA15_GPIO (21) -#elif CONFIG_IDF_TARGET_ESP32S2 -#define TEST_LCD_BK_LIGHT_GPIO (0) -#define TEST_LCD_RST_GPIO (18) -#define TEST_LCD_CS_GPIO (19) -#define TEST_LCD_DC_GPIO (38) -#define TEST_LCD_PCLK_GPIO (33) -#define TEST_LCD_DATA0_GPIO (1) -#define TEST_LCD_DATA1_GPIO (10) -#define TEST_LCD_DATA2_GPIO (2) -#define TEST_LCD_DATA3_GPIO (11) -#define TEST_LCD_DATA4_GPIO (3) -#define TEST_LCD_DATA5_GPIO (12) -#define TEST_LCD_DATA6_GPIO (4) -#define TEST_LCD_DATA7_GPIO (13) -#define TEST_LCD_DATA8_GPIO (5) -#define TEST_LCD_DATA9_GPIO (14) -#define TEST_LCD_DATA10_GPIO (6) -#define TEST_LCD_DATA11_GPIO (15) -#define TEST_LCD_DATA12_GPIO (7) -#define TEST_LCD_DATA13_GPIO (16) -#define TEST_LCD_DATA14_GPIO (8) -#define TEST_LCD_DATA15_GPIO (17) -#elif CONFIG_IDF_TARGET_ESP32 -#define TEST_LCD_BK_LIGHT_GPIO (2) -#define TEST_LCD_RST_GPIO (-1) -#define TEST_LCD_CS_GPIO (4) -#define TEST_LCD_DC_GPIO (5) -#define TEST_LCD_PCLK_GPIO (18) -#define TEST_LCD_DATA0_GPIO (19) -#define TEST_LCD_DATA1_GPIO (21) -#define TEST_LCD_DATA2_GPIO (0) -#define TEST_LCD_DATA3_GPIO (22) -#define TEST_LCD_DATA4_GPIO (23) -#define TEST_LCD_DATA5_GPIO (33) -#define TEST_LCD_DATA6_GPIO (32) -#define TEST_LCD_DATA7_GPIO (27) -#define TEST_LCD_DATA8_GPIO (12) -#define TEST_LCD_DATA9_GPIO (13) -#define TEST_LCD_DATA10_GPIO (14) -#define TEST_LCD_DATA11_GPIO (15) -#define TEST_LCD_DATA12_GPIO (26) -#define TEST_LCD_DATA13_GPIO (25) -#define TEST_LCD_DATA14_GPIO (16) -#define TEST_LCD_DATA15_GPIO (17) -#endif - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c deleted file mode 100644 index e2f6e2cf1..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/main/test_i80_lcd_panel.c +++ /dev/null @@ -1,564 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "unity.h" -#include "esp_random.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "soc/soc_caps.h" -#include "driver/gpio.h" -#include "test_i80_board.h" - -#if SOC_I2S_LCD_I80_VARIANT -#include "driver/i2s_std.h" - -TEST_CASE("i80_and_i2s_driver_co-existence", "[lcd][i2s]") -{ - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = 20, - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - - i2s_chan_handle_t tx_handle = NULL; - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - // I2S driver won't be installed as the same I2S port has been used by LCD - TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, i2s_new_channel(&chan_cfg, &tx_handle, NULL)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); -} -#endif // SOC_I2S_LCD_I80_VARIANT - -#if SOC_LCDCAM_SUPPORTED -TEST_CASE("lcd_i80_device_swap_color_bytes", "[lcd]") -{ - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = 20, - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - - esp_lcd_panel_io_handle_t io_handles[4] = {}; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 5000000, - .trans_queue_depth = 10, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - - io_config.flags.reverse_color_bits = 0; - io_config.flags.swap_color_bytes = 0; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[0])); - io_config.flags.reverse_color_bits = 0; - io_config.flags.swap_color_bytes = 1; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[1])); - io_config.flags.reverse_color_bits = 1; - io_config.flags.swap_color_bytes = 0; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[2])); - io_config.flags.reverse_color_bits = 1; - io_config.flags.swap_color_bytes = 1; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[3])); - - for (int i = 0; i < 4; i++) { - esp_lcd_panel_io_tx_param(io_handles[i], 0xA5, (uint8_t[]) { - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 - }, 6); - esp_lcd_panel_io_tx_color(io_handles[i], 0x5A, (uint8_t[]) { - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 - }, 6); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handles[i])); - } - - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); -} - -TEST_CASE("lcd_i80_device_clock_mode", "[lcd]") -{ - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = 20, - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - - esp_lcd_panel_io_handle_t io_handles[4] = {}; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 5000000, - .trans_queue_depth = 10, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - - io_config.flags.pclk_idle_low = 0; - io_config.flags.pclk_active_neg = 0; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[0])); - io_config.flags.pclk_idle_low = 0; - io_config.flags.pclk_active_neg = 1; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[1])); - io_config.flags.pclk_idle_low = 1; - io_config.flags.pclk_active_neg = 0; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[2])); - io_config.flags.pclk_idle_low = 1; - io_config.flags.pclk_active_neg = 1; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[3])); - - for (int i = 0; i < 4; i++) { - esp_lcd_panel_io_tx_param(io_handles[i], 0xA5, (uint8_t[]) { - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 - }, 6); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handles[i])); - } - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); -} -#endif // SOC_LCDCAM_SUPPORTED - -TEST_CASE("lcd_i80_bus_and_device_allocation", "[lcd]") -{ - esp_lcd_i80_bus_handle_t i80_buses[SOC_LCD_I80_BUSES] = {}; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t) - }; - for (int i = 0; i < SOC_LCD_I80_BUSES; i++) { - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_buses[i])); - } - TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, esp_lcd_new_i80_bus(&bus_config, &i80_buses[0])); - esp_lcd_panel_io_handle_t io_handles[10] = {}; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 5000000, - .trans_queue_depth = 4, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - for (int i = 0; i < 10; i++) { - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_buses[0], &io_config, &io_handles[i])); - } - // can't delete bus handle before we delete all devices - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_lcd_del_i80_bus(i80_buses[0])); - for (int i = 0; i < 10; i++) { - TEST_ESP_OK(esp_lcd_panel_io_del(io_handles[i])); - } - for (int i = 0; i < SOC_LCD_I80_BUSES; i++) { - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_buses[i])); - } -} - -TEST_CASE("lcd_i80_bus_exclusively_owned_by_one_device", "[lcd]") -{ - esp_lcd_i80_bus_handle_t i80_bus_handle = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t) - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus_handle)); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = -1, // own the bus exclusively - .pclk_hz = 5000000, - .trans_queue_depth = 4, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus_handle, &io_config, &io_handle)); - io_config.cs_gpio_num = 0; - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_lcd_new_panel_io_i80(i80_bus_handle, &io_config, &io_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus_handle)); -} - -TEST_CASE("lcd_panel_i80_io_test", "[lcd]") -{ - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - TEST_LCD_DATA8_GPIO, - TEST_LCD_DATA9_GPIO, - TEST_LCD_DATA10_GPIO, - TEST_LCD_DATA11_GPIO, - TEST_LCD_DATA12_GPIO, - TEST_LCD_DATA13_GPIO, - TEST_LCD_DATA14_GPIO, - TEST_LCD_DATA15_GPIO, - }, - .bus_width = 16, - .max_transfer_bytes = 100, - }; - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 8000000, // 8MHz - .trans_queue_depth = 10, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - }; - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - - printf("testing bus-width=16bit, cmd/param bit-width=8bit\r\n"); - bus_config.bus_width = 16; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - - esp_lcd_panel_io_tx_param(io_handle, 0x1A, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B, (uint8_t[]) { - 0x11, 0x22, 0x33 - }, 3); - esp_lcd_panel_io_tx_param(io_handle, 0x1C, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); - - printf("testing bus-width=16bit, cmd/param bit-width=16bit\r\n"); - bus_config.bus_width = 16; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - io_config.lcd_cmd_bits = 16; - io_config.lcd_param_bits = 16; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle)); - esp_lcd_panel_io_tx_param(io_handle, 0x1A01, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B02, (uint16_t[]) { - 0x11, 0x22, 0x33 - }, 6); - esp_lcd_panel_io_tx_param(io_handle, 0x1C03, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); - - printf("testing bus-width=8bit, cmd/param bit-width=8bit\r\n"); - bus_config.bus_width = 8; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - io_config.lcd_cmd_bits = 8; - io_config.lcd_param_bits = 8; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - esp_lcd_panel_io_tx_param(io_handle, 0x1A, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B, (uint8_t[]) { - 0x11, 0x22, 0x33 - }, 3); - esp_lcd_panel_io_tx_param(io_handle, 0x1C, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); - - printf("testing bus-width=8bit, cmd/param bit-width=16bit\r\n"); - bus_config.bus_width = 8; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - io_config.lcd_cmd_bits = 16; - io_config.lcd_param_bits = 16; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle)); - esp_lcd_panel_io_tx_param(io_handle, 0x1A01, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B02, (uint16_t[]) { - 0x11, 0x22, 0x33 - }, 6); - esp_lcd_panel_io_tx_param(io_handle, 0x1C03, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); -} - -TEST_CASE("lcd_panel_with_i80_interface_(st7789, 8bits)", "[lcd]") -{ -#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) - uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); - TEST_ASSERT_NOT_NULL(img); - - gpio_config_t bk_gpio_config = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << TEST_LCD_BK_LIGHT_GPIO - }; - TEST_ESP_OK(gpio_config(&bk_gpio_config)); - - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = TEST_IMG_SIZE + 10, - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 8000000, // 8MHz - .trans_queue_depth = 10, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - - // turn off backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0); - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_invert_color(panel_handle, true); - // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value - esp_lcd_panel_set_gap(panel_handle, 0, 20); - // turn on display - esp_lcd_panel_disp_on_off(panel_handle, true); - // turn on backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); - - for (int i = 0; i < 200; i++) { - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - } - // turn off screen - esp_lcd_panel_disp_on_off(panel_handle, false); - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); - TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO)); - free(img); -#undef TEST_IMG_SIZE -} - -// TODO: support the test on I2S LCD (IDF-7202) -#if !SOC_I2S_LCD_I80_VARIANT -TEST_CASE("i80_lcd_send_colors_to_fixed_region", "[lcd]") -{ - int x_start = 100; - int y_start = 100; - int x_end = 200; - int y_end = 200; - size_t color_size = (x_end - x_start) * (y_end - y_start) * 2; - void *color_data = malloc(color_size); - TEST_ASSERT_NOT_NULL(color_data); - uint8_t color_byte = esp_random() & 0xFF; - memset(color_data, color_byte, color_size); - - printf("creating i80 bus and IO driver\r\n"); - esp_lcd_i80_bus_handle_t i80_bus = NULL; - esp_lcd_i80_bus_config_t bus_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .wr_gpio_num = TEST_LCD_PCLK_GPIO, - .clk_src = LCD_CLK_SRC_DEFAULT, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - }, - .bus_width = 8, - .max_transfer_bytes = color_size * 2, - }; - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_io_i80_config_t io_config = { - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = 5000000, // 5MHz - .trans_queue_depth = 10, - .dc_levels = { - .dc_idle_level = 0, - .dc_cmd_level = 0, - .dc_dummy_level = 0, - .dc_data_level = 1, - }, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - }; - TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); - TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle)); - - printf("creating LCD panel\r\n"); - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - - // we don't use the panel handle in this test, creating the panel just for a quick initialization - printf("initialize LCD panel\r\n"); - // turn off backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0); - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_invert_color(panel_handle, true); - // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value - esp_lcd_panel_set_gap(panel_handle, 0, 20); - // turn on display - esp_lcd_panel_disp_on_off(panel_handle, true); - // turn on backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); - - printf("set the flush window for only once\r\n"); - esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_CASET, (uint8_t[]) { - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, 4); - esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_RASET, (uint8_t[]) { - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, 4); - esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_RAMWR, NULL, 0); - - printf("send colors to the fixed region in multiple steps\r\n"); - const int steps = 10; - int color_size_per_step = color_size / steps; - for (int i = 0; i < steps; i++) { - TEST_ESP_OK(esp_lcd_panel_io_tx_color(io_handle, -1, color_data + i * color_size_per_step, color_size_per_step)); - } - vTaskDelay(pdMS_TO_TICKS(1000)); - // change to another color - color_byte = esp_random() & 0xFF; - memset(color_data, color_byte, color_size); - for (int i = 0; i < steps; i++) { - TEST_ESP_OK(esp_lcd_panel_io_tx_color(io_handle, -1, color_data + i * color_size_per_step, color_size_per_step)); - } - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); - free(color_data); -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py deleted file mode 100644 index 718a445c4..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/pytest_i80_lcd.py +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - - -@pytest.mark.esp32 -@pytest.mark.esp32s2 -@pytest.mark.esp32s3 -@pytest.mark.generic -@pytest.mark.parametrize( - 'config', - [ - 'release', - ], - indirect=True, -) -def test_i80_lcd(dut: Dut) -> None: - dut.run_all_single_board_cases() diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release deleted file mode 100644 index 91d93f163..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/sdkconfig.ci.release +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG_PM_ENABLE=y -CONFIG_FREERTOS_USE_TICKLESS_IDLE=y -CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults b/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults deleted file mode 100644 index ccc43c6fd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/i80_lcd/sdkconfig.defaults +++ /dev/null @@ -1,5 +0,0 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# -# CONFIG_ESP_TASK_WDT_INIT is not set -CONFIG_FREERTOS_HZ=1000 diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/CMakeLists.txt deleted file mode 100644 index 272f66f14..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -# This is the project CMakeLists.txt file for the test subproject -cmake_minimum_required(VERSION 3.16) - -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -set(COMPONENTS main) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(rgb_lcd_panel_test) - -target_add_binary_data(rgb_lcd_panel_test.elf "resources/pictures/hello.yuv" BINARY) -target_add_binary_data(rgb_lcd_panel_test.elf "resources/pictures/world.yuv" BINARY) - -if(CONFIG_COMPILER_DUMP_RTL_FILES) - add_custom_target(check_test_app_sections ALL - COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py - --rtl-dirs ${CMAKE_BINARY_DIR}/esp-idf/esp_lcd/,${CMAKE_BINARY_DIR}/esp-idf/hal/ - --elf-file ${CMAKE_BINARY_DIR}/rgb_lcd_panel_test.elf - find-refs - --from-sections=.iram0.text - --to-sections=.flash.text,.flash.rodata - --exit-code - DEPENDS ${elf} - ) -endif() diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/README.md b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/README.md deleted file mode 100644 index 47ecb029f..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/README.md +++ /dev/null @@ -1,4 +0,0 @@ -| Supported Targets | ESP32-S3 | -| ----------------- | -------- | - -This test app is used to test RGB565 interfaced LCDs. diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt deleted file mode 100644 index 084940a1c..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -set(srcs "test_app_main.c" - "test_rgb_panel.c") - -if(CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV) - list(APPEND srcs "test_yuv_rgb_conv.c") -endif() - -# In order for the cases defined by `TEST_CASE` to be linked into the final elf, -# the component can be registered as WHOLE_ARCHIVE -idf_component_register(SRCS ${srcs} - PRIV_REQUIRES esp_lcd unity driver esp_timer spi_flash - WHOLE_ARCHIVE) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_app_main.c b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_app_main.c deleted file mode 100644 index 7b7ff5c86..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_app_main.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "unity.h" -#include "unity_test_runner.h" -#include "esp_heap_caps.h" - -// Some resources are lazy allocated in LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-500) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -void app_main(void) -{ - // ____ ____ ____ _ ____ ____ _____ _ - // | _ \ / ___| __ ) | | / ___| _ \ |_ _|__ ___| |_ - // | |_) | | _| _ \ | | | | | | | | | |/ _ \/ __| __| - // | _ <| |_| | |_) | | |__| |___| |_| | | | __/\__ \ |_ - // |_| \_\\____|____/ |_____\____|____/ |_|\___||___/\__| - printf(" ____ ____ ____ _ ____ ____ _____ _\r\n"); - printf("| _ \\ / ___| __ ) | | / ___| _ \\ |_ _|__ ___| |_\r\n"); - printf("| |_) | | _| _ \\ | | | | | | | | | |/ _ \\/ __| __|\r\n"); - printf("| _ <| |_| | |_) | | |__| |___| |_| | | | __/\\__ \\ |_\r\n"); - printf("|_| \\_\\\\____|____/ |_____\\____|____/ |_|\\___||___/\\__|\r\n"); - unity_run_menu(); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h deleted file mode 100644 index 180c519e5..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -#define TEST_LCD_H_RES 800 -#define TEST_LCD_V_RES 480 - -#define TEST_LCD_VSYNC_GPIO 3 -#define TEST_LCD_HSYNC_GPIO 46 -#define TEST_LCD_DE_GPIO 0 -#define TEST_LCD_PCLK_GPIO 9 -#define TEST_LCD_DATA0_GPIO 14 // B0 -#define TEST_LCD_DATA1_GPIO 13 // B1 -#define TEST_LCD_DATA2_GPIO 12 // B2 -#define TEST_LCD_DATA3_GPIO 11 // B3 -#define TEST_LCD_DATA4_GPIO 10 // B4 -#define TEST_LCD_DATA5_GPIO 39 // G0 -#define TEST_LCD_DATA6_GPIO 38 // G1 -#define TEST_LCD_DATA7_GPIO 45 // G2 -#define TEST_LCD_DATA8_GPIO 48 // G3 -#define TEST_LCD_DATA9_GPIO 47 // G4 -#define TEST_LCD_DATA10_GPIO 21 // G5 -#define TEST_LCD_DATA11_GPIO 1 // R0 -#define TEST_LCD_DATA12_GPIO 2 // R1 -#define TEST_LCD_DATA13_GPIO 42 // R2 -#define TEST_LCD_DATA14_GPIO 41 // R3 -#define TEST_LCD_DATA15_GPIO 40 // R4 -#define TEST_LCD_DISP_EN_GPIO -1 - -#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000) - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c deleted file mode 100644 index 4e1eb3edd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c +++ /dev/null @@ -1,313 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "unity.h" -#include "esp_lcd_panel_rgb.h" -#include "esp_lcd_panel_ops.h" -#include "esp_random.h" -#include "esp_timer.h" -#include "esp_attr.h" -#include "test_rgb_board.h" -#include "esp_private/spi_flash_os.h" - -#if CONFIG_LCD_RGB_ISR_IRAM_SAFE -#define TEST_LCD_CALLBACK_ATTR IRAM_ATTR -#else -#define TEST_LCD_CALLBACK_ATTR -#endif // CONFIG_LCD_RGB_ISR_IRAM_SAFE - -#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) - -static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, size_t bpp, size_t bb_pixels, bool refresh_on_demand, - esp_lcd_rgb_panel_vsync_cb_t vsync_cb, void *user_data) -{ - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_rgb_panel_config_t panel_config = { - .data_width = data_width, - .psram_trans_align = 64, - .bounce_buffer_size_px = bb_pixels, - .bits_per_pixel = bpp, - .clk_src = LCD_CLK_SRC_DEFAULT, - .disp_gpio_num = TEST_LCD_DISP_EN_GPIO, - .pclk_gpio_num = TEST_LCD_PCLK_GPIO, - .vsync_gpio_num = TEST_LCD_VSYNC_GPIO, - .hsync_gpio_num = TEST_LCD_HSYNC_GPIO, - .de_gpio_num = TEST_LCD_DE_GPIO, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - TEST_LCD_DATA8_GPIO, - TEST_LCD_DATA9_GPIO, - TEST_LCD_DATA10_GPIO, - TEST_LCD_DATA11_GPIO, - TEST_LCD_DATA12_GPIO, - TEST_LCD_DATA13_GPIO, - TEST_LCD_DATA14_GPIO, - TEST_LCD_DATA15_GPIO, - }, - .timings = { - .pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ, - .h_res = TEST_LCD_H_RES, - .v_res = TEST_LCD_V_RES, - .hsync_back_porch = 68, - .hsync_front_porch = 20, - .hsync_pulse_width = 5, - .vsync_back_porch = 18, - .vsync_front_porch = 4, - .vsync_pulse_width = 1, - }, - .flags.fb_in_psram = 1, // allocate frame buffer in PSRAM - .flags.refresh_on_demand = refresh_on_demand, - }; - - TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); - - esp_lcd_rgb_panel_event_callbacks_t cbs = { - .on_vsync = vsync_cb, - }; - TEST_ESP_OK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, user_data)); - TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - - return panel_handle; -} - -TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]") -{ - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); - - printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, NULL, NULL); - printf("flush random color block\r\n"); - for (int i = 0; i < 200; i++) { - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - } - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -TEST_CASE("lcd_rgb_panel_8bit_interface", "[lcd]") -{ - uint8_t *img = malloc(100 * 100 * 3); - TEST_ASSERT_NOT_NULL(img); - - printf("initialize RGB panel with stream mode\r\n"); - // bpp for RGB888 is 24 - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(8, 24, 0, false, NULL, NULL); - uint8_t color_byte = esp_random() & 0xFF; - printf("flush random color block 0x%x\r\n", color_byte); - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, 100 * 100 * 3); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - vTaskDelay(pdMS_TO_TICKS(2000)); - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) -{ - TaskHandle_t task_to_notify = (TaskHandle_t)user_ctx; - BaseType_t high_task_wakeup; - vTaskNotifyGiveFromISR(task_to_notify, &high_task_wakeup); - return high_task_wakeup == pdTRUE; -} - -TEST_CASE("lcd_rgb_panel_refresh_on_demand", "[lcd]") -{ - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); - TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); - - printf("initialize RGB panel with non-stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, true, test_rgb_panel_trans_done, cur_task); - printf("flush random color block\r\n"); - for (int i = 0; i < 200; i++) { - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - esp_lcd_rgb_panel_refresh(panel_handle); - // wait for flush done - TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); - } - - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -TEST_CASE("lcd_rgb_panel_bounce_buffer", "[lcd]") -{ - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); - TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); - - printf("initialize RGB panel with non-stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 10 * TEST_LCD_H_RES, false, test_rgb_panel_trans_done, cur_task); - printf("flush random color block\r\n"); - for (int i = 0; i < 200; i++) { - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - // wait for flush done - TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); - } - - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -TEST_CASE("lcd_rgb_panel_update_pclk", "[lcd]") -{ - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); - - printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, NULL, NULL); - printf("flush one clock block to the LCD\r\n"); - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - printf("The LCD driver should keep flushing the color block in the background (as it's in stream mode)\r\n"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - printf("Update the PCLK in the background\r\n"); - const uint32_t test_pclk_freq[] = {10000000, 12000000, 8000000}; - for (size_t i = 0; i < sizeof(test_pclk_freq) / sizeof(test_pclk_freq[0]); i++) { - esp_lcd_rgb_panel_set_pclk(panel_handle, test_pclk_freq[i]); - vTaskDelay(pdMS_TO_TICKS(1000)); - } - - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -TEST_CASE("lcd_rgb_panel_restart", "[lcd]") -{ - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); - - printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, NULL, NULL); - printf("flush one clock block to the LCD\r\n"); - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - printf("The LCD driver should keep flushing the color block in the background (as it's in stream mode)\r\n"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - printf("Restart the DMA transmission in the background\r\n"); - TEST_ESP_OK(esp_lcd_rgb_panel_restart(panel_handle)); - vTaskDelay(pdMS_TO_TICKS(1000)); - - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -TEST_CASE("lcd_rgb_panel_rotate", "[lcd]") -{ - const int w = 200; - const int h = 100; - uint64_t t = 0; - uint8_t *img = malloc(w * h * sizeof(uint16_t)); - TEST_ASSERT_NOT_NULL(img); - uint8_t color_byte = esp_random() & 0xFF; - memset(img, color_byte, w * h * sizeof(uint16_t)); - - printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, NULL, NULL); - - printf("Update the rotation of panel\r\n"); - for (size_t i = 0; i < 8; i++) { - esp_lcd_panel_swap_xy(panel_handle, i & 4); - esp_lcd_panel_mirror(panel_handle, i & 2, i & 1); - printf("Panel Rotation=%d\r\n", i); - t = esp_timer_get_time(); - esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, w, h, img); - t = esp_timer_get_time() - t; - printf("@resolution %dx%d time per frame=%.2fMS\r\n", w, h, (float)t / 1000.0f); - vTaskDelay(pdMS_TO_TICKS(1000)); - } - - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} - -#if CONFIG_LCD_RGB_ISR_IRAM_SAFE -TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_count_in_callback(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) -{ - uint32_t *count = (uint32_t *)user_ctx; - *count = *count + 1; - return false; -} - -static void IRAM_ATTR test_disable_flash_cache(void) -{ - // disable flash cache - spi_flash_guard_get()->start(); - esp_rom_delay_us(200000); - // enable flash cache - spi_flash_guard_get()->end(); -} - -TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]") -{ - uint8_t *img = malloc(TEST_IMG_SIZE); - TEST_ASSERT_NOT_NULL(img); - - uint32_t callback_calls = 0; - - printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, test_rgb_panel_count_in_callback, &callback_calls); - printf("flush one clock block to the LCD\r\n"); - uint8_t color_byte = esp_random() & 0xFF; - int x_start = esp_random() % (TEST_LCD_H_RES - 100); - int y_start = esp_random() % (TEST_LCD_V_RES - 100); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); - printf("The LCD driver should keep flushing the color block in the background (as it's in stream mode)\r\n"); - - // read/write the SPI Flash by NVS APIs, the LCD driver should stay work - printf("disable the cache for a while\r\n"); - test_disable_flash_cache(); - printf("the RGB ISR handle should keep working while the flash cache is disabled\r\n"); - printf("callback calls: %"PRIu32"\r\n", callback_calls); - TEST_ASSERT(callback_calls > 2); - - printf("delete RGB panel\r\n"); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - free(img); -} -#endif // CONFIG_LCD_RGB_ISR_IRAM_SAFE diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_yuv_rgb_conv.c b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_yuv_rgb_conv.c deleted file mode 100644 index 4bcf65dbd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/main/test_yuv_rgb_conv.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "unity.h" -#include "esp_lcd_panel_rgb.h" -#include "esp_lcd_panel_ops.h" -#include "esp_random.h" -#include "esp_attr.h" -#include "test_rgb_board.h" - -#define TEST_IMG_SIZE (320 * 320 * sizeof(uint16_t)) - -// YUV images are embedded in the firmware binary -extern const uint8_t image_hello_yuv_start[] asm("_binary_hello_yuv_start"); -extern const uint8_t image_hello_yuv_end[] asm("_binary_hello_yuv_end"); -extern const uint8_t image_world_yuv_start[] asm("_binary_world_yuv_start"); -extern const uint8_t image_world_yuv_end[] asm("_binary_world_yuv_end"); - -TEST_CASE("lcd_rgb_panel_yuv422_conversion", "[lcd]") -{ - esp_lcd_panel_handle_t panel_handle = NULL; - esp_lcd_rgb_panel_config_t panel_config = { - .data_width = 16, - .psram_trans_align = 64, - .bits_per_pixel = 16, // YUV422: 16bits per pixel - .clk_src = LCD_CLK_SRC_DEFAULT, - .disp_gpio_num = TEST_LCD_DISP_EN_GPIO, - .pclk_gpio_num = TEST_LCD_PCLK_GPIO, - .vsync_gpio_num = TEST_LCD_VSYNC_GPIO, - .hsync_gpio_num = TEST_LCD_HSYNC_GPIO, - .de_gpio_num = TEST_LCD_DE_GPIO, - .data_gpio_nums = { - TEST_LCD_DATA0_GPIO, - TEST_LCD_DATA1_GPIO, - TEST_LCD_DATA2_GPIO, - TEST_LCD_DATA3_GPIO, - TEST_LCD_DATA4_GPIO, - TEST_LCD_DATA5_GPIO, - TEST_LCD_DATA6_GPIO, - TEST_LCD_DATA7_GPIO, - TEST_LCD_DATA8_GPIO, - TEST_LCD_DATA9_GPIO, - TEST_LCD_DATA10_GPIO, - TEST_LCD_DATA11_GPIO, - TEST_LCD_DATA12_GPIO, - TEST_LCD_DATA13_GPIO, - TEST_LCD_DATA14_GPIO, - TEST_LCD_DATA15_GPIO, - }, - .timings = { - .pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ, - .h_res = TEST_LCD_H_RES, - .v_res = TEST_LCD_V_RES, - .hsync_back_porch = 68, - .hsync_front_porch = 20, - .hsync_pulse_width = 5, - .vsync_back_porch = 18, - .vsync_front_porch = 4, - .vsync_pulse_width = 1, - }, - .flags.fb_in_psram = 1, // allocate frame buffer in PSRAM - }; - - printf("Create RGB LCD panel\r\n"); - TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); - TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); - - printf("Set YUV-RGB conversion profile\r\n"); - esp_lcd_yuv_conv_config_t conv_config = { - .std = LCD_YUV_CONV_STD_BT601, - .src = { - .color_range = LCD_COLOR_RANGE_FULL, - .color_space = LCD_COLOR_SPACE_RGB, - }, - .dst = { - .color_range = LCD_COLOR_RANGE_FULL, - .color_space = LCD_COLOR_SPACE_RGB, - }, - }; - TEST_ESP_ERR(ESP_ERR_INVALID_ARG, esp_lcd_rgb_panel_set_yuv_conversion(panel_handle, &conv_config)); - - conv_config.src.color_space = LCD_COLOR_SPACE_YUV; - conv_config.src.yuv_sample = LCD_YUV_SAMPLE_422; - TEST_ESP_OK(esp_lcd_rgb_panel_set_yuv_conversion(panel_handle, &conv_config)); - - TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); - - printf("Draw YUV images\r\n"); - for (int i = 0; i < 4; i++) { - TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 320, 320, image_hello_yuv_start)); - vTaskDelay(pdMS_TO_TICKS(1000)); - TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, 320, 320, image_world_yuv_start)); - vTaskDelay(pdMS_TO_TICKS(1000)); - } - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py deleted file mode 100644 index 497ca91e4..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/pytest_rgb_lcd.py +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - - -@pytest.mark.esp32s3 -@pytest.mark.octal_psram -@pytest.mark.parametrize( - 'config', - [ - 'iram_safe', - 'release', - ], - indirect=True, -) -def test_rgb_lcd(dut: Dut) -> None: - dut.run_all_single_board_cases() diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/README.md b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/README.md deleted file mode 100644 index 3c6ead694..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# How to generate the YUV image from the PNG image - -```bash -ffmpeg -i hello.png -pix_fmt uyvy422 hello.yuv -``` diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/hello.png b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/hello.png deleted file mode 100644 index 380298e8e..000000000 Binary files a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/hello.png and /dev/null differ diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/hello.yuv b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/hello.yuv deleted file mode 100644 index 73582c5f7..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/hello.yuv +++ /dev/null @@ -1 +0,0 @@ -¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶†^•¥¤i²”ÁsКÞo¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†­•c¤²nÁŒÐyÞµ^€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶†^•¥¤i²”ÁsКÞo¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€·€]–™¸pƉÕzã€ë€ë€ë€ë€ë€ëën‡¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€§–g¸‘ÆuÕ‚ãë€ë€ë€ë€ë€ë€ë»ˆZ€¾€Y€¾€Y€¾€Y€·€]–™¸pƉÕzã€ë€ë€ë€ë€ë€ëënˆ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ª€eÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ûj€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‹Åyë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ܾ€Y€¾€Y€¾€Y€¾€Y€ª€eÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Üj€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€£€jÜ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÅe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€„Û}ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyž€Y€¾€Y€¾€Y€¾€Y€£€jÛ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÅf€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‡në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±®a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëu¯¾€Y€¾€Y€¾€Y€¾€Y€‡në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë°¯a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€–žrë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë·—]€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€µ€^ž€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq˜¾€Y€¾€Y€¾€Y€¾€Y€–žrë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë·˜]€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€µvë€ë€ë€ë€ë€ë€ë€ë€ëë€è½ƒY€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€c´€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžém„¾€Y€¾€Y€¾€Y€¾€Y€´vë€ë€ë€ë€ë€ë€ë€ë€ëë齄Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‰Ìzë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ô¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¨€gË€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Öi€¾€Y€¾€Y€¾€Y€¾€Y€‰Ëzë€ë€ë€ë€ë€ë€ë€ë€ë†ë|Ö¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‚ã~ë€ë€ë€ë€ë€ë€ë€ë€ëëx½¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€kâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¿d€¾€Y€¾€Y€¾€Y€¾€Y€ƒâ~ë€ë€ë€ë€ë€ë€ë€ë€ëëx¿¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[Ž€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”ës¦¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€›oë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²¨`€¾€Y€¾€Y€¾€Y€º€[€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë“ët¨¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³€`¥€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëo¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€•¤së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹‘\€¾€Y€¾€Y€¾€Y€³€`¤€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëp‘¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¬€d¼€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡äl€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Ž»wë€ë€ë€ë€ë€ë€ë€ë€ë‚ëå¾Y€¾€Y€¾€Y€¾€Y€­€d»€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë åk¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¦€hÓ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¨Íg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‡Ò{ë€ë€ë€ë€ë€ë€ë€ë€ëˆë{о€Y€¾€Y€¾€Y€¾€Y€¦€hÒ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë§Ðg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Ÿ‚lç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¶c€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Yçë€ë€ë€ë€ë€ë€ë€ë€ëëw¹¾€Y€¾€Y€¾€Y€¾€Y€ lç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­¹c€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€™•pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¸€\”€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës¢¾€Y€¾€Y€¾€Y€¾€Y€™”pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë´¢_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€’­uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‰Z€¾€Y€¾€Y€µŠ^”±ša”»‰Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Z„¦h·ÏxÚƒâ~é†ä|ÝšËp²»‹[€¾€Y€¾€Y€¾€Y€¾€Y€±€a«€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëœën‹¾€Y€¾€Y€¾€Y€¾€Y€’«të€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‹[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[Œ¡¥k¾ÌvÓŠÛy×–Îs¾´Ÿ_ƒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÃyë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ݾ€Y€¹€\‘’¾uÛ€ë€ë€ë€ë„ë}Ü«¸eŠ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\»vä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëèw»½‚Y€¾€Y€¾€Y€¾€Y€«€e€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢àk€¾€Y€¾€Y€¾€Y€¾€Y€ŒÂxë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ྀY€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€±„a¨‡Ó|ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~೦`€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€…Ú}ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyÆ»€[ŠÀxê€ë€ë€ë€ë€ë€ë€ë€ë‚ëå³¥`€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z…ºwé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¨Êg€¾€Y€¾€Y€¾€Y€¤€iØ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Éf€¾€Y€¾€Y€¾€Y€¾€Y€†Ø}ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzɾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³€`¦ƒà~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëx¿¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëu¯š™oâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës¤¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œ—n߀ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë’ët«¾€Y€¾€Y€¾€Y€ž…mê€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¯²b€¾€Y€¾€Y€¾€Y€¼€Z…€ê€ë€ë€ë€ë€ë€ë€ë€ë€ëëv²¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€[Š‡Ñ{ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­¹c€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶€^œ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëvµçë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëë滉[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³€`¦çë€ë€ë€ë€ë€ë€ë€ë€ë˜Ôq®›¤oÖ€ë€ë€ë€ë…ë}Ú¾€Y€¾€Y€¾€Y€—šqë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶›^€¾€Y€¾€Y€¾€Y€¶€^š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ëq›¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€›™oá€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™êp”¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€b³€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­ºd€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€“¨të€ë€ë€ë€ë€ë€ë€ë€ëëw¸½Y€º€\€ë€ë€ë€ë€ë€ë¼‡Z€¾€Y€¾€Y€‘±uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¼†Z€¾€Y€¾€Y€¾€Y€°€b±€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëm†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶€^›çë€ë€ë€ë€ë€ë€ë€ë€ëëxÀ·’]…˜nÚ€ë€ë€ë€ë‰ëz̾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¨€gÊ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Üj€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´€_¢€ê€ë€ë€ë€ë€ë€ë€ë€ë€ë­»d€¾€Y€¾€Y€ƒà~ë€ë€ë€ë€ë¸•]€¾€Y€¾€Y€ŠÇzë€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ù¾€Y€¾€Y€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€™™pæ€ë€ë€ë€ë€ë€ë€ë€ëë€è´¡_€¾€Y€¹€\’€ë€ë€ë€ë€ë€ê»‰[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€ká€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëœëo‹¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Œnå€ë€ë€ë€ë€ë€ë€ë€ëˆë{оY€¾€Y€¾€Y€…Ù}ë€ë€ë€ë€ë´¢_€¾€Y€¾€Y€„Þ~ë€ë€ë€ë€ë€ë€ë€ë€ëŒëyþ€Y€¾€Y€¾€Y€¾€Y€£€jÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÃe€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡ƒß~ë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€†Ø|ë€ë€ë€ë€ë±«a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€›oë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë’æu±¸‹]‰“­tè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëq–¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ˆÎ{ë€ë€ë€ë€ë€ë€ë€ë€ë›èo‘¾€Y€¾€Y€¾€Y€ƒá~ë€ë€ë€ë€ë¶œ^€¾€Y€»€[Š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë’ët¬¾€Y€¾€Y€¾€Y€¾€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±¬a€¾€Y€¾€Y€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÄe€¾€Y€¾€Y€¾€Y€ŠÇzë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶€^›·™]€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€•¤së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëë続^€¾€Y€®€c¶€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–ërŸ¾€Y€¾€Y€¾€Y€¾€Y€±€a­€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­¹c€¾€Y€¾€Y€½€Y€ê€ë€ë€ë€ë€ë¹\€¾€Y€´€_¡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp•¾€Y€¾€Y€¾€Y€¾€Y€•¡rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸•]€¾€Y€¾€Y€¾€Y€¾€Y€”§të€ë€ë€ë€ë€ë€ë€ë€ëƒë~ß¼†Z€¾€Y€¾€Y€¾€Y€‹Åyë€ë€ë€ë€ë¥Ôh€¾€Y€¾€Y€¾€Y€¾€Y€»€Z‰ŒÃyëªÄe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Žºwë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—êr¾€Y€¾€Y€¹€\€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ërœ¾€Y€¾€Y€¾€Y€¾€Y€œnç€ë€ë€ë€ë€ë€ë€ë€ëë终[€¾€Y€¾€Y€¸€\”€ë€ë€ë€ë€ë€ë½„Z€¾€Y€®€c¸€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸçl‚¾€Y€¾€Y€¾€Y€¾€Y€¸wë€ë€ë€ë€ë€ë€ë€ë€ëë罂Y€¾€Y€¾€Y€¾€Y€º€[‚åë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€©fÇ€ë€ë€ë€ë€ë€ë¢àk€¾€Y€¾€Y€¾€Y€»€[ˆ‘¸uå€ë€ë§Ðg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‡Ñ{ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¾d€¾€Y€¾€Y€½€Z„€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp•¾€Y€¾€Y€¾€Y€¾€Y€‹Æyë€ë€ë€ë€ë€ë€ë€ë€ëëx¼¾€Y€¾€Y€¾€Y€¯€b³€ë€ë€ë€ë…ë}Ù¾€Y€¾€Y€§€gÏ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦Óh€¾€Y€¾€Y€¾€Y€¾€Y€ˆÏ{ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ó¾€Y€¾€Y€¾€Y€¾€Y€ª€eÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ûj¾€Y€¾€Y€¾€Y€’¬uë€ë€ë€ë€ë€ë€ë æl€¾€Y€¾€Y€¸€\“¼wç€ë€ëŠëzʽ„Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Yçë€ë€ë€ë€ë€ë€ë€ë€ë‚ë仈Z€¾€Y€¾€Y€¾€Y€ê€ë€ë€ë€ë€ë€ë€ë€ë€ë›ëoŒ¾€Y€¾€Y€¾€Y€º€[€ê€ë€ë€ë€ë€ë€ë€ë€ë€ë›ëoŽ¾€Y€¾€Y€¾€Y€¥€iÖ€ë€ë€ë€ëëv²¾€Y€¾€Y€¡€lå€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¼d€¾€Y€¾€Y€¾€Y€¾€Y€‚åë€ë€ë€ë€ë€ë€ë€ë€ëëx¼¾€Y€¾€Y€¾€Y€¾€Y€›Žoé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²¨`€¾€Y€¾€Y€¾€Y€†Õ|ë€ë€ë€ë€ë€ë€ëžëm‚º[¦›h·…Ú}ë€ë€ë€ë€ê­ºd¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¸€\”€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŽëw¹¾€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë æl€¾€Y€¾€Y€¾€Y€¬€d¼€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë§Ðg€¾€Y€¾€Y€¾€Y€–rë€ë€ë€ë€ë›ëo¾€Y€¾€Y€š‘pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë³¥`€¾€Y€¾€Y€¾€Y€¹€\‘€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€¾€Y€¿xë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ὂY€¾€Y€¾€Y€¾€Y€‚åë€ë€ë€ë€ë€ë€ëëçè€ë€ë€ë€ë€ë€ë€ë¡Ñl”¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€±€a«€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp“¾€Y€¾€Y€¾€Y€¹€\’€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Öi€¾€Y€¾€Y€¾€Y€ž‡mæ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²¨`€¾€Y€¾€Y€¾€Y€‡Ò|ë€ë€ë€ë€ë¨Êf€¾€Y€¾€Y€”§së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëº\€¾€Y€¾€Y€¾€Y€²€`§€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›ëo¾€Y€¾€Y€¾€Y€»€[Šçë€ë€ë€ë€ë€ë€ë€ë€ëŽëw»¾€Y€¾€Y€¾€Y€¾€Y€æë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›×o£¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€«€e€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢ák€¾€Y€¾€Y€¾€Y€´€_£€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë«Âe€¾€Y€¾€Y€¾€Y€²uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¼‡Z€¾€Y€¾€Y€°€b°€ë€ë€ë€ëëæº[€¾€Y€¾€Y€¾xë€ë€ë€ë€ë€ë€ë€ë€ë‚ëä¾€Y€¾€Y€¾€Y€¾€Y€¬€d¾€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡äk€¾€Y€¾€Y€¾€Y€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp”¾€Y€¾€Y€¾€Y€¾€Y€‡Ò|ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‚ë㦽h”¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¤€iÙ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Éf€¾€Y€¾€Y€¾€Y€¯€bµ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²©`€¾€Y€¾€Y€¾€Y€†Ö|ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ô¾€Y€¾€Y€¾€Y€™—pè€ë€ë€ë€ë‘ëu°¾€Y€¾€Y€¾€Y€†Õ|ë€ë€ë€ë€ë€ë€ë€ë€ë‰ëz;€Y€¾€Y€¾€Y€¾€Y€¥€hÕ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¨Íg€¾€Y€¾€Y€¾€Y€£€iÛ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Øi€¾€Y€¾€Y€¾€Y€¾€Y€™špç€ë€ë€ë€ë€ë€ë‚ëå™Íp³µ›^ƒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ž…mê€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¯²b€¾€Y€¾€Y€¾€Y€¨€gË€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹‘\€¾€Y€¾€Y€»€[‹€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëw¸¾€Y€¾€Y€·€]—‚äë€ë€ë€ë€ë¨Ëg¾€Y€¾€Y€½€Yƒè€ë€ë€ë€ë€ë€ë€ë€ë€ëëv¶¾€Y€¾€Y€¾€Y€¾€Y€Ÿƒmè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¶c€¾€Y€¾€Y€¾€Y€š‘pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®·c€¾€Y€¾€Y€¾€Y€¾€Y€»€Z‰‚äë€ë€ë€ë€ë¦Ñh¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€—›që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶›^€¾€Y€¾€Y€¾€Y€¡€kâ€ë€ë€ë€ë€ë€ë€ë€ë‚ëã¾€Y€¾€Y€¾€Y€±€a«€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ëq›¾€Y€¾€Y€–£rå€ë€ë€ë€ëˆë{н„Z€¾€Y€¾€Y€·€]—€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–ërŸ¾€Y€¾€Y€¾€Y€¾€Y€˜—që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€¾€Y€³vë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶š^€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†€ë€ë€ë€ë€ë€ë¬¾d€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€²uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¼†Z€¾€Y€¾€Y€¾€Y€šoë€ë€ë€ë€ë€ë€ë€ë€ë‰ëz˾€Y€¾€Y€¾€Y€¨€gÌ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸçlƒ¾€Y€¦hÆ€ë€ë€ë€ë€ë€ë¥Ñi†¾€Y€¾€Y€¾€Y€±€a®€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëœën‰¾€Y€¾€Y€¾€Y€¾€Y€’®uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‰Z€¾€Y€¾€Y€¾€Y€†Õ|ë€ë€ë€ë€ë€ë€ë€ë€ë‚ë潂Y€¾€Y€¾€Y€¾€Y€¾€Y€·€]™€ë€ë€ë€ë€ë€ë°¯a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŠÈzë€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ù¾€Y€¾€Y€¾€Y€¾€Y€”§së€ë€ë€ë€ë€ë€ë€ë€ëëv²¾€Y€¾€Y€¾€Y€Ÿ‚lç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢Ðk‘ž¥mÉ€ê€ë€ë€ë€ë€ëëw¹½‚Y€¾€Y€¾€Y€¾€Y€ª€eÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ýj€¾€Y€¾€Y€¾€Y€¾€Y€‹Åyë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ݾ€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‰ëz̾€Y€¾€Y€¾€Y€¾€Y€¾€Y€±€a¬€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ƒà~ë€ë€ë€ë€ë€ë€ë€ë€ëŒëy¾€Y€¾€Y€¾€Y€¾€Y€¿xë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq™¾€Y€¾€Y€¾€Y€š’pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ßµž^€¾€Y€¾€Y€¾€Y€¾€Y€£€iÛ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Æf€¾€Y€¾€Y€¾€Y€¾€Y€…Û}ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyƾ€Y€¾€Y€¾€Y€¶€^›€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëv·¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ª€fÅ€ë€ë€ë€ë€ë€ëº\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€[‹€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë’ët¬¾€Y€¾€Y€¾€Y€¾€Y€†×|ë€ë€ë€ë€ë€ë€ë€ë€ëŸçlƒ¾€Y€¾€Y€¾€Y€”¦së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~á°­aƒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‡më€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë°°b€¾€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëu°¾€Y€¾€Y€¾€Y€°€b°€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës¤¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€ká€ë€ë€ë€ë‚ëæ½Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´€_¢€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp”¾€Y€¾€Y€¾€Y€¼€Z…€ê€ë€ë€ë€ë€ë€ë€ë€ë€ë§Ðg€¾€Y€¾€Y€¾€Y€Ž¹wë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŠçzʱ¨a„¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€–rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë·™]€¾€Y€¾€Y€¾€Y€µ€^€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq™¾€Y€¾€Y€¾€Y€ª€eÄ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëp‘¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€š’pë€ë€ë€ë€ëŠëzʾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€­€c¹€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸçl‚¾€Y€¾€Y€¾€Y€¶€^›€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®µc€¾€Y€¾€Y€¾€Y€‰Ìzë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê“ÒtÃ¥³h¢¼…Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´vë€ë€ë€ë€ë€ë€ë€ë€ëë€é½„Z€¾€Y€¾€Y€¾€Y€¯€b´€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžém„¾€Y€¾€Y€¾€Y€¤€iØ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžëm‚¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´vë€ë€ë€ë€ë’ëu«¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€§€gЀë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦Òh€¾€Y€¾€Y€¾€Y€¯€b³€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶›^€¾€Y€¾€Y€¾€Y€…Ù}ë€ë€ë€ë€ë€ë€ë€ë€ë‚ë~ã¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‰Ëzë€ë€ë€ë€ë€ë€ë€ë€ë†ë|×¾€Y€¾€Y€¾€Y€¾€Y€¨€gË€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥×i€¾€Y€¾€Y€¾€Y€¡€kâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡äk€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€…Û}ë€ë€ë€ë€ë›ëo¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ kå€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­»d€¾€Y€¾€Y€¾€Y€¨€gÊ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê½„Z€¾€Y€¾€Y€¾€Y€ƒà~ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ݾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€[‹ƒá~ë€ë€ë€ë€ë€ë€ë€ë€ëŒëxÀ¾€Y€¾€Y€¾€Y€¾€Y€¢€ká€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¾€Y€Ÿ€lé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ýj€¾€Y€¾€Y€¾€Y€¾€Y€·€]—€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€š’pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë³¥`€¾€Y€¾€Y€¾€Y€¢€kâ€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ú¾€Y€¾€Y€¾€Y€¾€Y€çë€ë€ë€ë€ë€ë€ë€ë€ë†ë}ؾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŸŒlÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë“ët©¾€Y€¾€Y€¾€Y€¾€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²©`€¾€Y€¾€Y€¾€Y€†në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥×i€¾€Y€¾€Y€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë¯³b€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€“©të€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëº[€¾€Y€¾€Y€¾€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ëŠëyǾ€Y€¾€Y€¾€Y€½€Y‚€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë†ë}×¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€Zˆ…Ú}ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp“¾€Y€¾€Y€¾€Y€¾€Y€•¢së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸“]€¾€Y€¾€Y€¾€Y€œ‹oë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Øi€¾€Y€¾€Y€¾€Y€¾€Y€š‘oê€ë€ë€ë€ëë漇Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÀxë€ë€ë€ë€ë€ë€ë€ë€ëƒëâ¾€Y€¾€Y€¾€Y€¾€Y€—›që€ë€ë€ë€ë€ë€ë€ë€ëŽëw»¾€Y€¾€Y€¾€Y€¾€Y€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ܾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¤…iÔ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸémƒ¾€Y€¾€Y€¾€Y€¾€Y€ˆÑ{ë€ë€ë€ë€ë€ë€ë€ë€ëë€é½ƒY€¾€Y€¾€Y€¾€Y€†në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ýj€¾€Y€¾€Y€¾€Y€¾€Y€ŠÉzë€ë€ë€ë€ëëw½¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€†×|ë€ë€ë€ë€ë€ë€ë€ë€ë‰ëz˾€Y€¾€Y€¾€Y€¾€Y€’«uë€ë€ë€ë€ë€ë€ë€ë€ëëv´¾€Y€¾€Y€¾€Y€³€`¦€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëç¾Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[Œ†Ö|ë‡ë|Ô€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢ák€¾€Y€¾€Y€¾€Y€±€a­€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~á¾€Y€¾€Y€¾€Y€¾€Y€’­uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë èl€¾€Y€¾€Y€¾€Y€´€_¤€ë€ë€ë€ë€ë€ë›êoŽ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Z„é€ë€ë€ë€ë€ë€ë€ë€ë€ëëv´¾€Y€¾€Y€¾€Y€¾€Y€³vë€ë€ë€ë€ë€ë€ë€ë€ëëx½¾€Y€¾€Y€¾€Y€Ÿ‡lâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹’\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€›˜oá€ë€ëŽæwÀ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡åk€¾€Y€¾€Y€¾€Y€š•pè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‚ëå¾€Y€¾€Y€¾€Y€¸€]•èë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp”¾€Y€¾€Y€¾€Y€nã€ë€ë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶€]™€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–ër¾€Y€¾€Y€¾€Y€¾€Y€Žºwë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ݽ‚Y€¾€Y€»€[Š‡Ô|ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¸c€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€±€a¬èë€ë€ë ¥l€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëp‘¾€Y€¾€Y€´€_£‚äë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹‘\€¾€Y€¾€Y€—£qä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒëxÀ¾€Y€¾€Y€º€[Ž„Þ~ë€ë€ë€ë€ë„ë~Þ¼†Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€°€b°€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëm‡¾€Y€¾€Y€¾€Y€¾€Y€·vë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢Ëj”º„[‰˜¥qÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™çp˜¾€Y€¾€Y€¾€Y€¾€Y€µ_ž†Õ|ë€ë€ë‘ëu°­€dº€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë†ë}×´›_†¶„^—ŒÃyë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë×n›»†[„¤—iÀë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Âi”¸†]Ž’°tæ€ë€ë€ë€ë€ë€ë—ëqœ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ûi€¾€Y€¾€Y€¾€Y€¾€Y€’¬uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¤Ài™¹\ƒ·‡]£¢j»„Þ~ë€ë€ë€ë€ë­¹c€°€b°€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„Þ~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë~Þé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬½d€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€£€jÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÅe€¾€Y€¾€Y€¾€Y€¾€Y€™•pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë“ÀtÕ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëv³¾€Y€·€]™€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‚ë昙që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›æo”ŒÀxë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyƽY€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œ‰në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±­a€¾€Y€¾€Y€¾€Y€¾€Y€£jÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ù³„`¢€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëë鲧`€¾€Y€½€Y‚‚äë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–êrŸ ‚lä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€êµŸ_€Œmä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Çf¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€– rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë·—]€¾€Y€¾€Y€¾€Y€¾€Y€±€b®€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëán‘¾€Y€‹Æyë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëÚn—¾€Y€¾€Y€¾€Y€·wë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëë鲨`€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”és¨¾€Y€¶€^›è€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒëx½‚Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€·wë€ë€ë€ë€ë€ë€ë€ë€ëë€è½ƒY€¾€Y€¾€Y€¾€Y€¾€Y€½€Y‡Ò{ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ß¹\€¾€Y€¥„iÓ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŽêw»½„Z€¾€Y€¾€Y€¾€Y€¡‡kÝ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšâo™¾€Y€¼€Z‡„Ý}ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~⶙]€¾€Y€¾€Y€˜Ÿpã€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ßµŸ_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ˆÎ{ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ô¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€£ˆjÓ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ðj¾€Y€¾€Y€½€Yƒ¾xë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒéx¹‘\€¾€Y€¾€Y€¾€Y€¾€Y€¸€\”ƒâ~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyƺŒ[€¾€Y€¾€Y€œ”nâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦ÆhŒ¾€Y€¾€Y€¾€Y€»€[Š‹Æyé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‚ëã«»e†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‚äë€ë€ë€ë€ë€ë€ë€ë€ëëx½¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Z„’³uå€ë€ë€ë€ë€ë€ë€ë€ë˜Úq©¾€Y€¾€Y€¾€Y€¾€Y€·€]˜‹Çyé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê–Ôr¶ºŒ[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¤ŒiÌ€ë€ë€ë€ë€ë€ë€ë€ë‹çyÇ·–]€¾€Y€¾€Y€¾€Y€º€[Œ‰Ìzë€ë€ë€ë€ë€ë€ëëç£Çj–¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€[‹™³pÍ‚äë€ë€ëëéŽÝwɳ¢`‚¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Z„¤Ÿj»“ÆtÏ“Ïtƨ³g™¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡¥žh¶–ÂrÈ”ÍsÅž»m²±¤a‡¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ª’e²“ÆtÏ’ÏtǤ¸i£¼†Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\’›²oÆÏvÏ™Çp¸²£`†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡´Ž_”º‹[¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ \ No newline at end of file diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/world.png b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/world.png deleted file mode 100644 index 13682f8b8..000000000 Binary files a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/world.png and /dev/null differ diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/world.yuv b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/world.yuv deleted file mode 100644 index aa338e19b..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/resources/pictures/world.yuv +++ /dev/null @@ -1 +0,0 @@ -¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶†^•¥¤i²”ÁsКÞo¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º[©œf«˜ºqÉ’ØuÀ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€·€]–™¸pƉÕzã€ë€ë€ë€ë€ë€ëënˆ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z…Ÿ­l¼ŽËwÚè€ë€ë€ë€ë€ë’ëu®¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ª€eÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Üj€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€µ€^€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq—¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€£€jÛ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÅf€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€b³€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸémƒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‡në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë°¯a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¨€fÊ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Õi€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€–žrë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë·˜]€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€kà€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¾d€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´vë€ë€ë€ë€ë€ë€ë€ë€ëë齄Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œ‹në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²¨`€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‰Ëzë€ë€ë€ë€ë€ë€ë€ë€ë†ë|Ö¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€•¢së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹‘\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ƒâ~ë€ë€ë€ë€ë€ë€ë€ë€ëëx¿¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¸wë€ë€ë€ë€ë€ë€ë€ë€ë‚ëå¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë“ët¨¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ˆÎ{ë€ë€ë€ë€ë€ë€ë€ë€ëˆë{Ͼ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³€`¤€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëp‘¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‚äë€ë€ë€ë€ë€ë€ë€ë€ëëw¸¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€­€d»€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë åk¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës¢¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¦€hÒ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë§Ðg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³€`¦€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëœën‹¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ lç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­¹c€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¬€d¼€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢àk€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†¬—d¦³Ÿ`†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€™”pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë´¢_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¦€hÒ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Éf€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€½€Yƒ·vç€ë€ëëæ³¥`€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[Œ¡¥k¾ÌvÓŠÛy×–Îs¾´Ÿ_ƒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€’«të€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‹[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Ÿ‚lç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¯²b€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€•¸sÕÕxÕÕxÕÕxÕÕxÕÕvʾ€Y€¾€Y€¾€Y€¾€Y€ÍwÕÕxÕÕxÕÕxÕÕxÕ–Õs¶¾€Y€¾€Y€­€d»€ë€ë€ë€ë€ë€ë•êr£¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€±„a¨‡Ó|ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~೦`€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŠÇzë€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ú¾€Y€¾€Y€±ƒaªÇvØ„ç}à¦ÅhŽ¾€Y€¾€Y€¾€Y€ŒÂxë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ྀY€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€«’e°ÉwÚƒâ~èŠàzÔ£Äj™¾€Y€™”pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶›^€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€…Ú}ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzȾ€Y€¾€Y€¾€Y€»€[‰€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëu¯¾€Y€¾€Y€ˆnê€ë€ë€ë€ë€ë€ë„ë}ܾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³€`¦ƒà~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëx¿¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€„Þ~ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyľ€Y€¯‚c²ƒá~ë€ë€ë€ë€ë‚ë䵟_€¾€Y€¾€Y€†Ø}ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzɾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€›¥oÒ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë§Ìg‚“ªtë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¼†Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëv²¾€Y€¾€Y€¾€Y€µ€_ €ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq™¾€Y€¾€Y€—›që€ë€ë€ë€ë€ë€ë€ë€ë¶™]€¾€Y€¾€Y€¾€Y€¾€Y€»€[Š‡Ñ{ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­¹c€¾€Y€¾€Y€¾€Y€¾€Y€»€[Š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë’ëu­¹€\‘†Ö|ë€ë€ë€ë€ë€ë€ë€ë€ëžæmŠ¾€Y€¼€Z…€ê€ë€ë€ë€ë€ë€ë€ë€ë€ëëv²¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ ”lÓ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŽëw»ŒÁxë€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ù¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶€^›€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ëq›¾€Y€¾€Y€¾€Y€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžém„¾€Y€¾€Y€—›që€ë€ë€ë€ë€ë€ë€ë€ë¯²b€¾€Y€¾€Y€¾€Y€¾€Y€›™oá€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™êp”¾€Y€¾€Y€¾€Y€¾€Y€´€_¡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq—•¦sç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëx¾¾€Y€¶€^š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ëq›¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´€_¤èë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}܆×}ë€ë€ë€ë€ë€ë€ë€ë€ëŒëyþ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€b²€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëên†¾€Y€¾€Y€¾€Y€¨€gÍ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥×i€¾€Y€¾€Y€ˆné€ë€ë€ë€ë€ë€ë€ë€ë¬¼d€¾€Y€¾€Y€¾€Y€¶€^›çë€ë€ë€ë€ë€ë€ë€ë€ëëxÀ·’]…˜nÚ€ë€ë€ë€ë‰ëz̾€Y€¾€Y€¾€Y€¾€Y€®€c¸€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŽéw»€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ܾ€Y€°€b±€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëm†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€’¯uê€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëé€ë€ë€ë€ë€ë€ë€ë€ë€ë’ëu¬¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€©€fÉ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€¾€Y€¡€kä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë«Àd€¾€Y€¾€Y€ª€eÀë€ë€ë€ë€ë€ë€ë€ëªÆf€¾€Y€¾€Y€¾€Y€™™pæ€ë€ë€ë€ë€ë€ë€ë€ëë€è´¡_€¾€Y€¹€\’€ë€ë€ë€ë€ë€ê»‰[€¾€Y€¾€Y€¾€Y€§€g΀ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê½„Z€©€fÇ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€­€dº€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzǵš^„±‡a¥‚ã~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp•¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€jà€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë«Âe€¾€Y€¾€Y€¾€Y€šoë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²©`€¾€Y€¾€Y€½€Z„ÅxåÞv‚åë€ë€ë«Âe€¾€Y€¾€Y€¼€Z‡ƒß~ë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€†Ø|ë€ë€ë€ë€ë±«a€¾€Y€¾€Y€¾€Y€¡€kä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëç”Îs„Ý}ë€ë€ë€ë€ë€ë€ë€ë€ê½„Z€£€jÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÃe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€³vë€ë€ë€ë€ë€ë€ë€ë€ëëç´£_€¾€Y€¾€Y€˜™që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸçl‚¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œ‹në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±¬a€¾€Y€¾€Y€¾€Y€”¦së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹’\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€é€ë€ë€ë­»d€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÄe€¾€Y€¾€Y€¾€Y€ŠÇzë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¾€Y€š‘pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¬¸d‡¾€Y€™”pë€ë€ë€ë€ë€ë€ë„ë}ܾ€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±¬a€¾€Y€¾€Y€¾€Y€¾€Y€·€]™€ê€ë€ë€ë€ë€ë€ë€ë€ë€ë—êr¾€Y€¾€Y€¾€Y€¤€iØ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦Óh€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€•¢së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸”]€¾€Y€¾€Y€¾€Y€¼xë€ë€ë€ë€ë€ë€ë€ë€ë‚ëæ¾Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë¯µb€¾€Y€¾€Y€”§të€ë€ë€ë€ë€ë€ë€ë€ëƒë~ß¼†Z€¾€Y€¾€Y€¾€Y€‹Åyë€ë€ë€ë€ë¥Ôh€¾€Y€¾€Y€¾€Y€”§së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšâo™¾€Y€¾€Y€¢€kâ€ë€ë€ë€ë€ë€ëŽëw¹¾€Y€•¡rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸•]€¾€Y€¾€Y€¾€Y€¾€Y€¡‡kÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¶c€¾€Y€¾€Y€¾€Y€¢€kà€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¼d€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¸wë€ë€ë€ë€ë€ë€ë€ë€ëë罂Y€¾€Y€¾€Y€¾€Y€‡Ó|ë€ë€ë€ë€ë€ë€ë€ë€ëˆë{о€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€\€ë€ë€ë€ë±®b€¾€Y€º€[‚åë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€©fÇ€ë€ë€ë€ë€ë€ë¢àk€¾€Y€¾€Y€¾€Y€¾xë€ë€ë€ë€ë€ë€ë€ë€ë‚ëäº\€¾€Y€¾€Y€©€fÆ€ë€ë€ë€ë€ë€ë âl†¾€Y€¸wë€ë€ë€ë€ë€ë€ë€ë€ëë罂Y€¾€Y€¾€Y€¾€Y€¾€Y€ŠÈzë€ë€ë€ë€ë€ë€ë€ë€ëˆë{оY€¾€Y€¾€Y€¾€Y€›Œoë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë³¦`€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ˆÏ{ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ò¾€Y€¾€Y€¾€Y€½€Y‚çë€ë€ë€ë€ë€ë€ë€ë€ëŽëw¹¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€µ€^ž€ë€ë€ë€ë²§`€¾€Y€ª€eÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ûj¾€Y€¾€Y€¾€Y€’¬uë€ë€ë€ë€ë€ë€ë æl€¾€Y€¾€Y€¸€\“†Õ|ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzɾ€Y€¾€Y€¾€Y€¶€^š€ë€ë€ë€ë…ë}Ú¹’\€¾€Y€ˆÏ{ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ó¾€Y€¾€Y€¾€Y€¾€Y€µ€^ž€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšêp’¾€Y€¾€Y€¾€Y€¾€Y€•£së€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‚åë€ë€ë€ë€ë€ë€ë€ë€ëŽëw¼¾€Y€¾€Y€¾€Y€¸€]•€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës£¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€b²€ë€ë€ë€ë·™]€¾€Y€›Žoé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²¨`€¾€Y€¾€Y€¾€Y€†Õ|ë€ë€ë€ë€ë€ë€ëžëm‚º[¦›h·…Ú}ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëv²¾€Y€¾€Y€¾€Y€¾€Y€ž¡mÌŽ×wή±c…¾€Y€¾€Y€‚åë€ë€ë€ë€ë€ë€ë€ë€ëëx¼¾€Y€¾€Y€¾€Y€¾€Y€¦€hÓ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÅe€¾€Y€¾€Y€¾€Y€¾€Y€Ž¹wë€ë€ë€ë€ë€ë€ë€ë€ë‚ëä¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\‘€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€±€a¬€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›ëoŒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ª€fÆ€ë€ë€ë€ë¼‡Z€¾€Y€¿xë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ὂY€¾€Y€¾€Y€¾€Y€‚åë€ë€ë€ë€ë€ë€ëëçè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ërœ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\‘€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”ës¥¾€Y€¾€Y€¾€Y€¾€Y€–žrë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¹\€¾€Y€¾€Y€¾€Y€¾€Y€ˆÑ{ë€ë€ë€ë€ë€ë€ë€ë€ë‰ë{;€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€²€`§€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›ëo¾€Y€¾€Y€¾€Y€«€e€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢ák€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¤€iÚ€ë€ëƒë~á¾€Y€»€[Šçë€ë€ë€ë€ë€ë€ë€ë€ëŽëw»¾€Y€¾€Y€¾€Y€¾€Y€æë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëm†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€²€`§€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›ëo¾€Y€¾€Y€¾€Y€¾€Y€‡Ô|ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzɾ€Y€¾€Y€¾€Y€¾€Y€½€Yæë€ë€ë€ë€ë€ë€ë€ë€ëëv¶¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¬€d¾€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡ãk€¾€Y€¾€Y€¾€Y€¤€iÙ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¨Êg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‡nê€ë€ëˆë{Ͼ€Y€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp”¾€Y€¾€Y€¾€Y€¾€Y€‡Ò|ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‚ë㘽qÅ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Úi€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¬€d¾€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡äk€¾€Y€¾€Y€¾€Y€¶€^›€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës¢¾€Y€¾€Y€¾€Y€¾€Y€¸€]“€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–ërŸ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¥€hÕ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¨Ìg€¾€Y€¾€Y€¾€Y€†nê€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¯³b€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€”¥së€ë€ëëx¼¾€Y€£€iÛ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Øi€¾€Y€¾€Y€¾€Y€¾€Y€™špç€ë€ë€ë€ë€ë€ë‚ëå™Íp³µ›^ƒ£€iÛ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÄe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¥€hÕ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¨Íg€¾€Y€¾€Y€¾€Y€«€eÁ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë äkƒ¾€Y€¾€Y€¾€Y€¾€Y€²€aª€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëœën‰¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Ÿƒmè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¶c€¾€Y€¾€Y€¾€Y€—›që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶œ^€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÃyë€ë€ë“ët©¾€Y€š‘pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®·c€¾€Y€¾€Y€¾€Y€¾€Y€»€Z‰‚äë€ë€ë€ë€ë¦Ñh¾€Y€¾€Y€‡më€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±­a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Ÿƒmè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¶c€¾€Y€¾€Y€¾€Y€¡‚kã€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¿d€¾€Y€¾€Y€¾€Y€¾€Y€«€eÁ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Þj€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€˜—që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€¾€Y€²uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¼‡Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ƒá~ë€ë€ë›ëoŒ¾€Y€³vë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶š^€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†€ë€ë€ë€ë€ë€ë¬¾d€¾€Y€¾€Y€–rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë·–]€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€˜—që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€¾€Y€–žrë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëµ _€¾€Y€¾€Y€¾€Y€¾€Y€¤€iØ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Çf€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€’®uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‰Z€¾€Y€¾€Y€¾€Y€ŠÈzë€ë€ë€ë€ë€ë€ë€ë€ë…ë}Ú¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¸€]•€ë€ë€ë€ë¤Ùi€¾€Y€†Õ|ë€ë€ë€ë€ë€ë€ë€ë€ë‚ë潂Y€¾€Y€¾€Y€¾€Y€¾€Y€·€]™€ë€ë€ë€ë€ë€ë°¯a€¾€Y€¾€Y€´vë€ë€ë€ë€ë€ë€ë€ë€ëë€è½ƒY€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€’®uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‰Z€¾€Y€¾€Y€¾€Y€ŒÂyë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¼†Z€¾€Y€¾€Y€¾€Y€¾€Y€ž…mê€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë°°b€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‹Åyë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ݾ€Y€¾€Y€¾€Y€¾€Y€ƒß~ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyľ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¬€d¼€ë€ë€ë€ë­»d€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‰ëz̾€Y€¾€Y€¾€Y€¾€Y€¾€Y€±€a¬€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€‰Ëzë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ô¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‹Åyë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ݾ€Y€¾€Y€¾€Y€¾€Y€ƒà~ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ó¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€—›që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶š^€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€…Û}ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyƾ€Y€¾€Y€¾€Y€»€[Š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë’ëu­¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¡ƒkã€ë€ë€ë€ëµ^€¶€^›€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëv·¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ª€fÅ€ë€ë€ë€ë€ë€ëº\€¾€Y€¾€Y€ƒâ~ë€ë€ë€ë€ë€ë€ë€ë€ëëx¾¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€…Û}ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyƾ€Y€¾€Y€¾€Y€»€[Š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŽëwº¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€²uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ê¼…Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëu°¾€Y€¾€Y€¾€Y€´€_¡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq—¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€•¤së€ë€ëë罄Z€°€b°€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë•ës¤¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€ká€ë€ë€ë€ë‚ëæ½Y€¾€Y€º€[€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”ës§¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‘ëu°¾€Y€¾€Y€¾€Y€µ€_ €ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”ët§¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŠÈzë€ë€ë€ë€ë€ë€ë€ë€ë†ë}×¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€µ€^€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq™¾€Y€¾€Y€¾€Y€®€c¸€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸèmƒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‰Ìzë€ë€ëŠëzɾ€Y€ª€eÄ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëp‘¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€š’pë€ë€ë€ë€ëŠëzʾ€Y€¾€Y€´€_¤€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšëp‘¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€µ€^€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë˜ëq™¾€Y€¾€Y€¾€Y€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp•¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ƒà~ë€ë€ë€ë€ë€ë€ë€ë€ëŒëxÁ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€b³€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžêm„¾€Y€¾€Y€¾€Y€§€gÏ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Ôh€¾€Y€¾€Y€¾€Y€¾€Y€¸€]“€ë€ë€ë€ë–ër¾€Y€¤€iØ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžëm‚¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€´vë€ë€ë€ë€ë’ëu«¾€Y€¾€Y€­€d»€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡ål€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¯€b´€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžém„¾€Y€¾€Y€¾€Y€¨€gÌ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëžëm„¾€Y€¾€Y€¾€Y€¾€Y€»€[‹€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë“ëtª¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¨€gÊ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥×i€¾€Y€¾€Y€¾€Y€¡€lå€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬¾d€¾€Y€¾€Y€¾€Y€¾€Y€¨€gÊ€ë€ë€ë€ë£Ûj€¾€Y€¡€kâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡äk€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€…Û}ë€ë€ë€ë€ë›ëo¾€Y€¾€Y€¦€hÑ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë§Ïg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¨€gË€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥×i€¾€Y€¾€Y€¾€Y€¥€iÖ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡åk€¾€Y€¾€Y€¾€Y€¾€Y€´€_£€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp“¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€ká€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¾€Y€š‘pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²§`€¾€Y€¾€Y€¾€Y€¾€Y€˜—që€ë€ë€ë€ë±®b€¾€Y€Ÿ€lé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ýj€¾€Y€¾€Y€¾€Y€¾€Y€·€]—€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€ lç€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®¸c€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¢€ká€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¾€Y€£€jÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Þj€¾€Y€¾€Y€¾€Y€¾€Y€­€c¹€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë çl¾€Y€¾€Y€¾€Y€¾€Y€´‘_‘·‘]†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²©a€¾€Y€¾€Y€¾€Y€‘±uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹‘\€¾€Y€¾€Y€¾€Y€¾€Y€ˆÏ{ë€ë€ëë漆Z€¾€Y€†në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥×i€¾€Y€¾€Y€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë¯³b€¾€Y€¾€Y€™”pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë´¡_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²©`€¾€Y€¾€Y€¾€Y€¡€kå€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Øi€¾€Y€¾€Y€¾€Y€¾€Y€¨€gÍ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦Ñh€¾€Y€¾€Y€¾€Y€¶€^š€ë€ëœêoŒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€—œrë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸”]€¾€Y€¾€Y€¾€Y€…Ú}ë€ë€ë€ë€ë€ë€ë€ë€ë‚ëå¾€Y€¾€Y€¾€Y€¾€Y€²€a©€ë€ë€ë€ëëx¿¾€Y€¾€Y€œ‹oë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Øi€¾€Y€¾€Y€¾€Y€¾€Y€š‘oê€ë€ë€ë€ëë漇Z€¾€Y€¾€Y€“ªtë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë»‹[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€–rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸“]€¾€Y€¾€Y€¾€Y€ž‚më€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€¾€Y€¾€Y€œ‹né€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë­»d€¾€Y€¾€Y€¾€Y€ª€eÀë€ë¨Ìg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€’®uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¼‡Z€¾€Y€¾€Y€¶€]™€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë{Ò¾€Y€¾€Y€¾€Y€¾€Y€›‘oç€ë€ë€ë€ëænŒ¾€Y€¾€Y€†në€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Ýj€¾€Y€¾€Y€¾€Y€¾€Y€ŠÉzë€ë€ë€ë€ëëw½¾€Y€¾€Y€¾€Y€ŒÁxë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ß¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‘¯uë€ë€ë€ë€ë€ë€ë€ë€ëë€é½ƒY€¾€Y€¾€Y€¾€Y€Ÿmë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¢ßj€¾€Y€¾€Y€¾€Y€¾€Y€´vë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë²¨`€¾€Y€¾€Y€¾€Y€ž†mæ€ë€ëµ _€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹wë€ë€ë€ë€ë€ë€ë€ë€ëëé¾€Y€¾€Y€¾€Y€¥hÔ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒëyþ€Y€¾€Y€¾€Y€½€Z„†Ø}ë€ë€ë€ë€ë°¯b€¾€Y€¾€Y€ €lè€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë èl€¾€Y€¾€Y€¾€Y€´€_¤€ë€ë€ë€ë€ë€ë›êoŽ¾€Y€¾€Y€¾€Y€†Ø}ë€ë€ë€ë€ë€ë€ë€ë€ëŠëzȾ€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹wë€ë€ë€ë€ë€ë€ë€ë€ëƒë~á¾€Y€¾€Y€¾€Y€¾€Y€’­uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëên†¾€Y€¾€Y€¾€Y€»€[Šƒá~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¶œ^€¾€Y€¾€Y€¾€Y€¾xë…ë}Ù¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÀxë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¼…Z€¾€Y€¾€Y€’®uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëx¾¾€Y€¾€Y€¾€Y€§ƒgË€ë€ë€ë€ë†ë|×½Y€¾€Y€¾€Y€¢€kà€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë™ëp”¾€Y€¾€Y€¾€Y€nã€ë€ë€ë€ë€ë€ë«Àe€¾€Y€¾€Y€¼€Z…€ê€ë€ë€ë€ë€ë€ë€ë€ë€ëëu²¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÀxë€ë€ë€ë€ë€ë€ë€ë€ë‚ëå¾€Y€¾€Y€¾€Y€¸€]•èë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–ërŸ¾€Y€¾€Y€¾€Y€¥ƒhÒ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëµŸ_€¾€Y€¾€Y€·€]™€ë€ë•ës£¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÂxë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë´¡_€¾€Y€²€`¨è€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ó¾€Y€¾€Y€º€[Œ‡Ñ{ë€ë€ë€ë€ë™êq–¾€Y€¾€Y€¾€Y€©€fÈ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒëxÀ¾€Y€¾€Y€º€[Ž„Þ~ë€ë€ë€ë€ë„ë~Þ¼†Z€¾€Y€¾€Y€¶€^š€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë—ëq›¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÂxë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¹‘\€¾€Y€¾€Y€—£qä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëˆë{о€Y€¾€Y€»€ZˆŠÉzë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë®·c€¾€Y€¾€Y€ššpâ€ë€ë§Îg€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Žºwë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›Þo™·„]“¿xë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëª¹fŒ·„]“¹vå€ë€ë€ë€ë€ë€ë¬½d€¾€Y€¾€Y€¾€Y€±€a¬€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Âi”¸†]Ž’°tæ€ë€ë€ë€ë€ë€ë—ëqœ¾€Y€¾€Y€¾€Y€°€b±€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëm†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Žºwë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë×n›»†[„¤—iÀë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë£Êj“¸ƒ]‘•­sâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë“çt¬ºŠ[ƒªŽe´éëë€è¹\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‘°uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë†ë|Õ½„Z€¾€Y€¾€Y€¾€Y€º€[Œé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¬½d€¾€Y€¾€Y€¾€Y€©€fÇ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¤Ùi€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‘°uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë~Þ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë†ë|×€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëëv´¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€˜™që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ÜŒÂxë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŸßlŠ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ŒÀxë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‹ëyƽY€¾€Y€¾€Y€¾€Y€£€jÞ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëªÃe€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€˜™që€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë›æo”„Þ~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¥Ài˜€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Çf€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ ‚lä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëšçp”š‘pë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ß¹‘\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€Œmä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë©Çf¾€Y€¾€Y€¾€Y€¾€Y€œŠnë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë±¬a€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ ‚lä€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€êµŸ_€’­uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë†ë|×½ƒZ€ƒá~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ô½„Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëë€é´¢_€¦€hÑ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡ÙkŒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¶€^›è€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒëx½‚Y€¾€Y€¾€Y€¾€Y€¾€Y€•¡rë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¸•]€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€®€c·€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë”és¨¾€Y€¤„j×€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¡Ùk‹¾€Y€‘°uë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦Ìh†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡„Ý}ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë–çr£¾€Y€¹€\‘ƒâ~ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëŒëyü†Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€˜Ÿpã€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~ßµŸ_€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¸wë€ë€ë€ë€ë€ë€ë€ë€ëë轃Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡„Ý}ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëƒë~⶙]€¾€Y€º€\‚äë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë{Ѽ‡Z€¾€Y€©€fÆ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëêw¸¾Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œ”nâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}Û¸”]€¾€Y€¾€Y€œ’nã€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë„ë}ܵ _€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€[Š‹Æyé€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë‚ëã«»e†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ˆÎ{ë€ë€ë€ë€ë€ë€ë€ë€ë‡ë|Ó¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€œ”nâ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë€ë¦ÆhŒ¾€Y€¾€Y€¾€Y€ž’mÜ€ë€ë€ë€ë€ë€ë€ë€ë€ë€ëë髾eƒ¾€Y€¾€Y€½€Z„¾xé€ë€ë€ë€ë€ë€ë€ë€ëŠëzɹ‘\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[Œ‰Ìzë€ë€ë€ë€ë€ë€ë…ë}Ú¯±bƒ¾€Y€¾€Y€¾€Y€º€[Œºvå€ë€ë€ë€ë€ë€ë€ë€ë‡ê{Ò³¤_¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€»€[‹™³pÍ‚äë€ë€ëëéŽÝwɳ¢`‚¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€‚äë€ë€ë€ë€ë€ë€ë€ë€ëëx½¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€º€[Œ‰Ìzë€ë€ë€ë€ë€ë€ëëç£Çj–¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡¹væ€ë€ë€ë€ë€ë€ë€ë€ê§Âg¾€Y€¾€Y€¾€Y€¾€Y€¹€\•¾sЄß}é†å|ÚœÈo®»‹[€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\’›²oÆ‘ÏuÌ›Âoµ¸”\€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z…¤¦i´•ÁsÍ”Ìsãºj¤¼‡Z€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z‡´Ž_”º‹[¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¹€\’›²oÆÏvÏ™Çp¸²£`†¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¼€Z†¢£j½’ÈuЖÊrÁ®©cŒ¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€¾€Y€ \ No newline at end of file diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.iram_safe b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.iram_safe deleted file mode 100644 index 8800405f6..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.iram_safe +++ /dev/null @@ -1,10 +0,0 @@ -CONFIG_COMPILER_DUMP_RTL_FILES=y -CONFIG_LCD_RGB_ISR_IRAM_SAFE=y -CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y -# bounce buffer mode relies on GDMA EOF interrupt to be service-able -CONFIG_GDMA_ISR_IRAM_SAFE=y -CONFIG_COMPILER_OPTIMIZATION_NONE=y -# silent the error check, as the error string are stored in rodata, causing RTL check failure -CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y -# place non-ISR FreeRTOS functions in Flash -CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.release b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.release deleted file mode 100644 index 91d93f163..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.ci.release +++ /dev/null @@ -1,5 +0,0 @@ -CONFIG_PM_ENABLE=y -CONFIG_FREERTOS_USE_TICKLESS_IDLE=y -CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.defaults b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.defaults deleted file mode 100644 index ccc43c6fd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.defaults +++ /dev/null @@ -1,5 +0,0 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# -# CONFIG_ESP_TASK_WDT_INIT is not set -CONFIG_FREERTOS_HZ=1000 diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.defaults.esp32s3 b/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.defaults.esp32s3 deleted file mode 100644 index 36a4a647e..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/rgb_lcd/sdkconfig.defaults.esp32s3 +++ /dev/null @@ -1,7 +0,0 @@ -CONFIG_SPIRAM=y -CONFIG_SPIRAM_MODE_OCT=y -CONFIG_SPIRAM_SPEED_80M=y - -# Enable the XIP-PSRAM feature, so the ext-mem cache won't be disabled when SPI1 is operating the main flash -CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y -CONFIG_SPIRAM_RODATA=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/CMakeLists.txt deleted file mode 100644 index 3265d9a8d..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -# This is the project CMakeLists.txt file for the test subproject -cmake_minimum_required(VERSION 3.16) - -# "Trim" the build. Include the minimal set of components, main, and anything it depends on. -set(COMPONENTS main) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(spi_lcd_panel_test) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/README.md b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/README.md deleted file mode 100644 index d8d99fb86..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/README.md +++ /dev/null @@ -1,4 +0,0 @@ -| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | - -This test app is used to test LCDs with SPI interface. diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/CMakeLists.txt b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/CMakeLists.txt deleted file mode 100644 index 72a8ae1d4..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -set(srcs "test_app_main.c" - "test_spi_lcd_panel.c") - -# In order for the cases defined by `TEST_CASE` to be linked into the final elf, -# the component can be registered as WHOLE_ARCHIVE -idf_component_register(SRCS ${srcs} - PRIV_REQUIRES esp_lcd unity driver - WHOLE_ARCHIVE) diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_app_main.c b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_app_main.c deleted file mode 100644 index 5c978d553..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_app_main.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "unity.h" -#include "unity_test_runner.h" -#include "esp_heap_caps.h" - -// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -void app_main(void) -{ - // ____ ____ ___ _ ____ ____ _____ _ - // / ___|| _ \_ _| | | / ___| _ \ |_ _|__ ___| |_ - // \___ \| |_) | | | | | | | | | | | |/ _ \/ __| __| - // ___) | __/| | | |__| |___| |_| | | | __/\__ \ |_ - // |____/|_| |___| |_____\____|____/ |_|\___||___/\__| - printf(" ____ ____ ___ _ ____ ____ _____ _\r\n"); - printf("/ ___|| _ \\_ _| | | / ___| _ \\ |_ _|__ ___| |_\r\n"); - printf("\\___ \\| |_) | | | | | | | | | | | |/ _ \\/ __| __|\r\n"); - printf(" ___) | __/| | | |__| |___| |_| | | | __/\\__ \\ |_\r\n"); - printf("|____/|_| |___| |_____\\____|____/ |_|\\___||___/\\__|\r\n"); - unity_run_menu(); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_spi_board.h b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_spi_board.h deleted file mode 100644 index ec4600376..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_spi_board.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#pragma once - -#include "sdkconfig.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#define TEST_LCD_H_RES 240 -#define TEST_LCD_V_RES 280 - -#if CONFIG_IDF_TARGET_ESP32H2 -#define TEST_LCD_BK_LIGHT_GPIO 10 -#define TEST_LCD_RST_GPIO 5 -#define TEST_LCD_CS_GPIO 3 -#define TEST_LCD_DC_GPIO 4 -#define TEST_LCD_PCLK_GPIO 2 -#define TEST_LCD_DATA0_GPIO 1 -#elif CONFIG_IDF_TARGET_ESP32C2 -#define TEST_LCD_BK_LIGHT_GPIO 18 -#define TEST_LCD_RST_GPIO 5 -#define TEST_LCD_CS_GPIO 0 -#define TEST_LCD_DC_GPIO 1 -#define TEST_LCD_PCLK_GPIO 2 -#define TEST_LCD_DATA0_GPIO 4 -#else -#define TEST_LCD_BK_LIGHT_GPIO 18 -#define TEST_LCD_RST_GPIO 5 -#define TEST_LCD_CS_GPIO 0 -#define TEST_LCD_DC_GPIO 19 -#define TEST_LCD_PCLK_GPIO 2 -#define TEST_LCD_DATA0_GPIO 4 -#define TEST_LCD_DATA1_GPIO 7 -#define TEST_LCD_DATA2_GPIO 8 -#define TEST_LCD_DATA3_GPIO 9 -#define TEST_LCD_DATA4_GPIO 10 -#define TEST_LCD_DATA5_GPIO 11 -#define TEST_LCD_DATA6_GPIO 12 -#define TEST_LCD_DATA7_GPIO 13 -#endif - -#define TEST_LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000) - -#ifdef __cplusplus -} -#endif diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c deleted file mode 100644 index eb40f3dcf..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/main/test_spi_lcd_panel.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "unity.h" -#include "driver/spi_master.h" -#include "driver/gpio.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" -#include "soc/soc_caps.h" -#include "test_spi_board.h" - -#define TEST_SPI_HOST_ID SPI2_HOST - -void test_spi_lcd_common_initialize(esp_lcd_panel_io_handle_t *io_handle, esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done, - void *user_data, int cmd_bits, int param_bits, bool oct_mode) -{ - // turn off backlight - gpio_config_t bk_gpio_config = { - .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << TEST_LCD_BK_LIGHT_GPIO - }; - TEST_ESP_OK(gpio_config(&bk_gpio_config)); - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0); - - spi_bus_config_t buscfg = { - .sclk_io_num = TEST_LCD_PCLK_GPIO, - .mosi_io_num = TEST_LCD_DATA0_GPIO, - .miso_io_num = -1, - .quadwp_io_num = -1, - .quadhd_io_num = -1, - .max_transfer_sz = 100 * 100 * sizeof(uint16_t), - }; -#if SOC_SPI_SUPPORT_OCT - if (oct_mode) { - buscfg.data1_io_num = TEST_LCD_DATA1_GPIO; - buscfg.data2_io_num = TEST_LCD_DATA2_GPIO; - buscfg.data3_io_num = TEST_LCD_DATA3_GPIO; - buscfg.data4_io_num = TEST_LCD_DATA4_GPIO; - buscfg.data5_io_num = TEST_LCD_DATA5_GPIO; - buscfg.data6_io_num = TEST_LCD_DATA6_GPIO; - buscfg.data7_io_num = TEST_LCD_DATA7_GPIO; - buscfg.flags = SPICOMMON_BUSFLAG_OCTAL; - } -#endif - TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST_ID, &buscfg, SPI_DMA_CH_AUTO)); - - esp_lcd_panel_io_spi_config_t io_config = { - .dc_gpio_num = TEST_LCD_DC_GPIO, - .cs_gpio_num = TEST_LCD_CS_GPIO, - .pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ, - .spi_mode = 0, - .trans_queue_depth = 10, - .lcd_cmd_bits = cmd_bits, - .lcd_param_bits = param_bits, - .on_color_trans_done = on_color_trans_done, - .user_ctx = user_data, - }; -#if SOC_SPI_SUPPORT_OCT - if (oct_mode) { - io_config.flags.octal_mode = 1; - io_config.spi_mode = 3; - } -#endif - TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TEST_SPI_HOST_ID, &io_config, io_handle)); -} - -#define TEST_IMG_SIZE (200 * 200 * sizeof(uint16_t)) - -static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle) -{ - uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA); - TEST_ASSERT_NOT_NULL(img); - - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_invert_color(panel_handle, true); - // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value - esp_lcd_panel_set_gap(panel_handle, 0, 20); - // turn on display - esp_lcd_panel_disp_on_off(panel_handle, true); - // turn on backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); - - for (int i = 0; i < 100; i++) { - uint8_t color_byte = rand() & 0xFF; - int x_start = rand() % (TEST_LCD_H_RES - 200); - int y_start = rand() % (TEST_LCD_V_RES - 200); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 200, y_start + 200, img); - } - - printf("go into sleep mode\r\n"); - esp_lcd_panel_disp_sleep(panel_handle, true); - vTaskDelay(pdMS_TO_TICKS(500)); - printf("exit sleep mode\r\n"); - esp_lcd_panel_disp_sleep(panel_handle, false); - - for (int i = 0; i < 100; i++) { - uint8_t color_byte = rand() & 0xFF; - int x_start = rand() % (TEST_LCD_H_RES - 200); - int y_start = rand() % (TEST_LCD_V_RES - 200); - memset(img, color_byte, TEST_IMG_SIZE); - esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 200, y_start + 200, img); - } - - printf("turn off the panel\r\n"); - esp_lcd_panel_disp_on_off(panel_handle, false); - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); - TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO)); - free(img); -} - -TEST_CASE("lcd_panel_spi_io_test", "[lcd]") -{ - esp_lcd_panel_io_handle_t io_handle = NULL; - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 8, 8, false); - esp_lcd_panel_io_tx_param(io_handle, 0x1A, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B, (uint8_t[]) { - 0x11, 0x22, 0x33 - }, 3); - esp_lcd_panel_io_tx_param(io_handle, 0x1C, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); - - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 16, 16, false); - esp_lcd_panel_io_tx_param(io_handle, 0x1A01, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B02, (uint16_t[]) { - 0x11, 0x22, 0x33 - }, 6); - esp_lcd_panel_io_tx_param(io_handle, 0x1C03, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); - -#if SOC_SPI_SUPPORT_OCT - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 8, 8, true); - esp_lcd_panel_io_tx_param(io_handle, 0x1A, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B, (uint8_t[]) { - 0x11, 0x22, 0x33 - }, 3); - esp_lcd_panel_io_tx_param(io_handle, 0x1C, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); - - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 16, 16, true); - esp_lcd_panel_io_tx_param(io_handle, 0x1A01, NULL, 0); - esp_lcd_panel_io_tx_param(io_handle, 0x1B02, (uint16_t[]) { - 0x11, 0x22, 0x33 - }, 6); - esp_lcd_panel_io_tx_param(io_handle, 0x1C03, NULL, 0); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); -#endif // SOC_SPI_SUPPORT_OCT -} - -#if SOC_SPI_SUPPORT_OCT -TEST_CASE("lcd_panel_with_8-line_spi_interface_(st7789)", "[lcd]") -{ - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 8, 8, true); - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - lcd_panel_test(io_handle, panel_handle); -} - -TEST_CASE("lcd_panel_with_8-line_spi_interface_(nt35510)", "[lcd]") -{ - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 16, 16, true); - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle)); - lcd_panel_test(io_handle, panel_handle); -} -#endif // SOC_SPI_SUPPORT_OCT - -TEST_CASE("lcd_panel_with_1-line_spi_interface_(st7789)", "[lcd]") -{ - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 8, 8, false); - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - lcd_panel_test(io_handle, panel_handle); -} - -TEST_CASE("spi_lcd_send_colors_to_fixed_region", "[lcd]") -{ - int x_start = 100; - int y_start = 100; - int x_end = 200; - int y_end = 200; - size_t color_size = (x_end - x_start) * (y_end - y_start) * 2; - void *color_data = malloc(color_size); - TEST_ASSERT_NOT_NULL(color_data); - uint8_t color_byte = rand() & 0xFF; - memset(color_data, color_byte, color_size); - - esp_lcd_panel_io_handle_t io_handle = NULL; - esp_lcd_panel_handle_t panel_handle = NULL; - test_spi_lcd_common_initialize(&io_handle, NULL, NULL, 8, 8, false); - - // we don't use the panel handle in this test, creating the panel just for a quick initialization - esp_lcd_panel_dev_config_t panel_config = { - .reset_gpio_num = TEST_LCD_RST_GPIO, - .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, - .bits_per_pixel = 16, - }; - TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); - esp_lcd_panel_reset(panel_handle); - esp_lcd_panel_init(panel_handle); - esp_lcd_panel_invert_color(panel_handle, true); - // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value - esp_lcd_panel_set_gap(panel_handle, 0, 20); - // turn on display - esp_lcd_panel_disp_on_off(panel_handle, true); - // turn on backlight - gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1); - - printf("set the flush window for only once\r\n"); - esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_CASET, (uint8_t[]) { - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, 4); - esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_RASET, (uint8_t[]) { - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, 4); - esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_RAMWR, NULL, 0); - - printf("send colors to the fixed region in multiple steps\r\n"); - const int steps = 10; - int color_size_per_step = color_size / steps; - for (int i = 0; i < steps; i++) { - TEST_ESP_OK(esp_lcd_panel_io_tx_color(io_handle, -1, color_data + i * color_size_per_step, color_size_per_step)); - } - vTaskDelay(pdMS_TO_TICKS(1000)); - - printf("change to another color\r\n"); - color_byte = rand() & 0xFF; - memset(color_data, color_byte, color_size); - for (int i = 0; i < steps; i++) { - TEST_ESP_OK(esp_lcd_panel_io_tx_color(io_handle, -1, color_data + i * color_size_per_step, color_size_per_step)); - } - vTaskDelay(pdMS_TO_TICKS(1000)); - - TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); - TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); - TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID)); - free(color_data); -} diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py deleted file mode 100644 index b6590fc4c..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/pytest_spi_lcd.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - - -@pytest.mark.supported_targets -@pytest.mark.generic -@pytest.mark.parametrize( - 'config', - [ - 'release', - ], - indirect=True, -) -def test_spi_lcd(dut: Dut) -> None: - dut.run_all_single_board_cases() diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/sdkconfig.ci.release b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/sdkconfig.ci.release deleted file mode 100644 index 3cff15d49..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/sdkconfig.ci.release +++ /dev/null @@ -1,3 +0,0 @@ -CONFIG_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y -CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/sdkconfig.defaults b/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/sdkconfig.defaults deleted file mode 100644 index ccc43c6fd..000000000 --- a/tulip/esp32s3/components/esp_lcd/test_apps/spi_lcd/sdkconfig.defaults +++ /dev/null @@ -1,5 +0,0 @@ -# This file was generated using idf.py save-defconfig. It can be edited manually. -# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration -# -# CONFIG_ESP_TASK_WDT_INIT is not set -CONFIG_FREERTOS_HZ=1000 diff --git a/tulip/esp32s3/esp32_common.cmake b/tulip/esp32s3/esp32_common.cmake index 67fe189ec..83bd8754c 100644 --- a/tulip/esp32s3/esp32_common.cmake +++ b/tulip/esp32s3/esp32_common.cmake @@ -3,6 +3,11 @@ if(NOT MICROPY_DIR) get_filename_component(MICROPY_DIR ${CMAKE_CURRENT_LIST_DIR}/../../micropython ABSOLUTE) endif() +# Set location of base MicroPython esp32 port (which this is based on). +if(NOT MICROPY_ESP32_DIR) + get_filename_component(MICROPY_ESP32_DIR ${CMAKE_CURRENT_LIST_DIR}/../../micropython/ports/esp32 ABSOLUTE) +endif() + # Set location of the ESP32 port directory. if(NOT MICROPY_PORT_DIR) get_filename_component(MICROPY_PORT_DIR ${CMAKE_CURRENT_LIST_DIR}/ ABSOLUTE) @@ -59,70 +64,105 @@ list(APPEND MICROPY_SOURCE_SHARED ${MICROPY_DIR}/shared/netutils/netutils.c ${MICROPY_DIR}/shared/timeutils/timeutils.c ${MICROPY_DIR}/shared/runtime/interrupt_char.c + ${MICROPY_DIR}/shared/runtime/mpirq.c ${MICROPY_DIR}/shared/runtime/stdout_helpers.c ${MICROPY_DIR}/shared/runtime/sys_stdio_mphal.c ${MICROPY_DIR}/shared/runtime/pyexec.c ) + list(APPEND MICROPY_SOURCE_LIB ${MICROPY_DIR}/lib/littlefs/lfs1.c ${MICROPY_DIR}/lib/littlefs/lfs1_util.c ${MICROPY_DIR}/lib/littlefs/lfs2.c ${MICROPY_DIR}/lib/littlefs/lfs2_util.c - #${MICROPY_DIR}/lib/mbedtls_errors/esp32_mbedtls_errors.c + ${MICROPY_DIR}/lib/mbedtls_errors/esp32_mbedtls_errors.c ${MICROPY_DIR}/lib/oofatfs/ff.c ${MICROPY_DIR}/lib/oofatfs/ffunicode.c ) + list(APPEND MICROPY_SOURCE_DRIVERS ${MICROPY_DIR}/drivers/bus/softspi.c ${MICROPY_DIR}/drivers/dht/dht.c ) + +string(CONCAT GIT_SUBMODULES "${GIT_SUBMODULES} " lib/tinyusb) +if(MICROPY_PY_TINYUSB) + set(TINYUSB_SRC "${MICROPY_DIR}/lib/tinyusb/src") + string(TOUPPER OPT_MCU_${IDF_TARGET} tusb_mcu) + + list(APPEND MICROPY_DEF_TINYUSB + CFG_TUSB_MCU=${tusb_mcu} + ) + + list(APPEND MICROPY_SOURCE_TINYUSB + ${TINYUSB_SRC}/tusb.c + ${TINYUSB_SRC}/common/tusb_fifo.c + ${TINYUSB_SRC}/device/usbd.c + ${TINYUSB_SRC}/device/usbd_control.c + ${TINYUSB_SRC}/class/cdc/cdc_device.c + ${TINYUSB_SRC}/portable/synopsys/dwc2/dcd_dwc2.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_cdc.c + ${MICROPY_DIR}/shared/tinyusb/mp_usbd_descriptor.c + ) + + list(APPEND MICROPY_INC_TINYUSB + ${TINYUSB_SRC} + ${MICROPY_DIR}/shared/tinyusb/ + ) + + list(APPEND MICROPY_LINK_TINYUSB + -Wl,--wrap=dcd_event_handler + ) +endif() + + list(APPEND MICROPY_SOURCE_PORT - multicast.c - mphalport.c - network_common.c - main.c - uart.c - help.c - build/lv_mpy.c - usb_serial_jtag.c - gccollect.c - fatfs_port.c - machine_bitstream.c - machine_sdcard.c - machine_timer.c - machine_pin.c - machine_touchpad.c - machine_adc.c - machine_adcblock.c - machine_dac.c - machine_i2c.c - machine_uart.c - modmachine.c - network_lan.c - network_wlan.c - network_ppp.c - ppp_set_auth.c - #mpnimbleport.c - modsocket.c - modesp.c - esp32_nvs.c - esp32_partition.c - esp32_rmt.c - esp32_ulp.c - modesp32.c - machine_hw_spi.c - machine_wdt.c - mpthreadport.c - machine_rtc.c - machine_sdcard.c - modespnow.c - usb.c + ${MICROPY_PORT_DIR}/main.c + ${MICROPY_PORT_DIR}/multicast.c + ${MICROPY_PORT_DIR}/help.c + ${MICROPY_PORT_DIR}/build/lv_mpy.c + ${MICROPY_PORT_DIR}/network_common.c + ${MICROPY_PORT_DIR}/esp_lcd_touch.c + ${MICROPY_PORT_DIR}/uart.c + ${MICROPY_PORT_DIR}/modsocket.c + ${MICROPY_ESP32_DIR}/panichandler.c + ${MICROPY_ESP32_DIR}/adc.c + ${MICROPY_ESP32_DIR}/mphalport.c + #${MICROPY_ESP32_DIR}/usb_serial_jtag.c + ${MICROPY_ESP32_DIR}/gccollect.c + ${MICROPY_ESP32_DIR}/fatfs_port.c + ${MICROPY_ESP32_DIR}/machine_bitstream.c + ${MICROPY_ESP32_DIR}/machine_sdcard.c + ${MICROPY_ESP32_DIR}/machine_timer.c + ${MICROPY_ESP32_DIR}/machine_pin.c + ${MICROPY_ESP32_DIR}/machine_touchpad.c + ${MICROPY_ESP32_DIR}/machine_dac.c + ${MICROPY_ESP32_DIR}/machine_i2c.c + ${MICROPY_ESP32_DIR}/network_lan.c + ${MICROPY_ESP32_DIR}/network_wlan.c + ${MICROPY_ESP32_DIR}/modesp.c + ${MICROPY_ESP32_DIR}/esp32_nvs.c + ${MICROPY_ESP32_DIR}/esp32_partition.c + ${MICROPY_ESP32_DIR}/esp32_rmt.c + ${MICROPY_ESP32_DIR}/esp32_ulp.c + ${MICROPY_ESP32_DIR}/modesp32.c + ${MICROPY_ESP32_DIR}/machine_hw_spi.c + ${MICROPY_ESP32_DIR}/mpthreadport.c + ${MICROPY_ESP32_DIR}/machine_rtc.c + ${MICROPY_ESP32_DIR}/machine_sdcard.c + ${MICROPY_ESP32_DIR}/modespnow.c + ${MICROPY_ESP32_DIR}/usb.c ) -list(TRANSFORM MICROPY_SOURCE_PORT PREPEND ${MICROPY_PORT_DIR}/) + +list(TRANSFORM MICROPY_SOURCE_BOARD PREPEND ${MICROPY_PORT_DIR}/) + +list(APPEND MICROPY_SOURCE_PORT ${CMAKE_BINARY_DIR}/pins.c) + list(APPEND MICROPY_SOURCE_EXTMOD ${TULIP_SHARED_DIR}/modtulip.c @@ -167,6 +207,7 @@ list(APPEND MICROPY_SOURCE_QSTR ${MICROPY_SOURCE_LIB} ${MICROPY_SOURCE_PORT} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} ) list(APPEND IDF_COMPONENTS @@ -174,11 +215,20 @@ list(APPEND IDF_COMPONENTS bootloader_support #bt driver + esp_driver_tsens esp_adc esp_app_format esp_bootloader_format esp_common esp_eth + esp_driver_uart + esp_driver_i2s + esp_driver_i2c + esp_driver_sdmmc + esp_driver_sdspi + esp_driver_spi + esp_driver_gpio + esp_driver_ledc esp_event esp_hw_support esp_lcd @@ -219,16 +269,16 @@ idf_component_register( ${MICROPY_SOURCE_DRIVERS} ${MICROPY_SOURCE_PORT} ${MICROPY_SOURCE_BOARD} + ${MICROPY_SOURCE_TINYUSB} ${LVGL_SOURCES} INCLUDE_DIRS - ../../tulip/esp32s3 - ../../tulip/esp32s3/managed_components/espressif__esp_lcd_touch_gt911/include - ../../tulip/esp32s3/managed_components/espressif__esp_lcd_touch/include + . ${MICROPY_INC_CORE} ${MICROPY_INC_USERMOD} - #${MICROPY_PORT_DIR} + ${MICROPY_ESP32_DIR} ${MICROPY_BOARD_DIR} ${CMAKE_BINARY_DIR} + ${MICROPY_INC_TINYUSB} ../../tulip/shared ../../amy/src ${LV_BINDING_DIR} @@ -260,6 +310,7 @@ target_compile_definitions(${MICROPY_TARGET} PUBLIC LV_CONF_INCLUDE_SIMPLE ${BOARD_DEFINITION1} ${BOARD_DEFINITION2} + ${MICROPY_DEF_TINYUSB} ) #LFS2_NO_DEBUG LFS2_NO_WARN LFS2_NO_ERROR @@ -275,6 +326,8 @@ target_compile_options(${MICROPY_TARGET} PUBLIC -fsingle-precision-constant -Wno-strict-aliasing -DESP_PLATFORM + -DSTATIC=static + -DLFS2_NO_DEBUG ) # Additional include directories needed for private NimBLE headers. @@ -282,6 +335,10 @@ target_compile_options(${MICROPY_TARGET} PUBLIC # ${IDF_PATH}/components/bt/host/nimble/nimble #) +target_link_options(${MICROPY_TARGET} PUBLIC + ${MICROPY_LINK_TINYUSB} +) + # Add additional extmod and usermod components. target_link_libraries(${MICROPY_TARGET} micropy_extmod_btree) target_link_libraries(${MICROPY_TARGET} usermod) @@ -295,3 +352,36 @@ endforeach() # Include the main MicroPython cmake rules. include(${MICROPY_DIR}/py/mkrules.cmake) + + + +# Generate source files for named pins (requires mkrules.cmake for MICROPY_GENHDR_DIR). + +set(GEN_PINS_PREFIX "${MICROPY_ESP32_DIR}/boards/pins_prefix.c") +set(GEN_PINS_MKPINS "${MICROPY_ESP32_DIR}/boards/make-pins.py") +set(GEN_PINS_SRC "${CMAKE_BINARY_DIR}/pins.c") +set(GEN_PINS_HDR "${MICROPY_GENHDR_DIR}/pins.h") + +if(EXISTS "${MICROPY_BOARD_DIR}/pins.csv") + set(GEN_PINS_BOARD_CSV "${MICROPY_BOARD_DIR}/pins.csv") + set(GEN_PINS_BOARD_CSV_ARG --board-csv "${GEN_PINS_BOARD_CSV}") +endif() + +target_sources(${MICROPY_TARGET} PRIVATE ${GEN_PINS_HDR}) + +add_custom_command( + OUTPUT ${GEN_PINS_SRC} ${GEN_PINS_HDR} + COMMAND ${Python3_EXECUTABLE} ${GEN_PINS_MKPINS} ${GEN_PINS_BOARD_CSV_ARG} + --prefix ${GEN_PINS_PREFIX} --output-source ${GEN_PINS_SRC} --output-header ${GEN_PINS_HDR} + DEPENDS + ${MICROPY_MPVERSION} + ${GEN_PINS_MKPINS} + ${GEN_PINS_BOARD_CSV} + ${GEN_PINS_PREFIX} + VERBATIM + COMMAND_EXPAND_LISTS +) + + + + diff --git a/tulip/esp32s3/esp32_nvs.c b/tulip/esp32s3/esp32_nvs.c deleted file mode 100644 index 0b3661918..000000000 --- a/tulip/esp32s3/esp32_nvs.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 by Thorsten von Eicken - * - * 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. - */ - -#include - -#include "py/runtime.h" -#include "py/mperrno.h" -#include "mphalport.h" -#include "modesp32.h" -#include "nvs_flash.h" -#include "nvs.h" - -// This file implements the NVS (Non-Volatile Storage) class in the esp32 module. -// It provides simple access to the NVS feature provided by ESP-IDF. - -// NVS python object that represents an NVS namespace. -typedef struct _esp32_nvs_obj_t { - mp_obj_base_t base; - nvs_handle_t namespace; -} esp32_nvs_obj_t; - -// *esp32_nvs_new allocates a python NVS object given a handle to an esp-idf namespace C obj. -STATIC esp32_nvs_obj_t *esp32_nvs_new(nvs_handle_t namespace) { - esp32_nvs_obj_t *self = mp_obj_malloc(esp32_nvs_obj_t, &esp32_nvs_type); - self->namespace = namespace; - return self; -} - -// esp32_nvs_print prints an NVS object, unfortunately it doesn't seem possible to extract the -// namespace string or anything else from the opaque handle provided by esp-idf. -STATIC void esp32_nvs_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - // esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, ""); -} - -// esp32_nvs_make_new constructs a handle to an NVS namespace. -STATIC mp_obj_t esp32_nvs_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - // Check args - mp_arg_check_num(n_args, n_kw, 1, 1, false); - - // Get requested nvs namespace - const char *ns_name = mp_obj_str_get_str(all_args[0]); - nvs_handle_t namespace; - check_esp_err(nvs_open(ns_name, NVS_READWRITE, &namespace)); - return MP_OBJ_FROM_PTR(esp32_nvs_new(namespace)); -} - -// esp32_nvs_set_i32 sets a 32-bit integer value -STATIC mp_obj_t esp32_nvs_set_i32(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { - esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - const char *key = mp_obj_str_get_str(key_in); - int32_t value = mp_obj_get_int(value_in); - check_esp_err(nvs_set_i32(self->namespace, key, value)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_set_i32_obj, esp32_nvs_set_i32); - -// esp32_nvs_get_i32 reads a 32-bit integer value -STATIC mp_obj_t esp32_nvs_get_i32(mp_obj_t self_in, mp_obj_t key_in) { - esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - const char *key = mp_obj_str_get_str(key_in); - int32_t value; - check_esp_err(nvs_get_i32(self->namespace, key, &value)); - return mp_obj_new_int(value); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_nvs_get_i32_obj, esp32_nvs_get_i32); - -// esp32_nvs_set_blob writes a buffer object into a binary blob value. -STATIC mp_obj_t esp32_nvs_set_blob(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { - esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - const char *key = mp_obj_str_get_str(key_in); - mp_buffer_info_t value; - mp_get_buffer_raise(value_in, &value, MP_BUFFER_READ); - check_esp_err(nvs_set_blob(self->namespace, key, value.buf, value.len)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_set_blob_obj, esp32_nvs_set_blob); - -// esp32_nvs_get_blob reads a binary blob value into a bytearray. Returns actual length. -STATIC mp_obj_t esp32_nvs_get_blob(mp_obj_t self_in, mp_obj_t key_in, mp_obj_t value_in) { - esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - const char *key = mp_obj_str_get_str(key_in); - // get buffer to be filled - mp_buffer_info_t value; - mp_get_buffer_raise(value_in, &value, MP_BUFFER_WRITE); - size_t length = value.len; - // fill the buffer with the value, will raise an esp-idf error if the length of - // the provided buffer (bytearray) is too small - check_esp_err(nvs_get_blob(self->namespace, key, value.buf, &length)); - return MP_OBJ_NEW_SMALL_INT(length); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_nvs_get_blob_obj, esp32_nvs_get_blob); - -// esp32_nvs_erase_key erases one key. -STATIC mp_obj_t esp32_nvs_erase_key(mp_obj_t self_in, mp_obj_t key_in) { - esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - const char *key = mp_obj_str_get_str(key_in); - check_esp_err(nvs_erase_key(self->namespace, key)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_nvs_erase_key_obj, esp32_nvs_erase_key); - -// esp32_nvs_commit commits any changes to flash. -STATIC mp_obj_t esp32_nvs_commit(mp_obj_t self_in) { - esp32_nvs_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_esp_err(nvs_commit(self->namespace)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_nvs_commit_obj, esp32_nvs_commit); - -STATIC const mp_rom_map_elem_t esp32_nvs_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_get_i32), MP_ROM_PTR(&esp32_nvs_get_i32_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_i32), MP_ROM_PTR(&esp32_nvs_set_i32_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_blob), MP_ROM_PTR(&esp32_nvs_get_blob_obj) }, - { MP_ROM_QSTR(MP_QSTR_set_blob), MP_ROM_PTR(&esp32_nvs_set_blob_obj) }, - { MP_ROM_QSTR(MP_QSTR_erase_key), MP_ROM_PTR(&esp32_nvs_erase_key_obj) }, - { MP_ROM_QSTR(MP_QSTR_commit), MP_ROM_PTR(&esp32_nvs_commit_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(esp32_nvs_locals_dict, esp32_nvs_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - esp32_nvs_type, - MP_QSTR_NVS, - MP_TYPE_FLAG_NONE, - make_new, esp32_nvs_make_new, - print, esp32_nvs_print, - locals_dict, &esp32_nvs_locals_dict - ); diff --git a/tulip/esp32s3/esp32_partition.c b/tulip/esp32s3/esp32_partition.c deleted file mode 100644 index 17aa34e56..000000000 --- a/tulip/esp32s3/esp32_partition.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Damien P. George - * - * 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. - */ - -#include - -#include "py/runtime.h" -#include "py/mperrno.h" -#include "extmod/vfs.h" -#include "mphalport.h" -#include "modesp32.h" -#include "esp_ota_ops.h" - -// esp_partition_read and esp_partition_write can operate on arbitrary bytes -// but esp_partition_erase_range operates on 4k blocks. The default block size -// for a Partition object is therefore 4k, to make writes efficient, and also -// make it work well with filesystems like littlefs. The Partition object also -// supports smaller block sizes, in which case a cache is used and writes may -// be less efficient. -#define NATIVE_BLOCK_SIZE_BYTES (4096) - -enum { - ESP32_PARTITION_BOOT, - ESP32_PARTITION_RUNNING, -}; - -typedef struct _esp32_partition_obj_t { - mp_obj_base_t base; - const esp_partition_t *part; - uint8_t *cache; - uint16_t block_size; -} esp32_partition_obj_t; - -STATIC esp32_partition_obj_t *esp32_partition_new(const esp_partition_t *part, uint16_t block_size) { - if (part == NULL) { - mp_raise_OSError(MP_ENOENT); - } - esp32_partition_obj_t *self = mp_obj_malloc(esp32_partition_obj_t, &esp32_partition_type); - self->part = part; - self->block_size = block_size; - if (self->block_size < NATIVE_BLOCK_SIZE_BYTES) { - self->cache = m_new(uint8_t, NATIVE_BLOCK_SIZE_BYTES); - } else { - self->cache = NULL; - } - return self; -} - -STATIC void esp32_partition_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "", - self->part->type, self->part->subtype, - self->part->address, self->part->size, - &self->part->label[0], self->part->encrypted - ); -} - -STATIC mp_obj_t esp32_partition_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - // Check args - mp_arg_check_num(n_args, n_kw, 1, 2, false); - - // Get requested partition - const esp_partition_t *part; - if (mp_obj_is_int(all_args[0])) { - // Integer given, get that particular partition - switch (mp_obj_get_int(all_args[0])) { - case ESP32_PARTITION_BOOT: - part = esp_ota_get_boot_partition(); - break; - case ESP32_PARTITION_RUNNING: - part = esp_ota_get_running_partition(); - break; - default: - mp_raise_ValueError(NULL); - } - } else { - // String given, search for partition with that label - const char *label = mp_obj_str_get_str(all_args[0]); - part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, label); - if (part == NULL) { - part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, label); - } - } - - // Get block size if given - uint16_t block_size = NATIVE_BLOCK_SIZE_BYTES; - if (n_args == 2) { - block_size = mp_obj_get_int(all_args[1]); - } - - // Return new object - return MP_OBJ_FROM_PTR(esp32_partition_new(part, block_size)); -} - -STATIC mp_obj_t esp32_partition_find(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - // Parse args - enum { ARG_type, ARG_subtype, ARG_label, ARG_block_size }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_type, MP_ARG_INT, {.u_int = ESP_PARTITION_TYPE_APP} }, - { MP_QSTR_subtype, MP_ARG_INT, {.u_int = ESP_PARTITION_SUBTYPE_ANY} }, - { MP_QSTR_label, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, - { MP_QSTR_block_size, MP_ARG_INT, {.u_int = NATIVE_BLOCK_SIZE_BYTES} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - // Get optional label string - const char *label = NULL; - if (args[ARG_label].u_obj != mp_const_none) { - label = mp_obj_str_get_str(args[ARG_label].u_obj); - } - - // Get block size - uint16_t block_size = args[ARG_block_size].u_int; - - // Build list of matching partitions - mp_obj_t list = mp_obj_new_list(0, NULL); - esp_partition_iterator_t iter = esp_partition_find(args[ARG_type].u_int, args[ARG_subtype].u_int, label); - while (iter != NULL) { - mp_obj_list_append(list, MP_OBJ_FROM_PTR(esp32_partition_new(esp_partition_get(iter), block_size))); - iter = esp_partition_next(iter); - } - esp_partition_iterator_release(iter); - - return list; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_partition_find_fun_obj, 0, esp32_partition_find); -STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_partition_find_obj, MP_ROM_PTR(&esp32_partition_find_fun_obj)); - -STATIC mp_obj_t esp32_partition_info(mp_obj_t self_in) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t tuple[] = { - MP_OBJ_NEW_SMALL_INT(self->part->type), - MP_OBJ_NEW_SMALL_INT(self->part->subtype), - mp_obj_new_int_from_uint(self->part->address), - mp_obj_new_int_from_uint(self->part->size), - mp_obj_new_str(&self->part->label[0], strlen(&self->part->label[0])), - mp_obj_new_bool(self->part->encrypted), - }; - return mp_obj_new_tuple(6, tuple); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_info_obj, esp32_partition_info); - -STATIC mp_obj_t esp32_partition_readblocks(size_t n_args, const mp_obj_t *args) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]); - uint32_t offset = mp_obj_get_int(args[1]) * self->block_size; - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); - if (n_args == 4) { - offset += mp_obj_get_int(args[3]); - } - check_esp_err(esp_partition_read(self->part, offset, bufinfo.buf, bufinfo.len)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_partition_readblocks_obj, 3, 4, esp32_partition_readblocks); - -STATIC mp_obj_t esp32_partition_writeblocks(size_t n_args, const mp_obj_t *args) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(args[0]); - uint32_t offset = mp_obj_get_int(args[1]) * self->block_size; - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); - if (n_args == 3) { - // A simple write, which requires erasing first. - if (self->block_size >= NATIVE_BLOCK_SIZE_BYTES) { - // Block size is at least native erase-page size, so do an efficient erase. - check_esp_err(esp_partition_erase_range(self->part, offset, bufinfo.len)); - } else { - // Block size is less than native erase-page size, so do erase in sections. - uint32_t addr = (offset / NATIVE_BLOCK_SIZE_BYTES) * NATIVE_BLOCK_SIZE_BYTES; - uint32_t o = offset % NATIVE_BLOCK_SIZE_BYTES; - uint32_t top_addr = offset + bufinfo.len; - while (addr < top_addr) { - if (o > 0 || top_addr < addr + NATIVE_BLOCK_SIZE_BYTES) { - check_esp_err(esp_partition_read(self->part, addr, self->cache, NATIVE_BLOCK_SIZE_BYTES)); - } - check_esp_err(esp_partition_erase_range(self->part, addr, NATIVE_BLOCK_SIZE_BYTES)); - if (o > 0) { - check_esp_err(esp_partition_write(self->part, addr, self->cache, o)); - } - if (top_addr < addr + NATIVE_BLOCK_SIZE_BYTES) { - check_esp_err(esp_partition_write(self->part, top_addr, self->cache, addr + NATIVE_BLOCK_SIZE_BYTES - top_addr)); - } - o = 0; - addr += NATIVE_BLOCK_SIZE_BYTES; - } - } - } else { - // An extended write, erasing must have been done explicitly before this write. - offset += mp_obj_get_int(args[3]); - } - check_esp_err(esp_partition_write(self->part, offset, bufinfo.buf, bufinfo.len)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_partition_writeblocks_obj, 3, 4, esp32_partition_writeblocks); - -STATIC mp_obj_t esp32_partition_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t cmd = mp_obj_get_int(cmd_in); - switch (cmd) { - case MP_BLOCKDEV_IOCTL_INIT: - return MP_OBJ_NEW_SMALL_INT(0); - case MP_BLOCKDEV_IOCTL_DEINIT: - return MP_OBJ_NEW_SMALL_INT(0); - case MP_BLOCKDEV_IOCTL_SYNC: - return MP_OBJ_NEW_SMALL_INT(0); - case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: - return MP_OBJ_NEW_SMALL_INT(self->part->size / self->block_size); - case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: - return MP_OBJ_NEW_SMALL_INT(self->block_size); - case MP_BLOCKDEV_IOCTL_BLOCK_ERASE: { - if (self->block_size != NATIVE_BLOCK_SIZE_BYTES) { - return MP_OBJ_NEW_SMALL_INT(-MP_EINVAL); - } - uint32_t offset = mp_obj_get_int(arg_in) * NATIVE_BLOCK_SIZE_BYTES; - check_esp_err(esp_partition_erase_range(self->part, offset, NATIVE_BLOCK_SIZE_BYTES)); - return MP_OBJ_NEW_SMALL_INT(0); - } - default: - return mp_const_none; - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_partition_ioctl_obj, esp32_partition_ioctl); - -STATIC mp_obj_t esp32_partition_set_boot(mp_obj_t self_in) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); - check_esp_err(esp_ota_set_boot_partition(self->part)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_set_boot_obj, esp32_partition_set_boot); - -STATIC mp_obj_t esp32_partition_get_next_update(mp_obj_t self_in) { - esp32_partition_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(esp32_partition_new(esp_ota_get_next_update_partition(self->part), NATIVE_BLOCK_SIZE_BYTES)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_get_next_update_obj, esp32_partition_get_next_update); - -STATIC mp_obj_t esp32_partition_mark_app_valid_cancel_rollback(mp_obj_t cls_in) { - check_esp_err(esp_ota_mark_app_valid_cancel_rollback()); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_partition_mark_app_valid_cancel_rollback_fun_obj, - esp32_partition_mark_app_valid_cancel_rollback); -STATIC MP_DEFINE_CONST_CLASSMETHOD_OBJ(esp32_partition_mark_app_valid_cancel_rollback_obj, - MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_fun_obj)); - -STATIC const mp_rom_map_elem_t esp32_partition_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_find), MP_ROM_PTR(&esp32_partition_find_obj) }, - - { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&esp32_partition_info_obj) }, - { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&esp32_partition_readblocks_obj) }, - { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&esp32_partition_writeblocks_obj) }, - { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&esp32_partition_ioctl_obj) }, - - { MP_ROM_QSTR(MP_QSTR_set_boot), MP_ROM_PTR(&esp32_partition_set_boot_obj) }, - { MP_ROM_QSTR(MP_QSTR_mark_app_valid_cancel_rollback), MP_ROM_PTR(&esp32_partition_mark_app_valid_cancel_rollback_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_next_update), MP_ROM_PTR(&esp32_partition_get_next_update_obj) }, - - { MP_ROM_QSTR(MP_QSTR_BOOT), MP_ROM_INT(ESP32_PARTITION_BOOT) }, - { MP_ROM_QSTR(MP_QSTR_RUNNING), MP_ROM_INT(ESP32_PARTITION_RUNNING) }, - { MP_ROM_QSTR(MP_QSTR_TYPE_APP), MP_ROM_INT(ESP_PARTITION_TYPE_APP) }, - { MP_ROM_QSTR(MP_QSTR_TYPE_DATA), MP_ROM_INT(ESP_PARTITION_TYPE_DATA) }, -}; -STATIC MP_DEFINE_CONST_DICT(esp32_partition_locals_dict, esp32_partition_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - esp32_partition_type, - MP_QSTR_Partition, - MP_TYPE_FLAG_NONE, - make_new, esp32_partition_make_new, - print, esp32_partition_print, - locals_dict, &esp32_partition_locals_dict - ); diff --git a/tulip/esp32s3/esp32_rmt.c b/tulip/esp32s3/esp32_rmt.c deleted file mode 100644 index f92af636f..000000000 --- a/tulip/esp32s3/esp32_rmt.c +++ /dev/null @@ -1,378 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 "Matt Trentini" - * - * 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. - */ - -#include "py/runtime.h" -#include "modmachine.h" -#include "mphalport.h" -#include "modesp32.h" - -#include "esp_task.h" -#include "driver/rmt.h" - -// This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: -// -// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html -// -// With some examples provided: -// -// https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT -// -// RMT allows accurate (down to 12.5ns resolution) transmit - and receive - of pulse signals. -// Originally designed to generate infrared remote control signals, the module is very -// flexible and quite easy-to-use. -// -// This current MicroPython implementation lacks some major features, notably receive pulses -// and carrier output. - -// Last available RMT channel that can transmit. -#define RMT_LAST_TX_CHANNEL (SOC_RMT_TX_CANDIDATES_PER_GROUP - 1) - -// Forward declaration -extern const mp_obj_type_t esp32_rmt_type; - -typedef struct _esp32_rmt_obj_t { - mp_obj_base_t base; - uint8_t channel_id; - gpio_num_t pin; - uint8_t clock_div; - mp_uint_t num_items; - rmt_item32_t *items; - bool loop_en; -} esp32_rmt_obj_t; - -// Current channel used for machine.bitstream, in the machine_bitstream_high_low_rmt -// implementation. A value of -1 means do not use RMT. -int8_t esp32_rmt_bitstream_channel_id = RMT_LAST_TX_CHANNEL; - -#if MP_TASK_COREID == 0 - -typedef struct _rmt_install_state_t { - SemaphoreHandle_t handle; - uint8_t channel_id; - esp_err_t ret; -} rmt_install_state_t; - -STATIC void rmt_install_task(void *pvParameter) { - rmt_install_state_t *state = pvParameter; - state->ret = rmt_driver_install(state->channel_id, 0, 0); - xSemaphoreGive(state->handle); - vTaskDelete(NULL); - for (;;) { - } -} - -// Call rmt_driver_install on core 1. This ensures that the RMT interrupt handler is -// serviced on core 1, so that WiFi (if active) does not interrupt it and cause glitches. -esp_err_t rmt_driver_install_core1(uint8_t channel_id) { - TaskHandle_t th; - rmt_install_state_t state; - state.handle = xSemaphoreCreateBinary(); - state.channel_id = channel_id; - xTaskCreatePinnedToCore(rmt_install_task, "rmt_install_task", 2048 / sizeof(StackType_t), &state, ESP_TASK_PRIO_MIN + 1, &th, 1); - xSemaphoreTake(state.handle, portMAX_DELAY); - vSemaphoreDelete(state.handle); - return state.ret; -} - -#else - -// MicroPython runs on core 1, so we can call the RMT installer directly and its -// interrupt handler will also run on core 1. -esp_err_t rmt_driver_install_core1(uint8_t channel_id) { - return rmt_driver_install(channel_id, 0, 0); -} - -#endif - -STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution - { MP_QSTR_idle_level, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, // low voltage - { MP_QSTR_tx_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, // no carrier - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_uint_t channel_id = args[0].u_int; - gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj); - mp_uint_t clock_div = args[2].u_int; - mp_uint_t idle_level = args[3].u_bool; - mp_obj_t tx_carrier_obj = args[4].u_obj; - - if (esp32_rmt_bitstream_channel_id >= 0 && channel_id == esp32_rmt_bitstream_channel_id) { - mp_raise_ValueError(MP_ERROR_TEXT("channel used by bitstream")); - } - - if (clock_div < 1 || clock_div > 255) { - mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255")); - } - - esp32_rmt_obj_t *self = m_new_obj_with_finaliser(esp32_rmt_obj_t); - self->base.type = &esp32_rmt_type; - self->channel_id = channel_id; - self->pin = pin_id; - self->clock_div = clock_div; - self->loop_en = false; - - rmt_config_t config = {0}; - config.rmt_mode = RMT_MODE_TX; - config.channel = (rmt_channel_t)self->channel_id; - config.gpio_num = self->pin; - config.mem_block_num = 1; - config.tx_config.loop_en = 0; - - if (tx_carrier_obj != mp_const_none) { - mp_obj_t *tx_carrier_details = NULL; - mp_obj_get_array_fixed_n(tx_carrier_obj, 3, &tx_carrier_details); - mp_uint_t frequency = mp_obj_get_int(tx_carrier_details[0]); - mp_uint_t duty = mp_obj_get_int(tx_carrier_details[1]); - mp_uint_t level = mp_obj_is_true(tx_carrier_details[2]); - - if (frequency == 0) { - mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier frequency must be >0")); - } - if (duty > 100) { - mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier duty must be 0..100")); - } - - config.tx_config.carrier_en = 1; - config.tx_config.carrier_freq_hz = frequency; - config.tx_config.carrier_duty_percent = duty; - config.tx_config.carrier_level = level; - } else { - config.tx_config.carrier_en = 0; - } - - config.tx_config.idle_output_en = 1; - config.tx_config.idle_level = idle_level; - - config.clk_div = self->clock_div; - - check_esp_err(rmt_config(&config)); - check_esp_err(rmt_driver_install_core1(config.channel)); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (self->pin != -1) { - bool idle_output_en; - rmt_idle_level_t idle_level; - check_esp_err(rmt_get_idle_level(self->channel_id, &idle_output_en, &idle_level)); - mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u, idle_level=%u)", - self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div, idle_level); - } else { - mp_printf(print, "RMT()"); - } -} - -STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { - // fixme: check for valid channel. Return exception if error occurs. - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (self->pin != -1) { // Check if channel has already been deinitialised. - rmt_driver_uninstall(self->channel_id); - self->pin = -1; // -1 to indicate RMT is unused - m_free(self->items); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); - -// Return the source frequency. -// Currently only the APB clock (80MHz) can be used but it is possible other -// clock sources will added in the future. -STATIC mp_obj_t esp32_rmt_source_freq(mp_obj_t self_in) { - return mp_obj_new_int(APB_CLK_FREQ); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_source_freq_obj, esp32_rmt_source_freq); - -// Return the clock divider. -STATIC mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) { - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int(self->clock_div); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); - -// Query whether the channel has finished sending pulses. Takes an optional -// timeout (in milliseconds), returning true if the pulse stream has -// completed or false if they are still transmitting (or timeout is reached). -STATIC mp_obj_t esp32_rmt_wait_done(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - static const mp_arg_t allowed_args[] = { - { MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj); - - esp_err_t err = rmt_wait_tx_done(self->channel_id, args[1].u_int / portTICK_PERIOD_MS); - return err == ESP_OK ? mp_const_true : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done); - -STATIC mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) { - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - self->loop_en = mp_obj_get_int(loop); - if (!self->loop_en) { - bool loop_en; - check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); - if (loop_en) { - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); - check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); - } - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); - -STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *args) { - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0]); - mp_obj_t duration_obj = args[1]; - mp_obj_t data_obj = n_args > 2 ? args[2] : mp_const_true; - - mp_uint_t duration = 0; - size_t duration_length = 0; - mp_obj_t *duration_ptr = NULL; - mp_uint_t data = 0; - size_t data_length = 0; - mp_obj_t *data_ptr = NULL; - mp_uint_t num_pulses = 0; - - if (!(mp_obj_is_type(data_obj, &mp_type_tuple) || mp_obj_is_type(data_obj, &mp_type_list))) { - // Mode 1: array of durations, toggle initial data value - mp_obj_get_array(duration_obj, &duration_length, &duration_ptr); - data = mp_obj_is_true(data_obj); - num_pulses = duration_length; - } else if (mp_obj_is_int(duration_obj)) { - // Mode 2: constant duration, array of data values - duration = mp_obj_get_int(duration_obj); - mp_obj_get_array(data_obj, &data_length, &data_ptr); - num_pulses = data_length; - } else { - // Mode 3: arrays of durations and data values - mp_obj_get_array(duration_obj, &duration_length, &duration_ptr); - mp_obj_get_array(data_obj, &data_length, &data_ptr); - if (duration_length != data_length) { - mp_raise_ValueError(MP_ERROR_TEXT("duration and data must have same length")); - } - num_pulses = duration_length; - } - - if (num_pulses == 0) { - mp_raise_ValueError(MP_ERROR_TEXT("No pulses")); - } - if (self->loop_en && num_pulses > 126) { - mp_raise_ValueError(MP_ERROR_TEXT("Too many pulses for loop")); - } - - mp_uint_t num_items = (num_pulses / 2) + (num_pulses % 2); - if (num_items > self->num_items) { - self->items = (rmt_item32_t *)m_realloc(self->items, num_items * sizeof(rmt_item32_t *)); - self->num_items = num_items; - } - - for (mp_uint_t item_index = 0, pulse_index = 0; item_index < num_items; item_index++) { - self->items[item_index].duration0 = duration_length ? mp_obj_get_int(duration_ptr[pulse_index]) : duration; - self->items[item_index].level0 = data_length ? mp_obj_is_true(data_ptr[pulse_index]) : data++; - pulse_index++; - if (pulse_index < num_pulses) { - self->items[item_index].duration1 = duration_length ? mp_obj_get_int(duration_ptr[pulse_index]) : duration; - self->items[item_index].level1 = data_length ? mp_obj_is_true(data_ptr[pulse_index]) : data++; - pulse_index++; - } else { - self->items[item_index].duration1 = 0; - self->items[item_index].level1 = 0; - } - } - - if (self->loop_en) { - bool loop_en; - check_esp_err(rmt_get_tx_loop_mode(self->channel_id, &loop_en)); - if (loop_en) { - check_esp_err(rmt_set_tx_intr_en(self->channel_id, true)); - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, false)); - } - check_esp_err(rmt_wait_tx_done(self->channel_id, portMAX_DELAY)); - } - - if (self->loop_en) { - check_esp_err(rmt_set_tx_intr_en(self->channel_id, false)); - check_esp_err(rmt_set_tx_loop_mode(self->channel_id, true)); - } - - check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false)); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_write_pulses_obj, 2, 3, esp32_rmt_write_pulses); - -STATIC mp_obj_t esp32_rmt_bitstream_channel(size_t n_args, const mp_obj_t *args) { - if (n_args > 0) { - if (args[0] == mp_const_none) { - esp32_rmt_bitstream_channel_id = -1; - } else { - mp_int_t channel_id = mp_obj_get_int(args[0]); - if (channel_id < 0 || channel_id > RMT_LAST_TX_CHANNEL) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid channel")); - } - esp32_rmt_bitstream_channel_id = channel_id; - } - } - if (esp32_rmt_bitstream_channel_id < 0) { - return mp_const_none; - } else { - return MP_OBJ_NEW_SMALL_INT(esp32_rmt_bitstream_channel_id); - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_rmt_bitstream_channel_fun_obj, 0, 1, esp32_rmt_bitstream_channel); -STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(esp32_rmt_bitstream_channel_obj, MP_ROM_PTR(&esp32_rmt_bitstream_channel_fun_obj)); - -STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_source_freq), MP_ROM_PTR(&esp32_rmt_source_freq_obj) }, - { MP_ROM_QSTR(MP_QSTR_clock_div), MP_ROM_PTR(&esp32_rmt_clock_div_obj) }, - { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, - { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, - { MP_ROM_QSTR(MP_QSTR_write_pulses), MP_ROM_PTR(&esp32_rmt_write_pulses_obj) }, - - // Static methods - { MP_ROM_QSTR(MP_QSTR_bitstream_channel), MP_ROM_PTR(&esp32_rmt_bitstream_channel_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - esp32_rmt_type, - MP_QSTR_RMT, - MP_TYPE_FLAG_NONE, - make_new, esp32_rmt_make_new, - print, esp32_rmt_print, - locals_dict, &esp32_rmt_locals_dict - ); diff --git a/tulip/esp32s3/esp32_ulp.c b/tulip/esp32s3/esp32_ulp.c deleted file mode 100644 index 97041c60b..000000000 --- a/tulip/esp32s3/esp32_ulp.c +++ /dev/null @@ -1,107 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2018 "Andreas Valder" - * - * 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. - */ - -#include "py/runtime.h" - -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - -#if CONFIG_IDF_TARGET_ESP32 -#include "esp32/ulp.h" -#elif CONFIG_IDF_TARGET_ESP32S2 -#include "esp32s2/ulp.h" -#elif CONFIG_IDF_TARGET_ESP32S3 -#include "esp32s3/ulp.h" -#endif - -typedef struct _esp32_ulp_obj_t { - mp_obj_base_t base; -} esp32_ulp_obj_t; - -const mp_obj_type_t esp32_ulp_type; - -// singleton ULP object -STATIC const esp32_ulp_obj_t esp32_ulp_obj = {{&esp32_ulp_type}}; - -STATIC mp_obj_t esp32_ulp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // check arguments - mp_arg_check_num(n_args, n_kw, 0, 0, false); - - // return constant object - return (mp_obj_t)&esp32_ulp_obj; -} - -STATIC mp_obj_t esp32_ulp_set_wakeup_period(mp_obj_t self_in, mp_obj_t period_index_in, mp_obj_t period_us_in) { - mp_uint_t period_index = mp_obj_get_int(period_index_in); - mp_uint_t period_us = mp_obj_get_int(period_us_in); - int _errno = ulp_set_wakeup_period(period_index, period_us); - if (_errno != ESP_OK) { - mp_raise_OSError(_errno); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_set_wakeup_period_obj, esp32_ulp_set_wakeup_period); - -STATIC mp_obj_t esp32_ulp_load_binary(mp_obj_t self_in, mp_obj_t load_addr_in, mp_obj_t program_binary_in) { - mp_uint_t load_addr = mp_obj_get_int(load_addr_in); - - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(program_binary_in, &bufinfo, MP_BUFFER_READ); - - int _errno = ulp_load_binary(load_addr, bufinfo.buf, bufinfo.len / sizeof(uint32_t)); - if (_errno != ESP_OK) { - mp_raise_OSError(_errno); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_load_binary_obj, esp32_ulp_load_binary); - -STATIC mp_obj_t esp32_ulp_run(mp_obj_t self_in, mp_obj_t entry_point_in) { - mp_uint_t entry_point = mp_obj_get_int(entry_point_in); - int _errno = ulp_run(entry_point / sizeof(uint32_t)); - if (_errno != ESP_OK) { - mp_raise_OSError(_errno); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ulp_run_obj, esp32_ulp_run); - -STATIC const mp_rom_map_elem_t esp32_ulp_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_set_wakeup_period), MP_ROM_PTR(&esp32_ulp_set_wakeup_period_obj) }, - { MP_ROM_QSTR(MP_QSTR_load_binary), MP_ROM_PTR(&esp32_ulp_load_binary_obj) }, - { MP_ROM_QSTR(MP_QSTR_run), MP_ROM_PTR(&esp32_ulp_run_obj) }, - { MP_ROM_QSTR(MP_QSTR_RESERVE_MEM), MP_ROM_INT(CONFIG_ULP_COPROC_RESERVE_MEM) }, -}; -STATIC MP_DEFINE_CONST_DICT(esp32_ulp_locals_dict, esp32_ulp_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - esp32_ulp_type, - MP_QSTR_ULP, - MP_TYPE_FLAG_NONE, - make_new, esp32_ulp_make_new, - locals_dict, &esp32_ulp_locals_dict - ); - -#endif // CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 diff --git a/tulip/esp32s3/esp_lcd_touch.c b/tulip/esp32s3/esp_lcd_touch.c new file mode 100644 index 000000000..16a228e90 --- /dev/null +++ b/tulip/esp32s3/esp_lcd_touch.c @@ -0,0 +1,266 @@ +/* + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_log.h" +#include "esp_lcd_touch.h" + +static const char *TAG = "TP"; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +/******************************************************************************* +* Local variables +*******************************************************************************/ + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t esp_lcd_touch_enter_sleep(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + if (tp->enter_sleep == NULL) { + ESP_LOGE(TAG, "Sleep mode not supported!"); + return ESP_FAIL; + } else { + return tp->enter_sleep(tp); + } +} + +esp_err_t esp_lcd_touch_exit_sleep(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + if (tp->exit_sleep == NULL) { + ESP_LOGE(TAG, "Sleep mode not supported!"); + return ESP_FAIL; + } else { + return tp->exit_sleep(tp); + } +} + +esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + assert(tp->read_data != NULL); + + return tp->read_data(tp); +} + +bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num) +{ + bool touched = false; + + assert(tp != NULL); + assert(x != NULL); + assert(y != NULL); + assert(tp->get_xy != NULL); + + touched = tp->get_xy(tp, x, y, strength, point_num, max_point_num); + if (!touched) { + return false; + } + + /* Process coordinates by user */ + if (tp->config.process_coordinates != NULL) { + tp->config.process_coordinates(tp, x, y, strength, point_num, max_point_num); + } + + /* Software coordinates adjustment needed */ + bool sw_adj_needed = ((tp->config.flags.mirror_x && (tp->set_mirror_x == NULL)) || + (tp->config.flags.mirror_y && (tp->set_mirror_y == NULL)) || + (tp->config.flags.swap_xy && (tp->set_swap_xy == NULL))); + + /* Adjust all coordinates */ + for (int i = 0; (sw_adj_needed && i < *point_num); i++) { + + /* Mirror X coordinates (if not supported by HW) */ + if (tp->config.flags.mirror_x && tp->set_mirror_x == NULL) { + x[i] = tp->config.x_max - x[i]; + } + + /* Mirror Y coordinates (if not supported by HW) */ + if (tp->config.flags.mirror_y && tp->set_mirror_y == NULL) { + y[i] = tp->config.y_max - y[i]; + } + + /* Swap X and Y coordinates (if not supported by HW) */ + if (tp->config.flags.swap_xy && tp->set_swap_xy == NULL) { + uint16_t tmp = x[i]; + x[i] = y[i]; + y[i] = tmp; + } + } + + return touched; +} + +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) +esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state) +{ + assert(tp != NULL); + assert(state != NULL); + + *state = 0; + + if (tp->get_button_state) { + return tp->get_button_state(tp, n, state); + } else { + return ESP_ERR_NOT_SUPPORTED; + } + + return ESP_OK; +} +#endif + +esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap) +{ + assert(tp != NULL); + + tp->config.flags.swap_xy = swap; + + /* Is swap supported by HW? */ + if (tp->set_swap_xy) { + return tp->set_swap_xy(tp, swap); + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap) +{ + assert(tp != NULL); + assert(swap != NULL); + + /* Is swap supported by HW? */ + if (tp->get_swap_xy) { + return tp->get_swap_xy(tp, swap); + } else { + *swap = tp->config.flags.swap_xy; + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror) +{ + assert(tp != NULL); + + tp->config.flags.mirror_x = mirror; + + /* Is mirror supported by HW? */ + if (tp->set_mirror_x) { + return tp->set_mirror_x(tp, mirror); + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror) +{ + assert(tp != NULL); + assert(mirror != NULL); + + /* Is swap supported by HW? */ + if (tp->get_mirror_x) { + return tp->get_mirror_x(tp, mirror); + } else { + *mirror = tp->config.flags.mirror_x; + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror) +{ + assert(tp != NULL); + + tp->config.flags.mirror_y = mirror; + + /* Is mirror supported by HW? */ + if (tp->set_mirror_y) { + return tp->set_mirror_y(tp, mirror); + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror) +{ + assert(tp != NULL); + assert(mirror != NULL); + + /* Is swap supported by HW? */ + if (tp->get_mirror_y) { + return tp->get_mirror_y(tp, mirror); + } else { + *mirror = tp->config.flags.mirror_y; + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + + if (tp->del != NULL) { + return tp->del(tp); + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback) +{ + esp_err_t ret = ESP_OK; + assert(tp != NULL); + + /* Interrupt pin is not selected */ + if (tp->config.int_gpio_num == GPIO_NUM_NC) { + return ESP_ERR_INVALID_ARG; + } + + tp->config.interrupt_callback = callback; + + if (callback != NULL) { + ret = gpio_install_isr_service(0); + /* ISR service can be installed from user before, then it returns invalid state */ + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "GPIO ISR install failed"); + return ret; + } + /* Add GPIO ISR handler */ + ret = gpio_intr_enable(tp->config.int_gpio_num); + ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed"); + ret = gpio_isr_handler_add(tp->config.int_gpio_num, (gpio_isr_t)tp->config.interrupt_callback, tp); + ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR install failed"); + } else { + /* Remove GPIO ISR handler */ + ret = gpio_isr_handler_remove(tp->config.int_gpio_num); + ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR remove handler failed"); + ret = gpio_intr_disable(tp->config.int_gpio_num); + ESP_RETURN_ON_ERROR(ret, TAG, "GPIO ISR disable failed"); + } + + return ESP_OK; +} + +esp_err_t esp_lcd_touch_register_interrupt_callback_with_data(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback, void *user_data) +{ + assert(tp != NULL); + + tp->config.user_data = user_data; + return esp_lcd_touch_register_interrupt_callback(tp, callback); +} \ No newline at end of file diff --git a/tulip/esp32s3/esp_lcd_touch.h b/tulip/esp32s3/esp_lcd_touch.h new file mode 100644 index 000000000..9e7a37be8 --- /dev/null +++ b/tulip/esp32s3/esp_lcd_touch.h @@ -0,0 +1,429 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LCD touch + */ + +#pragma once + +#include +#include "sdkconfig.h" +#include "esp_err.h" +#include "driver/gpio.h" +#include "esp_lcd_panel_io.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Touch controller type + * + */ +typedef struct esp_lcd_touch_s esp_lcd_touch_t; +typedef esp_lcd_touch_t *esp_lcd_touch_handle_t; + +/** + * @brief Touch controller interrupt callback type + * + */ +typedef void (*esp_lcd_touch_interrupt_callback_t)(esp_lcd_touch_handle_t tp); + +/** + * @brief Touch Configuration Type + * + */ +typedef struct { + uint16_t x_max; /*!< X coordinates max (for mirroring) */ + uint16_t y_max; /*!< Y coordinates max (for mirroring) */ + + gpio_num_t rst_gpio_num; /*!< GPIO number of reset pin */ + gpio_num_t int_gpio_num; /*!< GPIO number of interrupt pin */ + + struct { + unsigned int reset: 1; /*!< Level of reset pin in reset */ + unsigned int interrupt: 1;/*!< Active Level of interrupt pin */ + } levels; + + struct { + unsigned int swap_xy: 1; /*!< Swap X and Y after read coordinates */ + unsigned int mirror_x: 1; /*!< Mirror X after read coordinates */ + unsigned int mirror_y: 1; /*!< Mirror Y after read coordinates */ + } flags; + + /*!< User callback called after get coordinates from touch controller for apply user adjusting */ + void (*process_coordinates)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); + /*!< User callback called after the touch interrupt occurred */ + esp_lcd_touch_interrupt_callback_t interrupt_callback; + /*!< User data passed to callback */ + void *user_data; + /*!< User data passed to driver */ + void *driver_data; +} esp_lcd_touch_config_t; + +typedef struct { + uint8_t points; /*!< Count of touch points saved */ + + struct { + uint16_t x; /*!< X coordinate */ + uint16_t y; /*!< Y coordinate */ + uint16_t strength; /*!< Strength */ + } coords[CONFIG_ESP_LCD_TOUCH_MAX_POINTS]; + +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + uint8_t buttons; /*!< Count of buttons states saved */ + + struct { + uint8_t status; /*!< Status of button */ + } button[CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS]; +#endif + + portMUX_TYPE lock; /*!< Lock for read/write */ +} esp_lcd_touch_data_t; + +/** + * @brief Declare of Touch Type + * + */ +struct esp_lcd_touch_s { + + /** + * @brief set touch controller into sleep mode + * + * @note This function is usually blocking. + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*enter_sleep)(esp_lcd_touch_handle_t tp); + + /** + * @brief set touch controller into normal mode + * + * @note This function is usually blocking. + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*exit_sleep)(esp_lcd_touch_handle_t tp); + + /** + * @brief Read data from touch controller (mandatory) + * + * @note This function is usually blocking. + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*read_data)(esp_lcd_touch_handle_t tp); + + /** + * @brief Get coordinates from touch controller (mandatory) + * + * @param tp: Touch handler + * @param x: Array of X coordinates + * @param y: Array of Y coordinates + * @param strength: Array of strengths + * @param point_num: Count of points touched (equals with count of items in x and y array) + * @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array) + * + * @return + * - Returns true, when touched and coordinates readed. Otherwise returns false. + */ + bool (*get_xy)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); + + +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + /** + * @brief Get button state (optional) + * + * @param tp: Touch handler + * @param n: Button index + * @param state: Button state + * + * @return + * - Returns true, when touched and coordinates readed. Otherwise returns false. + */ + esp_err_t (*get_button_state)(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state); +#endif + + /** + * @brief Swap X and Y after read coordinates (optional) + * If set, then not used SW swapping. + * + * @param tp: Touch handler + * @param swap: Set swap value + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*set_swap_xy)(esp_lcd_touch_handle_t tp, bool swap); + + /** + * @brief Are X and Y coordinates swapped (optional) + * + * @param tp: Touch handler + * @param swap: Get swap value + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*get_swap_xy)(esp_lcd_touch_handle_t tp, bool *swap); + + /** + * @brief Mirror X after read coordinates + * If set, then not used SW mirroring. + * + * @param tp: Touch handler + * @param mirror: Set X mirror value + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*set_mirror_x)(esp_lcd_touch_handle_t tp, bool mirror); + + /** + * @brief Is mirrored X (optional) + * + * @param tp: Touch handler + * @param mirror: Get X mirror value + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*get_mirror_x)(esp_lcd_touch_handle_t tp, bool *mirror); + + /** + * @brief Mirror Y after read coordinates + * If set, then not used SW mirroring. + * + * @param tp: Touch handler + * @param mirror: Set Y mirror value + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*set_mirror_y)(esp_lcd_touch_handle_t tp, bool mirror); + + /** + * @brief Is mirrored Y (optional) + * + * @param tp: Touch handler + * @param mirror: Get Y mirror value + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*get_mirror_y)(esp_lcd_touch_handle_t tp, bool *mirror); + + /** + * @brief Delete Touch + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success, otherwise returns ESP_ERR_xxx + */ + esp_err_t (*del)(esp_lcd_touch_handle_t tp); + + /** + * @brief Configuration structure + */ + esp_lcd_touch_config_t config; + + /** + * @brief Communication interface + */ + esp_lcd_panel_io_handle_t io; + + /** + * @brief Data structure + */ + esp_lcd_touch_data_t data; +}; + +/** + * @brief Read data from touch controller + * + * @note This function is usually blocking. + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG parameter error + * - ESP_FAIL sending command error, slave hasn't ACK the transfer + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode + * - ESP_ERR_TIMEOUT operation timeout because the bus is busy + */ +esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp); + +/** + * @brief Read coordinates from touch controller + * + * @param tp: Touch handler + * @param x: Array of X coordinates + * @param y: Array of Y coordinates + * @param strength: Array of the strengths (can be NULL) + * @param point_num: Count of points touched (equals with count of items in x and y array) + * @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array) + * + * @return + * - Returns true, when touched and coordinates readed. Otherwise returns false. + */ +bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); + + +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) +/** + * @brief Get button state + * + * @param tp: Touch handler + * @param n: Button index + * @param state: Button state + * + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if this function is not supported by controller + * - ESP_ERR_INVALID_ARG if bad button index + */ +esp_err_t esp_lcd_touch_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state); +#endif + +/** + * @brief Swap X and Y after read coordinates + * + * @param tp: Touch handler + * @param swap: Set swap value + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_set_swap_xy(esp_lcd_touch_handle_t tp, bool swap); + +/** + * @brief Are X and Y coordinates swapped + * + * @param tp: Touch handler + * @param swap: Get swap value + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_get_swap_xy(esp_lcd_touch_handle_t tp, bool *swap); + +/** + * @brief Mirror X after read coordinates + * + * @param tp: Touch handler + * @param mirror: Set X mirror value + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_set_mirror_x(esp_lcd_touch_handle_t tp, bool mirror); + +/** + * @brief Is mirrored X + * + * @param tp: Touch handler + * @param mirror: Get X mirror value + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_get_mirror_x(esp_lcd_touch_handle_t tp, bool *mirror); + +/** + * @brief Mirror Y after read coordinates + * + * @param tp: Touch handler + * @param mirror: Set Y mirror value + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_set_mirror_y(esp_lcd_touch_handle_t tp, bool mirror); + +/** + * @brief Is mirrored Y + * + * @param tp: Touch handler + * @param mirror: Get Y mirror value + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_get_mirror_y(esp_lcd_touch_handle_t tp, bool *mirror); + +/** + * @brief Delete touch (free all allocated memory and restart HW) + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t tp); + +/** + * @brief Register user callback called after the touch interrupt occurred + * + * @param tp: Touch handler + * @param callback: Interrupt callback + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback); + +/** + * @brief Register user callback called after the touch interrupt occurred with user data + * + * @param tp: Touch handler + * @param callback: Interrupt callback + * @param user_data: User data passed to callback + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_lcd_touch_register_interrupt_callback_with_data(esp_lcd_touch_handle_t tp, esp_lcd_touch_interrupt_callback_t callback, void *user_data); + +/** + * @brief Enter sleep mode + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if parameter is invalid + */ +esp_err_t esp_lcd_touch_enter_sleep(esp_lcd_touch_handle_t tp); + +/** + * @brief Exit sleep mode + * + * @param tp: Touch handler + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if parameter is invalid + */ +esp_err_t esp_lcd_touch_exit_sleep(esp_lcd_touch_handle_t tp); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/tulip/esp32s3/esp_lcd_touch_gt911.c b/tulip/esp32s3/esp_lcd_touch_gt911.c new file mode 100644 index 000000000..5b591cb1a --- /dev/null +++ b/tulip/esp32s3/esp_lcd_touch_gt911.c @@ -0,0 +1,387 @@ +/* + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_touch.h" +#include "esp_lcd_touch_gt911.h" + +static const char *TAG = "GT911"; + +/* GT911 registers */ +#define ESP_LCD_TOUCH_GT911_READ_KEY_REG (0x8093) +#define ESP_LCD_TOUCH_GT911_READ_XY_REG (0x814E) +#define ESP_LCD_TOUCH_GT911_CONFIG_REG (0x8047) +#define ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG (0x8140) +#define ESP_LCD_TOUCH_GT911_ENTER_SLEEP (0x8040) + +/* GT911 support key num */ +#define ESP_GT911_TOUCH_MAX_BUTTONS (4) + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp); +static bool esp_lcd_touch_gt911_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) +static esp_err_t esp_lcd_touch_gt911_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state); +#endif +static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp); + +/* I2C read/write */ +static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len); +static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data); + +/* GT911 reset */ +static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp); +/* Read status and config register */ +static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp); + +/* GT911 enter/exit sleep mode */ +static esp_err_t esp_lcd_touch_gt911_enter_sleep(esp_lcd_touch_handle_t tp); +static esp_err_t esp_lcd_touch_gt911_exit_sleep(esp_lcd_touch_handle_t tp); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch) +{ + esp_err_t ret = ESP_OK; + + assert(io != NULL); + assert(config != NULL); + assert(out_touch != NULL); + + /* Prepare main structure */ + esp_lcd_touch_handle_t esp_lcd_touch_gt911 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT); + ESP_GOTO_ON_FALSE(esp_lcd_touch_gt911, ESP_ERR_NO_MEM, err, TAG, "no mem for GT911 controller"); + + /* Communication interface */ + esp_lcd_touch_gt911->io = io; + + /* Only supported callbacks are set */ + esp_lcd_touch_gt911->read_data = esp_lcd_touch_gt911_read_data; + esp_lcd_touch_gt911->get_xy = esp_lcd_touch_gt911_get_xy; +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + esp_lcd_touch_gt911->get_button_state = esp_lcd_touch_gt911_get_button_state; +#endif + esp_lcd_touch_gt911->del = esp_lcd_touch_gt911_del; + esp_lcd_touch_gt911->enter_sleep = esp_lcd_touch_gt911_enter_sleep; + esp_lcd_touch_gt911->exit_sleep = esp_lcd_touch_gt911_exit_sleep; + + /* Mutex */ + esp_lcd_touch_gt911->data.lock.owner = portMUX_FREE_VAL; + + /* Save config */ + memcpy(&esp_lcd_touch_gt911->config, config, sizeof(esp_lcd_touch_config_t)); + esp_lcd_touch_io_gt911_config_t *gt911_config = (esp_lcd_touch_io_gt911_config_t *)esp_lcd_touch_gt911->config.driver_data; + + /* Prepare pin for touch controller reset */ + if (esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC) { + const gpio_config_t rst_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.rst_gpio_num) + }; + ret = gpio_config(&rst_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + } + + if (gt911_config && esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC) { + ESP_RETURN_ON_ERROR(gpio_set_level(esp_lcd_touch_gt911->config.rst_gpio_num, esp_lcd_touch_gt911->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(11)); + ESP_RETURN_ON_ERROR(gpio_set_level(esp_lcd_touch_gt911->config.rst_gpio_num, !esp_lcd_touch_gt911->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(60)); + } else { + ESP_LOGW(TAG, "Unable to initialize the I2C address"); + /* Reset controller */ + ret = touch_gt911_reset(esp_lcd_touch_gt911); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 reset failed"); + } + + /* Prepare pin for touch interrupt */ + if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) { + const gpio_config_t int_gpio_config = { + .mode = GPIO_MODE_INPUT, + .intr_type = (esp_lcd_touch_gt911->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE), + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + + /* Register interrupt callback */ + if (esp_lcd_touch_gt911->config.interrupt_callback) { + esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_gt911, esp_lcd_touch_gt911->config.interrupt_callback); + } + } + + /* Read status and config info */ + ret = touch_gt911_read_cfg(esp_lcd_touch_gt911); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 init failed"); + +err: + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (0x%x)! Touch controller GT911 initialization failed!", ret); + if (esp_lcd_touch_gt911) { + esp_lcd_touch_gt911_del(esp_lcd_touch_gt911); + } + } + + *out_touch = esp_lcd_touch_gt911; + + return ret; +} + +static esp_err_t esp_lcd_touch_gt911_enter_sleep(esp_lcd_touch_handle_t tp) +{ + esp_err_t err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_ENTER_SLEEP, 0x05); + ESP_RETURN_ON_ERROR(err, TAG, "Enter Sleep failed!"); + + return ESP_OK; +} + +static esp_err_t esp_lcd_touch_gt911_exit_sleep(esp_lcd_touch_handle_t tp) +{ + esp_err_t ret; + esp_lcd_touch_handle_t esp_lcd_touch_gt911 = tp; + + if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) { + const gpio_config_t int_gpio_config_high = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config_high); + ESP_RETURN_ON_ERROR(ret, TAG, "High GPIO config failed"); + gpio_set_level(esp_lcd_touch_gt911->config.int_gpio_num, 1); + + vTaskDelay(pdMS_TO_TICKS(5)); + + const gpio_config_t int_gpio_config_float = { + .mode = GPIO_MODE_OUTPUT_OD, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config_float); + ESP_RETURN_ON_ERROR(ret, TAG, "Float GPIO config failed"); + } + + return ESP_OK; +} + +static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp) +{ + esp_err_t err; + uint8_t buf[41]; + uint8_t touch_cnt = 0; + uint8_t clear = 0; + size_t i = 0; + + assert(tp != NULL); + + err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, buf, 1); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + /* Any touch data? */ + if ((buf[0] & 0x80) == 0x00) { + touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + } else if ((buf[0] & 0x10) == 0x10) { + /* Read all keys */ + uint8_t key_max = ((ESP_GT911_TOUCH_MAX_BUTTONS < CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS) ? \ + (ESP_GT911_TOUCH_MAX_BUTTONS) : (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS)); + err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_KEY_REG, &buf[0], key_max); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + /* Clear all */ + touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); + ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Buttons count */ + tp->data.buttons = key_max; + for (i = 0; i < key_max; i++) { + tp->data.button[i].status = buf[0] ? 1 : 0; + } + + portEXIT_CRITICAL(&tp->data.lock); +#endif + } else if ((buf[0] & 0x80) == 0x80) { +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + portENTER_CRITICAL(&tp->data.lock); + for (i = 0; i < CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS; i++) { + tp->data.button[i].status = 0; + } + portEXIT_CRITICAL(&tp->data.lock); +#endif + /* Count of touched points */ + touch_cnt = buf[0] & 0x0f; + if (touch_cnt > 5 || touch_cnt == 0) { + touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); + return ESP_OK; + } + + /* Read all points */ + err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG + 1, &buf[1], touch_cnt * 8); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + /* Clear all */ + err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Number of touched points */ + touch_cnt = (touch_cnt > CONFIG_ESP_LCD_TOUCH_MAX_POINTS ? CONFIG_ESP_LCD_TOUCH_MAX_POINTS : touch_cnt); + tp->data.points = touch_cnt; + + /* Fill all coordinates */ + for (i = 0; i < touch_cnt; i++) { + tp->data.coords[i].x = ((uint16_t)buf[(i * 8) + 3] << 8) + buf[(i * 8) + 2]; + tp->data.coords[i].y = (((uint16_t)buf[(i * 8) + 5] << 8) + buf[(i * 8) + 4]); + tp->data.coords[i].strength = (((uint16_t)buf[(i * 8) + 7] << 8) + buf[(i * 8) + 6]); + } + + portEXIT_CRITICAL(&tp->data.lock); + } + + return ESP_OK; +} + +static bool esp_lcd_touch_gt911_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num) +{ + assert(tp != NULL); + assert(x != NULL); + assert(y != NULL); + assert(point_num != NULL); + assert(max_point_num > 0); + + portENTER_CRITICAL(&tp->data.lock); + + /* Count of points */ + *point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points); + + for (size_t i = 0; i < *point_num; i++) { + x[i] = tp->data.coords[i].x; + y[i] = tp->data.coords[i].y; + + if (strength) { + strength[i] = tp->data.coords[i].strength; + } + } + + /* Invalidate */ + tp->data.points = 0; + + portEXIT_CRITICAL(&tp->data.lock); + + return (*point_num > 0); +} + +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) +static esp_err_t esp_lcd_touch_gt911_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state) +{ + esp_err_t err = ESP_OK; + assert(tp != NULL); + assert(state != NULL); + + *state = 0; + + portENTER_CRITICAL(&tp->data.lock); + + if (n > tp->data.buttons) { + err = ESP_ERR_INVALID_ARG; + } else { + *state = tp->data.button[n].status; + } + + portEXIT_CRITICAL(&tp->data.lock); + + return err; +} +#endif + +static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + + /* Reset GPIO pin settings */ + if (tp->config.int_gpio_num != GPIO_NUM_NC) { + gpio_reset_pin(tp->config.int_gpio_num); + if (tp->config.interrupt_callback) { + gpio_isr_handler_remove(tp->config.int_gpio_num); + } + } + + /* Reset GPIO pin settings */ + if (tp->config.rst_gpio_num != GPIO_NUM_NC) { + gpio_reset_pin(tp->config.rst_gpio_num); + } + + free(tp); + + return ESP_OK; +} + +/******************************************************************************* +* Private API function +*******************************************************************************/ + +/* Reset controller */ +static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp) +{ + assert(tp != NULL); + + if (tp->config.rst_gpio_num != GPIO_NUM_NC) { + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + return ESP_OK; +} + +static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp) +{ + uint8_t buf[4]; + + assert(tp != NULL); + + ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG, (uint8_t *)&buf[0], 3), TAG, "GT911 read error!"); + ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_CONFIG_REG, (uint8_t *)&buf[3], 1), TAG, "GT911 read error!"); + + ESP_LOGI(TAG, "TouchPad_ID:0x%02x,0x%02x,0x%02x", buf[0], buf[1], buf[2]); + ESP_LOGI(TAG, "TouchPad_Config_Version:%d", buf[3]); + + return ESP_OK; +} + +static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len) +{ + assert(tp != NULL); + assert(data != NULL); + + /* Read data */ + return esp_lcd_panel_io_rx_param(tp->io, reg, data, len); +} + +static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data) +{ + assert(tp != NULL); + + // *INDENT-OFF* + /* Write data */ + return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1); + // *INDENT-ON* +} \ No newline at end of file diff --git a/tulip/esp32s3/esp_lcd_touch_gt911.h b/tulip/esp32s3/esp_lcd_touch_gt911.h new file mode 100644 index 000000000..fa0c7daab --- /dev/null +++ b/tulip/esp32s3/esp_lcd_touch_gt911.h @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LCD touch: GT911 + */ + +#pragma once + +#include "esp_lcd_touch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a new GT911 touch driver + * + * @note The I2C communication should be initialized before use this function. + * + * @param io LCD/Touch panel IO handle + * @param config: Touch configuration + * @param out_touch: Touch instance handle + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if there is no memory for allocating main structure + */ +esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, esp_lcd_touch_handle_t *out_touch); + +/** + * @brief I2C address of the GT911 controller + * + * @note When power-on detects low level of the interrupt gpio, address is 0x5D. + * @note Interrupt gpio is high level, address is 0x14. + * + */ +#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS (0x5D) +#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP (0x14) + +/** + * @brief GT911 Configuration Type + * + */ +typedef struct { + uint8_t dev_addr; /*!< I2C device address */ +} esp_lcd_touch_io_gt911_config_t; + +/** + * @brief Touch IO configuration structure + * + */ +#define ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG() \ + { \ + .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, \ + .control_phase_bytes = 1, \ + .dc_bit_offset = 0, \ + .lcd_cmd_bits = 16, \ + .flags = \ + { \ + .disable_control_phase = 1, \ + } \ + } + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/tulip/esp32s3/fatfs_port.c b/tulip/esp32s3/fatfs_port.c deleted file mode 100644 index b9ad30a12..000000000 --- a/tulip/esp32s3/fatfs_port.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2013, 2014, 2016 Damien P. George - * - * 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. - */ - -#include -#include "lib/oofatfs/ff.h" -#include "shared/timeutils/timeutils.h" - -DWORD get_fattime(void) { - struct timeval tv; - gettimeofday(&tv, NULL); - timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); - - return ((DWORD)(tm.tm_year - 1980) << 25) | ((DWORD)tm.tm_mon << 21) | ((DWORD)tm.tm_mday << 16) | - ((DWORD)tm.tm_hour << 11) | ((DWORD)tm.tm_min << 5) | ((DWORD)tm.tm_sec >> 1); -} diff --git a/tulip/esp32s3/gccollect.c b/tulip/esp32s3/gccollect.c deleted file mode 100644 index 6fa287de2..000000000 --- a/tulip/esp32s3/gccollect.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Damien P. George - * Copyright (c) 2017 Pycom Limited - * - * 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. - */ - -#include - -#include "py/mpconfig.h" -#include "py/mpstate.h" -#include "py/gc.h" -#include "py/mpthread.h" -#include "gccollect.h" - -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - -#include "xtensa/hal.h" - -static void gc_collect_inner(int level) { - if (level < XCHAL_NUM_AREGS / 8) { - gc_collect_inner(level + 1); - if (level != 0) { - return; - } - } - - if (level == XCHAL_NUM_AREGS / 8) { - // get the sp - volatile uint32_t sp = (uint32_t)esp_cpu_get_sp(); - gc_collect_root((void **)sp, ((mp_uint_t)MP_STATE_THREAD(stack_top) - sp) / sizeof(uint32_t)); - return; - } - - // trace root pointers from any threads - #if MICROPY_PY_THREAD - mp_thread_gc_others(); - #endif -} - -void gc_collect(void) { - gc_collect_start(); - gc_collect_inner(0); - gc_collect_end(); -} - -#elif CONFIG_IDF_TARGET_ESP32C3 - -#include "shared/runtime/gchelper.h" - -void gc_collect(void) { - gc_collect_start(); - gc_helper_collect_regs_and_stack(); - #if MICROPY_PY_THREAD - mp_thread_gc_others(); - #endif - gc_collect_end(); -} - -#endif diff --git a/tulip/esp32s3/gt911_touchscreen.c b/tulip/esp32s3/gt911_touchscreen.c index 8f5f0463f..05d935853 100644 --- a/tulip/esp32s3/gt911_touchscreen.c +++ b/tulip/esp32s3/gt911_touchscreen.c @@ -2,6 +2,8 @@ #include "display.h" #include "gt911_touchscreen.h" #include "pins.h" +#include "driver/i2c.h" + #ifndef TDECK // Default for my V4R11 int16_t touch_x_delta = -2; @@ -16,10 +18,11 @@ float touch_y_scale = 1.0f; esp_lcd_touch_handle_t tp; -void touch_init(uint8_t alternate) { +esp_err_t touch_init(uint8_t alternate) { tp = NULL; - esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_handle_t io_handle = NULL; + i2c_config_t i2c_conf = { .mode = I2C_MODE_MASTER, .sda_io_num = I2C_SDA, @@ -28,16 +31,22 @@ void touch_init(uint8_t alternate) { .scl_pullup_en = GPIO_PULLUP_DISABLE, .master.clk_speed = I2C_CLK_FREQ, }; - ESP_ERROR_CHECK(i2c_param_config(I2C_NUM, &i2c_conf)); ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM, i2c_conf.mode, 0, 0, 0)); + + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + + uint8_t dev_addr = 0x5D; if(alternate) { - io_config.dev_addr = 0x14; + dev_addr = 0x14; } - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)I2C_NUM, &io_config, &io_handle)); + esp_lcd_touch_io_gt911_config_t tp_gt911_config = { + .dev_addr = dev_addr, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)I2C_NUM, &io_config, &io_handle)); esp_lcd_touch_config_t tp_cfg = { .x_max = H_RES, @@ -50,7 +59,7 @@ void touch_init(uint8_t alternate) { #elif defined MATOUCH7 .reset = 1, #elif defined TULIP4_R11 - .reset = 0, + .reset = 1, #else .reset = 0, #endif @@ -67,9 +76,10 @@ void touch_init(uint8_t alternate) { .mirror_y = 0, #endif }, + .driver_data = &tp_gt911_config, }; - - ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(io_handle, &tp_cfg, &tp)); + return esp_lcd_touch_new_i2c_gt911(io_handle, &tp_cfg, &tp); + } @@ -81,42 +91,40 @@ void run_gt911(void *param) { uint16_t touch_y[3]; uint16_t touch_strength[3]; uint8_t touch_cnt = 0; - - #ifdef TULIP4_R11 + // We wait a bit before initializing the touchscreen + delay_ms(2000); + uint8_t touchscreen_ok = 1; + + // We have to toggle the RST pin more than once (the driver only does it once) + // So we do that here, wait a second, then init the driver. fprintf(stderr, "Resetting touch i2c RST pin twice\n"); - gpio_config_t peri_gpio_config = { + const gpio_config_t rst_gpio_config = { .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = 1ULL << TOUCH_RST + .pin_bit_mask = BIT64(TOUCH_RST) }; - gpio_config(&peri_gpio_config); - - gpio_set_level(TOUCH_RST, 0); - delay_ms(500); + gpio_config(&rst_gpio_config); + gpio_set_level(TOUCH_RST, 1); - delay_ms(500); - gpio_set_level(TOUCH_RST, 0); - delay_ms(500); + delay_ms(11); + gpio_set_level(TOUCH_RST, !1); + delay_ms(60); gpio_set_level(TOUCH_RST, 1); - delay_ms(500); - #endif - - touch_init(0); - uint8_t bytes[1] = {0}; - esp_err_t err= i2c_master_write_to_device(I2C_NUM_0, 0x5D, bytes, 0, pdMS_TO_TICKS(10)); - if(err) fprintf(stderr, "\nerr for writing to 0x5D is %d %s\n", err, esp_err_to_name(err)); - uint8_t touchscreen_ok = 1; - if(err != 0) { + delay_ms(11); + gpio_set_level(TOUCH_RST, !1); + delay_ms(1000); + if(touch_init(0) != ESP_OK) { fprintf(stderr, "attempting to fall back on 0x14 for touch\n"); - touch_init(1); - err= i2c_master_write_to_device(I2C_NUM_0, 0x14, bytes, 0, pdMS_TO_TICKS(10)); - if(err) fprintf(stderr, "\nerr for writing to 0x14 is %d %s\n", err, esp_err_to_name(err)); - if(err != 0) { - // no touchscreen. probably not connected. + delay_ms(500); + if(touch_init(1) != ESP_OK) { fprintf(stderr, "Touchscreen could not be booted. Please try fixing the cable and restarting.\n"); - touchscreen_ok = 0; + } else { + fprintf(stderr, "touchscreen OK at 0x14\n"); + touchscreen_ok = 1; } + } else { + fprintf(stderr, "touchscreen OK at 0x5D\n"); + touchscreen_ok = 1; } - while(1) { if (touchscreen_ok) { diff --git a/tulip/esp32s3/gt911_touchscreen.h b/tulip/esp32s3/gt911_touchscreen.h index 811b756d0..d05cf91c6 100644 --- a/tulip/esp32s3/gt911_touchscreen.h +++ b/tulip/esp32s3/gt911_touchscreen.h @@ -1,7 +1,6 @@ #ifndef __TOUCHSCREENH__ #define __TOUCHSCREENH__ #include "esp_lcd_touch_gt911.h" -#include "driver/i2c.h" void run_gt911(void *param); diff --git a/tulip/esp32s3/machine_adc.c b/tulip/esp32s3/machine_adc.c deleted file mode 100644 index 1e20186b9..000000000 --- a/tulip/esp32s3/machine_adc.c +++ /dev/null @@ -1,266 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 Nick Moore - * Copyright (c) 2021 Jonathan Hogg - * - * 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. - */ - -#include - -#include "esp_log.h" - -#include "driver/gpio.h" -#include "driver/adc.h" - -#include "py/runtime.h" -#include "py/mphal.h" -#include "modmachine.h" -#include "machine_adc.h" - -#define ADCBLOCK1 (&madcblock_obj[0]) -#define ADCBLOCK2 (&madcblock_obj[1]) - -STATIC const madc_obj_t madc_obj[] = { - #if CONFIG_IDF_TARGET_ESP32 - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_36}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_37}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_38}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_39}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_32}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_33}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_34}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_35}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_4}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_0}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_2}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_15}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_13}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_12}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_6, GPIO_NUM_14}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_27}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_25}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_26}, - #elif CONFIG_IDF_TARGET_ESP32C3 - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_0}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_1}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_2}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_3}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_4}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_5}, - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_0, GPIO_NUM_1}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_1, GPIO_NUM_2}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_2, GPIO_NUM_3}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_3, GPIO_NUM_4}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_4, GPIO_NUM_5}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_5, GPIO_NUM_6}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_6, GPIO_NUM_7}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_7, GPIO_NUM_8}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_8, GPIO_NUM_9}, - {{&machine_adc_type}, ADCBLOCK1, ADC_CHANNEL_9, GPIO_NUM_10}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_0, GPIO_NUM_11}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_1, GPIO_NUM_12}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_2, GPIO_NUM_13}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_3, GPIO_NUM_14}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_4, GPIO_NUM_15}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_5, GPIO_NUM_16}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_6, GPIO_NUM_17}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_7, GPIO_NUM_18}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_8, GPIO_NUM_19}, - {{&machine_adc_type}, ADCBLOCK2, ADC_CHANNEL_9, GPIO_NUM_20}, - #endif -}; - -// These values are initialised to 0, which means the corresponding ADC channel is not initialised. -// The madc_atten_get/madc_atten_set functions store (atten+1) here so that the uninitialised state -// can be distinguished from the initialised state. -STATIC uint8_t madc_obj_atten[MP_ARRAY_SIZE(madc_obj)]; - -static inline adc_atten_t madc_atten_get(const madc_obj_t *self) { - uint8_t value = madc_obj_atten[self - &madc_obj[0]]; - return value == 0 ? ADC_ATTEN_MAX : value - 1; -} - -static inline void madc_atten_set(const madc_obj_t *self, adc_atten_t atten) { - madc_obj_atten[self - &madc_obj[0]] = atten + 1; -} - -const madc_obj_t *madc_search_helper(madcblock_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id) { - for (int i = 0; i < MP_ARRAY_SIZE(madc_obj); i++) { - const madc_obj_t *adc = &madc_obj[i]; - if ((block == NULL || block == adc->block) && (channel_id == -1 || channel_id == adc->channel_id) && (gpio_id == -1 || gpio_id == adc->gpio_id)) { - return adc; - } - } - return NULL; -} - -STATIC void madc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "ADC(Pin(%u), atten=%u)", self->gpio_id, madc_atten_get(self)); -} - -STATIC void madc_atten_helper(const madc_obj_t *self, mp_int_t atten) { - esp_err_t err; - if (self->block->unit_id == ADC_UNIT_1) { - err = adc1_config_channel_atten(self->channel_id, atten); - } else { - err = adc2_config_channel_atten(self->channel_id, atten); - } - if (err != ESP_OK) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid atten")); - } - madc_atten_set(self, atten); -} - -void madc_init_helper(const madc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { - ARG_atten, - }; - - static const mp_arg_t allowed_args[] = { - { MP_QSTR_atten, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - mp_int_t atten = args[ARG_atten].u_int; - if (atten != -1) { - madc_atten_helper(self, atten); - } else if (madc_atten_get(self) == ADC_ATTEN_MAX) { - madc_atten_helper(self, ADC_ATTEN_DB_0); - } -} - -STATIC mp_obj_t madc_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { - mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); - gpio_num_t gpio_id = machine_pin_get_id(args[0]); - const madc_obj_t *self = madc_search_helper(NULL, -1, gpio_id); - if (!self) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); - } - - if (self->block->width == -1) { - madcblock_bits_helper(self->block, self->block->bits); - } - - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); - madc_init_helper(self, n_pos_args - 1, args + 1, &kw_args); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t madc_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - const madc_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - madc_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madc_init_obj, 1, madc_init); - -STATIC mp_obj_t madc_block(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_FROM_PTR(self->block); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_block_obj, madc_block); - -STATIC mp_obj_t madc_read(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t raw = madcblock_read_helper(self->block, self->channel_id); - return MP_OBJ_NEW_SMALL_INT(raw); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_obj, madc_read); - -STATIC mp_obj_t madc_read_u16(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_uint_t raw = madcblock_read_helper(self->block, self->channel_id); - // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) - mp_int_t bits = self->block->bits; - mp_uint_t u16 = raw << (16 - bits) | raw >> (2 * bits - 16); - return MP_OBJ_NEW_SMALL_INT(u16); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_u16_obj, madc_read_u16); - -STATIC mp_obj_t madc_read_uv(mp_obj_t self_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - adc_atten_t atten = madc_atten_get(self); - return MP_OBJ_NEW_SMALL_INT(madcblock_read_uv_helper(self->block, self->channel_id, atten)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(madc_read_uv_obj, madc_read_uv); - -STATIC mp_obj_t madc_atten(mp_obj_t self_in, mp_obj_t atten_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t atten = mp_obj_get_int(atten_in); - madc_atten_helper(self, atten); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(madc_atten_obj, madc_atten); - -STATIC mp_obj_t madc_width(mp_obj_t self_in, mp_obj_t bits_in) { - const madc_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t bits = mp_obj_get_int(bits_in); - madcblock_bits_helper(self->block, bits); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_2(madc_width_obj, madc_width); - -STATIC const mp_rom_map_elem_t madc_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&madc_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_block), MP_ROM_PTR(&madc_block_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&madc_read_obj) }, - { MP_ROM_QSTR(MP_QSTR_read_u16), MP_ROM_PTR(&madc_read_u16_obj) }, - { MP_ROM_QSTR(MP_QSTR_read_uv), MP_ROM_PTR(&madc_read_uv_obj) }, - - // Legacy API methods: - { MP_ROM_QSTR(MP_QSTR_atten), MP_ROM_PTR(&madc_atten_obj) }, - { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&madc_width_obj) }, - - { MP_ROM_QSTR(MP_QSTR_ATTN_0DB), MP_ROM_INT(ADC_ATTEN_DB_0) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_2_5DB), MP_ROM_INT(ADC_ATTEN_DB_2_5) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_6DB), MP_ROM_INT(ADC_ATTEN_DB_6) }, - { MP_ROM_QSTR(MP_QSTR_ATTN_11DB), MP_ROM_INT(ADC_ATTEN_DB_11) }, - - #if CONFIG_IDF_TARGET_ESP32 - { MP_ROM_QSTR(MP_QSTR_WIDTH_9BIT), MP_ROM_INT(9) }, - { MP_ROM_QSTR(MP_QSTR_WIDTH_10BIT), MP_ROM_INT(10) }, - { MP_ROM_QSTR(MP_QSTR_WIDTH_11BIT), MP_ROM_INT(11) }, - #endif - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - { MP_ROM_QSTR(MP_QSTR_WIDTH_12BIT), MP_ROM_INT(12) }, - #endif - #if CONFIG_IDF_TARGET_ESP32S2 - { MP_ROM_QSTR(MP_QSTR_WIDTH_13BIT), MP_ROM_INT(13) }, - #endif - -}; -STATIC MP_DEFINE_CONST_DICT(madc_locals_dict, madc_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_adc_type, - MP_QSTR_ADC, - MP_TYPE_FLAG_NONE, - make_new, madc_make_new, - print, madc_print, - locals_dict, &madc_locals_dict - ); diff --git a/tulip/esp32s3/machine_adc.h b/tulip/esp32s3/machine_adc.h deleted file mode 100644 index 0f229a2c5..000000000 --- a/tulip/esp32s3/machine_adc.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MICROPY_INCLUDED_MACHINE_ADC_H -#define MICROPY_INCLUDED_MACHINE_ADC_H - -#include "machine_adcblock.h" - -typedef struct _madc_obj_t { - mp_obj_base_t base; - madcblock_obj_t *block; - adc_channel_t channel_id; - gpio_num_t gpio_id; -} madc_obj_t; - -extern const madc_obj_t *madc_search_helper(madcblock_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id); -extern void madc_init_helper(const madc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args); - -#endif // MICROPY_INCLUDED_MACHINE_ADC_H diff --git a/tulip/esp32s3/machine_adcblock.c b/tulip/esp32s3/machine_adcblock.c deleted file mode 100644 index afe8fdea4..000000000 --- a/tulip/esp32s3/machine_adcblock.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Jonathan Hogg - * - * 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. - */ - -#include - -#include "esp_log.h" - -#include "driver/gpio.h" -#include "driver/adc.h" - -#include "py/runtime.h" -#include "py/mphal.h" -#include "modmachine.h" -#include "machine_adc.h" -#include "machine_adcblock.h" - -#define DEFAULT_VREF 1100 - -madcblock_obj_t madcblock_obj[] = { - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - {{&machine_adcblock_type}, ADC_UNIT_1, 12, -1, {0}}, - {{&machine_adcblock_type}, ADC_UNIT_2, 12, -1, {0}}, - #elif CONFIG_IDF_TARGET_ESP32S2 - {{&machine_adcblock_type}, ADC_UNIT_1, 13, -1, {0}}, - {{&machine_adcblock_type}, ADC_UNIT_2, 13, -1, {0}}, - #endif -}; - -STATIC void madcblock_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - madcblock_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "ADCBlock(%u, bits=%u)", self->unit_id, self->bits); -} - -void madcblock_bits_helper(madcblock_obj_t *self, mp_int_t bits) { - switch (bits) { - #if CONFIG_IDF_TARGET_ESP32 - case 9: - self->width = ADC_WIDTH_BIT_9; - break; - case 10: - self->width = ADC_WIDTH_BIT_10; - break; - case 11: - self->width = ADC_WIDTH_BIT_11; - break; - #endif - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 - case 12: - self->width = ADC_WIDTH_BIT_12; - break; - #endif - #if CONFIG_IDF_TARGET_ESP32S2 - case 13: - self->width = ADC_WIDTH_BIT_13; - break; - #endif - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); - } - self->bits = bits; - - if (self->unit_id == ADC_UNIT_1) { - adc1_config_width(self->width); - } - for (adc_atten_t atten = ADC_ATTEN_DB_0; atten < ADC_ATTEN_MAX; atten++) { - if (self->characteristics[atten] != NULL) { - esp_adc_cal_characterize(self->unit_id, atten, self->width, DEFAULT_VREF, self->characteristics[atten]); - } - } -} - -STATIC void madcblock_init_helper(madcblock_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { - ARG_bits, - }; - - static const mp_arg_t allowed_args[] = { - { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - mp_int_t bits = args[ARG_bits].u_int; - if (bits != -1) { - madcblock_bits_helper(self, bits); - } else if (self->width == -1) { - madcblock_bits_helper(self, self->bits); - } -} - -STATIC mp_obj_t madcblock_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { - mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); - adc_unit_t unit = mp_obj_get_int(args[0]); - madcblock_obj_t *self = NULL; - for (int i = 0; i < MP_ARRAY_SIZE(madcblock_obj); i++) { - if (unit == madcblock_obj[i].unit_id) { - self = &madcblock_obj[i]; - break; - } - } - if (!self) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid block id")); - } - - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); - madcblock_init_helper(self, n_pos_args - 1, args + 1, &kw_args); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t madcblock_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - madcblock_obj_t *self = pos_args[0]; - madcblock_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madcblock_init_obj, 1, madcblock_init); - -STATIC mp_obj_t madcblock_connect(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - madcblock_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - adc_channel_t channel_id = -1; - gpio_num_t gpio_id = -1; - if (n_pos_args == 2) { - if (mp_obj_is_int(pos_args[1])) { - channel_id = mp_obj_get_int(pos_args[1]); - } else { - gpio_id = machine_pin_get_id(pos_args[1]); - } - } else if (n_pos_args == 3) { - channel_id = mp_obj_get_int(pos_args[1]); - gpio_id = machine_pin_get_id(pos_args[2]); - } else { - mp_raise_TypeError(MP_ERROR_TEXT("too many positional args")); - } - - const madc_obj_t *adc = madc_search_helper(self, channel_id, gpio_id); - if (adc != NULL) { - madc_init_helper(adc, 0, pos_args + n_pos_args, kw_args); - return MP_OBJ_FROM_PTR(adc); - } - mp_raise_ValueError(MP_ERROR_TEXT("no matching ADC")); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(madcblock_connect_obj, 2, madcblock_connect); - -mp_int_t madcblock_read_helper(madcblock_obj_t *self, adc_channel_t channel_id) { - int raw; - if (self->unit_id == ADC_UNIT_1) { - raw = adc1_get_raw(channel_id); - } else { - check_esp_err(adc2_get_raw(channel_id, self->width, &raw)); - } - return raw; -} - -mp_int_t madcblock_read_uv_helper(madcblock_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) { - int raw = madcblock_read_helper(self, channel_id); - esp_adc_cal_characteristics_t *adc_chars = self->characteristics[atten]; - if (adc_chars == NULL) { - adc_chars = malloc(sizeof(esp_adc_cal_characteristics_t)); - esp_adc_cal_characterize(self->unit_id, atten, self->width, DEFAULT_VREF, adc_chars); - self->characteristics[atten] = adc_chars; - } - mp_int_t uv = esp_adc_cal_raw_to_voltage(raw, adc_chars) * 1000; - return uv; -} - -STATIC const mp_rom_map_elem_t madcblock_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&madcblock_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&madcblock_connect_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(madcblock_locals_dict, madcblock_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_adcblock_type, - MP_QSTR_ADCBlock, - MP_TYPE_FLAG_NONE, - make_new, madcblock_make_new, - print, madcblock_print, - locals_dict, &madcblock_locals_dict - ); diff --git a/tulip/esp32s3/machine_adcblock.h b/tulip/esp32s3/machine_adcblock.h deleted file mode 100644 index 7c9249b07..000000000 --- a/tulip/esp32s3/machine_adcblock.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef MICROPY_INCLUDED_MACHINE_ADCBLOCK_H -#define MICROPY_INCLUDED_MACHINE_ADCBLOCK_H - -#include "esp_adc_cal.h" - -#define ADC_ATTEN_MAX SOC_ADC_ATTEN_NUM - -typedef struct _madcblock_obj_t { - mp_obj_base_t base; - adc_unit_t unit_id; - mp_int_t bits; - adc_bits_width_t width; - esp_adc_cal_characteristics_t *characteristics[ADC_ATTEN_MAX]; -} madcblock_obj_t; - -extern madcblock_obj_t madcblock_obj[]; - -extern void madcblock_bits_helper(madcblock_obj_t *self, mp_int_t bits); -extern mp_int_t madcblock_read_helper(madcblock_obj_t *self, adc_channel_t channel_id); -extern mp_int_t madcblock_read_uv_helper(madcblock_obj_t *self, adc_channel_t channel_id, adc_atten_t atten); - -#endif // MICROPY_INCLUDED_MACHINE_ADCBLOCK_H diff --git a/tulip/esp32s3/machine_bitstream.c b/tulip/esp32s3/machine_bitstream.c deleted file mode 100644 index 87a5ae53c..000000000 --- a/tulip/esp32s3/machine_bitstream.c +++ /dev/null @@ -1,187 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Jim Mussared - * - * 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. - */ - -#include "py/mpconfig.h" -#include "py/mphal.h" -#include "modesp32.h" - -#include "rom/gpio.h" -#include "soc/gpio_reg.h" -#include "soc/gpio_sig_map.h" - -#if MICROPY_PY_MACHINE_BITSTREAM - -/******************************************************************************/ -// Bit-bang implementation - -#define NS_TICKS_OVERHEAD (6) - -// This is a translation of the cycle counter implementation in ports/stm32/machine_bitstream.c. -STATIC void IRAM_ATTR machine_bitstream_high_low_bitbang(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { - uint32_t pin_mask, gpio_reg_set, gpio_reg_clear; - #if !CONFIG_IDF_TARGET_ESP32C3 - if (pin >= 32) { - pin_mask = 1 << (pin - 32); - gpio_reg_set = GPIO_OUT1_W1TS_REG; - gpio_reg_clear = GPIO_OUT1_W1TC_REG; - } else - #endif - { - pin_mask = 1 << pin; - gpio_reg_set = GPIO_OUT_W1TS_REG; - gpio_reg_clear = GPIO_OUT_W1TC_REG; - } - - // Convert ns to cpu ticks [high_time_0, period_0, high_time_1, period_1]. - uint32_t fcpu_mhz = esp_rom_get_cpu_ticks_per_us(); - for (size_t i = 0; i < 4; ++i) { - timing_ns[i] = fcpu_mhz * timing_ns[i] / 1000; - if (timing_ns[i] > NS_TICKS_OVERHEAD) { - timing_ns[i] -= NS_TICKS_OVERHEAD; - } - if (i % 2 == 1) { - // Convert low_time to period (i.e. add high_time). - timing_ns[i] += timing_ns[i - 1]; - } - } - - uint32_t irq_state = mp_hal_quiet_timing_enter(); - - for (size_t i = 0; i < len; ++i) { - uint8_t b = buf[i]; - for (size_t j = 0; j < 8; ++j) { - GPIO_REG_WRITE(gpio_reg_set, pin_mask); - uint32_t start_ticks = mp_hal_ticks_cpu(); - uint32_t *t = &timing_ns[b >> 6 & 2]; - while (mp_hal_ticks_cpu() - start_ticks < t[0]) { - ; - } - GPIO_REG_WRITE(gpio_reg_clear, pin_mask); - b <<= 1; - while (mp_hal_ticks_cpu() - start_ticks < t[1]) { - ; - } - } - } - - mp_hal_quiet_timing_exit(irq_state); -} - -/******************************************************************************/ -// RMT implementation - -#include "driver/rmt.h" - -// Logical 0 and 1 values (encoded as a rmt_item32_t). -// The duration fields will be set later. -STATIC rmt_item32_t bitstream_high_low_0 = {{{ 0, 1, 0, 0 }}}; -STATIC rmt_item32_t bitstream_high_low_1 = {{{ 0, 1, 0, 0 }}}; - -// See https://github.com/espressif/esp-idf/blob/master/examples/common_components/led_strip/led_strip_rmt_ws2812.c -// This is called automatically by the IDF during rmt_write_sample in order to -// convert the byte stream to rmt_item32_t's. -STATIC void IRAM_ATTR bitstream_high_low_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, size_t wanted_num, size_t *translated_size, size_t *item_num) { - if (src == NULL || dest == NULL) { - *translated_size = 0; - *item_num = 0; - return; - } - - size_t size = 0; - size_t num = 0; - uint8_t *psrc = (uint8_t *)src; - rmt_item32_t *pdest = dest; - while (size < src_size && num < wanted_num) { - for (int i = 0; i < 8; i++) { - // MSB first - if (*psrc & (1 << (7 - i))) { - pdest->val = bitstream_high_low_1.val; - } else { - pdest->val = bitstream_high_low_0.val; - } - num++; - pdest++; - } - size++; - psrc++; - } - - *translated_size = size; - *item_num = num; -} - -// Use the reserved RMT channel to stream high/low data on the specified pin. -STATIC void machine_bitstream_high_low_rmt(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len, uint8_t channel_id) { - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel_id); - - // Use 40MHz clock (although 2MHz would probably be sufficient). - config.clk_div = 2; - - // Install the driver on this channel & pin. - check_esp_err(rmt_config(&config)); - check_esp_err(rmt_driver_install_core1(config.channel)); - - // Get the tick rate in kHz (this will likely be 40000). - uint32_t counter_clk_khz = 0; - check_esp_err(rmt_get_counter_clock(config.channel, &counter_clk_khz)); - - counter_clk_khz /= 1000; - - // Convert nanoseconds to pulse duration. - bitstream_high_low_0.duration0 = (counter_clk_khz * timing_ns[0]) / 1e6; - bitstream_high_low_0.duration1 = (counter_clk_khz * timing_ns[1]) / 1e6; - bitstream_high_low_1.duration0 = (counter_clk_khz * timing_ns[2]) / 1e6; - bitstream_high_low_1.duration1 = (counter_clk_khz * timing_ns[3]) / 1e6; - - // Install the bits->highlow translator. - rmt_translator_init(config.channel, bitstream_high_low_rmt_adapter); - - // Stream the byte data using the translator. - check_esp_err(rmt_write_sample(config.channel, buf, len, true)); - - // Wait 50% longer than we expect (if every bit takes the maximum time). - uint32_t timeout_ms = (3 * len / 2) * (1 + (8 * MAX(timing_ns[0] + timing_ns[1], timing_ns[2] + timing_ns[3])) / 1000); - check_esp_err(rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(timeout_ms))); - - // Uninstall the driver. - check_esp_err(rmt_driver_uninstall(config.channel)); - - // Cancel RMT output to GPIO pin. - esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); -} - -/******************************************************************************/ -// Interface to machine.bitstream - -void machine_bitstream_high_low(mp_hal_pin_obj_t pin, uint32_t *timing_ns, const uint8_t *buf, size_t len) { - if (esp32_rmt_bitstream_channel_id < 0) { - machine_bitstream_high_low_bitbang(pin, timing_ns, buf, len); - } else { - machine_bitstream_high_low_rmt(pin, timing_ns, buf, len, esp32_rmt_bitstream_channel_id); - } -} - -#endif // MICROPY_PY_MACHINE_BITSTREAM diff --git a/tulip/esp32s3/machine_dac.c b/tulip/esp32s3/machine_dac.c deleted file mode 100644 index 0e85dc9c9..000000000 --- a/tulip/esp32s3/machine_dac.c +++ /dev/null @@ -1,116 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 Nick Moore - * - * 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. - */ - - -#include - -#include "py/runtime.h" -#include "py/mphal.h" -#include "modmachine.h" - -#if MICROPY_PY_MACHINE_DAC - -#include "driver/gpio.h" -#include "driver/dac.h" - -typedef struct _mdac_obj_t { - mp_obj_base_t base; - gpio_num_t gpio_id; - dac_channel_t dac_id; -} mdac_obj_t; - -STATIC const mdac_obj_t mdac_obj[] = { - #if CONFIG_IDF_TARGET_ESP32 - {{&machine_dac_type}, GPIO_NUM_25, DAC_CHANNEL_1}, - {{&machine_dac_type}, GPIO_NUM_26, DAC_CHANNEL_2}, - #else - {{&machine_dac_type}, GPIO_NUM_17, DAC_CHANNEL_1}, - {{&machine_dac_type}, GPIO_NUM_18, DAC_CHANNEL_2}, - #endif -}; - -STATIC mp_obj_t mdac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, - const mp_obj_t *args) { - - mp_arg_check_num(n_args, n_kw, 1, 1, true); - gpio_num_t pin_id = machine_pin_get_id(args[0]); - const mdac_obj_t *self = NULL; - for (int i = 0; i < MP_ARRAY_SIZE(mdac_obj); i++) { - if (pin_id == mdac_obj[i].gpio_id) { - self = &mdac_obj[i]; - break; - } - } - if (!self) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid Pin for DAC")); - } - - esp_err_t err = dac_output_enable(self->dac_id); - if (err == ESP_OK) { - err = dac_output_voltage(self->dac_id, 0); - } - if (err == ESP_OK) { - return MP_OBJ_FROM_PTR(self); - } - mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); -} - -STATIC void mdac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - mdac_obj_t *self = self_in; - mp_printf(print, "DAC(Pin(%u))", self->gpio_id); -} - -STATIC mp_obj_t mdac_write(mp_obj_t self_in, mp_obj_t value_in) { - mdac_obj_t *self = self_in; - int value = mp_obj_get_int(value_in); - if (value < 0 || value > 255) { - mp_raise_ValueError(MP_ERROR_TEXT("value out of range")); - } - - esp_err_t err = dac_output_voltage(self->dac_id, value); - if (err == ESP_OK) { - return mp_const_none; - } - mp_raise_ValueError(MP_ERROR_TEXT("parameter error")); -} -MP_DEFINE_CONST_FUN_OBJ_2(mdac_write_obj, mdac_write); - -STATIC const mp_rom_map_elem_t mdac_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mdac_write_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(mdac_locals_dict, mdac_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_dac_type, - MP_QSTR_DAC, - MP_TYPE_FLAG_NONE, - make_new, mdac_make_new, - print, mdac_print, - locals_dict, &mdac_locals_dict - ); - -#endif // MICROPY_PY_MACHINE_DAC diff --git a/tulip/esp32s3/machine_hw_spi.c b/tulip/esp32s3/machine_hw_spi.c deleted file mode 100644 index 662d0e599..000000000 --- a/tulip/esp32s3/machine_hw_spi.c +++ /dev/null @@ -1,560 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 "Eric Poulsen" - * - * 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. - */ - -#include -#include -#include - -#include "py/runtime.h" -#include "py/stream.h" -#include "py/mphal.h" -#include "extmod/machine_spi.h" -#include "modmachine.h" - -#include "driver/spi_master.h" -#include "soc/gpio_sig_map.h" -#include "soc/spi_pins.h" - -// SPI mappings by device, naming used by IDF old/new -// upython | ESP32 | ESP32S2 | ESP32S3 | ESP32C3 -// ----------+-----------+-----------+---------+--------- -// SPI(id=1) | HSPI/SPI2 | FSPI/SPI2 | SPI2 | SPI2 -// SPI(id=2) | VSPI/SPI3 | HSPI/SPI3 | SPI3 | err - -// Default pins for SPI(id=1) aka IDF SPI2, can be overridden by a board -#ifndef MICROPY_HW_SPI1_SCK -#ifdef SPI2_IOMUX_PIN_NUM_CLK -// Use IO_MUX pins by default. -// If SPI lines are routed to other pins through GPIO matrix -// routing adds some delay and lower limit applies to SPI clk freq -#define MICROPY_HW_SPI1_SCK SPI2_IOMUX_PIN_NUM_CLK // pin 14 on ESP32 -#define MICROPY_HW_SPI1_MOSI SPI2_IOMUX_PIN_NUM_MOSI // pin 13 on ESP32 -#define MICROPY_HW_SPI1_MISO SPI2_IOMUX_PIN_NUM_MISO // pin 12 on ESP32 -// Only for compatibility with IDF 4.2 and older -#elif CONFIG_IDF_TARGET_ESP32S2 -#define MICROPY_HW_SPI1_SCK FSPI_IOMUX_PIN_NUM_CLK -#define MICROPY_HW_SPI1_MOSI FSPI_IOMUX_PIN_NUM_MOSI -#define MICROPY_HW_SPI1_MISO FSPI_IOMUX_PIN_NUM_MISO -#else -#define MICROPY_HW_SPI1_SCK SPI2_IOMUX_PIN_NUM_CLK -#define MICROPY_HW_SPI1_MOSI SPI2_IOMUX_PIN_NUM_MOSI -#define MICROPY_HW_SPI1_MISO SPI2_IOMUX_PIN_NUM_MISO -#endif -#endif - -// Default pins for SPI(id=2) aka IDF SPI3, can be overridden by a board -#ifndef MICROPY_HW_SPI2_SCK -#if CONFIG_IDF_TARGET_ESP32 -// ESP32 has IO_MUX pins for VSPI/SPI3 lines, use them as defaults -#define MICROPY_HW_SPI2_SCK SPI3_IOMUX_PIN_NUM_CLK // pin 18 -#define MICROPY_HW_SPI2_MOSI SPI3_IOMUX_PIN_NUM_MOSI // pin 23 -#define MICROPY_HW_SPI2_MISO SPI3_IOMUX_PIN_NUM_MISO // pin 19 -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 -// ESP32S2 and S3 uses GPIO matrix for SPI3 pins, no IO_MUX possible -// Set defaults to the pins used by SPI2 in Octal mode -#define MICROPY_HW_SPI2_SCK (36) -#define MICROPY_HW_SPI2_MOSI (35) -#define MICROPY_HW_SPI2_MISO (37) -#endif -#endif - -#define MP_HW_SPI_MAX_XFER_BYTES (4092) -#define MP_HW_SPI_MAX_XFER_BITS (MP_HW_SPI_MAX_XFER_BYTES * 8) // Has to be an even multiple of 8 - -#if CONFIG_IDF_TARGET_ESP32C3 -#define SPI2_HOST SPI2_HOST -#elif CONFIG_IDF_TARGET_ESP32S3 -#define SPI2_HOST SPI3_HOST -#define FSPI_HOST SPI2_HOST -#endif - -typedef struct _machine_hw_spi_default_pins_t { - int8_t sck; - int8_t mosi; - int8_t miso; -} machine_hw_spi_default_pins_t; - -typedef struct _machine_hw_spi_obj_t { - mp_obj_base_t base; - spi_host_device_t host; - uint32_t baudrate; - uint8_t polarity; - uint8_t phase; - uint8_t bits; - uint8_t firstbit; - int8_t sck; - int8_t mosi; - int8_t miso; - spi_device_handle_t spi; - enum { - MACHINE_HW_SPI_STATE_NONE, - MACHINE_HW_SPI_STATE_INIT, - MACHINE_HW_SPI_STATE_DEINIT - } state; -} machine_hw_spi_obj_t; - -// Default pin mappings for the hardware SPI instances -STATIC const machine_hw_spi_default_pins_t machine_hw_spi_default_pins[2] = { - { .sck = MICROPY_HW_SPI1_SCK, .mosi = MICROPY_HW_SPI1_MOSI, .miso = MICROPY_HW_SPI1_MISO }, - #ifdef MICROPY_HW_SPI2_SCK - { .sck = MICROPY_HW_SPI2_SCK, .mosi = MICROPY_HW_SPI2_MOSI, .miso = MICROPY_HW_SPI2_MISO }, - #endif -}; - -// Static objects mapping to SPI2 and SPI3 hardware peripherals -STATIC machine_hw_spi_obj_t machine_hw_spi_obj[2]; - -STATIC void machine_hw_spi_deinit_internal(machine_hw_spi_obj_t *self) { - switch (spi_bus_remove_device(self->spi)) { - case ESP_ERR_INVALID_ARG: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); - return; - - case ESP_ERR_INVALID_STATE: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI device already freed")); - return; - } - - switch (spi_bus_free(self->host)) { - case ESP_ERR_INVALID_ARG: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); - return; - - case ESP_ERR_INVALID_STATE: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI bus already freed")); - return; - } - - int8_t pins[3] = {self->miso, self->mosi, self->sck}; - - for (int i = 0; i < 3; i++) { - if (pins[i] != -1) { - esp_rom_gpio_pad_select_gpio(pins[i]); - esp_rom_gpio_connect_out_signal(pins[i], SIG_GPIO_OUT_IDX, false, false); - gpio_set_direction(pins[i], GPIO_MODE_INPUT); - } - } -} - -STATIC void machine_hw_spi_init_internal( - machine_hw_spi_obj_t *self, - int8_t host, - int32_t baudrate, - int8_t polarity, - int8_t phase, - int8_t bits, - int8_t firstbit, - int8_t sck, - int8_t mosi, - int8_t miso) { - - // if we're not initialized, then we're - // implicitly 'changed', since this is the init routine - bool changed = self->state != MACHINE_HW_SPI_STATE_INIT; - - esp_err_t ret; - - machine_hw_spi_obj_t old_self = *self; - - if (host != -1 && host != self->host) { - self->host = host; - changed = true; - } - - if (baudrate != -1) { - // calculate the actual clock frequency that the SPI peripheral can produce - baudrate = spi_get_actual_clock(APB_CLK_FREQ, baudrate, 0); - if (baudrate != self->baudrate) { - self->baudrate = baudrate; - changed = true; - } - } - - if (polarity != -1 && polarity != self->polarity) { - self->polarity = polarity; - changed = true; - } - - if (phase != -1 && phase != self->phase) { - self->phase = phase; - changed = true; - } - - if (bits != -1 && bits != self->bits) { - self->bits = bits; - changed = true; - } - - if (firstbit != -1 && firstbit != self->firstbit) { - self->firstbit = firstbit; - changed = true; - } - - if (sck != -2 && sck != self->sck) { - self->sck = sck; - changed = true; - } - - if (mosi != -2 && mosi != self->mosi) { - self->mosi = mosi; - changed = true; - } - - if (miso != -2 && miso != self->miso) { - self->miso = miso; - changed = true; - } - - if (self->host != SPI2_HOST - #ifdef FSPI_HOST - && self->host != FSPI_HOST - #endif - #ifdef SPI3_HOST - && self->host != SPI3_HOST - #endif - ) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), self->host); - } - - if (changed) { - if (self->state == MACHINE_HW_SPI_STATE_INIT) { - self->state = MACHINE_HW_SPI_STATE_DEINIT; - machine_hw_spi_deinit_internal(&old_self); - } - } else { - return; // no changes - } - - spi_bus_config_t buscfg = { - .miso_io_num = self->miso, - .mosi_io_num = self->mosi, - .sclk_io_num = self->sck, - .quadwp_io_num = -1, - .quadhd_io_num = -1 - }; - - spi_device_interface_config_t devcfg = { - .clock_speed_hz = self->baudrate, - .mode = self->phase | (self->polarity << 1), - .spics_io_num = -1, // No CS pin - .queue_size = 2, - .flags = self->firstbit == MICROPY_PY_MACHINE_SPI_LSB ? SPI_DEVICE_TXBIT_LSBFIRST | SPI_DEVICE_RXBIT_LSBFIRST : 0, - .pre_cb = NULL - }; - - // Initialize the SPI bus - - // Select DMA channel based on the hardware SPI host - int dma_chan = 0; - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 - dma_chan = SPI_DMA_CH_AUTO; - #else - if (self->host == SPI2_HOST) { - dma_chan = 1; - } else { - dma_chan = 2; - } - #endif - - ret = spi_bus_initialize(self->host, &buscfg, dma_chan); - switch (ret) { - case ESP_ERR_INVALID_ARG: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); - return; - - case ESP_ERR_INVALID_STATE: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("SPI host already in use")); - return; - } - - ret = spi_bus_add_device(self->host, &devcfg, &self->spi); - switch (ret) { - case ESP_ERR_INVALID_ARG: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("invalid configuration")); - spi_bus_free(self->host); - return; - - case ESP_ERR_NO_MEM: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("out of memory")); - spi_bus_free(self->host); - return; - - case ESP_ERR_NOT_FOUND: - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("no free slots")); - spi_bus_free(self->host); - return; - } - self->state = MACHINE_HW_SPI_STATE_INIT; -} - -STATIC void machine_hw_spi_deinit(mp_obj_base_t *self_in) { - machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)self_in; - if (self->state == MACHINE_HW_SPI_STATE_INIT) { - self->state = MACHINE_HW_SPI_STATE_DEINIT; - machine_hw_spi_deinit_internal(self); - } -} - -STATIC mp_uint_t gcd(mp_uint_t x, mp_uint_t y) { - while (x != y) { - if (x > y) { - x -= y; - } else { - y -= x; - } - } - return x; -} - -STATIC void machine_hw_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) { - machine_hw_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); - - if (self->state == MACHINE_HW_SPI_STATE_DEINIT) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("transfer on deinitialized SPI")); - return; - } - - // Round to nearest whole set of bits - int bits_to_send = len * 8 / self->bits * self->bits; - - if (!bits_to_send) { - mp_raise_ValueError(MP_ERROR_TEXT("buffer too short")); - } - - if (len <= 4) { - spi_transaction_t transaction = { 0 }; - - if (src != NULL) { - memcpy(&transaction.tx_data, src, len); - } - - transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA; - transaction.length = bits_to_send; - spi_device_transmit(self->spi, &transaction); - - if (dest != NULL) { - memcpy(dest, &transaction.rx_data, len); - } - } else { - int offset = 0; - int bits_remaining = bits_to_send; - int optimum_word_size = 8 * self->bits / gcd(8, self->bits); - int max_transaction_bits = MP_HW_SPI_MAX_XFER_BITS / optimum_word_size * optimum_word_size; - spi_transaction_t *transaction, *result, transactions[2]; - int i = 0; - - spi_device_acquire_bus(self->spi, portMAX_DELAY); - - while (bits_remaining) { - transaction = transactions + i++ % 2; - memset(transaction, 0, sizeof(spi_transaction_t)); - - transaction->length = - bits_remaining > max_transaction_bits ? max_transaction_bits : bits_remaining; - - if (src != NULL) { - transaction->tx_buffer = src + offset; - } - if (dest != NULL) { - transaction->rx_buffer = dest + offset; - } - - spi_device_queue_trans(self->spi, transaction, portMAX_DELAY); - bits_remaining -= transaction->length; - - if (offset > 0) { - // wait for previously queued transaction - MP_THREAD_GIL_EXIT(); - spi_device_get_trans_result(self->spi, &result, portMAX_DELAY); - MP_THREAD_GIL_ENTER(); - } - - // doesn't need ceil(); loop ends when bits_remaining is 0 - offset += transaction->length / 8; - } - - // wait for last transaction - MP_THREAD_GIL_EXIT(); - spi_device_get_trans_result(self->spi, &result, portMAX_DELAY); - MP_THREAD_GIL_ENTER(); - spi_device_release_bus(self->spi); - } -} - -/******************************************************************************/ -// MicroPython bindings for hw_spi - -STATIC void machine_hw_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_hw_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "SPI(id=%u, baudrate=%u, polarity=%u, phase=%u, bits=%u, firstbit=%u, sck=%d, mosi=%d, miso=%d)", - self->host, self->baudrate, self->polarity, - self->phase, self->bits, self->firstbit, - self->sck, self->mosi, self->miso); -} - -STATIC void machine_hw_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)self_in; - - enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), - allowed_args, args); - int8_t sck, mosi, miso; - - if (args[ARG_sck].u_obj == MP_OBJ_NULL) { - sck = -2; - } else if (args[ARG_sck].u_obj == mp_const_none) { - sck = -1; - } else { - sck = machine_pin_get_id(args[ARG_sck].u_obj); - } - - if (args[ARG_miso].u_obj == MP_OBJ_NULL) { - miso = -2; - } else if (args[ARG_miso].u_obj == mp_const_none) { - miso = -1; - } else { - miso = machine_pin_get_id(args[ARG_miso].u_obj); - } - - if (args[ARG_mosi].u_obj == MP_OBJ_NULL) { - mosi = -2; - } else if (args[ARG_mosi].u_obj == mp_const_none) { - mosi = -1; - } else { - mosi = machine_pin_get_id(args[ARG_mosi].u_obj); - } - - machine_hw_spi_init_internal(self, args[ARG_id].u_int, args[ARG_baudrate].u_int, - args[ARG_polarity].u_int, args[ARG_phase].u_int, args[ARG_bits].u_int, - args[ARG_firstbit].u_int, sck, mosi, miso); -} - -mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - MP_MACHINE_SPI_CHECK_FOR_LEGACY_SOFTSPI_CONSTRUCTION(n_args, n_kw, all_args); - - enum { ARG_id, ARG_baudrate, ARG_polarity, ARG_phase, ARG_bits, ARG_firstbit, ARG_sck, ARG_mosi, ARG_miso }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 500000} }, - { MP_QSTR_polarity, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_phase, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, - { MP_QSTR_firstbit, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = MICROPY_PY_MACHINE_SPI_MSB} }, - { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - machine_hw_spi_obj_t *self; - const machine_hw_spi_default_pins_t *default_pins; - if (args[ARG_id].u_int == 1) { // SPI2_HOST which is FSPI_HOST on ESP32Sx, SPI2_HOST on others - self = &machine_hw_spi_obj[0]; - default_pins = &machine_hw_spi_default_pins[0]; - } else { - self = &machine_hw_spi_obj[1]; - default_pins = &machine_hw_spi_default_pins[1]; - } - self->base.type = &machine_spi_type; - - int8_t sck, mosi, miso; - - if (args[ARG_sck].u_obj == MP_OBJ_NULL) { - sck = default_pins->sck; - } else if (args[ARG_sck].u_obj == mp_const_none) { - sck = -1; - } else { - sck = machine_pin_get_id(args[ARG_sck].u_obj); - } - - if (args[ARG_mosi].u_obj == MP_OBJ_NULL) { - mosi = default_pins->mosi; - } else if (args[ARG_mosi].u_obj == mp_const_none) { - mosi = -1; - } else { - mosi = machine_pin_get_id(args[ARG_mosi].u_obj); - } - - if (args[ARG_miso].u_obj == MP_OBJ_NULL) { - miso = default_pins->miso; - } else if (args[ARG_miso].u_obj == mp_const_none) { - miso = -1; - } else { - miso = machine_pin_get_id(args[ARG_miso].u_obj); - } - - machine_hw_spi_init_internal( - self, - args[ARG_id].u_int, - args[ARG_baudrate].u_int, - args[ARG_polarity].u_int, - args[ARG_phase].u_int, - args[ARG_bits].u_int, - args[ARG_firstbit].u_int, - sck, - mosi, - miso); - - return MP_OBJ_FROM_PTR(self); -} - -spi_host_device_t machine_hw_spi_get_host(mp_obj_t in) { - if (mp_obj_get_type(in) != &machine_spi_type) { - mp_raise_ValueError(MP_ERROR_TEXT("expecting a SPI object")); - } - machine_hw_spi_obj_t *self = (machine_hw_spi_obj_t *)in; - return self->host; -} - -STATIC const mp_machine_spi_p_t machine_hw_spi_p = { - .init = machine_hw_spi_init, - .deinit = machine_hw_spi_deinit, - .transfer = machine_hw_spi_transfer, -}; - -MP_DEFINE_CONST_OBJ_TYPE( - machine_spi_type, - MP_QSTR_SPI, - MP_TYPE_FLAG_NONE, - make_new, machine_hw_spi_make_new, - print, machine_hw_spi_print, - protocol, &machine_hw_spi_p, - locals_dict, &mp_machine_spi_locals_dict - ); diff --git a/tulip/esp32s3/machine_i2c.c b/tulip/esp32s3/machine_i2c.c deleted file mode 100644 index e3b764779..000000000 --- a/tulip/esp32s3/machine_i2c.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Damien P. George - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mphal.h" -#include "py/mperrno.h" -#include "extmod/machine_i2c.h" -#include "modmachine.h" - -#include "driver/i2c.h" -#include "hal/i2c_ll.h" - -#ifndef MICROPY_HW_I2C0_SCL -#define MICROPY_HW_I2C0_SCL (GPIO_NUM_18) -#define MICROPY_HW_I2C0_SDA (GPIO_NUM_19) -#endif - -#ifndef MICROPY_HW_I2C1_SCL -#if CONFIG_IDF_TARGET_ESP32 -#define MICROPY_HW_I2C1_SCL (GPIO_NUM_25) -#define MICROPY_HW_I2C1_SDA (GPIO_NUM_26) -#else -#define MICROPY_HW_I2C1_SCL (GPIO_NUM_9) -#define MICROPY_HW_I2C1_SDA (GPIO_NUM_8) -#endif -#endif - -#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 -#define I2C_SCLK_FREQ XTAL_CLK_FREQ -#elif CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 -#define I2C_SCLK_FREQ I2C_APB_CLK_FREQ -#else -#error "unsupported I2C for ESP32 SoC variant" -#endif - -#define I2C_DEFAULT_TIMEOUT_US (50000) // 50ms - -typedef struct _machine_hw_i2c_obj_t { - mp_obj_base_t base; - i2c_port_t port : 8; - gpio_num_t scl : 8; - gpio_num_t sda : 8; -} machine_hw_i2c_obj_t; - -STATIC machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; - -STATIC void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint32_t timeout_us, bool first_init) { - if (!first_init) { - i2c_driver_delete(self->port); - } - i2c_config_t conf = { - .mode = I2C_MODE_MASTER, - .sda_io_num = self->sda, - .sda_pullup_en = GPIO_PULLUP_ENABLE, - .scl_io_num = self->scl, - .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = freq, - }; - i2c_param_config(self->port, &conf); - int timeout = I2C_SCLK_FREQ / 1000000 * timeout_us; - i2c_set_timeout(self->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); - i2c_driver_install(self->port, I2C_MODE_MASTER, 0, 0, 0); -} - -int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { - machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - int data_len = 0; - - if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { - i2c_master_start(cmd); - i2c_master_write_byte(cmd, addr << 1, true); - i2c_master_write(cmd, bufs->buf, bufs->len, true); - data_len += bufs->len; - --n; - ++bufs; - } - - i2c_master_start(cmd); - i2c_master_write_byte(cmd, addr << 1 | (flags & MP_MACHINE_I2C_FLAG_READ), true); - - for (; n--; ++bufs) { - if (flags & MP_MACHINE_I2C_FLAG_READ) { - i2c_master_read(cmd, bufs->buf, bufs->len, n == 0 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); - } else { - if (bufs->len != 0) { - i2c_master_write(cmd, bufs->buf, bufs->len, true); - } - } - data_len += bufs->len; - } - - if (flags & MP_MACHINE_I2C_FLAG_STOP) { - i2c_master_stop(cmd); - } - - // TODO proper timeout - esp_err_t err = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_PERIOD_MS); - i2c_cmd_link_delete(cmd); - - if (err == ESP_FAIL) { - return -MP_ENODEV; - } else if (err == ESP_ERR_TIMEOUT) { - return -MP_ETIMEDOUT; - } else if (err != ESP_OK) { - return -abs(err); - } - - return data_len; -} - -/******************************************************************************/ -// MicroPython bindings for machine API - -STATIC void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - int h, l; - i2c_get_period(self->port, &h, &l); - mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u)", - self->port, self->scl, self->sda, I2C_SCLK_FREQ / (h + l)); -} - -mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - MP_MACHINE_I2C_CHECK_FOR_LEGACY_SOFTI2C_CONSTRUCTION(n_args, n_kw, all_args); - - // Parse args - enum { ARG_id, ARG_scl, ARG_sda, ARG_freq, ARG_timeout }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2C_DEFAULT_TIMEOUT_US} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - // Get I2C bus - mp_int_t i2c_id = mp_obj_get_int(args[ARG_id].u_obj); - if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); - } - - // Get static peripheral object - machine_hw_i2c_obj_t *self = (machine_hw_i2c_obj_t *)&machine_hw_i2c_obj[i2c_id]; - - bool first_init = false; - if (self->base.type == NULL) { - // Created for the first time, set default pins - self->base.type = &machine_i2c_type; - self->port = i2c_id; - if (self->port == I2C_NUM_0) { - self->scl = MICROPY_HW_I2C0_SCL; - self->sda = MICROPY_HW_I2C0_SDA; - } else { - self->scl = MICROPY_HW_I2C1_SCL; - self->sda = MICROPY_HW_I2C1_SDA; - } - first_init = true; - } - - // Set SCL/SDA pins if given - if (args[ARG_scl].u_obj != MP_OBJ_NULL) { - self->scl = mp_hal_get_pin_obj(args[ARG_scl].u_obj); - } - if (args[ARG_sda].u_obj != MP_OBJ_NULL) { - self->sda = mp_hal_get_pin_obj(args[ARG_sda].u_obj); - } - - // Initialise the I2C peripheral - machine_hw_i2c_init(self, args[ARG_freq].u_int, args[ARG_timeout].u_int, first_init); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC const mp_machine_i2c_p_t machine_hw_i2c_p = { - .transfer_supports_write1 = true, - .transfer = machine_hw_i2c_transfer, -}; - -MP_DEFINE_CONST_OBJ_TYPE( - machine_i2c_type, - MP_QSTR_I2C, - MP_TYPE_FLAG_NONE, - make_new, machine_hw_i2c_make_new, - print, machine_hw_i2c_print, - protocol, &machine_hw_i2c_p, - locals_dict, &mp_machine_i2c_locals_dict - ); diff --git a/tulip/esp32s3/machine_i2s.c b/tulip/esp32s3/machine_i2s.c deleted file mode 100644 index 59b24ae70..000000000 --- a/tulip/esp32s3/machine_i2s.c +++ /dev/null @@ -1,824 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Mike Teachman - * - * 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. - */ - -#include -#include -#include -#include -#include - -#include "py/obj.h" -#include "py/runtime.h" -#include "py/misc.h" -#include "py/stream.h" -#include "py/objstr.h" -#include "modmachine.h" -#include "mphalport.h" - -#if MICROPY_PY_MACHINE_I2S - -#include "driver/i2s.h" -#include "soc/i2s_reg.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "esp_task.h" - -// The I2S module has 3 modes of operation: -// -// Mode1: Blocking -// - readinto() and write() methods block until the supplied buffer is filled (read) or emptied (write) -// - this is the default mode of operation -// -// Mode2: Non-Blocking -// - readinto() and write() methods return immediately. -// - buffer filling and emptying happens asynchronously to the main MicroPython task -// - a callback function is called when the supplied buffer has been filled (read) or emptied (write) -// - non-blocking mode is enabled when a callback is set with the irq() method -// - a FreeRTOS task is created to implement the asynchronous background operations -// - a FreeRTOS queue is used to transfer the supplied buffer to the background task -// -// Mode3: Asyncio -// - implements the stream protocol -// - asyncio mode is enabled when the ioctl() function is called -// - the I2S event queue is used to detect that I2S samples can be read or written from/to DMA memory -// -// The samples contained in the app buffer supplied for the readinto() and write() methods have the following convention: -// Mono: little endian format -// Stereo: little endian format, left channel first -// -// I2S terms: -// "frame": consists of two audio samples (Left audio sample + Right audio sample) -// -// Misc: -// - for Mono configuration: -// - readinto method: samples are gathered from the L channel only -// - write method: every sample is output to both the L and R channels -// - for readinto method the I2S hardware is read using 8-byte frames -// (this is standard for almost all I2S hardware, such as MEMS microphones) -// - all sample data transfers use DMA - -#define I2S_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) -#define I2S_TASK_STACK_SIZE (2048) - -#define DMA_BUF_LEN_IN_I2S_FRAMES (256) - -// The transform buffer is used with the readinto() method to bridge the opaque DMA memory on the ESP devices -// with the app buffer. It facilitates audio sample transformations. e.g. 32-bits samples to 16-bit samples. -// The size of 240 bytes is an engineering optimum that balances transfer performance with an acceptable use of heap space -#define SIZEOF_TRANSFORM_BUFFER_IN_BYTES (240) - -#define NUM_I2S_USER_FORMATS (4) -#define I2S_RX_FRAME_SIZE_IN_BYTES (8) - -typedef enum { - MONO, - STEREO -} format_t; - -typedef enum { - BLOCKING, - NON_BLOCKING, - ASYNCIO -} io_mode_t; - -typedef enum { - I2S_TX_TRANSFER, - I2S_RX_TRANSFER, -} direction_t; - -typedef struct _non_blocking_descriptor_t { - mp_buffer_info_t appbuf; - mp_obj_t callback; - direction_t direction; -} non_blocking_descriptor_t; - -typedef struct _machine_i2s_obj_t { - mp_obj_base_t base; - i2s_port_t port; - mp_hal_pin_obj_t sck; - mp_hal_pin_obj_t ws; - mp_hal_pin_obj_t sd; - int8_t mode; - i2s_bits_per_sample_t bits; - format_t format; - int32_t rate; - int32_t ibuf; - mp_obj_t callback_for_non_blocking; - io_mode_t io_mode; - uint8_t transform_buffer[SIZEOF_TRANSFORM_BUFFER_IN_BYTES]; - QueueHandle_t i2s_event_queue; - QueueHandle_t non_blocking_mode_queue; - TaskHandle_t non_blocking_mode_task; -} machine_i2s_obj_t; - -STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in); - -// The frame map is used with the readinto() method to transform the audio sample data coming -// from DMA memory (32-bit stereo, with the L and R channels reversed) to the format specified -// in the I2S constructor. e.g. 16-bit mono -STATIC const int8_t i2s_frame_map[NUM_I2S_USER_FORMATS][I2S_RX_FRAME_SIZE_IN_BYTES] = { - { 6, 7, -1, -1, -1, -1, -1, -1 }, // Mono, 16-bits - { 4, 5, 6, 7, -1, -1, -1, -1 }, // Mono, 32-bits - { 6, 7, 2, 3, -1, -1, -1, -1 }, // Stereo, 16-bits - { 4, 5, 6, 7, 0, 1, 2, 3 }, // Stereo, 32-bits -}; - -void machine_i2s_init0() { - for (i2s_port_t p = 0; p < I2S_NUM_AUTO; p++) { - MP_STATE_PORT(machine_i2s_obj)[p] = NULL; - } -} - -// The following function takes a sample buffer and swaps L/R channels -// -// Background: For 32-bit stereo, the ESP-IDF API has a L/R channel orientation that breaks -// convention with other ESP32 channel formats -// -// appbuf[] = [L_0-7, L_8-15, L_16-23, L_24-31, R_0-7, R_8-15, R_16-23, R_24-31] = [Left channel, Right channel] -// dma[] = [R_0-7, R_8-15, R_16-23, R_24-31, L_0-7, L_8-15, L_16-23, L_24-31] = [Right channel, Left channel] -// -// where: -// L_0-7 is the least significant byte of the 32 bit sample in the Left channel -// L_24-31 is the most significant byte of the 32 bit sample in the Left channel -// -// Example: -// -// appbuf[] = [0x99, 0xBB, 0x11, 0x22, 0x44, 0x55, 0xAB, 0x77] = [Left channel, Right channel] -// dma[] = [0x44, 0x55, 0xAB, 0x77, 0x99, 0xBB, 0x11, 0x22] = [Right channel, Left channel] -// where: -// LEFT Channel = 0x99, 0xBB, 0x11, 0x22 -// RIGHT Channel = 0x44, 0x55, 0xAB, 0x77 -// -// samples in appbuf are in little endian format: -// 0x77 is the most significant byte of the 32-bit sample -// 0x44 is the least significant byte of the 32-bit sample -STATIC void swap_32_bit_stereo_channels(mp_buffer_info_t *bufinfo) { - int32_t swap_sample; - int32_t *sample = bufinfo->buf; - uint32_t num_samples = bufinfo->len / 4; - for (uint32_t i = 0; i < num_samples; i += 2) { - swap_sample = sample[i + 1]; - sample[i + 1] = sample[i]; - sample[i] = swap_sample; - } -} - -STATIC int8_t get_frame_mapping_index(i2s_bits_per_sample_t bits, format_t format) { - if (format == MONO) { - if (bits == I2S_BITS_PER_SAMPLE_16BIT) { - return 0; - } else { // 32 bits - return 1; - } - } else { // STEREO - if (bits == I2S_BITS_PER_SAMPLE_16BIT) { - return 2; - } else { // 32 bits - return 3; - } - } -} - -STATIC i2s_bits_per_sample_t get_dma_bits(uint8_t mode, i2s_bits_per_sample_t bits) { - if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) { - return bits; - } else { // Master Rx - // read 32 bit samples for I2S hardware. e.g. MEMS microphones - return I2S_BITS_PER_SAMPLE_32BIT; - } -} - -STATIC i2s_channel_fmt_t get_dma_format(uint8_t mode, format_t format) { - if (mode == (I2S_MODE_MASTER | I2S_MODE_TX)) { - if (format == MONO) { - return I2S_CHANNEL_FMT_ONLY_LEFT; - } else { // STEREO - return I2S_CHANNEL_FMT_RIGHT_LEFT; - } - } else { // Master Rx - // read stereo frames for all I2S hardware - return I2S_CHANNEL_FMT_RIGHT_LEFT; - } -} - -STATIC uint32_t get_dma_buf_count(uint8_t mode, i2s_bits_per_sample_t bits, format_t format, int32_t ibuf) { - // calculate how many DMA buffers need to be allocated - uint32_t dma_frame_size_in_bytes = - (get_dma_bits(mode, bits) / 8) * (get_dma_format(mode, format) == I2S_CHANNEL_FMT_RIGHT_LEFT ? 2: 1); - - uint32_t dma_buf_count = ibuf / (DMA_BUF_LEN_IN_I2S_FRAMES * dma_frame_size_in_bytes); - - return dma_buf_count; -} - -STATIC uint32_t fill_appbuf_from_dma(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) { - - // copy audio samples from DMA memory to the app buffer - // audio samples are read from DMA memory in chunks - // loop, reading and copying chunks until the app buffer is filled - // For asyncio mode, the loop will make an early exit if DMA memory becomes empty - // Example: - // a MicroPython I2S object is configured for 16-bit mono (2 bytes per audio sample). - // For every frame coming from DMA (8 bytes), 2 bytes are "cherry picked" and - // copied to the supplied app buffer. - // Thus, for every 1 byte copied to the app buffer, 4 bytes are read from DMA memory. - // If a 8kB app buffer is supplied, 32kB of audio samples is read from DMA memory. - - uint32_t a_index = 0; - uint8_t *app_p = appbuf->buf; - uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1); - uint32_t num_bytes_needed_from_dma = appbuf->len * (I2S_RX_FRAME_SIZE_IN_BYTES / appbuf_sample_size_in_bytes); - while (num_bytes_needed_from_dma) { - size_t num_bytes_requested_from_dma = MIN(sizeof(self->transform_buffer), num_bytes_needed_from_dma); - size_t num_bytes_received_from_dma = 0; - - TickType_t delay; - if (self->io_mode == ASYNCIO) { - delay = 0; // stop i2s_read() operation if DMA memory becomes empty - } else { - delay = portMAX_DELAY; // block until supplied buffer is filled - } - - esp_err_t ret = i2s_read( - self->port, - self->transform_buffer, - num_bytes_requested_from_dma, - &num_bytes_received_from_dma, - delay); - check_esp_err(ret); - - // process the transform buffer one frame at a time. - // copy selected bytes from the transform buffer into the user supplied appbuf. - // Example: - // a MicroPython I2S object is configured for 16-bit mono. This configuration associates to - // a frame map index of 0 = { 6, 7, -1, -1, -1, -1, -1, -1 } in the i2s_frame_map array - // This mapping indicates: - // appbuf[x+0] = frame[6] - // appbuf[x+1] = frame[7] - // frame bytes 0-5 are not used - - uint32_t t_index = 0; - uint8_t f_index = get_frame_mapping_index(self->bits, self->format); - while (t_index < num_bytes_received_from_dma) { - uint8_t *transform_p = self->transform_buffer + t_index; - - for (uint8_t i = 0; i < I2S_RX_FRAME_SIZE_IN_BYTES; i++) { - int8_t t_to_a_mapping = i2s_frame_map[f_index][i]; - if (t_to_a_mapping != -1) { - *app_p++ = transform_p[t_to_a_mapping]; - a_index++; - } - t_index++; - } - } - - num_bytes_needed_from_dma -= num_bytes_received_from_dma; - - if ((self->io_mode == ASYNCIO) && (num_bytes_received_from_dma < num_bytes_requested_from_dma)) { - // Unable to fill the entire app buffer from DMA memory. This indicates all DMA RX buffers are empty. - // Clear the I2S event queue so ioctl() indicates that the I2S object cannot currently - // supply more audio samples - xQueueReset(self->i2s_event_queue); - break; - } - } - - return a_index; -} - -STATIC size_t copy_appbuf_to_dma(machine_i2s_obj_t *self, mp_buffer_info_t *appbuf) { - if ((self->bits == I2S_BITS_PER_SAMPLE_32BIT) && (self->format == STEREO)) { - swap_32_bit_stereo_channels(appbuf); - } - - size_t num_bytes_written = 0; - - TickType_t delay; - if (self->io_mode == ASYNCIO) { - delay = 0; // stop i2s_write() operation if DMA memory becomes full - } else { - delay = portMAX_DELAY; // block until supplied buffer is emptied - } - - esp_err_t ret = i2s_write(self->port, appbuf->buf, appbuf->len, &num_bytes_written, delay); - check_esp_err(ret); - - if ((self->io_mode == ASYNCIO) && (num_bytes_written < appbuf->len)) { - // Unable to empty the entire app buffer into DMA memory. This indicates all DMA TX buffers are full. - // Clear the I2S event queue so ioctl() indicates that the I2S object cannot currently - // accept more audio samples - xQueueReset(self->i2s_event_queue); - - // Undo the swap transformation as the buffer has not been completely emptied. - // The asyncio stream writer will use the same buffer in a future write call. - if ((self->bits == I2S_BITS_PER_SAMPLE_32BIT) && (self->format == STEREO)) { - swap_32_bit_stereo_channels(appbuf); - } - } - return num_bytes_written; -} - -// FreeRTOS task used for non-blocking mode -STATIC void task_for_non_blocking_mode(void *self_in) { - machine_i2s_obj_t *self = (machine_i2s_obj_t *)self_in; - - non_blocking_descriptor_t descriptor; - - for (;;) { - if (xQueueReceive(self->non_blocking_mode_queue, &descriptor, portMAX_DELAY)) { - if (descriptor.direction == I2S_TX_TRANSFER) { - copy_appbuf_to_dma(self, &descriptor.appbuf); - } else { // RX - fill_appbuf_from_dma(self, &descriptor.appbuf); - } - mp_sched_schedule(descriptor.callback, MP_OBJ_FROM_PTR(self)); - } - } -} - -STATIC void machine_i2s_init_helper(machine_i2s_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - - enum { - ARG_sck, - ARG_ws, - ARG_sd, - ARG_mode, - ARG_bits, - ARG_format, - ARG_rate, - ARG_ibuf, - }; - - static const mp_arg_t allowed_args[] = { - { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_ws, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_sd, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_bits, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_format, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_ibuf, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_pos_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - // - // ---- Check validity of arguments ---- - // - - // are Pins valid? - int8_t sck = args[ARG_sck].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sck].u_obj); - int8_t ws = args[ARG_ws].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_ws].u_obj); - int8_t sd = args[ARG_sd].u_obj == MP_OBJ_NULL ? -1 : mp_hal_get_pin_obj(args[ARG_sd].u_obj); - - // is Mode valid? - i2s_mode_t mode = args[ARG_mode].u_int; - if ((mode != (I2S_MODE_MASTER | I2S_MODE_RX)) && - (mode != (I2S_MODE_MASTER | I2S_MODE_TX))) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid mode")); - } - - // is Bits valid? - i2s_bits_per_sample_t bits = args[ARG_bits].u_int; - if ((bits != I2S_BITS_PER_SAMPLE_16BIT) && - (bits != I2S_BITS_PER_SAMPLE_32BIT)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); - } - - // is Format valid? - format_t format = args[ARG_format].u_int; - if ((format != STEREO) && - (format != MONO)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid format")); - } - - // is Rate valid? - // Not checked: ESP-IDF I2S API does not indicate a valid range for sample rate - - // is Ibuf valid? - // Not checked: ESP-IDF I2S API will return error if requested buffer size exceeds available memory - - self->sck = sck; - self->ws = ws; - self->sd = sd; - self->mode = mode; - self->bits = bits; - self->format = format; - self->rate = args[ARG_rate].u_int; - self->ibuf = args[ARG_ibuf].u_int; - self->callback_for_non_blocking = MP_OBJ_NULL; - self->i2s_event_queue = NULL; - self->non_blocking_mode_queue = NULL; - self->non_blocking_mode_task = NULL; - self->io_mode = BLOCKING; - - i2s_config_t i2s_config; - i2s_config.communication_format = I2S_COMM_FORMAT_I2S; - i2s_config.mode = mode; - i2s_config.bits_per_sample = get_dma_bits(mode, bits); - i2s_config.channel_format = get_dma_format(mode, format); - i2s_config.sample_rate = self->rate; - i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LOWMED; - i2s_config.dma_buf_count = get_dma_buf_count(mode, bits, format, self->ibuf); - i2s_config.dma_buf_len = DMA_BUF_LEN_IN_I2S_FRAMES; - i2s_config.use_apll = false; - i2s_config.tx_desc_auto_clear = true; - i2s_config.fixed_mclk = 0; - i2s_config.mclk_multiple = I2S_MCLK_MULTIPLE_256; - i2s_config.bits_per_chan = 0; - - // I2S queue size equals the number of DMA buffers - check_esp_err(i2s_driver_install(self->port, &i2s_config, i2s_config.dma_buf_count, &self->i2s_event_queue)); - - // apply low-level workaround for bug in some ESP-IDF versions that swap - // the left and right channels - // https://github.com/espressif/esp-idf/issues/6625 - #if CONFIG_IDF_TARGET_ESP32S3 - REG_SET_BIT(I2S_TX_CONF_REG(self->port), I2S_TX_MSB_SHIFT); - REG_SET_BIT(I2S_TX_CONF_REG(self->port), I2S_RX_MSB_SHIFT); - #else - REG_SET_BIT(I2S_CONF_REG(self->port), I2S_TX_MSB_RIGHT); - REG_SET_BIT(I2S_CONF_REG(self->port), I2S_RX_MSB_RIGHT); - #endif - - i2s_pin_config_t pin_config; - pin_config.mck_io_num = I2S_PIN_NO_CHANGE; - pin_config.bck_io_num = self->sck; - pin_config.ws_io_num = self->ws; - - if (mode == (I2S_MODE_MASTER | I2S_MODE_RX)) { - pin_config.data_in_num = self->sd; - pin_config.data_out_num = I2S_PIN_NO_CHANGE; - } else { // TX - pin_config.data_in_num = I2S_PIN_NO_CHANGE; - pin_config.data_out_num = self->sd; - } - - check_esp_err(i2s_set_pin(self->port, &pin_config)); -} - -STATIC void machine_i2s_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "I2S(id=%u,\n" - "sck="MP_HAL_PIN_FMT ",\n" - "ws="MP_HAL_PIN_FMT ",\n" - "sd="MP_HAL_PIN_FMT ",\n" - "mode=%u,\n" - "bits=%u, format=%u,\n" - "rate=%d, ibuf=%d)", - self->port, - mp_hal_pin_name(self->sck), - mp_hal_pin_name(self->ws), - mp_hal_pin_name(self->sd), - self->mode, - self->bits, self->format, - self->rate, self->ibuf - ); -} - -STATIC mp_obj_t machine_i2s_make_new(const mp_obj_type_t *type, size_t n_pos_args, size_t n_kw_args, const mp_obj_t *args) { - mp_arg_check_num(n_pos_args, n_kw_args, 1, MP_OBJ_FUN_ARGS_MAX, true); - - i2s_port_t port = mp_obj_get_int(args[0]); - if (port < 0 || port >= I2S_NUM_AUTO) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid id")); - } - - machine_i2s_obj_t *self; - if (MP_STATE_PORT(machine_i2s_obj)[port] == NULL) { - self = m_new_obj_with_finaliser(machine_i2s_obj_t); - self->base.type = &machine_i2s_type; - MP_STATE_PORT(machine_i2s_obj)[port] = self; - self->port = port; - } else { - self = MP_STATE_PORT(machine_i2s_obj)[port]; - machine_i2s_deinit(self); - } - - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw_args, args + n_pos_args); - machine_i2s_init_helper(self, n_pos_args - 1, args + 1, &kw_args); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t machine_i2s_obj_init(size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - machine_i2s_obj_t *self = pos_args[0]; - machine_i2s_deinit(self); - machine_i2s_init_helper(self, n_pos_args - 1, pos_args + 1, kw_args); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_init_obj, 1, machine_i2s_obj_init); - -STATIC mp_obj_t machine_i2s_deinit(mp_obj_t self_in) { - machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); - i2s_driver_uninstall(self->port); - - if (self->non_blocking_mode_task != NULL) { - vTaskDelete(self->non_blocking_mode_task); - self->non_blocking_mode_task = NULL; - } - - if (self->non_blocking_mode_queue != NULL) { - vQueueDelete(self->non_blocking_mode_queue); - self->non_blocking_mode_queue = NULL; - } - - self->i2s_event_queue = NULL; - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_i2s_deinit_obj, machine_i2s_deinit); - -STATIC mp_obj_t machine_i2s_irq(mp_obj_t self_in, mp_obj_t handler) { - machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (handler != mp_const_none && !mp_obj_is_callable(handler)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid callback")); - } - - if (handler != mp_const_none) { - self->io_mode = NON_BLOCKING; - - // create a queue linking the MicroPython task to a FreeRTOS task - // that manages the non blocking mode of operation - self->non_blocking_mode_queue = xQueueCreate(1, sizeof(non_blocking_descriptor_t)); - - // non-blocking mode requires a background FreeRTOS task - if (xTaskCreatePinnedToCore( - task_for_non_blocking_mode, - "i2s_non_blocking", - I2S_TASK_STACK_SIZE, - self, - I2S_TASK_PRIORITY, - (TaskHandle_t *)&self->non_blocking_mode_task, - MP_TASK_COREID) != pdPASS) { - - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create I2S task")); - } - } else { - if (self->non_blocking_mode_task != NULL) { - vTaskDelete(self->non_blocking_mode_task); - self->non_blocking_mode_task = NULL; - } - - if (self->non_blocking_mode_queue != NULL) { - vQueueDelete(self->non_blocking_mode_queue); - self->non_blocking_mode_queue = NULL; - } - - self->io_mode = BLOCKING; - } - - self->callback_for_non_blocking = handler; - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_i2s_irq_obj, machine_i2s_irq); - -// Shift() is typically used as a volume control. -// shift=1 increases volume by 6dB, shift=-1 decreases volume by 6dB -STATIC mp_obj_t machine_i2s_shift(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_buf, ARG_bits, ARG_shift}; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_buf, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_bits, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_shift, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - - // parse args - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[ARG_buf].u_obj, &bufinfo, MP_BUFFER_RW); - - int16_t *buf_16 = bufinfo.buf; - int32_t *buf_32 = bufinfo.buf; - - uint8_t bits = args[ARG_bits].u_int; - int8_t shift = args[ARG_shift].u_int; - - uint32_t num_audio_samples; - switch (bits) { - case 16: - num_audio_samples = bufinfo.len / 2; - break; - - case 32: - num_audio_samples = bufinfo.len / 4; - break; - - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid bits")); - break; - } - - for (uint32_t i = 0; i < num_audio_samples; i++) { - switch (bits) { - case 16: - if (shift >= 0) { - buf_16[i] = buf_16[i] << shift; - } else { - buf_16[i] = buf_16[i] >> abs(shift); - } - break; - case 32: - if (shift >= 0) { - buf_32[i] = buf_32[i] << shift; - } else { - buf_32[i] = buf_32[i] >> abs(shift); - } - break; - } - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_i2s_shift_fun_obj, 0, machine_i2s_shift); -STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(machine_i2s_shift_obj, MP_ROM_PTR(&machine_i2s_shift_fun_obj)); - -STATIC const mp_rom_map_elem_t machine_i2s_locals_dict_table[] = { - // Methods - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_i2s_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_i2s_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_i2s_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_i2s_deinit_obj) }, - - // Static method - { MP_ROM_QSTR(MP_QSTR_shift), MP_ROM_PTR(&machine_i2s_shift_obj) }, - - // Constants - { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_RX) }, - { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_INT(I2S_MODE_MASTER | I2S_MODE_TX) }, - { MP_ROM_QSTR(MP_QSTR_STEREO), MP_ROM_INT(STEREO) }, - { MP_ROM_QSTR(MP_QSTR_MONO), MP_ROM_INT(MONO) }, -}; -MP_DEFINE_CONST_DICT(machine_i2s_locals_dict, machine_i2s_locals_dict_table); - -STATIC mp_uint_t machine_i2s_stream_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { - machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); - - if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) { - *errcode = MP_EPERM; - return MP_STREAM_ERROR; - } - - uint8_t appbuf_sample_size_in_bytes = (self->bits / 8) * (self->format == STEREO ? 2: 1); - if (size % appbuf_sample_size_in_bytes != 0) { - *errcode = MP_EINVAL; - return MP_STREAM_ERROR; - } - - if (size == 0) { - return 0; - } - - if (self->io_mode == NON_BLOCKING) { - non_blocking_descriptor_t descriptor; - descriptor.appbuf.buf = (void *)buf_in; - descriptor.appbuf.len = size; - descriptor.callback = self->callback_for_non_blocking; - descriptor.direction = I2S_RX_TRANSFER; - // send the descriptor to the task that handles non-blocking mode - xQueueSend(self->non_blocking_mode_queue, &descriptor, 0); - return size; - } else { // blocking or asyncio mode - mp_buffer_info_t appbuf; - appbuf.buf = (void *)buf_in; - appbuf.len = size; - uint32_t num_bytes_read = fill_appbuf_from_dma(self, &appbuf); - return num_bytes_read; - } -} - -STATIC mp_uint_t machine_i2s_stream_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { - machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); - - if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) { - *errcode = MP_EPERM; - return MP_STREAM_ERROR; - } - - if (size == 0) { - return 0; - } - - if (self->io_mode == NON_BLOCKING) { - non_blocking_descriptor_t descriptor; - descriptor.appbuf.buf = (void *)buf_in; - descriptor.appbuf.len = size; - descriptor.callback = self->callback_for_non_blocking; - descriptor.direction = I2S_TX_TRANSFER; - // send the descriptor to the task that handles non-blocking mode - xQueueSend(self->non_blocking_mode_queue, &descriptor, 0); - return size; - } else { // blocking or asyncio mode - mp_buffer_info_t appbuf; - appbuf.buf = (void *)buf_in; - appbuf.len = size; - size_t num_bytes_written = copy_appbuf_to_dma(self, &appbuf); - return num_bytes_written; - } -} - -STATIC mp_uint_t machine_i2s_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { - machine_i2s_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_uint_t ret; - mp_uint_t flags = arg; - self->io_mode = ASYNCIO; // a call to ioctl() is an indication that asyncio is being used - - if (request == MP_STREAM_POLL) { - ret = 0; - - if (flags & MP_STREAM_POLL_RD) { - if (self->mode != (I2S_MODE_MASTER | I2S_MODE_RX)) { - *errcode = MP_EPERM; - return MP_STREAM_ERROR; - } - - i2s_event_t i2s_event; - - // check event queue to determine if a DMA buffer has been filled - // (which is an indication that at least one DMA buffer is available to be read) - // note: timeout = 0 so the call is non-blocking - if (xQueueReceive(self->i2s_event_queue, &i2s_event, 0)) { - if (i2s_event.type == I2S_EVENT_RX_DONE) { - // getting here means that at least one DMA buffer is now full - // indicating that audio samples can be read from the I2S object - ret |= MP_STREAM_POLL_RD; - } - } - } - - if (flags & MP_STREAM_POLL_WR) { - if (self->mode != (I2S_MODE_MASTER | I2S_MODE_TX)) { - *errcode = MP_EPERM; - return MP_STREAM_ERROR; - } - - i2s_event_t i2s_event; - - // check event queue to determine if a DMA buffer has been emptied - // (which is an indication that at least one DMA buffer is available to be written) - // note: timeout = 0 so the call is non-blocking - if (xQueueReceive(self->i2s_event_queue, &i2s_event, 0)) { - if (i2s_event.type == I2S_EVENT_TX_DONE) { - // getting here means that at least one DMA buffer is now empty - // indicating that audio samples can be written to the I2S object - ret |= MP_STREAM_POLL_WR; - } - } - } - } else { - *errcode = MP_EINVAL; - ret = MP_STREAM_ERROR; - } - - return ret; -} - -STATIC const mp_stream_p_t i2s_stream_p = { - .read = machine_i2s_stream_read, - .write = machine_i2s_stream_write, - .ioctl = machine_i2s_ioctl, - .is_text = false, -}; - -MP_DEFINE_CONST_OBJ_TYPE( - machine_i2s_type, - MP_QSTR_I2S, - MP_TYPE_FLAG_ITER_IS_STREAM, - make_new, machine_i2s_make_new, - print, machine_i2s_print, - protocol, &i2s_stream_p, - locals_dict, &machine_i2s_locals_dict - ); - -MP_REGISTER_ROOT_POINTER(struct _machine_i2s_obj_t *machine_i2s_obj[I2S_NUM_AUTO]); - -#endif // MICROPY_PY_MACHINE_I2S diff --git a/tulip/esp32s3/machine_pin.c b/tulip/esp32s3/machine_pin.c deleted file mode 100644 index 5ea41701a..000000000 --- a/tulip/esp32s3/machine_pin.c +++ /dev/null @@ -1,728 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Damien P. George - * - * 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. - */ - -#include -#include - -#include "driver/gpio.h" -#include "driver/rtc_io.h" -#include "hal/gpio_ll.h" - -#include "py/runtime.h" -#include "py/mphal.h" -#include "mphalport.h" -#include "modmachine.h" -#include "extmod/virtpin.h" -#include "machine_rtc.h" -#include "modesp32.h" - -#if CONFIG_IDF_TARGET_ESP32C3 -#include "soc/usb_serial_jtag_reg.h" -#endif - -// Used to implement a range of pull capabilities -#define GPIO_PULL_DOWN (1) -#define GPIO_PULL_UP (2) - -#if CONFIG_IDF_TARGET_ESP32 -#define GPIO_FIRST_NON_OUTPUT (34) -#elif CONFIG_IDF_TARGET_ESP32S2 -#define GPIO_FIRST_NON_OUTPUT (46) -#endif - -typedef struct _machine_pin_obj_t { - mp_obj_base_t base; - gpio_num_t id; -} machine_pin_obj_t; - -typedef struct _machine_pin_irq_obj_t { - mp_obj_base_t base; - gpio_num_t id; -} machine_pin_irq_obj_t; - -STATIC const machine_pin_obj_t machine_pin_obj[GPIO_NUM_MAX] = { - #if CONFIG_IDF_TARGET_ESP32 - - {{&machine_pin_type}, GPIO_NUM_0}, - {{&machine_pin_type}, GPIO_NUM_1}, - {{&machine_pin_type}, GPIO_NUM_2}, - {{&machine_pin_type}, GPIO_NUM_3}, - {{&machine_pin_type}, GPIO_NUM_4}, - {{&machine_pin_type}, GPIO_NUM_5}, - {{&machine_pin_type}, GPIO_NUM_6}, - {{&machine_pin_type}, GPIO_NUM_7}, - {{&machine_pin_type}, GPIO_NUM_8}, - {{&machine_pin_type}, GPIO_NUM_9}, - {{&machine_pin_type}, GPIO_NUM_10}, - {{&machine_pin_type}, GPIO_NUM_11}, - {{&machine_pin_type}, GPIO_NUM_12}, - {{&machine_pin_type}, GPIO_NUM_13}, - {{&machine_pin_type}, GPIO_NUM_14}, - {{&machine_pin_type}, GPIO_NUM_15}, - #if CONFIG_ESP32_SPIRAM_SUPPORT - {{NULL}, -1}, - {{NULL}, -1}, - #else - {{&machine_pin_type}, GPIO_NUM_16}, - {{&machine_pin_type}, GPIO_NUM_17}, - #endif - {{&machine_pin_type}, GPIO_NUM_18}, - {{&machine_pin_type}, GPIO_NUM_19}, - {{&machine_pin_type}, GPIO_NUM_20}, - {{&machine_pin_type}, GPIO_NUM_21}, - {{&machine_pin_type}, GPIO_NUM_22}, - {{&machine_pin_type}, GPIO_NUM_23}, - {{NULL}, -1}, - {{&machine_pin_type}, GPIO_NUM_25}, - {{&machine_pin_type}, GPIO_NUM_26}, - {{&machine_pin_type}, GPIO_NUM_27}, - {{NULL}, -1}, - {{NULL}, -1}, - {{NULL}, -1}, - {{NULL}, -1}, - {{&machine_pin_type}, GPIO_NUM_32}, - {{&machine_pin_type}, GPIO_NUM_33}, - {{&machine_pin_type}, GPIO_NUM_34}, - {{&machine_pin_type}, GPIO_NUM_35}, - {{&machine_pin_type}, GPIO_NUM_36}, - {{&machine_pin_type}, GPIO_NUM_37}, - {{&machine_pin_type}, GPIO_NUM_38}, - {{&machine_pin_type}, GPIO_NUM_39}, - - #elif CONFIG_IDF_TARGET_ESP32C3 - - {{&machine_pin_type}, GPIO_NUM_0}, - {{&machine_pin_type}, GPIO_NUM_1}, - {{&machine_pin_type}, GPIO_NUM_2}, - {{&machine_pin_type}, GPIO_NUM_3}, - {{&machine_pin_type}, GPIO_NUM_4}, - {{&machine_pin_type}, GPIO_NUM_5}, - {{&machine_pin_type}, GPIO_NUM_6}, - {{&machine_pin_type}, GPIO_NUM_7}, - {{&machine_pin_type}, GPIO_NUM_8}, - {{&machine_pin_type}, GPIO_NUM_9}, - {{&machine_pin_type}, GPIO_NUM_10}, - {{&machine_pin_type}, GPIO_NUM_11}, - {{&machine_pin_type}, GPIO_NUM_12}, - {{&machine_pin_type}, GPIO_NUM_13}, - {{NULL}, -1}, // 14 FLASH - {{NULL}, -1}, // 15 FLASH - {{NULL}, -1}, // 16 FLASH - {{NULL}, -1}, // 17 FLASH - #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - {{NULL}, -1}, // 18 is for native USB D- - {{NULL}, -1}, // 19 is for native USB D+ - #else - {{&machine_pin_type}, GPIO_NUM_18}, - {{&machine_pin_type}, GPIO_NUM_19}, - #endif - {{&machine_pin_type}, GPIO_NUM_20}, - {{&machine_pin_type}, GPIO_NUM_21}, - - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - - {{&machine_pin_type}, GPIO_NUM_0}, - {{&machine_pin_type}, GPIO_NUM_1}, - {{&machine_pin_type}, GPIO_NUM_2}, - {{&machine_pin_type}, GPIO_NUM_3}, - {{&machine_pin_type}, GPIO_NUM_4}, - {{&machine_pin_type}, GPIO_NUM_5}, - {{&machine_pin_type}, GPIO_NUM_6}, - {{&machine_pin_type}, GPIO_NUM_7}, - {{&machine_pin_type}, GPIO_NUM_8}, - {{&machine_pin_type}, GPIO_NUM_9}, - {{&machine_pin_type}, GPIO_NUM_10}, - {{&machine_pin_type}, GPIO_NUM_11}, - {{&machine_pin_type}, GPIO_NUM_12}, - {{&machine_pin_type}, GPIO_NUM_13}, - {{&machine_pin_type}, GPIO_NUM_14}, - {{&machine_pin_type}, GPIO_NUM_15}, - {{&machine_pin_type}, GPIO_NUM_16}, - {{&machine_pin_type}, GPIO_NUM_17}, - {{&machine_pin_type}, GPIO_NUM_18}, - #if CONFIG_USB_CDC_ENABLED - {{NULL}, -1}, // 19 is for native USB D- - {{NULL}, -1}, // 20 is for native USB D- - #else - {{&machine_pin_type}, GPIO_NUM_19}, - {{&machine_pin_type}, GPIO_NUM_20}, - #endif - {{&machine_pin_type}, GPIO_NUM_21}, - {{NULL}, -1}, // 22 not a pin - {{NULL}, -1}, // 23 not a pin - {{NULL}, -1}, // 24 not a pin - {{NULL}, -1}, // 25 not a pin - #if CONFIG_SPIRAM - {{NULL}, -1}, // 26 PSRAM - #else - {{&machine_pin_type}, GPIO_NUM_26}, - #endif - {{NULL}, -1}, // 27 FLASH/PSRAM - {{NULL}, -1}, // 28 FLASH/PSRAM - {{NULL}, -1}, // 29 FLASH/PSRAM - {{NULL}, -1}, // 30 FLASH/PSRAM - {{NULL}, -1}, // 31 FLASH/PSRAM - {{NULL}, -1}, // 32 FLASH/PSRAM - #if CONFIG_SPIRAM_MODE_OCT - {{NULL}, -1}, // 33 FLASH/PSRAM - {{NULL}, -1}, // 34 FLASH/PSRAM - {{NULL}, -1}, // 35 FLASH/PSRAM - {{NULL}, -1}, // 36 FLASH/PSRAM - {{NULL}, -1}, // 37 FLASH/PSRAM - #else - {{&machine_pin_type}, GPIO_NUM_33}, - {{&machine_pin_type}, GPIO_NUM_34}, - {{&machine_pin_type}, GPIO_NUM_35}, - {{&machine_pin_type}, GPIO_NUM_36}, - {{&machine_pin_type}, GPIO_NUM_37}, - #endif - {{&machine_pin_type}, GPIO_NUM_38}, - {{&machine_pin_type}, GPIO_NUM_39}, // MTCLK - {{&machine_pin_type}, GPIO_NUM_40}, // MTDO - {{&machine_pin_type}, GPIO_NUM_41}, // MTDI - {{&machine_pin_type}, GPIO_NUM_42}, // MTMS - {{&machine_pin_type}, GPIO_NUM_43}, // U0TXD - {{&machine_pin_type}, GPIO_NUM_44}, // U0RXD - {{&machine_pin_type}, GPIO_NUM_45}, - {{&machine_pin_type}, GPIO_NUM_46}, - - #endif - - #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_ESP32S3_EXTENDED_IO - - {{&machine_pin_type}, GPIO_NUM_47}, - {{&machine_pin_type}, GPIO_NUM_48}, - - #endif -}; - -// forward declaration -STATIC const machine_pin_irq_obj_t machine_pin_irq_object[GPIO_NUM_MAX]; - -void machine_pins_init(void) { - static bool did_install = false; - if (!did_install) { - gpio_install_isr_service(0); - did_install = true; - } - memset(&MP_STATE_PORT(machine_pin_irq_handler[0]), 0, sizeof(MP_STATE_PORT(machine_pin_irq_handler))); -} - -void machine_pins_deinit(void) { - for (int i = 0; i < MP_ARRAY_SIZE(machine_pin_obj); ++i) { - if (machine_pin_obj[i].id != (gpio_num_t)-1) { - gpio_isr_handler_remove(machine_pin_obj[i].id); - } - } -} - -STATIC void machine_pin_isr_handler(void *arg) { - machine_pin_obj_t *self = arg; - mp_obj_t handler = MP_STATE_PORT(machine_pin_irq_handler)[self->id]; - mp_sched_schedule(handler, MP_OBJ_FROM_PTR(self)); - mp_hal_wake_main_task_from_isr(); -} - -gpio_num_t machine_pin_get_id(mp_obj_t pin_in) { - if (mp_obj_get_type(pin_in) != &machine_pin_type) { - mp_raise_ValueError(MP_ERROR_TEXT("expecting a pin")); - } - machine_pin_obj_t *self = pin_in; - return self->id; -} - -STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_pin_obj_t *self = self_in; - mp_printf(print, "Pin(%u)", self->id); -} - -// pin.init(mode=None, pull=-1, *, value, drive, hold) -STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_pull, ARG_value, ARG_drive, ARG_hold }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = mp_const_none}}, - { MP_QSTR_pull, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_SMALL_INT(-1)}}, - { MP_QSTR_value, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, - { MP_QSTR_drive, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, - { MP_QSTR_hold, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL}}, - }; - - // parse args - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - // reset the pin to digital if this is a mode-setting init (grab it back from ADC) - if (args[ARG_mode].u_obj != mp_const_none) { - if (rtc_gpio_is_valid_gpio(self->id)) { - #if !CONFIG_IDF_TARGET_ESP32C3 - rtc_gpio_deinit(self->id); - #endif - } - } - - #if CONFIG_IDF_TARGET_ESP32C3 - if (self->id == 18 || self->id == 19) { - CLEAR_PERI_REG_MASK(USB_SERIAL_JTAG_CONF0_REG, USB_SERIAL_JTAG_USB_PAD_ENABLE); - } - #endif - - // configure the pin for gpio - esp_rom_gpio_pad_select_gpio(self->id); - - // set initial value (do this before configuring mode/pull) - if (args[ARG_value].u_obj != MP_OBJ_NULL) { - gpio_set_level(self->id, mp_obj_is_true(args[ARG_value].u_obj)); - } - - // set drive capability (do this before configuring mode) - if (args[ARG_drive].u_obj != MP_OBJ_NULL && GPIO_IS_VALID_OUTPUT_GPIO(self->id)) { - mp_int_t strength = mp_obj_get_int(args[ARG_drive].u_obj); - if (0 <= strength && strength < GPIO_DRIVE_CAP_MAX) { - gpio_set_drive_capability(self->id, strength); - } - } - - // configure mode - if (args[ARG_mode].u_obj != mp_const_none) { - mp_int_t pin_io_mode = mp_obj_get_int(args[ARG_mode].u_obj); - #ifdef GPIO_FIRST_NON_OUTPUT - if (self->id >= GPIO_FIRST_NON_OUTPUT && (pin_io_mode & GPIO_MODE_DEF_OUTPUT)) { - mp_raise_ValueError(MP_ERROR_TEXT("pin can only be input")); - } - #endif - gpio_set_direction(self->id, pin_io_mode); - } - - // configure pull - if (args[ARG_pull].u_obj != MP_OBJ_NEW_SMALL_INT(-1)) { - int mode = 0; - if (args[ARG_pull].u_obj != mp_const_none) { - mode = mp_obj_get_int(args[ARG_pull].u_obj); - } - if (mode & GPIO_PULL_DOWN) { - gpio_pulldown_en(self->id); - } else { - gpio_pulldown_dis(self->id); - } - if (mode & GPIO_PULL_UP) { - gpio_pullup_en(self->id); - } else { - gpio_pullup_dis(self->id); - } - } - - // configure pad hold - if (args[ARG_hold].u_obj != MP_OBJ_NULL && GPIO_IS_VALID_OUTPUT_GPIO(self->id)) { - // always disable pad hold to apply outstanding config changes - gpio_hold_dis(self->id); - // (re-)enable pad hold if requested - if (mp_obj_is_true(args[ARG_hold].u_obj)) { - gpio_hold_en(self->id); - } - } - - return mp_const_none; -} - -// constructor(id, ...) -mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); - - // get the wanted pin object - int wanted_pin = mp_obj_get_int(args[0]); - const machine_pin_obj_t *self = NULL; - if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj)) { - self = (machine_pin_obj_t *)&machine_pin_obj[wanted_pin]; - } - if (self == NULL || self->base.type == NULL) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); - } - - if (n_args > 1 || n_kw > 0) { - // pin mode given, so configure this GPIO - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_pin_obj_init_helper(self, n_args - 1, args + 1, &kw_args); - } - - return MP_OBJ_FROM_PTR(self); -} - -// fast method for getting/setting pin value -STATIC mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 0, 1, false); - machine_pin_obj_t *self = self_in; - if (n_args == 0) { - // get pin - return MP_OBJ_NEW_SMALL_INT(gpio_get_level(self->id)); - } else { - // set pin - gpio_set_level(self->id, mp_obj_is_true(args[0])); - return mp_const_none; - } -} - -// pin.init(mode, pull) -STATIC mp_obj_t machine_pin_obj_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - return machine_pin_obj_init_helper(args[0], n_args - 1, args + 1, kw_args); -} -MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_init_obj, 1, machine_pin_obj_init); - -// pin.value([value]) -STATIC mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) { - return machine_pin_call(args[0], n_args - 1, 0, args + 1); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_pin_value); - -// pin.off() -STATIC mp_obj_t machine_pin_off(mp_obj_t self_in) { - machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - gpio_set_level(self->id, 0); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_off_obj, machine_pin_off); - -// pin.on() -STATIC mp_obj_t machine_pin_on(mp_obj_t self_in) { - machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - gpio_set_level(self->id, 1); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on); - -// pin.irq(handler=None, trigger=IRQ_FALLING|IRQ_RISING) -STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_handler, ARG_trigger, ARG_wake }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_handler, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_trigger, MP_ARG_INT, {.u_int = GPIO_INTR_POSEDGE | GPIO_INTR_NEGEDGE} }, - { MP_QSTR_wake, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - }; - machine_pin_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - if (n_args > 1 || kw_args->used != 0) { - // configure irq - mp_obj_t handler = args[ARG_handler].u_obj; - uint32_t trigger = args[ARG_trigger].u_int; - mp_obj_t wake_obj = args[ARG_wake].u_obj; - - if ((trigger == GPIO_INTR_LOW_LEVEL || trigger == GPIO_INTR_HIGH_LEVEL) && wake_obj != mp_const_none) { - mp_int_t wake; - if (mp_obj_get_int_maybe(wake_obj, &wake)) { - if (wake < 2 || wake > 7) { - mp_raise_ValueError(MP_ERROR_TEXT("bad wake value")); - } - } else { - mp_raise_ValueError(MP_ERROR_TEXT("bad wake value")); - } - - if (machine_rtc_config.wake_on_touch) { // not compatible - mp_raise_ValueError(MP_ERROR_TEXT("no resources")); - } - - if (!RTC_IS_VALID_EXT_PIN(self->id)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for wake")); - } - - if (machine_rtc_config.ext0_pin == -1) { - machine_rtc_config.ext0_pin = self->id; - } else if (machine_rtc_config.ext0_pin != self->id) { - mp_raise_ValueError(MP_ERROR_TEXT("no resources")); - } - - machine_rtc_config.ext0_level = trigger == GPIO_INTR_LOW_LEVEL ? 0 : 1; - machine_rtc_config.ext0_wake_types = wake; - } else { - if (machine_rtc_config.ext0_pin == self->id) { - machine_rtc_config.ext0_pin = -1; - } - - if (handler == mp_const_none) { - handler = MP_OBJ_NULL; - trigger = 0; - } - gpio_isr_handler_remove(self->id); - MP_STATE_PORT(machine_pin_irq_handler)[self->id] = handler; - gpio_set_intr_type(self->id, trigger); - gpio_isr_handler_add(self->id, machine_pin_isr_handler, (void *)self); - } - } - - // return the irq object - return MP_OBJ_FROM_PTR(&machine_pin_irq_object[self->id]); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pin_irq_obj, 1, machine_pin_irq); - -STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { - // instance methods - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, - { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_off_obj) }, - { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) }, - { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, - - // class constants - { MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(GPIO_MODE_INPUT) }, - { MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(GPIO_MODE_INPUT_OUTPUT) }, - { MP_ROM_QSTR(MP_QSTR_OPEN_DRAIN), MP_ROM_INT(GPIO_MODE_INPUT_OUTPUT_OD) }, - { MP_ROM_QSTR(MP_QSTR_PULL_UP), MP_ROM_INT(GPIO_PULL_UP) }, - { MP_ROM_QSTR(MP_QSTR_PULL_DOWN), MP_ROM_INT(GPIO_PULL_DOWN) }, - { MP_ROM_QSTR(MP_QSTR_IRQ_RISING), MP_ROM_INT(GPIO_INTR_POSEDGE) }, - { MP_ROM_QSTR(MP_QSTR_IRQ_FALLING), MP_ROM_INT(GPIO_INTR_NEGEDGE) }, - { MP_ROM_QSTR(MP_QSTR_WAKE_LOW), MP_ROM_INT(GPIO_INTR_LOW_LEVEL) }, - { MP_ROM_QSTR(MP_QSTR_WAKE_HIGH), MP_ROM_INT(GPIO_INTR_HIGH_LEVEL) }, - { MP_ROM_QSTR(MP_QSTR_DRIVE_0), MP_ROM_INT(GPIO_DRIVE_CAP_0) }, - { MP_ROM_QSTR(MP_QSTR_DRIVE_1), MP_ROM_INT(GPIO_DRIVE_CAP_1) }, - { MP_ROM_QSTR(MP_QSTR_DRIVE_2), MP_ROM_INT(GPIO_DRIVE_CAP_2) }, - { MP_ROM_QSTR(MP_QSTR_DRIVE_3), MP_ROM_INT(GPIO_DRIVE_CAP_3) }, -}; - -STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { - (void)errcode; - machine_pin_obj_t *self = self_in; - - switch (request) { - case MP_PIN_READ: { - return gpio_get_level(self->id); - } - case MP_PIN_WRITE: { - gpio_set_level(self->id, arg); - return 0; - } - } - return -1; -} - -STATIC MP_DEFINE_CONST_DICT(machine_pin_locals_dict, machine_pin_locals_dict_table); - -STATIC const mp_pin_p_t pin_pin_p = { - .ioctl = pin_ioctl, -}; - -MP_DEFINE_CONST_OBJ_TYPE( - machine_pin_type, - MP_QSTR_Pin, - MP_TYPE_FLAG_NONE, - make_new, mp_pin_make_new, - print, machine_pin_print, - call, machine_pin_call, - protocol, &pin_pin_p, - locals_dict, &machine_pin_locals_dict - ); - -/******************************************************************************/ -// Pin IRQ object - -STATIC const mp_obj_type_t machine_pin_irq_type; - -STATIC const machine_pin_irq_obj_t machine_pin_irq_object[GPIO_NUM_MAX] = { - #if CONFIG_IDF_TARGET_ESP32 - - {{&machine_pin_irq_type}, GPIO_NUM_0}, - {{&machine_pin_irq_type}, GPIO_NUM_1}, - {{&machine_pin_irq_type}, GPIO_NUM_2}, - {{&machine_pin_irq_type}, GPIO_NUM_3}, - {{&machine_pin_irq_type}, GPIO_NUM_4}, - {{&machine_pin_irq_type}, GPIO_NUM_5}, - {{&machine_pin_irq_type}, GPIO_NUM_6}, - {{&machine_pin_irq_type}, GPIO_NUM_7}, - {{&machine_pin_irq_type}, GPIO_NUM_8}, - {{&machine_pin_irq_type}, GPIO_NUM_9}, - {{&machine_pin_irq_type}, GPIO_NUM_10}, - {{&machine_pin_irq_type}, GPIO_NUM_11}, - {{&machine_pin_irq_type}, GPIO_NUM_12}, - {{&machine_pin_irq_type}, GPIO_NUM_13}, - {{&machine_pin_irq_type}, GPIO_NUM_14}, - {{&machine_pin_irq_type}, GPIO_NUM_15}, - #if CONFIG_ESP32_SPIRAM_SUPPORT - {{NULL}, -1}, - {{NULL}, -1}, - #else - {{&machine_pin_irq_type}, GPIO_NUM_16}, - {{&machine_pin_irq_type}, GPIO_NUM_17}, - #endif - {{&machine_pin_irq_type}, GPIO_NUM_18}, - {{&machine_pin_irq_type}, GPIO_NUM_19}, - {{&machine_pin_irq_type}, GPIO_NUM_20}, - {{&machine_pin_irq_type}, GPIO_NUM_21}, - {{&machine_pin_irq_type}, GPIO_NUM_22}, - {{&machine_pin_irq_type}, GPIO_NUM_23}, - {{NULL}, -1}, - {{&machine_pin_irq_type}, GPIO_NUM_25}, - {{&machine_pin_irq_type}, GPIO_NUM_26}, - {{&machine_pin_irq_type}, GPIO_NUM_27}, - {{NULL}, -1}, - {{NULL}, -1}, - {{NULL}, -1}, - {{NULL}, -1}, - {{&machine_pin_irq_type}, GPIO_NUM_32}, - {{&machine_pin_irq_type}, GPIO_NUM_33}, - {{&machine_pin_irq_type}, GPIO_NUM_34}, - {{&machine_pin_irq_type}, GPIO_NUM_35}, - {{&machine_pin_irq_type}, GPIO_NUM_36}, - {{&machine_pin_irq_type}, GPIO_NUM_37}, - {{&machine_pin_irq_type}, GPIO_NUM_38}, - {{&machine_pin_irq_type}, GPIO_NUM_39}, - - #elif CONFIG_IDF_TARGET_ESP32C3 - - {{&machine_pin_irq_type}, GPIO_NUM_0}, - {{&machine_pin_irq_type}, GPIO_NUM_1}, - {{&machine_pin_irq_type}, GPIO_NUM_2}, - {{&machine_pin_irq_type}, GPIO_NUM_3}, - {{&machine_pin_irq_type}, GPIO_NUM_4}, - {{&machine_pin_irq_type}, GPIO_NUM_5}, - {{&machine_pin_irq_type}, GPIO_NUM_6}, - {{&machine_pin_irq_type}, GPIO_NUM_7}, - {{&machine_pin_irq_type}, GPIO_NUM_8}, - {{&machine_pin_irq_type}, GPIO_NUM_9}, - {{&machine_pin_irq_type}, GPIO_NUM_10}, - {{&machine_pin_irq_type}, GPIO_NUM_11}, - {{&machine_pin_irq_type}, GPIO_NUM_12}, - {{&machine_pin_irq_type}, GPIO_NUM_13}, - {{&machine_pin_irq_type}, GPIO_NUM_14}, - {{&machine_pin_irq_type}, GPIO_NUM_15}, - {{&machine_pin_irq_type}, GPIO_NUM_16}, - {{&machine_pin_irq_type}, GPIO_NUM_17}, - {{&machine_pin_irq_type}, GPIO_NUM_18}, - {{&machine_pin_irq_type}, GPIO_NUM_19}, - {{&machine_pin_irq_type}, GPIO_NUM_20}, - {{&machine_pin_irq_type}, GPIO_NUM_21}, - - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - - {{&machine_pin_irq_type}, GPIO_NUM_0}, - {{&machine_pin_irq_type}, GPIO_NUM_1}, - {{&machine_pin_irq_type}, GPIO_NUM_2}, - {{&machine_pin_irq_type}, GPIO_NUM_3}, - {{&machine_pin_irq_type}, GPIO_NUM_4}, - {{&machine_pin_irq_type}, GPIO_NUM_5}, - {{&machine_pin_irq_type}, GPIO_NUM_6}, - {{&machine_pin_irq_type}, GPIO_NUM_7}, - {{&machine_pin_irq_type}, GPIO_NUM_8}, - {{&machine_pin_irq_type}, GPIO_NUM_9}, - {{&machine_pin_irq_type}, GPIO_NUM_10}, - {{&machine_pin_irq_type}, GPIO_NUM_11}, - {{&machine_pin_irq_type}, GPIO_NUM_12}, - {{&machine_pin_irq_type}, GPIO_NUM_13}, - {{&machine_pin_irq_type}, GPIO_NUM_14}, - {{&machine_pin_irq_type}, GPIO_NUM_15}, - {{&machine_pin_irq_type}, GPIO_NUM_16}, - {{&machine_pin_irq_type}, GPIO_NUM_17}, - {{&machine_pin_irq_type}, GPIO_NUM_18}, - #if CONFIG_USB_CDC_ENABLED - {{NULL}, -1}, // 19 is for native USB D- - {{NULL}, -1}, // 20 is for native USB D- - #else - {{&machine_pin_irq_type}, GPIO_NUM_19}, - {{&machine_pin_irq_type}, GPIO_NUM_20}, - #endif - {{&machine_pin_irq_type}, GPIO_NUM_21}, - {{NULL}, -1}, // 22 not a pin - {{NULL}, -1}, // 23 not a pin - {{NULL}, -1}, // 24 not a pin - {{NULL}, -1}, // 25 not a pin - #if CONFIG_SPIRAM - {{NULL}, -1}, // 26 PSRAM - #else - {{&machine_pin_irq_type}, GPIO_NUM_26}, - #endif - {{NULL}, -1}, // 27 FLASH/PSRAM - {{NULL}, -1}, // 28 FLASH/PSRAM - {{NULL}, -1}, // 29 FLASH/PSRAM - {{NULL}, -1}, // 30 FLASH/PSRAM - {{NULL}, -1}, // 31 FLASH/PSRAM - {{NULL}, -1}, // 32 FLASH/PSRAM - #if CONFIG_SPIRAM_MODE_OCT - {{NULL}, -1}, // 33 FLASH/PSRAM - {{NULL}, -1}, // 34 FLASH/PSRAM - {{NULL}, -1}, // 35 FLASH/PSRAM - {{NULL}, -1}, // 36 FLASH/PSRAM - {{NULL}, -1}, // 37 FLASH/PSRAM - #else - {{&machine_pin_irq_type}, GPIO_NUM_33}, - {{&machine_pin_irq_type}, GPIO_NUM_34}, - {{&machine_pin_irq_type}, GPIO_NUM_35}, - {{&machine_pin_irq_type}, GPIO_NUM_36}, - {{&machine_pin_irq_type}, GPIO_NUM_37}, - #endif - {{&machine_pin_irq_type}, GPIO_NUM_38}, - {{&machine_pin_irq_type}, GPIO_NUM_39}, - {{&machine_pin_irq_type}, GPIO_NUM_40}, - {{&machine_pin_irq_type}, GPIO_NUM_41}, - {{&machine_pin_irq_type}, GPIO_NUM_42}, - {{&machine_pin_irq_type}, GPIO_NUM_43}, - {{&machine_pin_irq_type}, GPIO_NUM_44}, - {{&machine_pin_irq_type}, GPIO_NUM_45}, - {{&machine_pin_irq_type}, GPIO_NUM_46}, - - #endif - - #if CONFIG_IDF_TARGET_ESP32S3 && MICROPY_HW_ESP32S3_EXTENDED_IO - - {{&machine_pin_irq_type}, GPIO_NUM_47}, - {{&machine_pin_irq_type}, GPIO_NUM_48}, - - #endif -}; - -STATIC mp_obj_t machine_pin_irq_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { - machine_pin_irq_obj_t *self = self_in; - mp_arg_check_num(n_args, n_kw, 0, 0, false); - machine_pin_isr_handler((void *)&machine_pin_obj[self->id]); - return mp_const_none; -} - -STATIC mp_obj_t machine_pin_irq_trigger(size_t n_args, const mp_obj_t *args) { - machine_pin_irq_obj_t *self = args[0]; - uint32_t orig_trig = GPIO.pin[self->id].int_type; - if (n_args == 2) { - // set trigger - gpio_set_intr_type(self->id, mp_obj_get_int(args[1])); - } - // return original trigger value - return MP_OBJ_NEW_SMALL_INT(orig_trig); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_irq_trigger_obj, 1, 2, machine_pin_irq_trigger); - -STATIC const mp_rom_map_elem_t machine_pin_irq_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_trigger), MP_ROM_PTR(&machine_pin_irq_trigger_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(machine_pin_irq_locals_dict, machine_pin_irq_locals_dict_table); - -STATIC MP_DEFINE_CONST_OBJ_TYPE( - machine_pin_irq_type, - MP_QSTR_IRQ, - MP_TYPE_FLAG_NONE, - call, machine_pin_irq_call, - locals_dict, &machine_pin_irq_locals_dict - ); - -MP_REGISTER_ROOT_POINTER(mp_obj_t machine_pin_irq_handler[GPIO_PIN_COUNT]); diff --git a/tulip/esp32s3/machine_pwm.c b/tulip/esp32s3/machine_pwm.c deleted file mode 100644 index 462d0fa79..000000000 --- a/tulip/esp32s3/machine_pwm.c +++ /dev/null @@ -1,683 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016-2021 Damien P. George - * Copyright (c) 2018 Alan Dragomirecky - * Copyright (c) 2020 Antoine Aubert - * Copyright (c) 2021 Ihor Nehrutsa - * - * 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. - */ - -#include - -#include "py/runtime.h" -#include "py/mphal.h" - -#include "driver/ledc.h" -#include "esp_err.h" -#include "soc/gpio_sig_map.h" - -#define PWM_DBG(...) -// #define PWM_DBG(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, "\n"); - -// Total number of channels -#define PWM_CHANNEL_MAX (LEDC_SPEED_MODE_MAX * LEDC_CHANNEL_MAX) - -typedef struct _chan_t { - // Which channel has which GPIO pin assigned? - // (-1 if not assigned) - gpio_num_t pin; - // Which channel has which timer assigned? - // (-1 if not assigned) - int timer_idx; -} chan_t; - -// List of PWM channels -STATIC chan_t chans[PWM_CHANNEL_MAX]; - -// channel_idx is an index (end-to-end sequential numbering) for all channels -// available on the chip and described in chans[] -#define CHANNEL_IDX(mode, channel) (mode * LEDC_CHANNEL_MAX + channel) -#define CHANNEL_IDX_TO_MODE(channel_idx) (channel_idx / LEDC_CHANNEL_MAX) -#define CHANNEL_IDX_TO_CHANNEL(channel_idx) (channel_idx % LEDC_CHANNEL_MAX) - -// Total number of timers -#define PWM_TIMER_MAX (LEDC_SPEED_MODE_MAX * LEDC_TIMER_MAX) - -// List of timer configs -STATIC ledc_timer_config_t timers[PWM_TIMER_MAX]; - -// timer_idx is an index (end-to-end sequential numbering) for all timers -// available on the chip and configured in timers[] -#define TIMER_IDX(mode, timer) (mode * LEDC_TIMER_MAX + timer) -#define TIMER_IDX_TO_MODE(timer_idx) (timer_idx / LEDC_TIMER_MAX) -#define TIMER_IDX_TO_TIMER(timer_idx) (timer_idx % LEDC_TIMER_MAX) - -// Params for PWM operation -// 5khz is default frequency -#define PWM_FREQ (5000) - -// 10-bit resolution (compatible with esp8266 PWM) -#define PWM_RES_10_BIT (LEDC_TIMER_10_BIT) - -// Maximum duty value on 10-bit resolution -#define MAX_DUTY_U10 ((1 << PWM_RES_10_BIT) - 1) -// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html#supported-range-of-frequency-and-duty-resolutions -// duty() uses 10-bit resolution or less -// duty_u16() and duty_ns() use 16-bit resolution or less - -// Possible highest resolution in device -#if (LEDC_TIMER_BIT_MAX - 1) < LEDC_TIMER_16_BIT -#define HIGHEST_PWM_RES (LEDC_TIMER_BIT_MAX - 1) -#else -#define HIGHEST_PWM_RES (LEDC_TIMER_16_BIT) // 20 bit for ESP32, but 16 bit is used -#endif -// Duty resolution of user interface in `duty_u16()` and `duty_u16` parameter in constructor/initializer -#define UI_RES_16_BIT (16) -// Maximum duty value on highest user interface resolution -#define UI_MAX_DUTY ((1 << UI_RES_16_BIT) - 1) -// How much to shift from the HIGHEST_PWM_RES duty resolution to the user interface duty resolution UI_RES_16_BIT -#define UI_RES_SHIFT (UI_RES_16_BIT - HIGHEST_PWM_RES) // 0 for ESP32, 2 for S2, S3, C3 - -#if SOC_LEDC_SUPPORT_REF_TICK -// If the PWM frequency is less than EMPIRIC_FREQ, then LEDC_REF_CLK_HZ(1 MHz) source is used, else LEDC_APB_CLK_HZ(80 MHz) source is used -#define EMPIRIC_FREQ (10) // Hz -#endif - -// Config of timer upon which we run all PWM'ed GPIO pins -STATIC bool pwm_inited = false; - -// MicroPython PWM object struct -typedef struct _machine_pwm_obj_t { - mp_obj_base_t base; - gpio_num_t pin; - bool active; - int mode; - int channel; - int timer; - int duty_x; // PWM_RES_10_BIT if duty(), HIGHEST_PWM_RES if duty_u16(), -HIGHEST_PWM_RES if duty_ns() - int duty_u10; // stored values from previous duty setters - int duty_u16; // - / - - int duty_ns; // - / - -} machine_pwm_obj_t; - -STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx); -STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty); -STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty); -STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns); - -STATIC void pwm_init(void) { - // Initial condition: no channels assigned - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - chans[i].pin = -1; - chans[i].timer_idx = -1; - } - - // Prepare all timers config - // Initial condition: no timers assigned - for (int i = 0; i < PWM_TIMER_MAX; ++i) { - timers[i].duty_resolution = HIGHEST_PWM_RES; - // unset timer is -1 - timers[i].freq_hz = -1; - timers[i].speed_mode = TIMER_IDX_TO_MODE(i); - timers[i].timer_num = TIMER_IDX_TO_TIMER(i); - timers[i].clk_cfg = LEDC_AUTO_CLK; // will reinstall later according to the EMPIRIC_FREQ - } -} - -// Deinit channel and timer if the timer is unused -STATIC void pwm_deinit(int channel_idx) { - // Valid channel? - if ((channel_idx >= 0) && (channel_idx < PWM_CHANNEL_MAX)) { - // Clean up timer if necessary - int timer_idx = chans[channel_idx].timer_idx; - if (timer_idx != -1) { - if (!is_timer_in_use(channel_idx, timer_idx)) { - check_esp_err(ledc_timer_rst(TIMER_IDX_TO_MODE(timer_idx), TIMER_IDX_TO_TIMER(timer_idx))); - // Flag it unused - timers[chans[channel_idx].timer_idx].freq_hz = -1; - } - } - - int pin = chans[channel_idx].pin; - if (pin != -1) { - int mode = CHANNEL_IDX_TO_MODE(channel_idx); - int channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - // Mark it unused, and tell the hardware to stop routing - check_esp_err(ledc_stop(mode, channel, 0)); - // Disable ledc signal for the pin - // esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); - if (mode == LEDC_LOW_SPEED_MODE) { - esp_rom_gpio_connect_out_signal(pin, LEDC_LS_SIG_OUT0_IDX + channel, false, true); - } else { - #if LEDC_SPEED_MODE_MAX > 1 - #if CONFIG_IDF_TARGET_ESP32 - esp_rom_gpio_connect_out_signal(pin, LEDC_HS_SIG_OUT0_IDX + channel, false, true); - #else - #error Add supported CONFIG_IDF_TARGET_ESP32_xxx - #endif - #endif - } - } - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; - } -} - -// This called from Ctrl-D soft reboot -void machine_pwm_deinit_all(void) { - if (pwm_inited) { - for (int channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { - pwm_deinit(channel_idx); - } - pwm_inited = false; - } -} - -STATIC void configure_channel(machine_pwm_obj_t *self) { - ledc_channel_config_t cfg = { - .channel = self->channel, - .duty = (1 << (timers[TIMER_IDX(self->mode, self->timer)].duty_resolution)) / 2, - .gpio_num = self->pin, - .intr_type = LEDC_INTR_DISABLE, - .speed_mode = self->mode, - .timer_sel = self->timer, - }; - if (ledc_channel_config(&cfg) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("PWM not supported on Pin(%d)"), self->pin); - } -} - -STATIC void set_freq(machine_pwm_obj_t *self, unsigned int freq, ledc_timer_config_t *timer) { - if (freq != timer->freq_hz) { - // Find the highest bit resolution for the requested frequency - unsigned int i = APB_CLK_FREQ; // 80 MHz - #if SOC_LEDC_SUPPORT_REF_TICK - if (freq < EMPIRIC_FREQ) { - i = REF_CLK_FREQ; // 1 MHz - } - #endif - - int divider = (i + freq / 2) / freq; // rounded - if (divider == 0) { - divider = 1; - } - float f = (float)i / divider; // actual frequency - if (f <= 1.0) { - f = 1.0; - } - i = (unsigned int)roundf((float)i / f); - - unsigned int res = 0; - for (; i > 1; i >>= 1) { - ++res; - } - if (res == 0) { - res = 1; - } else if (res > HIGHEST_PWM_RES) { - // Limit resolution to HIGHEST_PWM_RES to match units of our duty - res = HIGHEST_PWM_RES; - } - - // Configure the new resolution and frequency - timer->duty_resolution = res; - timer->freq_hz = freq; - timer->clk_cfg = LEDC_USE_APB_CLK; - #if SOC_LEDC_SUPPORT_REF_TICK - if (freq < EMPIRIC_FREQ) { - timer->clk_cfg = LEDC_USE_REF_TICK; - } - #endif - - // Set frequency - esp_err_t err = ledc_timer_config(timer); - if (err != ESP_OK) { - if (err == ESP_FAIL) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("unreachable frequency %d"), freq); - } else { - check_esp_err(err); - } - } - // Reset the timer if low speed - if (self->mode == LEDC_LOW_SPEED_MODE) { - check_esp_err(ledc_timer_rst(self->mode, self->timer)); - } - } - - // Save the same duty cycle when frequency is changed - if (self->duty_x == HIGHEST_PWM_RES) { - set_duty_u16(self, self->duty_u16); - } else if (self->duty_x == PWM_RES_10_BIT) { - set_duty_u10(self, self->duty_u10); - } else if (self->duty_x == -HIGHEST_PWM_RES) { - set_duty_ns(self, self->duty_ns); - } -} - -// Calculate the duty parameters based on an ns value -STATIC int ns_to_duty(machine_pwm_obj_t *self, int ns) { - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int64_t duty = ((int64_t)ns * UI_MAX_DUTY * timer.freq_hz + 500000000LL) / 1000000000LL; - if ((ns > 0) && (duty == 0)) { - duty = 1; - } else if (duty > UI_MAX_DUTY) { - duty = UI_MAX_DUTY; - } - return duty; -} - -STATIC int duty_to_ns(machine_pwm_obj_t *self, int duty) { - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int64_t ns = ((int64_t)duty * 1000000000LL + (int64_t)timer.freq_hz * UI_MAX_DUTY / 2) / ((int64_t)timer.freq_hz * UI_MAX_DUTY); - return ns; -} - -#define get_duty_raw(self) ledc_get_duty(self->mode, self->channel) - -STATIC void pwm_is_active(machine_pwm_obj_t *self) { - if (self->active == false) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("PWM inactive")); - } -} - -STATIC uint32_t get_duty_u16(machine_pwm_obj_t *self) { - pwm_is_active(self); - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - int duty = ledc_get_duty(self->mode, self->channel); - if (resolution <= UI_RES_16_BIT) { - duty <<= (UI_RES_16_BIT - resolution); - } else { - duty >>= (resolution - UI_RES_16_BIT); - } - return duty; -} - -STATIC uint32_t get_duty_u10(machine_pwm_obj_t *self) { - pwm_is_active(self); - return get_duty_u16(self) >> 6; // Scale down from 16 bit to 10 bit resolution -} - -STATIC uint32_t get_duty_ns(machine_pwm_obj_t *self) { - pwm_is_active(self); - return duty_to_ns(self, get_duty_u16(self)); -} - -STATIC void set_duty_u16(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - if ((duty < 0) || (duty > UI_MAX_DUTY)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_u16 must be from 0 to %d"), UI_MAX_DUTY); - } - ledc_timer_config_t timer = timers[TIMER_IDX(self->mode, self->timer)]; - int channel_duty; - if (timer.duty_resolution <= UI_RES_16_BIT) { - channel_duty = duty >> (UI_RES_16_BIT - timer.duty_resolution); - } else { - channel_duty = duty << (timer.duty_resolution - UI_RES_16_BIT); - } - int max_duty = (1 << timer.duty_resolution) - 1; - if (channel_duty < 0) { - channel_duty = 0; - } else if (channel_duty > max_duty) { - channel_duty = max_duty; - } - check_esp_err(ledc_set_duty(self->mode, self->channel, channel_duty)); - check_esp_err(ledc_update_duty(self->mode, self->channel)); - - /* - // Bug: Sometimes duty is not set right now. - // Not a bug. It's a feature. The duty is applied at the beginning of the next signal period. - // Bug: It has been experimentally established that the duty is set during 2 signal periods, but 1 period is expected. - // See https://github.com/espressif/esp-idf/issues/7288 - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - esp_rom_delay_us(2 * 1000000 / timer.freq_hz); - if (duty != get_duty_u16(self)) { - PWM_DBG("set_duty_u16(%u), get_duty_u16():%u, channel_duty:%d, duty_resolution:%d, freq_hz:%d", duty, get_duty_u16(self), channel_duty, timer.duty_resolution, timer.freq_hz); - } - } - */ - - self->duty_x = HIGHEST_PWM_RES; - self->duty_u16 = duty; -} - -STATIC void set_duty_u10(machine_pwm_obj_t *self, int duty) { - pwm_is_active(self); - if ((duty < 0) || (duty > MAX_DUTY_U10)) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty must be from 0 to %u"), MAX_DUTY_U10); - } - set_duty_u16(self, duty << (UI_RES_16_BIT - PWM_RES_10_BIT)); - self->duty_x = PWM_RES_10_BIT; - self->duty_u10 = duty; -} - -STATIC void set_duty_ns(machine_pwm_obj_t *self, int ns) { - pwm_is_active(self); - if ((ns < 0) || (ns > duty_to_ns(self, UI_MAX_DUTY))) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("duty_ns must be from 0 to %d ns"), duty_to_ns(self, UI_MAX_DUTY)); - } - set_duty_u16(self, ns_to_duty(self, ns)); - self->duty_x = -HIGHEST_PWM_RES; - self->duty_ns = ns; -} - -/******************************************************************************/ - -#define SAME_FREQ_ONLY (true) -#define SAME_FREQ_OR_FREE (false) -#define ANY_MODE (-1) - -// Return timer_idx. Use TIMER_IDX_TO_MODE(timer_idx) and TIMER_IDX_TO_TIMER(timer_idx) to get mode and timer -STATIC int find_timer(unsigned int freq, bool same_freq_only, int mode) { - int free_timer_idx_found = -1; - // Find a free PWM Timer using the same freq - for (int timer_idx = 0; timer_idx < PWM_TIMER_MAX; ++timer_idx) { - if ((mode == ANY_MODE) || (mode == TIMER_IDX_TO_MODE(timer_idx))) { - if (timers[timer_idx].freq_hz == freq) { - // A timer already uses the same freq. Use it now. - return timer_idx; - } - if (!same_freq_only && (free_timer_idx_found == -1) && (timers[timer_idx].freq_hz == -1)) { - free_timer_idx_found = timer_idx; - // Continue to check if a channel with the same freq is in use. - } - } - } - - return free_timer_idx_found; -} - -// Return true if the timer is in use in addition to current channel -STATIC bool is_timer_in_use(int current_channel_idx, int timer_idx) { - for (int i = 0; i < PWM_CHANNEL_MAX; ++i) { - if ((i != current_channel_idx) && (chans[i].timer_idx == timer_idx)) { - return true; - } - } - - return false; -} - -// Find a free PWM channel, also spot if our pin is already mentioned. -// Return channel_idx. Use CHANNEL_IDX_TO_MODE(channel_idx) and CHANNEL_IDX_TO_CHANNEL(channel_idx) to get mode and channel -STATIC int find_channel(int pin, int mode) { - int avail_idx = -1; - int channel_idx; - for (channel_idx = 0; channel_idx < PWM_CHANNEL_MAX; ++channel_idx) { - if ((mode == ANY_MODE) || (mode == CHANNEL_IDX_TO_MODE(channel_idx))) { - if (chans[channel_idx].pin == pin) { - break; - } - if ((avail_idx == -1) && (chans[channel_idx].pin == -1)) { - avail_idx = channel_idx; - } - } - } - if (channel_idx >= PWM_CHANNEL_MAX) { - channel_idx = avail_idx; - } - return channel_idx; -} - -/******************************************************************************/ -// MicroPython bindings for PWM - -STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "PWM(Pin(%u)", self->pin); - if (self->active) { - mp_printf(print, ", freq=%u", ledc_get_freq(self->mode, self->timer)); - - if (self->duty_x == PWM_RES_10_BIT) { - mp_printf(print, ", duty=%d", get_duty_u10(self)); - } else if (self->duty_x == -HIGHEST_PWM_RES) { - mp_printf(print, ", duty_ns=%d", get_duty_ns(self)); - } else { - mp_printf(print, ", duty_u16=%d", get_duty_u16(self)); - } - int resolution = timers[TIMER_IDX(self->mode, self->timer)].duty_resolution; - mp_printf(print, ", resolution=%d", resolution); - - mp_printf(print, ", (duty=%.2f%%, resolution=%.3f%%)", 100.0 * get_duty_raw(self) / (1 << resolution), 100.0 * 1 / (1 << resolution)); // percents - - mp_printf(print, ", mode=%d, channel=%d, timer=%d", self->mode, self->channel, self->timer); - } - mp_printf(print, ")"); -} - -// This called from pwm.init() method -STATIC void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, - size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_freq, ARG_duty, ARG_duty_u16, ARG_duty_ns }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_freq, MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_duty, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_duty_u16, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_duty_ns, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, - MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - int channel_idx = find_channel(self->pin, ANY_MODE); - if (channel_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in all modes - } - - int duty = args[ARG_duty].u_int; - int duty_u16 = args[ARG_duty_u16].u_int; - int duty_ns = args[ARG_duty_ns].u_int; - if (((duty != -1) && (duty_u16 != -1)) || ((duty != -1) && (duty_ns != -1)) || ((duty_u16 != -1) && (duty_ns != -1))) { - mp_raise_ValueError(MP_ERROR_TEXT("only one of parameters 'duty', 'duty_u16' or 'duty_ns' is allowed")); - } - - int freq = args[ARG_freq].u_int; - // Check if freq wasn't passed as an argument - if (freq == -1) { - // Check if already set, otherwise use the default freq. - // It is possible in case: - // pwm = PWM(pin, freq=1000, duty=256) - // pwm = PWM(pin, duty=128) - if (chans[channel_idx].timer_idx != -1) { - freq = timers[chans[channel_idx].timer_idx].freq_hz; - } - if (freq <= 0) { - freq = PWM_FREQ; - } - } - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - - int timer_idx; - int current_timer_idx = chans[channel_idx].timer_idx; - bool current_in_use = is_timer_in_use(channel_idx, current_timer_idx); - if (current_in_use) { - timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, CHANNEL_IDX_TO_MODE(channel_idx)); - } else { - timer_idx = chans[channel_idx].timer_idx; - } - - if (timer_idx == -1) { - timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, ANY_MODE); - } - if (timer_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in all modes - } - - int mode = TIMER_IDX_TO_MODE(timer_idx); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - // unregister old channel - chans[channel_idx].pin = -1; - chans[channel_idx].timer_idx = -1; - // find new channel - channel_idx = find_channel(self->pin, mode); - if (CHANNEL_IDX_TO_MODE(channel_idx) != mode) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM channels:%d"), PWM_CHANNEL_MAX); // in current mode - } - } - self->mode = mode; - self->timer = TIMER_IDX_TO_TIMER(timer_idx); - self->channel = CHANNEL_IDX_TO_CHANNEL(channel_idx); - - // New PWM assignment - if ((chans[channel_idx].pin == -1) || (chans[channel_idx].timer_idx != timer_idx)) { - configure_channel(self); - chans[channel_idx].pin = self->pin; - } - chans[channel_idx].timer_idx = timer_idx; - self->active = true; - - // Set timer frequency - set_freq(self, freq, &timers[timer_idx]); - - // Set duty cycle? - if (duty_u16 != -1) { - set_duty_u16(self, duty_u16); - } else if (duty_ns != -1) { - set_duty_ns(self, duty_ns); - } else if (duty != -1) { - set_duty_u10(self, duty); - } else if (self->duty_x == 0) { - set_duty_u10(self, (1 << PWM_RES_10_BIT) / 2); // 50% - } -} - -// This called from PWM() constructor -STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, - size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, 2, true); - gpio_num_t pin_id = machine_pin_get_id(args[0]); - - // create PWM object from the given pin - machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); - self->pin = pin_id; - self->active = false; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; - - // start the PWM subsystem if it's not already running - if (!pwm_inited) { - pwm_init(); - pwm_inited = true; - } - - // start the PWM running for this channel - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - mp_machine_pwm_init_helper(self, n_args - 1, args + 1, &kw_args); - - return MP_OBJ_FROM_PTR(self); -} - -// This called from pwm.deinit() method -STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { - int channel_idx = CHANNEL_IDX(self->mode, self->channel); - pwm_deinit(channel_idx); - self->active = false; - self->mode = -1; - self->channel = -1; - self->timer = -1; - self->duty_x = 0; -} - -// Set and get methods of PWM class - -STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { - pwm_is_active(self); - return MP_OBJ_NEW_SMALL_INT(ledc_get_freq(self->mode, self->timer)); -} - -STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { - pwm_is_active(self); - if ((freq <= 0) || (freq > 40000000)) { - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be from 1Hz to 40MHz")); - } - if (freq == timers[TIMER_IDX(self->mode, self->timer)].freq_hz) { - return; - } - - int current_timer_idx = chans[CHANNEL_IDX(self->mode, self->channel)].timer_idx; - bool current_in_use = is_timer_in_use(CHANNEL_IDX(self->mode, self->channel), current_timer_idx); - - // Check if an already running timer with the same freq is running - int new_timer_idx = find_timer(freq, SAME_FREQ_ONLY, self->mode); - - // If no existing timer was found, and the current one is in use, then find a new one - if ((new_timer_idx == -1) && current_in_use) { - // Have to find a new timer - new_timer_idx = find_timer(freq, SAME_FREQ_OR_FREE, self->mode); - - if (new_timer_idx == -1) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("out of PWM timers:%d"), PWM_TIMER_MAX); // in current mode - } - } - - if ((new_timer_idx != -1) && (new_timer_idx != current_timer_idx)) { - // Bind the channel to the new timer - chans[self->channel].timer_idx = new_timer_idx; - - if (ledc_bind_channel_timer(self->mode, self->channel, TIMER_IDX_TO_TIMER(new_timer_idx)) != ESP_OK) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("failed to bind timer to channel")); - } - - if (!current_in_use) { - // Free the old timer - check_esp_err(ledc_timer_rst(self->mode, self->timer)); - // Flag it unused - timers[current_timer_idx].freq_hz = -1; - } - - current_timer_idx = new_timer_idx; - } - self->mode = TIMER_IDX_TO_MODE(current_timer_idx); - self->timer = TIMER_IDX_TO_TIMER(current_timer_idx); - - // Set the frequency - set_freq(self, freq, &timers[current_timer_idx]); -} - -STATIC mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(get_duty_u10(self)); -} - -STATIC void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { - set_duty_u10(self, duty); -} - -STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(get_duty_u16(self)); -} - -STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) { - set_duty_u16(self, duty_u16); -} - -STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(get_duty_ns(self)); -} - -STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) { - set_duty_ns(self, duty_ns); -} diff --git a/tulip/esp32s3/machine_rtc.c b/tulip/esp32s3/machine_rtc.c deleted file mode 100644 index 3d620336c..000000000 --- a/tulip/esp32s3/machine_rtc.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 "Eric Poulsen" - * Copyright (c) 2017 "Tom Manning" - * - * 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. - */ - -#include -#include - -#include -#include -#include "driver/gpio.h" - -#include "py/nlr.h" -#include "py/obj.h" -#include "py/runtime.h" -#include "py/mphal.h" -#include "shared/timeutils/timeutils.h" -#include "modmachine.h" -#include "machine_rtc.h" - -typedef struct _machine_rtc_obj_t { - mp_obj_base_t base; -} machine_rtc_obj_t; - -/* There is 8K of rtc_slow_memory, but some is used by the system software - If the MICROPY_HW_RTC_USER_MEM_MAX is set too high, the following compile error will happen: - region `rtc_slow_seg' overflowed by N bytes - The current system software allows almost 4096 to be used. - To avoid running into issues if the system software uses more, 2048 was picked as a max length - - You can also change this max length at compile time by defining MICROPY_HW_RTC_USER_MEM_MAX - either on your make line, or in your board config. - - If MICROPY_HW_RTC_USER_MEM_MAX is set to 0, the RTC.memory() functionality will be not - be compiled which frees some extra flash and RTC memory. -*/ -#ifndef MICROPY_HW_RTC_USER_MEM_MAX -#define MICROPY_HW_RTC_USER_MEM_MAX 2048 -#endif - -// Optionally compile user memory functionality if the size of memory is greater than 0 -#if MICROPY_HW_RTC_USER_MEM_MAX > 0 -#define MEM_MAGIC 0x75507921 -RTC_DATA_ATTR uint32_t rtc_user_mem_magic; -RTC_DATA_ATTR uint16_t rtc_user_mem_len; -RTC_DATA_ATTR uint8_t rtc_user_mem_data[MICROPY_HW_RTC_USER_MEM_MAX]; -#endif - -// singleton RTC object -STATIC const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; - -machine_rtc_config_t machine_rtc_config = { - .ext1_pins = 0, - .ext0_pin = -1 -}; - -STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // check arguments - mp_arg_check_num(n_args, n_kw, 0, 0, false); - - // return constant object - return (mp_obj_t)&machine_rtc_obj; -} - -STATIC mp_obj_t machine_rtc_datetime_helper(mp_uint_t n_args, const mp_obj_t *args) { - if (n_args == 1) { - // Get time - - struct timeval tv; - - gettimeofday(&tv, NULL); - timeutils_struct_time_t tm; - - timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); - - mp_obj_t tuple[8] = { - mp_obj_new_int(tm.tm_year), - mp_obj_new_int(tm.tm_mon), - mp_obj_new_int(tm.tm_mday), - mp_obj_new_int(tm.tm_wday), - mp_obj_new_int(tm.tm_hour), - mp_obj_new_int(tm.tm_min), - mp_obj_new_int(tm.tm_sec), - mp_obj_new_int(tv.tv_usec) - }; - - return mp_obj_new_tuple(8, tuple); - } else { - // Set time - - mp_obj_t *items; - mp_obj_get_array_fixed_n(args[1], 8, &items); - - struct timeval tv = {0}; - tv.tv_sec = timeutils_seconds_since_epoch(mp_obj_get_int(items[0]), mp_obj_get_int(items[1]), mp_obj_get_int(items[2]), mp_obj_get_int(items[4]), mp_obj_get_int(items[5]), mp_obj_get_int(items[6])); - tv.tv_usec = mp_obj_get_int(items[7]); - settimeofday(&tv, NULL); - - return mp_const_none; - } -} -STATIC mp_obj_t machine_rtc_datetime(size_t n_args, const mp_obj_t *args) { - return machine_rtc_datetime_helper(n_args, args); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); - -STATIC mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { - mp_obj_t args[2] = {self_in, date}; - machine_rtc_datetime_helper(2, args); - - #if MICROPY_HW_RTC_USER_MEM_MAX > 0 - if (rtc_user_mem_magic != MEM_MAGIC) { - rtc_user_mem_magic = MEM_MAGIC; - rtc_user_mem_len = 0; - } - #endif - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_rtc_init_obj, machine_rtc_init); - -#if MICROPY_HW_RTC_USER_MEM_MAX > 0 -STATIC mp_obj_t machine_rtc_memory(size_t n_args, const mp_obj_t *args) { - if (n_args == 1) { - // read RTC memory - uint8_t rtcram[MICROPY_HW_RTC_USER_MEM_MAX]; - memcpy((char *)rtcram, (char *)rtc_user_mem_data, rtc_user_mem_len); - return mp_obj_new_bytes(rtcram, rtc_user_mem_len); - } else { - // write RTC memory - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(args[1], &bufinfo, MP_BUFFER_READ); - - if (bufinfo.len > MICROPY_HW_RTC_USER_MEM_MAX) { - mp_raise_ValueError(MP_ERROR_TEXT("buffer too long")); - } - memcpy((char *)rtc_user_mem_data, (char *)bufinfo.buf, bufinfo.len); - rtc_user_mem_len = bufinfo.len; - return mp_const_none; - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_memory_obj, 1, 2, machine_rtc_memory); -#endif - -STATIC const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_rtc_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, - #if MICROPY_HW_RTC_USER_MEM_MAX > 0 - { MP_ROM_QSTR(MP_QSTR_memory), MP_ROM_PTR(&machine_rtc_memory_obj) }, - #endif -}; -STATIC MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_rtc_type, - MP_QSTR_RTC, - MP_TYPE_FLAG_NONE, - make_new, machine_rtc_make_new, - locals_dict, &machine_rtc_locals_dict - ); diff --git a/tulip/esp32s3/machine_rtc.h b/tulip/esp32s3/machine_rtc.h deleted file mode 100644 index ce2a5482a..000000000 --- a/tulip/esp32s3/machine_rtc.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 "Eric Poulsen" - * Copyright (c) 2017 "Tom Manning" - * - * 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. - */ - -#ifndef MICROPY_INCLUDED_ESP32_MACHINE_RTC_H -#define MICROPY_INCLUDED_ESP32_MACHINE_RTC_H - -#include "modmachine.h" - -typedef struct { - uint64_t ext1_pins; // set bit == pin# - int8_t ext0_pin; // just the pin#, -1 == None - bool wake_on_touch : 1; - bool wake_on_ulp : 1; - bool ext0_level : 1; - wake_type_t ext0_wake_types; - bool ext1_level : 1; -} machine_rtc_config_t; - -extern machine_rtc_config_t machine_rtc_config; - -#endif diff --git a/tulip/esp32s3/machine_sdcard.c b/tulip/esp32s3/machine_sdcard.c deleted file mode 100644 index a2d133442..000000000 --- a/tulip/esp32s3/machine_sdcard.c +++ /dev/null @@ -1,463 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Nicko van Someren - * - * 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. - */ - -#include - -#include "py/runtime.h" -#include "py/mphal.h" -#include "py/mperrno.h" -#include "extmod/vfs_fat.h" - -#if MICROPY_HW_ENABLE_SDCARD - -#include "driver/sdmmc_host.h" -#include "driver/sdspi_host.h" -#include "sdmmc_cmd.h" -#include "esp_log.h" - -#define DEBUG 0 -#if DEBUG -#define DEBUG_printf(...) ESP_LOGI("modsdcard", __VA_ARGS__) -#else -#define DEBUG_printf(...) (void)0 -#endif - -// -// There are three layers of abstraction: host, slot and card. -// Creating an SD Card object will initialise the host and slot. -// Cards gets initialised by ioctl op==1 and de-inited by ioctl 2 -// Hosts are de-inited in __del__. Slots do not need de-initing. -// - -// Forward declaration -const mp_obj_type_t machine_sdcard_type; - -typedef struct _sdcard_obj_t { - mp_obj_base_t base; - mp_int_t flags; - sdmmc_host_t host; - // The card structure duplicates the host. It's not clear if we - // can avoid this given the way that it is copied. - sdmmc_card_t card; -} sdcard_card_obj_t; - -#define SDCARD_CARD_FLAGS_HOST_INIT_DONE 0x01 -#define SDCARD_CARD_FLAGS_CARD_INIT_DONE 0x02 - -#define _SECTOR_SIZE(self) (self->card.csd.sector_size) - -// SPI bus default bus and device configuration. - -static const spi_bus_config_t spi_bus_defaults[2] = { - { - #if CONFIG_IDF_TARGET_ESP32 - .miso_io_num = GPIO_NUM_19, - .mosi_io_num = GPIO_NUM_23, - .sclk_io_num = GPIO_NUM_18, - #else - .miso_io_num = GPIO_NUM_36, - .mosi_io_num = GPIO_NUM_35, - .sclk_io_num = GPIO_NUM_37, - #endif - .data2_io_num = GPIO_NUM_NC, - .data3_io_num = GPIO_NUM_NC, - .data4_io_num = GPIO_NUM_NC, - .data5_io_num = GPIO_NUM_NC, - .data6_io_num = GPIO_NUM_NC, - .data7_io_num = GPIO_NUM_NC, - .max_transfer_sz = 4000, - .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI, - .intr_flags = 0, - }, - { - .miso_io_num = GPIO_NUM_2, - .mosi_io_num = GPIO_NUM_15, - .sclk_io_num = GPIO_NUM_14, - .data2_io_num = GPIO_NUM_NC, - .data3_io_num = GPIO_NUM_NC, - .data4_io_num = GPIO_NUM_NC, - .data5_io_num = GPIO_NUM_NC, - .data6_io_num = GPIO_NUM_NC, - .data7_io_num = GPIO_NUM_NC, - .max_transfer_sz = 4000, - .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI, - .intr_flags = 0, - }, -}; - -#if CONFIG_IDF_TARGET_ESP32 -static const uint8_t spi_dma_channel_defaults[2] = { - 2, - 1, -}; -#endif - -static const sdspi_device_config_t spi_dev_defaults[2] = { - { - #if CONFIG_IDF_TARGET_ESP32 - .host_id = VSPI_HOST, - .gpio_cs = GPIO_NUM_5, - #else - .host_id = SPI3_HOST, - .gpio_cs = GPIO_NUM_34, - #endif - .gpio_cd = SDSPI_SLOT_NO_CD, - .gpio_wp = SDSPI_SLOT_NO_WP, - .gpio_int = SDSPI_SLOT_NO_INT, - }, - SDSPI_DEVICE_CONFIG_DEFAULT(), // HSPI (ESP32) / SPI2 (ESP32S3) -}; - -STATIC gpio_num_t pin_or_int(const mp_obj_t arg) { - if (mp_obj_is_small_int(arg)) { - return MP_OBJ_SMALL_INT_VALUE(arg); - } else { - // This raises a value error if the argument is not a Pin. - return machine_pin_get_id(arg); - } -} - -#define SET_CONFIG_PIN(config, pin_var, arg_id) \ - if (arg_vals[arg_id].u_obj != mp_const_none) \ - config.pin_var = pin_or_int(arg_vals[arg_id].u_obj) - -STATIC esp_err_t sdcard_ensure_card_init(sdcard_card_obj_t *self, bool force) { - if (force || !(self->flags & SDCARD_CARD_FLAGS_CARD_INIT_DONE)) { - DEBUG_printf(" Calling card init"); - - esp_err_t err = sdmmc_card_init(&(self->host), &(self->card)); - if (err == ESP_OK) { - self->flags |= SDCARD_CARD_FLAGS_CARD_INIT_DONE; - } else { - self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; - } - DEBUG_printf(" Card init returned: %i", err); - - return err; - } else { - return ESP_OK; - } -} - -/******************************************************************************/ -// MicroPython bindings -// -// Expose the SD card or MMC as an object with the block protocol. - -// Create a new SDCard object -// The driver supports either the host SD/MMC controller (default) or SPI mode -// In both cases there are two "slots". Slot 0 on the SD/MMC controller is -// typically tied up with the flash interface in most ESP32 modules but in -// theory supports 1, 4 or 8-bit transfers. Slot 1 supports only 1 and 4-bit -// transfers. Only 1-bit is supported on the SPI interfaces. -// card = SDCard(slot=1, width=None, present_pin=None, wp_pin=None) - -STATIC mp_obj_t machine_sdcard_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // check arguments - enum { - ARG_slot, - ARG_width, - ARG_cd, - ARG_wp, - ARG_miso, - ARG_mosi, - ARG_sck, - ARG_cs, - ARG_freq, - }; - STATIC const mp_arg_t allowed_args[] = { - { MP_QSTR_slot, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, - { MP_QSTR_width, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, - { MP_QSTR_cd, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_wp, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - // These are only needed if using SPI mode - { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - // freq is valid for both SPI and SDMMC interfaces - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 20000000} }, - }; - mp_arg_val_t arg_vals[MP_ARRAY_SIZE(allowed_args)]; - mp_map_t kw_args; - - DEBUG_printf("Making new SDCard:n"); - DEBUG_printf(" Unpacking arguments"); - - mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - - mp_arg_parse_all(n_args, args, &kw_args, - MP_ARRAY_SIZE(allowed_args), allowed_args, arg_vals); - - DEBUG_printf(" slot=%d, width=%d, cd=%p, wp=%p", - arg_vals[ARG_slot].u_int, arg_vals[ARG_width].u_int, - arg_vals[ARG_cd].u_obj, arg_vals[ARG_wp].u_obj); - - DEBUG_printf(" miso=%p, mosi=%p, sck=%p, cs=%p", - arg_vals[ARG_miso].u_obj, arg_vals[ARG_mosi].u_obj, - arg_vals[ARG_sck].u_obj, arg_vals[ARG_cs].u_obj); - - int slot_num = arg_vals[ARG_slot].u_int; - if (slot_num < 0 || slot_num > 3) { - mp_raise_ValueError(MP_ERROR_TEXT("slot number must be between 0 and 3 inclusive")); - } - - // Slots 0 and 1 are native SD/MMC, slots 2 and 3 are SPI - bool is_spi = (slot_num >= 2); - if (is_spi) { - slot_num -= 2; - } - - DEBUG_printf(" Setting up host configuration"); - - sdcard_card_obj_t *self = m_new_obj_with_finaliser(sdcard_card_obj_t); - self->base.type = &machine_sdcard_type; - self->flags = 0; - // Note that these defaults are macros that expand to structure - // constants so we can't directly assign them to fields. - int freq = arg_vals[ARG_freq].u_int; - if (is_spi) { - sdmmc_host_t _temp_host = SDSPI_HOST_DEFAULT(); - _temp_host.max_freq_khz = freq / 1000; - self->host = _temp_host; - } else { - sdmmc_host_t _temp_host = SDMMC_HOST_DEFAULT(); - _temp_host.max_freq_khz = freq / 1000; - self->host = _temp_host; - } - - if (is_spi) { - // Needs to match spi_dev_defaults above. - #if CONFIG_IDF_TARGET_ESP32 - self->host.slot = slot_num ? HSPI_HOST : VSPI_HOST; - #else - self->host.slot = slot_num ? SPI2_HOST : SPI3_HOST; - #endif - } - - DEBUG_printf(" Calling host.init()"); - - check_esp_err(self->host.init()); - self->flags |= SDCARD_CARD_FLAGS_HOST_INIT_DONE; - - if (is_spi) { - // SPI interface - DEBUG_printf(" Setting up SPI slot configuration"); - spi_host_device_t spi_host_id = self->host.slot; - spi_bus_config_t bus_config = spi_bus_defaults[slot_num]; - #if CONFIG_IDF_TARGET_ESP32 - spi_dma_chan_t dma_channel = spi_dma_channel_defaults[slot_num]; - #else - spi_dma_chan_t dma_channel = SPI_DMA_CH_AUTO; - #endif - sdspi_device_config_t dev_config = spi_dev_defaults[slot_num]; - - SET_CONFIG_PIN(bus_config, miso_io_num, ARG_miso); - SET_CONFIG_PIN(bus_config, mosi_io_num, ARG_mosi); - SET_CONFIG_PIN(bus_config, sclk_io_num, ARG_sck); - - SET_CONFIG_PIN(dev_config, gpio_cs, ARG_cs); - SET_CONFIG_PIN(dev_config, gpio_cd, ARG_cd); - SET_CONFIG_PIN(dev_config, gpio_wp, ARG_wp); - - DEBUG_printf(" Calling spi_bus_initialize()"); - check_esp_err(spi_bus_initialize(spi_host_id, &bus_config, dma_channel)); - - DEBUG_printf(" Calling sdspi_host_init_device()"); - sdspi_dev_handle_t sdspi_handle; - esp_err_t ret = sdspi_host_init_device(&dev_config, &sdspi_handle); - if (ret != ESP_OK) { - spi_bus_free(spi_host_id); - check_esp_err(ret); - } - if (self->host.slot != sdspi_handle) { - // MicroPython restriction: the SPI bus must be exclusively for the SD card. - spi_bus_free(spi_host_id); - mp_raise_ValueError(MP_ERROR_TEXT("SPI bus already in use")); - } - } else { - // SD/MMC interface - DEBUG_printf(" Setting up SDMMC slot configuration"); - sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); - - // Stronger external pull-ups are still needed but apparently - // it is a good idea to set the internal pull-ups anyway. - // slot_config.flags = SDMMC_SLOT_FLAG_INTERNAL_PULLUP; - - SET_CONFIG_PIN(slot_config, gpio_cd, ARG_cd); - SET_CONFIG_PIN(slot_config, gpio_wp, ARG_wp); - - int width = arg_vals[ARG_width].u_int; - if (width == 1 || width == 4 || (width == 8 && slot_num == 0)) { - slot_config.width = width; - } else { - mp_raise_ValueError(MP_ERROR_TEXT("width must be 1 or 4 (or 8 on slot 0)")); - } - - DEBUG_printf(" Calling init_slot()"); - check_esp_err(sdmmc_host_init_slot(self->host.slot, &slot_config)); - } - - DEBUG_printf(" Returning new card object: %p", self); - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t sd_deinit(mp_obj_t self_in) { - sdcard_card_obj_t *self = self_in; - - DEBUG_printf("De-init host\n"); - - if (self->flags & SDCARD_CARD_FLAGS_HOST_INIT_DONE) { - if (self->host.flags & SDMMC_HOST_FLAG_DEINIT_ARG) { - self->host.deinit_p(self->host.slot); - } else { - self->host.deinit(); - } - if (self->host.flags & SDMMC_HOST_FLAG_SPI) { - // SD card used a (dedicated) SPI bus, so free that SPI bus. - spi_bus_free(self->host.slot); - } - self->flags &= ~SDCARD_CARD_FLAGS_HOST_INIT_DONE; - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(sd_deinit_obj, sd_deinit); - -STATIC mp_obj_t sd_info(mp_obj_t self_in) { - sdcard_card_obj_t *self = self_in; - // We could potential return a great deal more SD card data but it - // is not clear that it is worth the extra code space to do - // so. For the most part people only care about the card size and - // block size. - - check_esp_err(sdcard_ensure_card_init((sdcard_card_obj_t *)self, false)); - - uint32_t log_block_nbr = self->card.csd.capacity; - uint32_t log_block_size = _SECTOR_SIZE(self); - - mp_obj_t tuple[2] = { - mp_obj_new_int_from_ull((uint64_t)log_block_nbr * (uint64_t)log_block_size), - mp_obj_new_int_from_uint(log_block_size), - }; - return mp_obj_new_tuple(2, tuple); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(sd_info_obj, sd_info); - -STATIC mp_obj_t machine_sdcard_readblocks(mp_obj_t self_in, mp_obj_t block_num, mp_obj_t buf) { - sdcard_card_obj_t *self = self_in; - mp_buffer_info_t bufinfo; - esp_err_t err; - - err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); - if (err != ESP_OK) { - return false; - } - - mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE); - err = sdmmc_read_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self)); - - return mp_obj_new_bool(err == ESP_OK); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_readblocks_obj, machine_sdcard_readblocks); - -STATIC mp_obj_t machine_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t block_num, mp_obj_t buf) { - sdcard_card_obj_t *self = self_in; - mp_buffer_info_t bufinfo; - esp_err_t err; - - err = sdcard_ensure_card_init((sdcard_card_obj_t *)self, false); - if (err != ESP_OK) { - return false; - } - - mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ); - err = sdmmc_write_sectors(&(self->card), bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / _SECTOR_SIZE(self)); - - return mp_obj_new_bool(err == ESP_OK); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_writeblocks_obj, machine_sdcard_writeblocks); - -STATIC mp_obj_t machine_sdcard_ioctl(mp_obj_t self_in, mp_obj_t cmd_in, mp_obj_t arg_in) { - sdcard_card_obj_t *self = self_in; - esp_err_t err = ESP_OK; - mp_int_t cmd = mp_obj_get_int(cmd_in); - - switch (cmd) { - case MP_BLOCKDEV_IOCTL_INIT: - err = sdcard_ensure_card_init(self, false); - return MP_OBJ_NEW_SMALL_INT((err == ESP_OK) ? 0 : -1); - - case MP_BLOCKDEV_IOCTL_DEINIT: - // Ensure that future attempts to look at info re-read the card - self->flags &= ~SDCARD_CARD_FLAGS_CARD_INIT_DONE; - return MP_OBJ_NEW_SMALL_INT(0); // success - - case MP_BLOCKDEV_IOCTL_SYNC: - // nothing to do - return MP_OBJ_NEW_SMALL_INT(0); // success - - case MP_BLOCKDEV_IOCTL_BLOCK_COUNT: - err = sdcard_ensure_card_init(self, false); - if (err != ESP_OK) { - return MP_OBJ_NEW_SMALL_INT(-1); - } - return MP_OBJ_NEW_SMALL_INT(self->card.csd.capacity); - - case MP_BLOCKDEV_IOCTL_BLOCK_SIZE: - err = sdcard_ensure_card_init(self, false); - if (err != ESP_OK) { - return MP_OBJ_NEW_SMALL_INT(-1); - } - return MP_OBJ_NEW_SMALL_INT(_SECTOR_SIZE(self)); - - default: // unknown command - return MP_OBJ_NEW_SMALL_INT(-1); // error - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(machine_sdcard_ioctl_obj, machine_sdcard_ioctl); - -STATIC const mp_rom_map_elem_t machine_sdcard_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&sd_info_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&sd_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&sd_deinit_obj) }, - // block device protocol - { MP_ROM_QSTR(MP_QSTR_readblocks), MP_ROM_PTR(&machine_sdcard_readblocks_obj) }, - { MP_ROM_QSTR(MP_QSTR_writeblocks), MP_ROM_PTR(&machine_sdcard_writeblocks_obj) }, - { MP_ROM_QSTR(MP_QSTR_ioctl), MP_ROM_PTR(&machine_sdcard_ioctl_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(machine_sdcard_locals_dict, machine_sdcard_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_sdcard_type, - MP_QSTR_SDCard, - MP_TYPE_FLAG_NONE, - make_new, machine_sdcard_make_new, - locals_dict, &machine_sdcard_locals_dict - ); - -#endif // MICROPY_HW_ENABLE_SDCARD diff --git a/tulip/esp32s3/machine_timer.c b/tulip/esp32s3/machine_timer.c deleted file mode 100644 index 5855cfb3e..000000000 --- a/tulip/esp32s3/machine_timer.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2013-2015 Damien P. George - * Copyright (c) 2016 Paul Sokolovsky - * - * 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. - */ - -#include -#include - -#include "py/obj.h" -#include "py/runtime.h" -#include "modmachine.h" -#include "mphalport.h" - -#include "driver/timer.h" -#include "hal/timer_ll.h" - -#define TIMER_INTR_SEL TIMER_INTR_LEVEL -#define TIMER_DIVIDER 8 - -// TIMER_BASE_CLK is normally 80MHz. TIMER_DIVIDER ought to divide this exactly -#define TIMER_SCALE (APB_CLK_FREQ / TIMER_DIVIDER) - -#define TIMER_FLAGS 0 - -typedef struct _machine_timer_obj_t { - mp_obj_base_t base; - mp_uint_t group; - mp_uint_t index; - - mp_uint_t repeat; - // ESP32 timers are 64-bit - uint64_t period; - - mp_obj_t callback; - - intr_handle_t handle; - - struct _machine_timer_obj_t *next; -} machine_timer_obj_t; - -const mp_obj_type_t machine_timer_type; - -STATIC void machine_timer_disable(machine_timer_obj_t *self); -STATIC mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); - -void machine_timer_deinit_all(void) { - // Disable, deallocate and remove all timers from list - machine_timer_obj_t **t = &MP_STATE_PORT(machine_timer_obj_head); - while (*t != NULL) { - machine_timer_disable(*t); - machine_timer_obj_t *next = (*t)->next; - m_del_obj(machine_timer_obj_t, *t); - *t = next; - } -} - -STATIC void machine_timer_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_timer_obj_t *self = self_in; - - timer_config_t config; - mp_printf(print, "Timer(%p; ", self); - - timer_get_config(self->group, self->index, &config); - - mp_printf(print, "alarm_en=%d, ", config.alarm_en); - mp_printf(print, "auto_reload=%d, ", config.auto_reload); - mp_printf(print, "counter_en=%d)", config.counter_en); -} - -STATIC mp_obj_t machine_timer_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); - mp_uint_t group = (mp_obj_get_int(args[0]) >> 1) & 1; - mp_uint_t index = mp_obj_get_int(args[0]) & 1; - - machine_timer_obj_t *self = NULL; - - // Check whether the timer is already initialized, if so use it - for (machine_timer_obj_t *t = MP_STATE_PORT(machine_timer_obj_head); t; t = t->next) { - if (t->group == group && t->index == index) { - self = t; - break; - } - } - // The timer does not exist, create it. - if (self == NULL) { - self = mp_obj_malloc(machine_timer_obj_t, &machine_timer_type); - self->group = group; - self->index = index; - - // Add the timer to the linked-list of timers - self->next = MP_STATE_PORT(machine_timer_obj_head); - MP_STATE_PORT(machine_timer_obj_head) = self; - } - - if (n_args > 1 || n_kw > 0) { - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_timer_init_helper(self, n_args - 1, args + 1, &kw_args); - } - - return self; -} - -STATIC void machine_timer_disable(machine_timer_obj_t *self) { - if (self->handle) { - timer_pause(self->group, self->index); - esp_intr_free(self->handle); - self->handle = NULL; - } - - // We let the disabled timer stay in the list, as it might be - // referenced elsewhere -} - -STATIC void machine_timer_isr(void *self_in) { - machine_timer_obj_t *self = self_in; - timg_dev_t *device = self->group ? &(TIMERG1) : &(TIMERG0); - - #if CONFIG_IDF_TARGET_ESP32S3 - device->hw_timer[self->index].update.tn_update = 1; - #else - device->hw_timer[self->index].update.tx_update = 1; - #endif - - timer_ll_clear_intr_status(device, self->index); - timer_ll_set_alarm_value(device, self->index, self->repeat); - - mp_sched_schedule(self->callback, self); - mp_hal_wake_main_task_from_isr(); -} - -STATIC void machine_timer_enable(machine_timer_obj_t *self) { - timer_config_t config; - config.alarm_en = TIMER_ALARM_EN; - config.auto_reload = self->repeat; - config.counter_dir = TIMER_COUNT_UP; - config.divider = TIMER_DIVIDER; - config.intr_type = TIMER_INTR_LEVEL; - config.counter_en = TIMER_PAUSE; - #if SOC_TIMER_GROUP_SUPPORT_XTAL - config.clk_src = TIMER_SRC_CLK_APB; - #endif - - check_esp_err(timer_init(self->group, self->index, &config)); - check_esp_err(timer_set_counter_value(self->group, self->index, 0x00000000)); - check_esp_err(timer_set_alarm_value(self->group, self->index, self->period)); - check_esp_err(timer_enable_intr(self->group, self->index)); - check_esp_err(timer_isr_register(self->group, self->index, machine_timer_isr, (void *)self, TIMER_FLAGS, &self->handle)); - check_esp_err(timer_start(self->group, self->index)); -} - -STATIC mp_obj_t machine_timer_init_helper(machine_timer_obj_t *self, mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { - ARG_mode, - ARG_callback, - ARG_period, - ARG_tick_hz, - ARG_freq, - }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} }, - { MP_QSTR_callback, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_period, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, - { MP_QSTR_tick_hz, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1000} }, - #if MICROPY_PY_BUILTINS_FLOAT - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - #else - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0xffffffff} }, - #endif - }; - - machine_timer_disable(self); - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - #if MICROPY_PY_BUILTINS_FLOAT - if (args[ARG_freq].u_obj != mp_const_none) { - self->period = (uint64_t)(TIMER_SCALE / mp_obj_get_float(args[ARG_freq].u_obj)); - } - #else - if (args[ARG_freq].u_int != 0xffffffff) { - self->period = TIMER_SCALE / ((uint64_t)args[ARG_freq].u_int); - } - #endif - else { - self->period = (((uint64_t)args[ARG_period].u_int) * TIMER_SCALE) / args[ARG_tick_hz].u_int; - } - - self->repeat = args[ARG_mode].u_int; - self->callback = args[ARG_callback].u_obj; - self->handle = NULL; - - machine_timer_enable(self); - - return mp_const_none; -} - -STATIC mp_obj_t machine_timer_deinit(mp_obj_t self_in) { - machine_timer_disable(self_in); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_deinit_obj, machine_timer_deinit); - -STATIC mp_obj_t machine_timer_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - return machine_timer_init_helper(args[0], n_args - 1, args + 1, kw_args); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_timer_init_obj, 1, machine_timer_init); - -STATIC mp_obj_t machine_timer_value(mp_obj_t self_in) { - machine_timer_obj_t *self = self_in; - double result; - - timer_get_counter_time_sec(self->group, self->index, &result); - - return MP_OBJ_NEW_SMALL_INT((mp_uint_t)(result * 1000)); // value in ms -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_timer_value_obj, machine_timer_value); - -STATIC const mp_rom_map_elem_t machine_timer_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_timer_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_timer_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_timer_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_timer_value_obj) }, - { MP_ROM_QSTR(MP_QSTR_ONE_SHOT), MP_ROM_INT(false) }, - { MP_ROM_QSTR(MP_QSTR_PERIODIC), MP_ROM_INT(true) }, -}; -STATIC MP_DEFINE_CONST_DICT(machine_timer_locals_dict, machine_timer_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_timer_type, - MP_QSTR_Timer, - MP_TYPE_FLAG_NONE, - make_new, machine_timer_make_new, - print, machine_timer_print, - locals_dict, &machine_timer_locals_dict - ); - -MP_REGISTER_ROOT_POINTER(struct _machine_timer_obj_t *machine_timer_obj_head); diff --git a/tulip/esp32s3/machine_touchpad.c b/tulip/esp32s3/machine_touchpad.c deleted file mode 100644 index d9f4ef3eb..000000000 --- a/tulip/esp32s3/machine_touchpad.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 Nick Moore - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mphal.h" -#include "modmachine.h" -#include "driver/gpio.h" - -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - -#if CONFIG_IDF_TARGET_ESP32 -#include "driver/touch_pad.h" -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 -#include "driver/touch_sensor.h" -#endif - -typedef struct _mtp_obj_t { - mp_obj_base_t base; - gpio_num_t gpio_id; - touch_pad_t touchpad_id; -} mtp_obj_t; - -STATIC const mtp_obj_t touchpad_obj[] = { - #if CONFIG_IDF_TARGET_ESP32 - {{&machine_touchpad_type}, GPIO_NUM_4, TOUCH_PAD_NUM0}, - {{&machine_touchpad_type}, GPIO_NUM_0, TOUCH_PAD_NUM1}, - {{&machine_touchpad_type}, GPIO_NUM_2, TOUCH_PAD_NUM2}, - {{&machine_touchpad_type}, GPIO_NUM_15, TOUCH_PAD_NUM3}, - {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM4}, - {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM5}, - {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM6}, - {{&machine_touchpad_type}, GPIO_NUM_27, TOUCH_PAD_NUM7}, - {{&machine_touchpad_type}, GPIO_NUM_33, TOUCH_PAD_NUM8}, - {{&machine_touchpad_type}, GPIO_NUM_32, TOUCH_PAD_NUM9}, - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - {{&machine_touchpad_type}, GPIO_NUM_1, TOUCH_PAD_NUM1}, - {{&machine_touchpad_type}, GPIO_NUM_2, TOUCH_PAD_NUM2}, - {{&machine_touchpad_type}, GPIO_NUM_3, TOUCH_PAD_NUM3}, - {{&machine_touchpad_type}, GPIO_NUM_4, TOUCH_PAD_NUM4}, - {{&machine_touchpad_type}, GPIO_NUM_5, TOUCH_PAD_NUM5}, - {{&machine_touchpad_type}, GPIO_NUM_6, TOUCH_PAD_NUM6}, - {{&machine_touchpad_type}, GPIO_NUM_7, TOUCH_PAD_NUM7}, - {{&machine_touchpad_type}, GPIO_NUM_8, TOUCH_PAD_NUM8}, - {{&machine_touchpad_type}, GPIO_NUM_9, TOUCH_PAD_NUM9}, - {{&machine_touchpad_type}, GPIO_NUM_10, TOUCH_PAD_NUM10}, - {{&machine_touchpad_type}, GPIO_NUM_11, TOUCH_PAD_NUM11}, - {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM12}, - {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM13}, - {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM14}, - #endif -}; - -STATIC mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, - const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, 1, true); - gpio_num_t pin_id = machine_pin_get_id(args[0]); - const mtp_obj_t *self = NULL; - for (int i = 0; i < MP_ARRAY_SIZE(touchpad_obj); i++) { - if (pin_id == touchpad_obj[i].gpio_id) { - self = &touchpad_obj[i]; - break; - } - } - if (!self) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for touchpad")); - } - - static int initialized = 0; - if (!initialized) { - touch_pad_init(); - touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - touch_pad_fsm_start(); - #endif - initialized = 1; - } - #if CONFIG_IDF_TARGET_ESP32 - esp_err_t err = touch_pad_config(self->touchpad_id, 0); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - esp_err_t err = touch_pad_config(self->touchpad_id); - #endif - if (err == ESP_OK) { - return MP_OBJ_FROM_PTR(self); - } - mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); -} - -STATIC mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { - mtp_obj_t *self = self_in; - #if CONFIG_IDF_TARGET_ESP32 - uint16_t value = mp_obj_get_int(value_in); - esp_err_t err = touch_pad_config(self->touchpad_id, value); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - esp_err_t err = touch_pad_config(self->touchpad_id); - #endif - if (err == ESP_OK) { - return mp_const_none; - } - mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); -} -MP_DEFINE_CONST_FUN_OBJ_2(mtp_config_obj, mtp_config); - -STATIC mp_obj_t mtp_read(mp_obj_t self_in) { - mtp_obj_t *self = self_in; - #if CONFIG_IDF_TARGET_ESP32 - uint16_t value; - esp_err_t err = touch_pad_read(self->touchpad_id, &value); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - uint32_t value; - esp_err_t err = touch_pad_read_raw_data(self->touchpad_id, &value); - #endif - if (err == ESP_OK) { - return MP_OBJ_NEW_SMALL_INT(value); - } - mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); -} -MP_DEFINE_CONST_FUN_OBJ_1(mtp_read_obj, mtp_read); - -STATIC const mp_rom_map_elem_t mtp_locals_dict_table[] = { - // instance methods - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&mtp_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mtp_read_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(mtp_locals_dict, mtp_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_touchpad_type, - MP_QSTR_TouchPad, - MP_TYPE_FLAG_NONE, - make_new, mtp_make_new, - locals_dict, &mtp_locals_dict - ); - -#endif diff --git a/tulip/esp32s3/machine_uart.c b/tulip/esp32s3/machine_uart.c deleted file mode 100644 index d9b845047..000000000 --- a/tulip/esp32s3/machine_uart.c +++ /dev/null @@ -1,536 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Damien P. George - * - * 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. - */ - -#include -#include -#include - -#include "driver/uart.h" -#include "freertos/FreeRTOS.h" - -#include "py/runtime.h" -#include "py/stream.h" -#include "py/mperrno.h" -#include "modmachine.h" -#include "uart.h" - -#define UART_INV_TX UART_SIGNAL_TXD_INV -#define UART_INV_RX UART_SIGNAL_RXD_INV -#define UART_INV_RTS UART_SIGNAL_RTS_INV -#define UART_INV_CTS UART_SIGNAL_CTS_INV - -#define UART_INV_MASK (UART_INV_TX | UART_INV_RX | UART_INV_RTS | UART_INV_CTS) - -typedef struct _machine_uart_obj_t { - mp_obj_base_t base; - uart_port_t uart_num; - uart_hw_flowcontrol_t flowcontrol; - uint8_t bits; - uint8_t parity; - uint8_t stop; - int8_t tx; - int8_t rx; - int8_t rts; - int8_t cts; - uint16_t txbuf; - uint16_t rxbuf; - uint16_t timeout; // timeout waiting for first char (in ms) - uint16_t timeout_char; // timeout waiting between chars (in ms) - uint32_t invert; // lines to invert -} machine_uart_obj_t; - -STATIC const char *_parity_name[] = {"None", "1", "0"}; - -/******************************************************************************/ -// MicroPython bindings for UART - -STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - uint32_t baudrate; - uart_get_baudrate(self->uart_num, &baudrate); - mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, tx=%d, rx=%d, rts=%d, cts=%d, txbuf=%u, rxbuf=%u, timeout=%u, timeout_char=%u", - self->uart_num, baudrate, self->bits, _parity_name[self->parity], - self->stop, self->tx, self->rx, self->rts, self->cts, self->txbuf, self->rxbuf, self->timeout, self->timeout_char); - if (self->invert) { - mp_printf(print, ", invert="); - uint32_t invert_mask = self->invert; - if (invert_mask & UART_INV_TX) { - mp_printf(print, "INV_TX"); - invert_mask &= ~UART_INV_TX; - if (invert_mask) { - mp_printf(print, "|"); - } - } - if (invert_mask & UART_INV_RX) { - mp_printf(print, "INV_RX"); - invert_mask &= ~UART_INV_RX; - if (invert_mask) { - mp_printf(print, "|"); - } - } - if (invert_mask & UART_INV_RTS) { - mp_printf(print, "INV_RTS"); - invert_mask &= ~UART_INV_RTS; - if (invert_mask) { - mp_printf(print, "|"); - } - } - if (invert_mask & UART_INV_CTS) { - mp_printf(print, "INV_CTS"); - } - } - if (self->flowcontrol) { - mp_printf(print, ", flow="); - uint32_t flow_mask = self->flowcontrol; - if (flow_mask & UART_HW_FLOWCTRL_RTS) { - mp_printf(print, "RTS"); - flow_mask &= ~UART_HW_FLOWCTRL_RTS; - if (flow_mask) { - mp_printf(print, "|"); - } - } - if (flow_mask & UART_HW_FLOWCTRL_CTS) { - mp_printf(print, "CTS"); - } - } - mp_printf(print, ")"); -} - -STATIC void machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_baudrate, ARG_bits, ARG_parity, ARG_stop, ARG_tx, ARG_rx, ARG_rts, ARG_cts, ARG_txbuf, ARG_rxbuf, ARG_timeout, ARG_timeout_char, ARG_invert, ARG_flow }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_bits, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_parity, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_stop, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_tx, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, - { MP_QSTR_rx, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, - { MP_QSTR_rts, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, - { MP_QSTR_cts, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = UART_PIN_NO_CHANGE} }, - { MP_QSTR_txbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_timeout_char, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_invert, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_flow, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - // wait for all data to be transmitted before changing settings - uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); - - if (args[ARG_txbuf].u_int >= 0 || args[ARG_rxbuf].u_int >= 0) { - // must reinitialise driver to change the tx/rx buffer size - if (self->uart_num == MICROPY_HW_UART_REPL) { - mp_raise_ValueError(MP_ERROR_TEXT("UART buffer size is fixed")); - } - - if (args[ARG_txbuf].u_int >= 0) { - self->txbuf = args[ARG_txbuf].u_int; - } - if (args[ARG_rxbuf].u_int >= 0) { - self->rxbuf = args[ARG_rxbuf].u_int; - } - uart_config_t uartcfg = { - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .rx_flow_ctrl_thresh = 0 - }; - uint32_t baudrate; - uart_get_baudrate(self->uart_num, &baudrate); - uartcfg.baud_rate = baudrate; - uart_get_word_length(self->uart_num, &uartcfg.data_bits); - uart_get_parity(self->uart_num, &uartcfg.parity); - uart_get_stop_bits(self->uart_num, &uartcfg.stop_bits); - uart_driver_delete(self->uart_num); - uart_param_config(self->uart_num, &uartcfg); - uart_driver_install(self->uart_num, self->rxbuf, self->txbuf, 0, NULL, 0); - } - - // set baudrate - uint32_t baudrate = 115200; - if (args[ARG_baudrate].u_int > 0) { - uart_set_baudrate(self->uart_num, args[ARG_baudrate].u_int); - } - uart_get_baudrate(self->uart_num, &baudrate); - - uart_set_pin(self->uart_num, args[ARG_tx].u_int, args[ARG_rx].u_int, args[ARG_rts].u_int, args[ARG_cts].u_int); - if (args[ARG_tx].u_int != UART_PIN_NO_CHANGE) { - self->tx = args[ARG_tx].u_int; - } - - if (args[ARG_rx].u_int != UART_PIN_NO_CHANGE) { - self->rx = args[ARG_rx].u_int; - } - - if (args[ARG_rts].u_int != UART_PIN_NO_CHANGE) { - self->rts = args[ARG_rts].u_int; - } - - if (args[ARG_cts].u_int != UART_PIN_NO_CHANGE) { - self->cts = args[ARG_cts].u_int; - } - - // set data bits - switch (args[ARG_bits].u_int) { - case 0: - break; - case 5: - uart_set_word_length(self->uart_num, UART_DATA_5_BITS); - self->bits = 5; - break; - case 6: - uart_set_word_length(self->uart_num, UART_DATA_6_BITS); - self->bits = 6; - break; - case 7: - uart_set_word_length(self->uart_num, UART_DATA_7_BITS); - self->bits = 7; - break; - case 8: - uart_set_word_length(self->uart_num, UART_DATA_8_BITS); - self->bits = 8; - break; - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid data bits")); - break; - } - - // set parity - if (args[ARG_parity].u_obj != MP_OBJ_NULL) { - if (args[ARG_parity].u_obj == mp_const_none) { - uart_set_parity(self->uart_num, UART_PARITY_DISABLE); - self->parity = 0; - } else { - mp_int_t parity = mp_obj_get_int(args[ARG_parity].u_obj); - if (parity & 1) { - uart_set_parity(self->uart_num, UART_PARITY_ODD); - self->parity = 1; - } else { - uart_set_parity(self->uart_num, UART_PARITY_EVEN); - self->parity = 2; - } - } - } - - // set stop bits - switch (args[ARG_stop].u_int) { - // FIXME: ESP32 also supports 1.5 stop bits - case 0: - break; - case 1: - uart_set_stop_bits(self->uart_num, UART_STOP_BITS_1); - self->stop = 1; - break; - case 2: - uart_set_stop_bits(self->uart_num, UART_STOP_BITS_2); - self->stop = 2; - break; - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid stop bits")); - break; - } - - // set timeout - if (args[ARG_timeout].u_int != -1) { - self->timeout = args[ARG_timeout].u_int; - } - - // set timeout_char - // make sure it is at least as long as a whole character (12 bits here) - if (args[ARG_timeout_char].u_int != -1) { - self->timeout_char = args[ARG_timeout_char].u_int; - uint32_t char_time_ms = 12000 / baudrate + 1; - uint32_t rx_timeout = self->timeout_char / char_time_ms; - if (rx_timeout < 1) { - uart_set_rx_full_threshold(self->uart_num, 1); - uart_set_rx_timeout(self->uart_num, 1); - } else { - uart_set_rx_timeout(self->uart_num, rx_timeout); - } - } - - // set line inversion - if (args[ARG_invert].u_int != -1) { - if (args[ARG_invert].u_int & ~UART_INV_MASK) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid inversion mask")); - } - self->invert = args[ARG_invert].u_int; - } - uart_set_line_inverse(self->uart_num, self->invert); - - // set hardware flow control - if (args[ARG_flow].u_int != -1) { - if (args[ARG_flow].u_int & ~UART_HW_FLOWCTRL_CTS_RTS) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid flow control mask")); - } - self->flowcontrol = args[ARG_flow].u_int; - } - uart_set_hw_flow_ctrl(self->uart_num, self->flowcontrol, UART_FIFO_LEN - UART_FIFO_LEN / 4); -} - -STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); - - // get uart id - mp_int_t uart_num = mp_obj_get_int(args[0]); - if (uart_num < 0 || uart_num >= UART_NUM_MAX) { - mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("UART(%d) does not exist"), uart_num); - } - - // Defaults - uart_config_t uartcfg = { - .baud_rate = 115200, - .data_bits = UART_DATA_8_BITS, - .parity = UART_PARITY_DISABLE, - .stop_bits = UART_STOP_BITS_1, - .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, - .rx_flow_ctrl_thresh = 0 - }; - #if SOC_UART_SUPPORT_XTAL_CLK - // works independently of APB frequency - uartcfg.source_clk = UART_SCLK_XTAL; // ESP32C3, ESP32S3 - #endif - - // create instance - machine_uart_obj_t *self = mp_obj_malloc(machine_uart_obj_t, &machine_uart_type); - self->uart_num = uart_num; - self->bits = 8; - self->parity = 0; - self->stop = 1; - self->rts = UART_PIN_NO_CHANGE; - self->cts = UART_PIN_NO_CHANGE; - self->txbuf = 256; - self->rxbuf = 256; // IDF minimum - self->timeout = 0; - self->timeout_char = 0; - self->invert = 0; - self->flowcontrol = 0; - - switch (uart_num) { - case UART_NUM_0: - self->rx = UART_PIN_NO_CHANGE; // GPIO 3 - self->tx = UART_PIN_NO_CHANGE; // GPIO 1 - break; - case UART_NUM_1: - self->rx = 9; - self->tx = 10; - break; - #if SOC_UART_NUM > 2 - case UART_NUM_2: - self->rx = 16; - self->tx = 17; - break; - #endif - } - - // Only reset the driver if it's not the REPL UART. - if (uart_num != MICROPY_HW_UART_REPL) { - // Remove any existing configuration - uart_driver_delete(self->uart_num); - - // init the peripheral - // Setup - uart_param_config(self->uart_num, &uartcfg); - - uart_driver_install(uart_num, self->rxbuf, self->txbuf, 0, NULL, 0); - } - - mp_map_t kw_args; - mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - machine_uart_init_helper(self, n_args - 1, args + 1, &kw_args); - - // Make sure pins are connected. - uart_set_pin(self->uart_num, self->tx, self->rx, self->rts, self->cts); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC mp_obj_t machine_uart_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - machine_uart_init_helper(args[0], n_args - 1, args + 1, kw_args); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_KW(machine_uart_init_obj, 1, machine_uart_init); - -STATIC mp_obj_t machine_uart_deinit(mp_obj_t self_in) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - uart_driver_delete(self->uart_num); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_deinit_obj, machine_uart_deinit); - -STATIC mp_obj_t machine_uart_any(mp_obj_t self_in) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - size_t rxbufsize; - uart_get_buffered_data_len(self->uart_num, &rxbufsize); - return MP_OBJ_NEW_SMALL_INT(rxbufsize); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_any_obj, machine_uart_any); - -STATIC mp_obj_t machine_uart_sendbreak(mp_obj_t self_in) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - - // Save settings - uint32_t baudrate; - uart_get_baudrate(self->uart_num, &baudrate); - - // Synthesise the break condition by reducing the baud rate, - // and cater for the worst case of 5 data bits, no parity. - uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); - uart_set_baudrate(self->uart_num, baudrate * 6 / 15); - char buf[1] = {0}; - uart_write_bytes(self->uart_num, buf, 1); - uart_wait_tx_done(self->uart_num, pdMS_TO_TICKS(1000)); - - // Restore original setting - uart_set_baudrate(self->uart_num, baudrate); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_sendbreak_obj, machine_uart_sendbreak); - -STATIC mp_obj_t machine_uart_txdone(mp_obj_t self_in) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - - if (uart_wait_tx_done(self->uart_num, 0) == ESP_OK) { - return mp_const_true; - } else { - return mp_const_false; - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_txdone_obj, machine_uart_txdone); - -STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_uart_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_uart_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&machine_uart_any_obj) }, - { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, - { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, - { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, - { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) }, - { MP_ROM_QSTR(MP_QSTR_txdone), MP_ROM_PTR(&machine_uart_txdone_obj) }, - - { MP_ROM_QSTR(MP_QSTR_INV_TX), MP_ROM_INT(UART_INV_TX) }, - { MP_ROM_QSTR(MP_QSTR_INV_RX), MP_ROM_INT(UART_INV_RX) }, - { MP_ROM_QSTR(MP_QSTR_INV_RTS), MP_ROM_INT(UART_INV_RTS) }, - { MP_ROM_QSTR(MP_QSTR_INV_CTS), MP_ROM_INT(UART_INV_CTS) }, - - { MP_ROM_QSTR(MP_QSTR_RTS), MP_ROM_INT(UART_HW_FLOWCTRL_RTS) }, - { MP_ROM_QSTR(MP_QSTR_CTS), MP_ROM_INT(UART_HW_FLOWCTRL_CTS) }, -}; - -STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); - -STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t size, int *errcode) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - - // make sure we want at least 1 char - if (size == 0) { - return 0; - } - - TickType_t time_to_wait; - if (self->timeout == 0) { - time_to_wait = 0; - } else { - time_to_wait = pdMS_TO_TICKS(self->timeout); - } - - int bytes_read = uart_read_bytes(self->uart_num, buf_in, size, time_to_wait); - - if (bytes_read <= 0) { - *errcode = MP_EAGAIN; - return MP_STREAM_ERROR; - } - - return bytes_read; -} - -STATIC mp_uint_t machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_uint_t size, int *errcode) { - machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); - - int bytes_written = uart_write_bytes(self->uart_num, buf_in, size); - - if (bytes_written < 0) { - *errcode = MP_EAGAIN; - return MP_STREAM_ERROR; - } - - // return number of bytes written - return bytes_written; -} - -STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { - machine_uart_obj_t *self = self_in; - mp_uint_t ret; - if (request == MP_STREAM_POLL) { - mp_uint_t flags = arg; - ret = 0; - size_t rxbufsize; - uart_get_buffered_data_len(self->uart_num, &rxbufsize); - if ((flags & MP_STREAM_POLL_RD) && rxbufsize > 0) { - ret |= MP_STREAM_POLL_RD; - } - if ((flags & MP_STREAM_POLL_WR) && 1) { // FIXME: uart_tx_any_room(self->uart_num) - ret |= MP_STREAM_POLL_WR; - } - } else if (request == MP_STREAM_FLUSH) { - // The timeout is estimated using the buffer size and the baudrate. - // Take the worst case assumptions at 13 bit symbol size times 2. - uint32_t baudrate; - uart_get_baudrate(self->uart_num, &baudrate); - uint32_t timeout = (3 + self->txbuf) * 13000 * 2 / baudrate; - if (uart_wait_tx_done(self->uart_num, timeout) == ESP_OK) { - ret = 0; - } else { - *errcode = MP_ETIMEDOUT; - ret = MP_STREAM_ERROR; - } - } else { - *errcode = MP_EINVAL; - ret = MP_STREAM_ERROR; - } - return ret; -} - -STATIC const mp_stream_p_t uart_stream_p = { - .read = machine_uart_read, - .write = machine_uart_write, - .ioctl = machine_uart_ioctl, - .is_text = false, -}; - -MP_DEFINE_CONST_OBJ_TYPE( - machine_uart_type, - MP_QSTR_UART, - MP_TYPE_FLAG_ITER_IS_STREAM, - make_new, machine_uart_make_new, - print, machine_uart_print, - protocol, &uart_stream_p, - locals_dict, &machine_uart_locals_dict - ); diff --git a/tulip/esp32s3/machine_wdt.c b/tulip/esp32s3/machine_wdt.c deleted file mode 100644 index 2cb6c5181..000000000 --- a/tulip/esp32s3/machine_wdt.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Paul Sokolovsky - * Copyright (c) 2017 Eric Poulsen - * - * 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. - */ - -#include - -#include "py/nlr.h" -#include "py/obj.h" -#include "py/runtime.h" - -#include "esp_task_wdt.h" - -const mp_obj_type_t machine_wdt_type; - -typedef struct _machine_wdt_obj_t { - mp_obj_base_t base; -} machine_wdt_obj_t; - -STATIC machine_wdt_obj_t wdt_default = {{&machine_wdt_type}}; - -STATIC mp_obj_t machine_wdt_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - enum { ARG_id, ARG_timeout }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_INT, {.u_int = 0} }, - { MP_QSTR_timeout, MP_ARG_INT, {.u_int = 5000} } - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - if (args[ARG_id].u_int != 0) { - mp_raise_ValueError(NULL); - } - - if (args[ARG_timeout].u_int <= 0) { - mp_raise_ValueError(MP_ERROR_TEXT("WDT timeout too short")); - } - - esp_task_wdt_config_t config = { - .timeout_ms = args[ARG_timeout].u_int, - .idle_core_mask = 0, - .trigger_panic = true, - }; - mp_int_t rs_code = esp_task_wdt_reconfigure(&config); - if (rs_code != ESP_OK) { - mp_raise_OSError(rs_code); - } - - esp_task_wdt_add(NULL); - - return &wdt_default; -} - -STATIC mp_obj_t machine_wdt_feed(mp_obj_t self_in) { - (void)self_in; - esp_task_wdt_reset(); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_wdt_feed_obj, machine_wdt_feed); - -STATIC const mp_rom_map_elem_t machine_wdt_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_feed), MP_ROM_PTR(&machine_wdt_feed_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(machine_wdt_locals_dict, machine_wdt_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_wdt_type, - MP_QSTR_WDT, - MP_TYPE_FLAG_NONE, - make_new, machine_wdt_make_new, - locals_dict, &machine_wdt_locals_dict - ); diff --git a/tulip/esp32s3/main.c b/tulip/esp32s3/main.c index 62847b1b9..0e2ddead4 100644 --- a/tulip/esp32s3/main.c +++ b/tulip/esp32s3/main.c @@ -50,6 +50,8 @@ #include "shared/readline/readline.h" #include "shared/runtime/pyexec.h" #include "uart.h" + + #include "usb.h" #include "sequencer.h" #include "usb_serial_jtag.h" @@ -101,7 +103,7 @@ #ifdef TDECK #include "tdeck_keyboard.h" #else -#include "usb_keyboard.h" +#include "usb_host.h" #endif TaskHandle_t display_handle; @@ -141,7 +143,6 @@ void esp_alloc_failed(size_t size, uint32_t caps, const char *function_name) { - float compute_cpu_usage(uint8_t debug) { TaskStatus_t *pxTaskStatusArray; volatile UBaseType_t uxArraySize, x, i; diff --git a/tulip/esp32s3/main/CMakeLists.txt b/tulip/esp32s3/main/CMakeLists.txt index beede3bbf..b69c3a473 100644 --- a/tulip/esp32s3/main/CMakeLists.txt +++ b/tulip/esp32s3/main/CMakeLists.txt @@ -1,5 +1,6 @@ # Set location of base MicroPython directory. # Set location of base MicroPython directory. +set(MICROPY_PY_TINYUSB ON) include(../esp32_common.cmake) diff --git a/tulip/esp32s3/main/idf_component.yml b/tulip/esp32s3/main/idf_component.yml index b7a02c3b0..59dafdb94 100644 --- a/tulip/esp32s3/main/idf_component.yml +++ b/tulip/esp32s3/main/idf_component.yml @@ -1,7 +1,7 @@ ## IDF Component Manager Manifest File dependencies: espressif/esp_lcd_touch_ft5x06: "^1.0.5~1" - espressif/esp_lcd_touch_gt911: "==1.0.0" idf: version: ">=5.0.2" espressif/esp_tinyusb: "~1.0.0" + espressif/mdns: "~1.1.0" diff --git a/tulip/esp32s3/memory.h b/tulip/esp32s3/memory.h deleted file mode 100644 index 1f07fe409..000000000 --- a/tulip/esp32s3/memory.h +++ /dev/null @@ -1,2 +0,0 @@ -// this is needed for lib/crypto-algorithms/sha256.c -#include diff --git a/tulip/esp32s3/modesp.c b/tulip/esp32s3/modesp.c deleted file mode 100644 index 4726ce587..000000000 --- a/tulip/esp32s3/modesp.c +++ /dev/null @@ -1,145 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2015 Paul Sokolovsky - * Copyright (c) 2016 Damien P. George - * - * 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. - */ - -#include - -#include "esp_flash.h" -#include "esp_log.h" - -#include "py/runtime.h" -#include "py/mperrno.h" -#include "py/mphal.h" - -STATIC mp_obj_t esp_osdebug(size_t n_args, const mp_obj_t *args) { - esp_log_level_t level = LOG_LOCAL_LEVEL; - if (n_args == 2) { - level = mp_obj_get_int(args[1]); - } - if (args[0] == mp_const_none) { - // Disable logging - esp_log_level_set("*", ESP_LOG_ERROR); - } else { - // Enable logging at the given level - // TODO args[0] should set the UART to which debug is sent - esp_log_level_set("*", level); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_osdebug_obj, 1, 2, esp_osdebug); - -STATIC mp_obj_t esp_flash_read_(mp_obj_t offset_in, mp_obj_t buf_in) { - mp_int_t offset = mp_obj_get_int(offset_in); - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE); - esp_err_t res = esp_flash_read(NULL, bufinfo.buf, offset, bufinfo.len); - if (res != ESP_OK) { - mp_raise_OSError(MP_EIO); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_flash_read_obj, esp_flash_read_); - -STATIC mp_obj_t esp_flash_write_(mp_obj_t offset_in, mp_obj_t buf_in) { - mp_int_t offset = mp_obj_get_int(offset_in); - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); - esp_err_t res = esp_flash_write(NULL, bufinfo.buf, offset, bufinfo.len); - if (res != ESP_OK) { - mp_raise_OSError(MP_EIO); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp_flash_write_obj, esp_flash_write_); - -STATIC mp_obj_t esp_flash_erase(mp_obj_t sector_in) { - mp_int_t sector = mp_obj_get_int(sector_in); - esp_err_t res = esp_flash_erase_region(NULL, sector * 4096, 4096); - if (res != ESP_OK) { - mp_raise_OSError(MP_EIO); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp_flash_erase_obj, esp_flash_erase); - -STATIC mp_obj_t esp_flash_size(void) { - uint32_t size; - esp_flash_get_size(NULL, &size); - return mp_obj_new_int_from_uint(size); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_size_obj, esp_flash_size); - -STATIC mp_obj_t esp_flash_user_start(void) { - return MP_OBJ_NEW_SMALL_INT(0x200000); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_flash_user_start_obj, esp_flash_user_start); - -STATIC mp_obj_t esp_gpio_matrix_in(mp_obj_t pin, mp_obj_t sig, mp_obj_t inv) { - esp_rom_gpio_connect_in_signal(mp_obj_get_int(pin), mp_obj_get_int(sig), mp_obj_get_int(inv)); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp_gpio_matrix_in_obj, esp_gpio_matrix_in); - -STATIC mp_obj_t esp_gpio_matrix_out(size_t n_args, const mp_obj_t *args) { - (void)n_args; - esp_rom_gpio_connect_out_signal(mp_obj_get_int(args[0]), mp_obj_get_int(args[1]), mp_obj_get_int(args[2]), mp_obj_get_int(args[3])); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_gpio_matrix_out_obj, 4, 4, esp_gpio_matrix_out); - -STATIC const mp_rom_map_elem_t esp_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp) }, - - { MP_ROM_QSTR(MP_QSTR_osdebug), MP_ROM_PTR(&esp_osdebug_obj) }, - - { MP_ROM_QSTR(MP_QSTR_flash_read), MP_ROM_PTR(&esp_flash_read_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_write), MP_ROM_PTR(&esp_flash_write_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_erase), MP_ROM_PTR(&esp_flash_erase_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_size), MP_ROM_PTR(&esp_flash_size_obj) }, - { MP_ROM_QSTR(MP_QSTR_flash_user_start), MP_ROM_PTR(&esp_flash_user_start_obj) }, - - { MP_ROM_QSTR(MP_QSTR_gpio_matrix_in), MP_ROM_PTR(&esp_gpio_matrix_in_obj) }, - { MP_ROM_QSTR(MP_QSTR_gpio_matrix_out), MP_ROM_PTR(&esp_gpio_matrix_out_obj) }, - - // Constants for second arg of osdebug() - { MP_ROM_QSTR(MP_QSTR_LOG_NONE), MP_ROM_INT((mp_uint_t)ESP_LOG_NONE)}, - { MP_ROM_QSTR(MP_QSTR_LOG_ERROR), MP_ROM_INT((mp_uint_t)ESP_LOG_ERROR)}, - { MP_ROM_QSTR(MP_QSTR_LOG_WARNING), MP_ROM_INT((mp_uint_t)ESP_LOG_WARN)}, - { MP_ROM_QSTR(MP_QSTR_LOG_INFO), MP_ROM_INT((mp_uint_t)ESP_LOG_INFO)}, - { MP_ROM_QSTR(MP_QSTR_LOG_DEBUG), MP_ROM_INT((mp_uint_t)ESP_LOG_DEBUG)}, - { MP_ROM_QSTR(MP_QSTR_LOG_VERBOSE), MP_ROM_INT((mp_uint_t)ESP_LOG_VERBOSE)}, -}; - -STATIC MP_DEFINE_CONST_DICT(esp_module_globals, esp_module_globals_table); - -const mp_obj_module_t esp_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&esp_module_globals, -}; - -MP_REGISTER_MODULE(MP_QSTR_esp, esp_module); diff --git a/tulip/esp32s3/modesp32.c b/tulip/esp32s3/modesp32.c deleted file mode 100644 index ef3ad0b76..000000000 --- a/tulip/esp32s3/modesp32.c +++ /dev/null @@ -1,229 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 "Eric Poulsen" - * - * 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. - */ - -#include -#include - -#include -#include -#include "soc/rtc_cntl_reg.h" -#include "driver/gpio.h" -#include "driver/adc.h" -#include "esp_heap_caps.h" -#include "multi_heap.h" - -#include "py/nlr.h" -#include "py/obj.h" -#include "py/runtime.h" -#include "py/mphal.h" -#include "shared/timeutils/timeutils.h" -#include "modmachine.h" -#include "machine_rtc.h" -#include "modesp32.h" - -// These private includes are needed for idf_heap_info. -#define MULTI_HEAP_FREERTOS -#include "../multi_heap_platform.h" -#include "../heap_private.h" - -STATIC mp_obj_t esp32_wake_on_touch(const mp_obj_t wake) { - - if (machine_rtc_config.ext0_pin != -1) { - mp_raise_ValueError(MP_ERROR_TEXT("no resources")); - } - // mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("touchpad wakeup not available for this version of ESP-IDF")); - - machine_rtc_config.wake_on_touch = mp_obj_is_true(wake); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_touch_obj, esp32_wake_on_touch); - -STATIC mp_obj_t esp32_wake_on_ext0(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - - if (machine_rtc_config.wake_on_touch) { - mp_raise_ValueError(MP_ERROR_TEXT("no resources")); - } - enum {ARG_pin, ARG_level}; - const mp_arg_t allowed_args[] = { - { MP_QSTR_pin, MP_ARG_OBJ, {.u_obj = mp_obj_new_int(machine_rtc_config.ext0_pin)} }, - { MP_QSTR_level, MP_ARG_BOOL, {.u_bool = machine_rtc_config.ext0_level} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - if (args[ARG_pin].u_obj == mp_const_none) { - machine_rtc_config.ext0_pin = -1; // "None" - } else { - gpio_num_t pin_id = machine_pin_get_id(args[ARG_pin].u_obj); - if (pin_id != machine_rtc_config.ext0_pin) { - if (!RTC_IS_VALID_EXT_PIN(pin_id)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); - } - machine_rtc_config.ext0_pin = pin_id; - } - } - - machine_rtc_config.ext0_level = args[ARG_level].u_bool; - machine_rtc_config.ext0_wake_types = MACHINE_WAKE_SLEEP | MACHINE_WAKE_DEEPSLEEP; - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext0_obj, 0, esp32_wake_on_ext0); - -STATIC mp_obj_t esp32_wake_on_ext1(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum {ARG_pins, ARG_level}; - const mp_arg_t allowed_args[] = { - { MP_QSTR_pins, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_level, MP_ARG_BOOL, {.u_bool = machine_rtc_config.ext1_level} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - uint64_t ext1_pins = machine_rtc_config.ext1_pins; - - - // Check that all pins are allowed - if (args[ARG_pins].u_obj != mp_const_none) { - size_t len = 0; - mp_obj_t *elem; - mp_obj_get_array(args[ARG_pins].u_obj, &len, &elem); - ext1_pins = 0; - - for (int i = 0; i < len; i++) { - - gpio_num_t pin_id = machine_pin_get_id(elem[i]); - if (!RTC_IS_VALID_EXT_PIN(pin_id)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); - break; - } - ext1_pins |= (1ll << pin_id); - } - } - - machine_rtc_config.ext1_level = args[ARG_level].u_bool; - machine_rtc_config.ext1_pins = ext1_pins; - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_wake_on_ext1_obj, 0, esp32_wake_on_ext1); - -STATIC mp_obj_t esp32_wake_on_ulp(const mp_obj_t wake) { - if (machine_rtc_config.ext0_pin != -1) { - mp_raise_ValueError(MP_ERROR_TEXT("no resources")); - } - machine_rtc_config.wake_on_ulp = mp_obj_is_true(wake); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_wake_on_ulp_obj, esp32_wake_on_ulp); - -STATIC mp_obj_t esp32_gpio_deep_sleep_hold(const mp_obj_t enable) { - if (mp_obj_is_true(enable)) { - gpio_deep_sleep_hold_en(); - } else { - gpio_deep_sleep_hold_dis(); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_gpio_deep_sleep_hold_obj, esp32_gpio_deep_sleep_hold); - -#if CONFIG_IDF_TARGET_ESP32 - -#include "soc/sens_reg.h" - -STATIC mp_obj_t esp32_raw_temperature(void) { - SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S); - SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 10, SENS_TSENS_CLK_DIV_S); - CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); - CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); - SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE); - SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP); - esp_rom_delay_us(100); - SET_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT); - esp_rom_delay_us(5); - int res = GET_PERI_REG_BITS2(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT, SENS_TSENS_OUT_S); - - return mp_obj_new_int(res); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp32_raw_temperature_obj, esp32_raw_temperature); - -#endif - -STATIC mp_obj_t esp32_idf_heap_info(const mp_obj_t cap_in) { - mp_int_t cap = mp_obj_get_int(cap_in); - multi_heap_info_t info; - heap_t *heap; - mp_obj_t heap_list = mp_obj_new_list(0, 0); - SLIST_FOREACH(heap, ®istered_heaps, next) { - if (heap_caps_match(heap, cap)) { - multi_heap_get_info(heap->heap, &info); - mp_obj_t data[] = { - MP_OBJ_NEW_SMALL_INT(heap->end - heap->start), // total heap size - MP_OBJ_NEW_SMALL_INT(info.total_free_bytes), // total free bytes - MP_OBJ_NEW_SMALL_INT(info.largest_free_block), // largest free contiguous - MP_OBJ_NEW_SMALL_INT(info.minimum_free_bytes), // minimum free seen - }; - mp_obj_t this_heap = mp_obj_new_tuple(4, data); - mp_obj_list_append(heap_list, this_heap); - } - } - return heap_list; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_idf_heap_info_obj, esp32_idf_heap_info); - -STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_esp32) }, - - { MP_ROM_QSTR(MP_QSTR_wake_on_touch), MP_ROM_PTR(&esp32_wake_on_touch_obj) }, - { MP_ROM_QSTR(MP_QSTR_wake_on_ext0), MP_ROM_PTR(&esp32_wake_on_ext0_obj) }, - { MP_ROM_QSTR(MP_QSTR_wake_on_ext1), MP_ROM_PTR(&esp32_wake_on_ext1_obj) }, - { MP_ROM_QSTR(MP_QSTR_wake_on_ulp), MP_ROM_PTR(&esp32_wake_on_ulp_obj) }, - { MP_ROM_QSTR(MP_QSTR_gpio_deep_sleep_hold), MP_ROM_PTR(&esp32_gpio_deep_sleep_hold_obj) }, - #if CONFIG_IDF_TARGET_ESP32 - { MP_ROM_QSTR(MP_QSTR_raw_temperature), MP_ROM_PTR(&esp32_raw_temperature_obj) }, - #endif - { MP_ROM_QSTR(MP_QSTR_idf_heap_info), MP_ROM_PTR(&esp32_idf_heap_info_obj) }, - - { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, - { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, - { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - { MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) }, - #endif - - { MP_ROM_QSTR(MP_QSTR_WAKEUP_ALL_LOW), MP_ROM_FALSE }, - { MP_ROM_QSTR(MP_QSTR_WAKEUP_ANY_HIGH), MP_ROM_TRUE }, - - { MP_ROM_QSTR(MP_QSTR_HEAP_DATA), MP_ROM_INT(MALLOC_CAP_8BIT) }, - { MP_ROM_QSTR(MP_QSTR_HEAP_EXEC), MP_ROM_INT(MALLOC_CAP_EXEC) }, -}; - -STATIC MP_DEFINE_CONST_DICT(esp32_module_globals, esp32_module_globals_table); - -const mp_obj_module_t esp32_module = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&esp32_module_globals, -}; - -MP_REGISTER_MODULE(MP_QSTR_esp32, esp32_module); diff --git a/tulip/esp32s3/modesp32.h b/tulip/esp32s3/modesp32.h deleted file mode 100644 index a685b7b38..000000000 --- a/tulip/esp32s3/modesp32.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef MICROPY_INCLUDED_ESP32_MODESP32_H -#define MICROPY_INCLUDED_ESP32_MODESP32_H - -#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - - #define RTC_VALID_EXT_PINS \ - ( \ - (1ll << 0) | \ - (1ll << 1) | \ - (1ll << 2) | \ - (1ll << 3) | \ - (1ll << 4) | \ - (1ll << 5) | \ - (1ll << 6) | \ - (1ll << 7) | \ - (1ll << 8) | \ - (1ll << 9) | \ - (1ll << 10) | \ - (1ll << 11) | \ - (1ll << 12) | \ - (1ll << 13) | \ - (1ll << 14) | \ - (1ll << 15) | \ - (1ll << 16) | \ - (1ll << 17) | \ - (1ll << 18) | \ - (1ll << 19) | \ - (1ll << 20) | \ - (1ll << 21) \ - ) - #define RTC_LAST_EXT_PIN 21 - -#else - - #define RTC_VALID_EXT_PINS \ - ( \ - (1ll << 0) | \ - (1ll << 2) | \ - (1ll << 4) | \ - (1ll << 12) | \ - (1ll << 13) | \ - (1ll << 14) | \ - (1ll << 15) | \ - (1ll << 25) | \ - (1ll << 26) | \ - (1ll << 27) | \ - (1ll << 32) | \ - (1ll << 33) | \ - (1ll << 34) | \ - (1ll << 35) | \ - (1ll << 36) | \ - (1ll << 37) | \ - (1ll << 38) | \ - (1ll << 39) \ - ) - #define RTC_LAST_EXT_PIN 39 - -#endif - -#define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS) - -extern int8_t esp32_rmt_bitstream_channel_id; - -extern const mp_obj_type_t esp32_nvs_type; -extern const mp_obj_type_t esp32_partition_type; -extern const mp_obj_type_t esp32_rmt_type; -extern const mp_obj_type_t esp32_ulp_type; - -esp_err_t rmt_driver_install_core1(uint8_t channel_id); - -#endif // MICROPY_INCLUDED_ESP32_MODESP32_H diff --git a/tulip/esp32s3/modespnow.c b/tulip/esp32s3/modespnow.c deleted file mode 100644 index 08836c0ad..000000000 --- a/tulip/esp32s3/modespnow.c +++ /dev/null @@ -1,861 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017-2020 Nick Moore - * Copyright (c) 2018 shawwwn - * Copyright (c) 2020-2021 Glenn Moloney @glenn20 - * - * 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. - */ - - -#include -#include -#include - -#include "esp_log.h" -#include "esp_now.h" -#include "esp_wifi.h" -#include "esp_wifi_types.h" - -#include "py/runtime.h" -#include "py/mphal.h" -#include "py/mperrno.h" -#include "py/obj.h" -#include "py/objstr.h" -#include "py/objarray.h" -#include "py/stream.h" -#include "py/binary.h" -#include "py/ringbuf.h" - -#include "mpconfigport.h" -#include "mphalport.h" -#include "modnetwork.h" -#include "modespnow.h" - -#ifndef MICROPY_ESPNOW_RSSI -// Include code to track rssi of peers -#define MICROPY_ESPNOW_RSSI 1 -#endif -#ifndef MICROPY_ESPNOW_EXTRA_PEER_METHODS -// Include mod_peer(),get_peer(),peer_count() -#define MICROPY_ESPNOW_EXTRA_PEER_METHODS 1 -#endif - -// Relies on gcc Variadic Macros and Statement Expressions -#define NEW_TUPLE(...) \ - ({mp_obj_t _z[] = {__VA_ARGS__}; mp_obj_new_tuple(MP_ARRAY_SIZE(_z), _z); }) - -static const uint8_t ESPNOW_MAGIC = 0x99; - -// ESPNow packet format for the receive buffer. -// Use this for peeking at the header of the next packet in the buffer. -typedef struct { - uint8_t magic; // = ESPNOW_MAGIC - uint8_t msg_len; // Length of the message - #if MICROPY_ESPNOW_RSSI - uint32_t time_ms; // Timestamp (ms) when packet is received - int8_t rssi; // RSSI value (dBm) (-127 to 0) - #endif // MICROPY_ESPNOW_RSSI -} __attribute__((packed)) espnow_hdr_t; - -typedef struct { - espnow_hdr_t hdr; // The header - uint8_t peer[6]; // Peer address - uint8_t msg[0]; // Message is up to 250 bytes -} __attribute__((packed)) espnow_pkt_t; - -// The maximum length of an espnow packet (bytes) -static const size_t MAX_PACKET_LEN = ( - (sizeof(espnow_pkt_t) + ESP_NOW_MAX_DATA_LEN)); - -// Enough for 2 full-size packets: 2 * (6 + 7 + 250) = 526 bytes -// Will allocate an additional 7 bytes for buffer overhead -static const size_t DEFAULT_RECV_BUFFER_SIZE = (2 * MAX_PACKET_LEN); - -// Default timeout (millisec) to wait for incoming ESPNow messages (5 minutes). -static const size_t DEFAULT_RECV_TIMEOUT_MS = (5 * 60 * 1000); - -// Time to wait (millisec) for responses from sent packets: (2 seconds). -static const size_t DEFAULT_SEND_TIMEOUT_MS = (2 * 1000); - -// Number of milliseconds to wait for pending responses to sent packets. -// This is a fallback which should never be reached. -static const mp_uint_t PENDING_RESPONSES_TIMEOUT_MS = 100; -static const mp_uint_t PENDING_RESPONSES_BUSY_POLL_MS = 10; - -// The data structure for the espnow_singleton. -typedef struct _esp_espnow_obj_t { - mp_obj_base_t base; - - ringbuf_t *recv_buffer; // A buffer for received packets - size_t recv_buffer_size; // The size of the recv_buffer - mp_int_t recv_timeout_ms; // Timeout for recv() - volatile size_t rx_packets; // # of received packets - size_t dropped_rx_pkts; // # of dropped packets (buffer full) - size_t tx_packets; // # of sent packets - volatile size_t tx_responses; // # of sent packet responses received - volatile size_t tx_failures; // # of sent packet responses failed - size_t peer_count; // Cache the # of peers for send(sync=True) - mp_obj_t recv_cb; // Callback when a packet is received - mp_obj_t recv_cb_arg; // Argument passed to callback - #if MICROPY_ESPNOW_RSSI - mp_obj_t peers_table; // A dictionary of discovered peers - #endif // MICROPY_ESPNOW_RSSI -} esp_espnow_obj_t; - -const mp_obj_type_t esp_espnow_type; - -// ### Initialisation and Config functions -// - -// Return a pointer to the ESPNow module singleton -// If state == INITIALISED check the device has been initialised. -// Raises OSError if not initialised and state == INITIALISED. -static esp_espnow_obj_t *_get_singleton() { - return MP_STATE_PORT(espnow_singleton); -} - -static esp_espnow_obj_t *_get_singleton_initialised() { - esp_espnow_obj_t *self = _get_singleton(); - // assert(self); - if (self->recv_buffer == NULL) { - // Throw an espnow not initialised error - check_esp_err(ESP_ERR_ESPNOW_NOT_INIT); - } - return self; -} - -// Allocate and initialise the ESPNow module as a singleton. -// Returns the initialised espnow_singleton. -STATIC mp_obj_t espnow_make_new(const mp_obj_type_t *type, size_t n_args, - size_t n_kw, const mp_obj_t *all_args) { - - // The espnow_singleton must be defined in MICROPY_PORT_ROOT_POINTERS - // (see mpconfigport.h) to prevent memory allocated here from being - // garbage collected. - // NOTE: on soft reset the espnow_singleton MUST be set to NULL and the - // ESP-NOW functions de-initialised (see main.c). - esp_espnow_obj_t *self = MP_STATE_PORT(espnow_singleton); - if (self != NULL) { - return self; - } - self = m_new_obj(esp_espnow_obj_t); - self->base.type = &esp_espnow_type; - self->recv_buffer_size = DEFAULT_RECV_BUFFER_SIZE; - self->recv_timeout_ms = DEFAULT_RECV_TIMEOUT_MS; - self->recv_buffer = NULL; // Buffer is allocated in espnow_init() - self->recv_cb = mp_const_none; - #if MICROPY_ESPNOW_RSSI - self->peers_table = mp_obj_new_dict(0); - // Prevent user code modifying the dict - mp_obj_dict_get_map(self->peers_table)->is_fixed = 1; - #endif // MICROPY_ESPNOW_RSSI - - // Set the global singleton pointer for the espnow protocol. - MP_STATE_PORT(espnow_singleton) = self; - - return self; -} - -// Forward declare the send and recv ESPNow callbacks -STATIC void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status); - -STATIC void recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *msg, int msg_len); - -// ESPNow.init(): Initialise the data buffers and ESP-NOW functions. -// Initialise the Espressif ESPNOW software stack, register callbacks and -// allocate the recv data buffers. -// Returns None. -static mp_obj_t espnow_init(mp_obj_t _) { - esp_espnow_obj_t *self = _get_singleton(); - if (self->recv_buffer == NULL) { // Already initialised - self->recv_buffer = m_new_obj(ringbuf_t); - ringbuf_alloc(self->recv_buffer, self->recv_buffer_size); - - esp_initialise_wifi(); // Call the wifi init code in network_wlan.c - check_esp_err(esp_now_init()); - check_esp_err(esp_now_register_recv_cb(recv_cb)); - check_esp_err(esp_now_register_send_cb(send_cb)); - } - return mp_const_none; -} - -// ESPNow.deinit(): De-initialise the ESPNOW software stack, disable callbacks -// and deallocate the recv data buffers. -// Note: this function is called from main.c:mp_task() to cleanup before soft -// reset, so cannot be declared STATIC and must guard against self == NULL;. -mp_obj_t espnow_deinit(mp_obj_t _) { - esp_espnow_obj_t *self = _get_singleton(); - if (self != NULL && self->recv_buffer != NULL) { - check_esp_err(esp_now_unregister_recv_cb()); - check_esp_err(esp_now_unregister_send_cb()); - check_esp_err(esp_now_deinit()); - self->recv_buffer->buf = NULL; - self->recv_buffer = NULL; - self->peer_count = 0; // esp_now_deinit() removes all peers. - self->tx_packets = self->tx_responses; - } - return mp_const_none; -} - -STATIC mp_obj_t espnow_active(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton(); - if (n_args > 1) { - if (mp_obj_is_true(args[1])) { - espnow_init(self); - } else { - espnow_deinit(self); - } - } - return self->recv_buffer != NULL ? mp_const_true : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_active_obj, 1, 2, espnow_active); - -// ESPNow.config(['param'|param=value, ..]) -// Get or set configuration values. Supported config params: -// buffer: size of buffer for rx packets (default=514 bytes) -// timeout: Default read timeout (default=300,000 milliseconds) -STATIC mp_obj_t espnow_config(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - esp_espnow_obj_t *self = _get_singleton(); - enum { ARG_get, ARG_rxbuf, ARG_timeout_ms, ARG_rate }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_rxbuf, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_timeout_ms, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MIN} }, - { MP_QSTR_rate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, - MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - if (args[ARG_rxbuf].u_int >= 0) { - self->recv_buffer_size = args[ARG_rxbuf].u_int; - } - if (args[ARG_timeout_ms].u_int != INT_MIN) { - self->recv_timeout_ms = args[ARG_timeout_ms].u_int; - } - if (args[ARG_rate].u_int >= 0) { - esp_initialise_wifi(); // Call the wifi init code in network_wlan.c - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_STA, args[ARG_rate].u_int)); - check_esp_err(esp_wifi_config_espnow_rate(ESP_IF_WIFI_AP, args[ARG_rate].u_int)); - } - if (args[ARG_get].u_obj == MP_OBJ_NULL) { - return mp_const_none; - } -#define QS(x) (uintptr_t)MP_OBJ_NEW_QSTR(x) - // Return the value of the requested parameter - uintptr_t name = (uintptr_t)args[ARG_get].u_obj; - if (name == QS(MP_QSTR_rxbuf)) { - return mp_obj_new_int(self->recv_buffer_size); - } else if (name == QS(MP_QSTR_timeout_ms)) { - return mp_obj_new_int(self->recv_timeout_ms); - } else { - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); - } -#undef QS - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_config_obj, 1, espnow_config); - -// ESPNow.irq(recv_cb) -// Set callback function to be invoked when a message is received. -STATIC mp_obj_t espnow_irq(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton(); - mp_obj_t recv_cb = args[1]; - if (recv_cb != mp_const_none && !mp_obj_is_callable(recv_cb)) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid handler")); - } - self->recv_cb = recv_cb; - self->recv_cb_arg = (n_args > 2) ? args[2] : mp_const_none; - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_irq_obj, 2, 3, espnow_irq); - -// ESPnow.stats(): Provide some useful stats. -// Returns a tuple of: -// (tx_pkts, tx_responses, tx_failures, rx_pkts, dropped_rx_pkts) -STATIC mp_obj_t espnow_stats(mp_obj_t _) { - const esp_espnow_obj_t *self = _get_singleton(); - return NEW_TUPLE( - mp_obj_new_int(self->tx_packets), - mp_obj_new_int(self->tx_responses), - mp_obj_new_int(self->tx_failures), - mp_obj_new_int(self->rx_packets), - mp_obj_new_int(self->dropped_rx_pkts)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_stats_obj, espnow_stats); - -#if MICROPY_ESPNOW_RSSI -// ### Maintaining the peer table and reading RSSI values -// -// We maintain a peers table for several reasons, to: -// - support monitoring the RSSI values for all peers; and -// - to return unique bytestrings for each peer which supports more efficient -// application memory usage and peer handling. - -// Lookup a peer in the peers table and return a reference to the item in the -// peers_table. Add peer to the table if it is not found (may alloc memory). -// Will not return NULL. -static mp_map_elem_t *_lookup_add_peer(esp_espnow_obj_t *self, const uint8_t *peer) { - // We do not want to allocate any new memory in the case that the peer - // already exists in the peers_table (which is almost all the time). - // So, we use a byte string on the stack and look that up in the dict. - mp_map_t *map = mp_obj_dict_get_map(self->peers_table); - mp_obj_str_t peer_obj = {{&mp_type_bytes}, 0, ESP_NOW_ETH_ALEN, peer}; - mp_map_elem_t *item = mp_map_lookup(map, &peer_obj, MP_MAP_LOOKUP); - if (item == NULL) { - // If not found, add the peer using a new bytestring - map->is_fixed = 0; // Allow to modify the dict - mp_obj_t new_peer = mp_obj_new_bytes(peer, ESP_NOW_ETH_ALEN); - item = mp_map_lookup(map, new_peer, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); - item->value = mp_obj_new_list(2, NULL); - map->is_fixed = 1; // Relock the dict - } - return item; -} - -// Update the peers table with the new rssi value from a received pkt and -// return a reference to the item in the peers_table. -static mp_map_elem_t *_update_rssi(const uint8_t *peer, int8_t rssi, uint32_t time_ms) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - // Lookup the peer in the device table - mp_map_elem_t *item = _lookup_add_peer(self, peer); - mp_obj_list_t *list = MP_OBJ_TO_PTR(item->value); - list->items[0] = MP_OBJ_NEW_SMALL_INT(rssi); - list->items[1] = mp_obj_new_int(time_ms); - return item; -} -#endif // MICROPY_ESPNOW_RSSI - -// Return C pointer to byte memory string/bytes/bytearray in obj. -// Raise ValueError if the length does not match expected len. -static uint8_t *_get_bytes_len_rw(mp_obj_t obj, size_t len, mp_uint_t rw) { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(obj, &bufinfo, rw); - if (bufinfo.len != len) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); - } - return (uint8_t *)bufinfo.buf; -} - -static uint8_t *_get_bytes_len(mp_obj_t obj, size_t len) { - return _get_bytes_len_rw(obj, len, MP_BUFFER_READ); -} - -static uint8_t *_get_bytes_len_w(mp_obj_t obj, size_t len) { - return _get_bytes_len_rw(obj, len, MP_BUFFER_WRITE); -} - -// Return C pointer to the MAC address. -// Raise ValueError if mac_addr is wrong type or is not 6 bytes long. -static const uint8_t *_get_peer(mp_obj_t mac_addr) { - return mp_obj_is_true(mac_addr) - ? _get_bytes_len(mac_addr, ESP_NOW_ETH_ALEN) : NULL; -} - -// Copy data from the ring buffer - wait if buffer is empty up to timeout_ms -// 0: Success -// -1: Not enough data available to complete read (try again later) -// -2: Requested read is larger than buffer - will never succeed -static int ringbuf_get_bytes_wait(ringbuf_t *r, uint8_t *data, size_t len, mp_int_t timeout_ms) { - mp_uint_t start = mp_hal_ticks_ms(); - int status = 0; - while (((status = ringbuf_get_bytes(r, data, len)) == -1) - && (timeout_ms < 0 || (mp_uint_t)(mp_hal_ticks_ms() - start) < (mp_uint_t)timeout_ms)) { - MICROPY_EVENT_POLL_HOOK; - } - return status; -} - -// ESPNow.recvinto(buffers[, timeout_ms]): -// Waits for an espnow message and copies the peer_addr and message into -// the buffers list. -// Arguments: -// buffers: (Optional) list of bytearrays to store return values. -// timeout_ms: (Optional) timeout in milliseconds (or None). -// Buffers should be a list: [bytearray(6), bytearray(250)] -// If buffers is 4 elements long, the rssi and timestamp values will be -// loaded into the 3rd and 4th elements. -// Default timeout is set with ESPNow.config(timeout=milliseconds). -// Return (None, None) on timeout. -STATIC mp_obj_t espnow_recvinto(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - - mp_int_t timeout_ms = ((n_args > 2 && args[2] != mp_const_none) - ? mp_obj_get_int(args[2]) : self->recv_timeout_ms); - - mp_obj_list_t *list = MP_OBJ_TO_PTR(args[1]); - if (!mp_obj_is_type(list, &mp_type_list) || list->len < 2) { - mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recvinto(): Invalid argument")); - } - mp_obj_array_t *msg = MP_OBJ_TO_PTR(list->items[1]); - if (mp_obj_is_type(msg, &mp_type_bytearray)) { - msg->len += msg->free; // Make all the space in msg array available - msg->free = 0; - } - #if MICROPY_ESPNOW_RSSI - uint8_t peer_buf[ESP_NOW_ETH_ALEN]; - #else - uint8_t *peer_buf = _get_bytes_len_w(list->items[0], ESP_NOW_ETH_ALEN); - #endif // MICROPY_ESPNOW_RSSI - uint8_t *msg_buf = _get_bytes_len_w(msg, ESP_NOW_MAX_DATA_LEN); - - // Read the packet header from the incoming buffer - espnow_hdr_t hdr; - if (ringbuf_get_bytes_wait(self->recv_buffer, (uint8_t *)&hdr, sizeof(hdr), timeout_ms) < 0) { - return MP_OBJ_NEW_SMALL_INT(0); // Timeout waiting for packet - } - int msg_len = hdr.msg_len; - - // Check the message packet header format and read the message data - if (hdr.magic != ESPNOW_MAGIC - || msg_len > ESP_NOW_MAX_DATA_LEN - || ringbuf_get_bytes(self->recv_buffer, peer_buf, ESP_NOW_ETH_ALEN) < 0 - || ringbuf_get_bytes(self->recv_buffer, msg_buf, msg_len) < 0) { - mp_raise_ValueError(MP_ERROR_TEXT("ESPNow.recv(): buffer error")); - } - if (mp_obj_is_type(msg, &mp_type_bytearray)) { - // Set the length of the message bytearray. - size_t size = msg->len + msg->free; - msg->len = msg_len; - msg->free = size - msg_len; - } - - #if MICROPY_ESPNOW_RSSI - // Update rssi value in the peer device table - mp_map_elem_t *entry = _update_rssi(peer_buf, hdr.rssi, hdr.time_ms); - list->items[0] = entry->key; // Set first element of list to peer - if (list->len >= 4) { - list->items[2] = MP_OBJ_NEW_SMALL_INT(hdr.rssi); - list->items[3] = mp_obj_new_int(hdr.time_ms); - } - #endif // MICROPY_ESPNOW_RSSI - - return MP_OBJ_NEW_SMALL_INT(msg_len); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_recvinto_obj, 2, 3, espnow_recvinto); - -// Test if data is available to read from the buffers -STATIC mp_obj_t espnow_any(const mp_obj_t _) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - - return ringbuf_avail(self->recv_buffer) ? mp_const_true : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_any_obj, espnow_any); - -// Used by espnow_send() for sends() with sync==True. -// Wait till all pending sent packet responses have been received. -// ie. self->tx_responses == self->tx_packets. -static void _wait_for_pending_responses(esp_espnow_obj_t *self) { - mp_uint_t start = mp_hal_ticks_ms(); - mp_uint_t t; - while (self->tx_responses < self->tx_packets) { - if ((t = mp_hal_ticks_ms() - start) > PENDING_RESPONSES_TIMEOUT_MS) { - mp_raise_OSError(MP_ETIMEDOUT); - } - if (t > PENDING_RESPONSES_BUSY_POLL_MS) { - // After 10ms of busy waiting give other tasks a look in. - MICROPY_EVENT_POLL_HOOK; - } - } -} - -// ESPNow.send(peer_addr, message, [sync (=true), size]) -// ESPNow.send(message) -// Send a message to the peer's mac address. Optionally wait for a response. -// If peer_addr == None or any non-true value, send to all registered peers. -// If sync == True, wait for response after sending. -// If size is provided it should be the number of bytes in message to send(). -// Returns: -// True if sync==False and message sent successfully. -// True if sync==True and message is received successfully by all recipients -// False if sync==True and message is not received by at least one recipient -// Raises: EAGAIN if the internal espnow buffers are full. -STATIC mp_obj_t espnow_send(size_t n_args, const mp_obj_t *args) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - // Check the various combinations of input arguments - const uint8_t *peer = (n_args > 2) ? _get_peer(args[1]) : NULL; - mp_obj_t msg = (n_args > 2) ? args[2] : (n_args == 2) ? args[1] : MP_OBJ_NULL; - bool sync = n_args <= 3 || args[3] == mp_const_none || mp_obj_is_true(args[3]); - - // Get a pointer to the data buffer of the message - mp_buffer_info_t message; - mp_get_buffer_raise(msg, &message, MP_BUFFER_READ); - - if (sync) { - // Flush out any pending responses. - // If the last call was sync==False there may be outstanding responses - // still to be received (possible many if we just had a burst of - // unsync send()s). We need to wait for all pending responses if this - // call has sync=True. - _wait_for_pending_responses(self); - } - int saved_failures = self->tx_failures; - // Send the packet - try, try again if internal esp-now buffers are full. - esp_err_t err; - mp_uint_t start = mp_hal_ticks_ms(); - while ((ESP_ERR_ESPNOW_NO_MEM == (err = esp_now_send(peer, message.buf, message.len))) - && (mp_uint_t)(mp_hal_ticks_ms() - start) < (mp_uint_t)DEFAULT_SEND_TIMEOUT_MS) { - MICROPY_EVENT_POLL_HOOK; - } - check_esp_err(err); // Will raise OSError if e != ESP_OK - // Increment the sent packet count. If peer_addr==NULL msg will be - // sent to all peers EXCEPT any broadcast or multicast addresses. - self->tx_packets += ((peer == NULL) ? self->peer_count : 1); - if (sync) { - // Wait for and tally all the expected responses from peers - _wait_for_pending_responses(self); - } - // Return False if sync and any peers did not respond. - return mp_obj_new_bool(!(sync && self->tx_failures != saved_failures)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(espnow_send_obj, 2, 4, espnow_send); - -// ### The ESP_Now send and recv callback routines -// - -// Callback triggered when a sent packet is acknowledged by the peer (or not). -// Just count the number of responses and number of failures. -// These are used in the send() logic. -STATIC void send_cb(const uint8_t *mac_addr, esp_now_send_status_t status) { - esp_espnow_obj_t *self = _get_singleton(); - self->tx_responses++; - if (status != ESP_NOW_SEND_SUCCESS) { - self->tx_failures++; - } -} - -// Callback triggered when an ESP-Now packet is received. -// Write the peer MAC address and the message into the recv_buffer as an -// ESPNow packet. -// If the buffer is full, drop the message and increment the dropped count. -// Schedules the user callback if one has been registered (ESPNow.config()). -STATIC void recv_cb(const esp_now_recv_info_t *recv_info, const uint8_t *msg, int msg_len) { - esp_espnow_obj_t *self = _get_singleton(); - ringbuf_t *buf = self->recv_buffer; - // TODO: Test this works with ">". - if (sizeof(espnow_pkt_t) + msg_len >= ringbuf_free(buf)) { - self->dropped_rx_pkts++; - return; - } - espnow_hdr_t header; - header.magic = ESPNOW_MAGIC; - header.msg_len = msg_len; - #if MICROPY_ESPNOW_RSSI - header.rssi = recv_info->rx_ctrl->rssi; - header.time_ms = mp_hal_ticks_ms(); - #endif // MICROPY_ESPNOW_RSSI - - ringbuf_put_bytes(buf, (uint8_t *)&header, sizeof(header)); - ringbuf_put_bytes(buf, recv_info->src_addr, ESP_NOW_ETH_ALEN); - ringbuf_put_bytes(buf, msg, msg_len); - self->rx_packets++; - if (self->recv_cb != mp_const_none) { - mp_sched_schedule(self->recv_cb, self->recv_cb_arg); - } -} - -// ### Peer Management Functions -// - -// Set the ESP-NOW Primary Master Key (pmk) (for encrypted communications). -// Raise OSError if ESP-NOW functions are not initialised. -// Raise ValueError if key is not a bytes-like object exactly 16 bytes long. -STATIC mp_obj_t espnow_set_pmk(mp_obj_t _, mp_obj_t key) { - check_esp_err(esp_now_set_pmk(_get_bytes_len(key, ESP_NOW_KEY_LEN))); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_set_pmk_obj, espnow_set_pmk); - -// Common code for add_peer() and mod_peer() to process the args and kw_args: -// Raise ValueError if the LMK is not a bytes-like object of exactly 16 bytes. -// Raise TypeError if invalid keyword args or too many positional args. -// Return true if all args parsed correctly. -STATIC bool _update_peer_info( - esp_now_peer_info_t *peer, size_t n_args, - const mp_obj_t *pos_args, mp_map_t *kw_args) { - - enum { ARG_lmk, ARG_channel, ARG_ifidx, ARG_encrypt }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_lmk, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_channel, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_ifidx, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_encrypt, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - }; - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - if (args[ARG_lmk].u_obj != mp_const_none) { - mp_obj_t obj = args[ARG_lmk].u_obj; - peer->encrypt = mp_obj_is_true(obj); - if (peer->encrypt) { - // Key must be 16 bytes in length. - memcpy(peer->lmk, _get_bytes_len(obj, ESP_NOW_KEY_LEN), ESP_NOW_KEY_LEN); - } - } - if (args[ARG_channel].u_obj != mp_const_none) { - peer->channel = mp_obj_get_int(args[ARG_channel].u_obj); - } - if (args[ARG_ifidx].u_obj != mp_const_none) { - peer->ifidx = mp_obj_get_int(args[ARG_ifidx].u_obj); - } - if (args[ARG_encrypt].u_obj != mp_const_none) { - peer->encrypt = mp_obj_is_true(args[ARG_encrypt].u_obj); - } - return true; -} - -// Update the cached peer count in self->peer_count; -// The peer_count ignores broadcast and multicast addresses and is used for the -// send() logic and is updated from add_peer(), mod_peer() and del_peer(). -STATIC void _update_peer_count() { - esp_espnow_obj_t *self = _get_singleton_initialised(); - - esp_now_peer_info_t peer = {0}; - bool from_head = true; - int count = 0; - // esp_now_fetch_peer() skips over any broadcast or multicast addresses - while (esp_now_fetch_peer(from_head, &peer) == ESP_OK) { - from_head = false; - if (++count >= ESP_NOW_MAX_TOTAL_PEER_NUM) { - break; // Should not happen - } - } - self->peer_count = count; -} - -// ESPNow.add_peer(peer_mac, [lmk, [channel, [ifidx, [encrypt]]]]) or -// ESPNow.add_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False], -// [channel=1..11|0], [ifidx=0|1], [encrypt=True|False]) -// Positional args set to None will be left at defaults. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if mac or LMK are not bytes-like objects or wrong length. -// Raise TypeError if invalid keyword args or too many positional args. -// Return None. -STATIC mp_obj_t espnow_add_peer(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN); - _update_peer_info(&peer, n_args - 2, args + 2, kw_args); - - check_esp_err(esp_now_add_peer(&peer)); - _update_peer_count(); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_add_peer_obj, 2, espnow_add_peer); - -// ESPNow.del_peer(peer_mac): Unregister peer_mac. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if peer is not a bytes-like objects or wrong length. -// Return None. -STATIC mp_obj_t espnow_del_peer(mp_obj_t _, mp_obj_t peer) { - uint8_t peer_addr[ESP_NOW_ETH_ALEN]; - memcpy(peer_addr, _get_peer(peer), ESP_NOW_ETH_ALEN); - - check_esp_err(esp_now_del_peer(peer_addr)); - _update_peer_count(); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_del_peer_obj, espnow_del_peer); - -// Convert a peer_info struct to python tuple -// Used by espnow_get_peer() and espnow_get_peers() -static mp_obj_t _peer_info_to_tuple(const esp_now_peer_info_t *peer) { - return NEW_TUPLE( - mp_obj_new_bytes(peer->peer_addr, MP_ARRAY_SIZE(peer->peer_addr)), - mp_obj_new_bytes(peer->lmk, MP_ARRAY_SIZE(peer->lmk)), - mp_obj_new_int(peer->channel), - mp_obj_new_int(peer->ifidx), - (peer->encrypt) ? mp_const_true : mp_const_false); -} - -// ESPNow.get_peers(): Fetch peer_info records for all registered ESPNow peers. -// Raise OSError if ESPNow.init() has not been called. -// Return a tuple of tuples: -// ((peer_addr, lmk, channel, ifidx, encrypt), -// (peer_addr, lmk, channel, ifidx, encrypt), ...) -STATIC mp_obj_t espnow_get_peers(mp_obj_t _) { - esp_espnow_obj_t *self = _get_singleton_initialised(); - - // Build and initialise the peer info tuple. - mp_obj_tuple_t *peerinfo_tuple = mp_obj_new_tuple(self->peer_count, NULL); - esp_now_peer_info_t peer = {0}; - for (int i = 0; i < peerinfo_tuple->len; i++) { - int status = esp_now_fetch_peer((i == 0), &peer); - peerinfo_tuple->items[i] = - (status == ESP_OK ? _peer_info_to_tuple(&peer) : mp_const_none); - } - - return peerinfo_tuple; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_get_peers_obj, espnow_get_peers); - -#if MICROPY_ESPNOW_EXTRA_PEER_METHODS -// ESPNow.get_peer(peer_mac): Get the peer info for peer_mac as a tuple. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if mac or LMK are not bytes-like objects or wrong length. -// Return a tuple of (peer_addr, lmk, channel, ifidx, encrypt). -STATIC mp_obj_t espnow_get_peer(mp_obj_t _, mp_obj_t arg1) { - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer(arg1), ESP_NOW_ETH_ALEN); - - check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); - - return _peer_info_to_tuple(&peer); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_2(espnow_get_peer_obj, espnow_get_peer); - -// ESPNow.mod_peer(peer_mac, [lmk, [channel, [ifidx, [encrypt]]]]) or -// ESPNow.mod_peer(peer_mac, [lmk=b'0123456789abcdef'|b''|None|False], -// [channel=1..11|0], [ifidx=0|1], [encrypt=True|False]) -// Positional args set to None will be left at current values. -// Raise OSError if ESPNow.init() has not been called. -// Raise ValueError if mac or LMK are not bytes-like objects or wrong length. -// Raise TypeError if invalid keyword args or too many positional args. -// Return None. -STATIC mp_obj_t espnow_mod_peer(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - esp_now_peer_info_t peer = {0}; - memcpy(peer.peer_addr, _get_peer(args[1]), ESP_NOW_ETH_ALEN); - check_esp_err(esp_now_get_peer(peer.peer_addr, &peer)); - - _update_peer_info(&peer, n_args - 2, args + 2, kw_args); - - check_esp_err(esp_now_mod_peer(&peer)); - _update_peer_count(); - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(espnow_mod_peer_obj, 2, espnow_mod_peer); - -// ESPNow.espnow_peer_count(): Get the number of registered peers. -// Raise OSError if ESPNow.init() has not been called. -// Return a tuple of (num_total_peers, num_encrypted_peers). -STATIC mp_obj_t espnow_peer_count(mp_obj_t _) { - esp_now_peer_num_t peer_num = {0}; - check_esp_err(esp_now_get_peer_num(&peer_num)); - - return NEW_TUPLE( - mp_obj_new_int(peer_num.total_num), - mp_obj_new_int(peer_num.encrypt_num)); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(espnow_peer_count_obj, espnow_peer_count); -#endif - -STATIC const mp_rom_map_elem_t esp_espnow_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&espnow_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&espnow_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&espnow_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_stats), MP_ROM_PTR(&espnow_stats_obj) }, - - // Send and receive messages - { MP_ROM_QSTR(MP_QSTR_recvinto), MP_ROM_PTR(&espnow_recvinto_obj) }, - { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&espnow_send_obj) }, - { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&espnow_any_obj) }, - - // Peer management functions - { MP_ROM_QSTR(MP_QSTR_set_pmk), MP_ROM_PTR(&espnow_set_pmk_obj) }, - { MP_ROM_QSTR(MP_QSTR_add_peer), MP_ROM_PTR(&espnow_add_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_del_peer), MP_ROM_PTR(&espnow_del_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_peers), MP_ROM_PTR(&espnow_get_peers_obj) }, - #if MICROPY_ESPNOW_EXTRA_PEER_METHODS - { MP_ROM_QSTR(MP_QSTR_mod_peer), MP_ROM_PTR(&espnow_mod_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_get_peer), MP_ROM_PTR(&espnow_get_peer_obj) }, - { MP_ROM_QSTR(MP_QSTR_peer_count), MP_ROM_PTR(&espnow_peer_count_obj) }, - #endif // MICROPY_ESPNOW_EXTRA_PEER_METHODS -}; -STATIC MP_DEFINE_CONST_DICT(esp_espnow_locals_dict, esp_espnow_locals_dict_table); - -STATIC const mp_rom_map_elem_t espnow_globals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__espnow) }, - { MP_ROM_QSTR(MP_QSTR_ESPNowBase), MP_ROM_PTR(&esp_espnow_type) }, - { MP_ROM_QSTR(MP_QSTR_MAX_DATA_LEN), MP_ROM_INT(ESP_NOW_MAX_DATA_LEN)}, - { MP_ROM_QSTR(MP_QSTR_ADDR_LEN), MP_ROM_INT(ESP_NOW_ETH_ALEN)}, - { MP_ROM_QSTR(MP_QSTR_KEY_LEN), MP_ROM_INT(ESP_NOW_KEY_LEN)}, - { MP_ROM_QSTR(MP_QSTR_MAX_TOTAL_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_TOTAL_PEER_NUM)}, - { MP_ROM_QSTR(MP_QSTR_MAX_ENCRYPT_PEER_NUM), MP_ROM_INT(ESP_NOW_MAX_ENCRYPT_PEER_NUM)}, -}; -STATIC MP_DEFINE_CONST_DICT(espnow_globals_dict, espnow_globals_dict_table); - -// ### Dummy Buffer Protocol support -// ...so asyncio can poll.ipoll() on this device - -// Support ioctl(MP_STREAM_POLL, ) for asyncio -STATIC mp_uint_t espnow_stream_ioctl( - mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { - if (request != MP_STREAM_POLL) { - *errcode = MP_EINVAL; - return MP_STREAM_ERROR; - } - esp_espnow_obj_t *self = _get_singleton(); - return (self->recv_buffer == NULL) ? 0 : // If not initialised - arg ^ ( - // If no data in the buffer, unset the Read ready flag - ((ringbuf_avail(self->recv_buffer) == 0) ? MP_STREAM_POLL_RD : 0) | - // If still waiting for responses, unset the Write ready flag - ((self->tx_responses < self->tx_packets) ? MP_STREAM_POLL_WR : 0)); -} - -STATIC const mp_stream_p_t espnow_stream_p = { - .ioctl = espnow_stream_ioctl, -}; - -#if MICROPY_ESPNOW_RSSI -// Return reference to the dictionary of peers we have seen: -// {peer1: (rssi, time_sec), peer2: (rssi, time_msec), ...} -// where: -// peerX is a byte string containing the 6-byte mac address of the peer, -// rssi is the wifi signal strength from the last msg received -// (in dBm from -127 to 0) -// time_sec is the time in milliseconds since device last booted. -STATIC void espnow_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { - esp_espnow_obj_t *self = _get_singleton(); - if (dest[0] != MP_OBJ_NULL) { // Only allow "Load" operation - return; - } - if (attr == MP_QSTR_peers_table) { - dest[0] = self->peers_table; - return; - } - dest[1] = MP_OBJ_SENTINEL; // Attribute not found -} -#endif // MICROPY_ESPNOW_RSSI - -MP_DEFINE_CONST_OBJ_TYPE( - esp_espnow_type, - MP_QSTR_ESPNowBase, - MP_TYPE_FLAG_NONE, - make_new, espnow_make_new, - #if MICROPY_ESPNOW_RSSI - attr, espnow_attr, - #endif // MICROPY_ESPNOW_RSSI - protocol, &espnow_stream_p, - locals_dict, &esp_espnow_locals_dict - ); - -const mp_obj_module_t mp_module_espnow = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&espnow_globals_dict, -}; - -MP_REGISTER_MODULE(MP_QSTR__espnow, mp_module_espnow); -MP_REGISTER_ROOT_POINTER(struct _esp_espnow_obj_t *espnow_singleton); diff --git a/tulip/esp32s3/modespnow.h b/tulip/esp32s3/modespnow.h deleted file mode 100644 index 3c6280b1c..000000000 --- a/tulip/esp32s3/modespnow.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Glenn Moloney @glenn20 - * - * 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. - */ - -#include "py/obj.h" - -// Called from main.c:mp_task() to reset the espnow software stack -mp_obj_t espnow_deinit(mp_obj_t _); diff --git a/tulip/esp32s3/modmachine.c b/tulip/esp32s3/modmachine.c deleted file mode 100644 index 01acb0102..000000000 --- a/tulip/esp32s3/modmachine.c +++ /dev/null @@ -1,348 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2013-2015 Damien P. George - * Copyright (c) 2016 Paul Sokolovsky - * - * 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. - */ - -#include -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_mac.h" -#include "esp_sleep.h" -#include "esp_pm.h" - -#include "py/obj.h" -#include "py/runtime.h" -#include "shared/runtime/pyexec.h" -#include "drivers/dht/dht.h" -#include "extmod/machine_bitstream.h" -#include "extmod/machine_mem.h" -#include "extmod/machine_signal.h" -#include "extmod/machine_pulse.h" -#include "extmod/machine_pwm.h" -#include "extmod/machine_i2c.h" -#include "extmod/machine_spi.h" -#include "modmachine.h" -#include "machine_rtc.h" - -#if MICROPY_PY_MACHINE - -typedef enum { - MP_PWRON_RESET = 1, - MP_HARD_RESET, - MP_WDT_RESET, - MP_DEEPSLEEP_RESET, - MP_SOFT_RESET -} reset_reason_t; - -STATIC bool is_soft_reset = 0; - -#if CONFIG_IDF_TARGET_ESP32C3 -int esp_clk_cpu_freq(void); -#endif - -STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { - if (n_args == 0) { - // get - return mp_obj_new_int(esp_rom_get_cpu_ticks_per_us() * 1000000); - } else { - // set - mp_int_t freq = mp_obj_get_int(args[0]) / 1000000; - if (freq != 20 && freq != 40 && freq != 80 && freq != 160 - #if !CONFIG_IDF_TARGET_ESP32C3 - && freq != 240 - #endif - ) { - #if CONFIG_IDF_TARGET_ESP32C3 - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 20MHz, 40MHz, 80Mhz or 160MHz")); - #else - mp_raise_ValueError(MP_ERROR_TEXT("frequency must be 20MHz, 40MHz, 80Mhz, 160MHz or 240MHz")); - #endif - } - #if CONFIG_IDF_TARGET_ESP32 - esp_pm_config_esp32_t pm; - #elif CONFIG_IDF_TARGET_ESP32C3 - esp_pm_config_esp32c3_t pm; - #elif CONFIG_IDF_TARGET_ESP32S2 - esp_pm_config_esp32s2_t pm; - #elif CONFIG_IDF_TARGET_ESP32S3 - esp_pm_config_esp32s3_t pm; - #endif - pm.max_freq_mhz = freq; - pm.min_freq_mhz = freq; - pm.light_sleep_enable = false; - esp_err_t ret = esp_pm_configure(&pm); - if (ret != ESP_OK) { - mp_raise_ValueError(NULL); - } - while (esp_rom_get_cpu_ticks_per_us() != freq) { - vTaskDelay(1); - } - return mp_const_none; - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_freq_obj, 0, 1, machine_freq); - -STATIC mp_obj_t machine_sleep_helper(wake_type_t wake_type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - - enum {ARG_sleep_ms}; - const mp_arg_t allowed_args[] = { - { MP_QSTR_sleep_ms, MP_ARG_INT, { .u_int = 0 } }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - - mp_int_t expiry = args[ARG_sleep_ms].u_int; - - if (expiry != 0) { - esp_sleep_enable_timer_wakeup(((uint64_t)expiry) * 1000); - } - - #if !CONFIG_IDF_TARGET_ESP32C3 - - if (machine_rtc_config.ext0_pin != -1 && (machine_rtc_config.ext0_wake_types & wake_type)) { - esp_sleep_enable_ext0_wakeup(machine_rtc_config.ext0_pin, machine_rtc_config.ext0_level ? 1 : 0); - } - - if (machine_rtc_config.ext1_pins != 0) { - esp_sleep_enable_ext1_wakeup( - machine_rtc_config.ext1_pins, - machine_rtc_config.ext1_level ? ESP_EXT1_WAKEUP_ANY_HIGH : ESP_EXT1_WAKEUP_ALL_LOW); - } - - if (machine_rtc_config.wake_on_touch) { - if (esp_sleep_enable_touchpad_wakeup() != ESP_OK) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("esp_sleep_enable_touchpad_wakeup() failed")); - } - } - - if (machine_rtc_config.wake_on_ulp) { - if (esp_sleep_enable_ulp_wakeup() != ESP_OK) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("esp_sleep_enable_ulp_wakeup() failed")); - } - } - - #endif - - switch (wake_type) { - case MACHINE_WAKE_SLEEP: - esp_light_sleep_start(); - break; - case MACHINE_WAKE_DEEPSLEEP: - esp_deep_sleep_start(); - break; - } - return mp_const_none; -} - -STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - return machine_sleep_helper(MACHINE_WAKE_SLEEP, n_args, pos_args, kw_args); -}; -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_lightsleep_obj, 0, machine_lightsleep); - -STATIC mp_obj_t machine_deepsleep(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - return machine_sleep_helper(MACHINE_WAKE_DEEPSLEEP, n_args, pos_args, kw_args); -}; -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_deepsleep_obj, 0, machine_deepsleep); - -STATIC mp_obj_t machine_reset_cause(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - if (is_soft_reset) { - return MP_OBJ_NEW_SMALL_INT(MP_SOFT_RESET); - } - switch (esp_reset_reason()) { - case ESP_RST_POWERON: - case ESP_RST_BROWNOUT: - return MP_OBJ_NEW_SMALL_INT(MP_PWRON_RESET); - break; - - case ESP_RST_INT_WDT: - case ESP_RST_TASK_WDT: - case ESP_RST_WDT: - return MP_OBJ_NEW_SMALL_INT(MP_WDT_RESET); - break; - - case ESP_RST_DEEPSLEEP: - return MP_OBJ_NEW_SMALL_INT(MP_DEEPSLEEP_RESET); - break; - - case ESP_RST_SW: - case ESP_RST_PANIC: - case ESP_RST_EXT: // Comment in ESP-IDF: "For ESP32, ESP_RST_EXT is never returned" - return MP_OBJ_NEW_SMALL_INT(MP_HARD_RESET); - break; - - case ESP_RST_SDIO: - case ESP_RST_UNKNOWN: - default: - return MP_OBJ_NEW_SMALL_INT(0); - break; - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_reset_cause_obj, 0, machine_reset_cause); - -void machine_init(void) { - is_soft_reset = 0; -} - -void machine_deinit(void) { - // we are doing a soft-reset so change the reset_cause - is_soft_reset = 1; -} - -STATIC mp_obj_t machine_wake_reason(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - return MP_OBJ_NEW_SMALL_INT(esp_sleep_get_wakeup_cause()); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_wake_reason_obj, 0, machine_wake_reason); - -STATIC mp_obj_t machine_reset(void) { - esp_restart(); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); - -STATIC mp_obj_t machine_soft_reset(void) { - pyexec_system_exit = PYEXEC_FORCED_EXIT; - mp_raise_type(&mp_type_SystemExit); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_soft_reset_obj, machine_soft_reset); - -STATIC mp_obj_t machine_unique_id(void) { - uint8_t chipid[6]; - esp_efuse_mac_get_default(chipid); - return mp_obj_new_bytes(chipid, 6); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_unique_id_obj, machine_unique_id); - -STATIC mp_obj_t machine_idle(void) { - MP_THREAD_GIL_EXIT(); - taskYIELD(); - MP_THREAD_GIL_ENTER(); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_idle_obj, machine_idle); - -STATIC mp_obj_t machine_disable_irq(void) { - uint32_t state = MICROPY_BEGIN_ATOMIC_SECTION(); - return mp_obj_new_int(state); -} -MP_DEFINE_CONST_FUN_OBJ_0(machine_disable_irq_obj, machine_disable_irq); - -STATIC mp_obj_t machine_enable_irq(mp_obj_t state_in) { - uint32_t state = mp_obj_get_int(state_in); - MICROPY_END_ATOMIC_SECTION(state); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_1(machine_enable_irq_obj, machine_enable_irq); - -STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { - { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_machine) }, - - { MP_ROM_QSTR(MP_QSTR_mem8), MP_ROM_PTR(&machine_mem8_obj) }, - { MP_ROM_QSTR(MP_QSTR_mem16), MP_ROM_PTR(&machine_mem16_obj) }, - { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, - - { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_freq_obj) }, - { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, - { MP_ROM_QSTR(MP_QSTR_soft_reset), MP_ROM_PTR(&machine_soft_reset_obj) }, - { MP_ROM_QSTR(MP_QSTR_unique_id), MP_ROM_PTR(&machine_unique_id_obj) }, - { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, - { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, - { MP_ROM_QSTR(MP_QSTR_deepsleep), MP_ROM_PTR(&machine_deepsleep_obj) }, - { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, - - { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, - { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, - - #if MICROPY_PY_MACHINE_BITSTREAM - { MP_ROM_QSTR(MP_QSTR_bitstream), MP_ROM_PTR(&machine_bitstream_obj) }, - #endif - #if MICROPY_PY_MACHINE_PULSE - { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, - #endif - { MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) }, - - { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, - { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, - #if MICROPY_HW_ENABLE_SDCARD - { MP_ROM_QSTR(MP_QSTR_SDCard), MP_ROM_PTR(&machine_sdcard_type) }, - #endif - - // wake abilities - { MP_ROM_QSTR(MP_QSTR_SLEEP), MP_ROM_INT(MACHINE_WAKE_SLEEP) }, - { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP), MP_ROM_INT(MACHINE_WAKE_DEEPSLEEP) }, - { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, - { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, - #if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - { MP_ROM_QSTR(MP_QSTR_TouchPad), MP_ROM_PTR(&machine_touchpad_type) }, - #endif - { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, - { MP_ROM_QSTR(MP_QSTR_ADCBlock), MP_ROM_PTR(&machine_adcblock_type) }, - #if MICROPY_PY_MACHINE_DAC - { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, - #endif - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, - { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, - #if MICROPY_PY_MACHINE_I2S - { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) }, - #endif - { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, - { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, - { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, - { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, - - // Reset reasons - { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, - { MP_ROM_QSTR(MP_QSTR_HARD_RESET), MP_ROM_INT(MP_HARD_RESET) }, - { MP_ROM_QSTR(MP_QSTR_PWRON_RESET), MP_ROM_INT(MP_PWRON_RESET) }, - { MP_ROM_QSTR(MP_QSTR_WDT_RESET), MP_ROM_INT(MP_WDT_RESET) }, - { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP_RESET), MP_ROM_INT(MP_DEEPSLEEP_RESET) }, - { MP_ROM_QSTR(MP_QSTR_SOFT_RESET), MP_ROM_INT(MP_SOFT_RESET) }, - - // Wake reasons - { MP_ROM_QSTR(MP_QSTR_wake_reason), MP_ROM_PTR(&machine_wake_reason_obj) }, - { MP_ROM_QSTR(MP_QSTR_PIN_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT0) }, - { MP_ROM_QSTR(MP_QSTR_EXT0_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT0) }, - { MP_ROM_QSTR(MP_QSTR_EXT1_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_EXT1) }, - { MP_ROM_QSTR(MP_QSTR_TIMER_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_TIMER) }, - { MP_ROM_QSTR(MP_QSTR_TOUCHPAD_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_TOUCHPAD) }, - { MP_ROM_QSTR(MP_QSTR_ULP_WAKE), MP_ROM_INT(ESP_SLEEP_WAKEUP_ULP) }, -}; - -STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); - -const mp_obj_module_t mp_module_machine = { - .base = { &mp_type_module }, - .globals = (mp_obj_dict_t *)&machine_module_globals, -}; - -MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_machine, mp_module_machine); - -#endif // MICROPY_PY_MACHINE diff --git a/tulip/esp32s3/modmachine.h b/tulip/esp32s3/modmachine.h deleted file mode 100644 index 138a89e9c..000000000 --- a/tulip/esp32s3/modmachine.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef MICROPY_INCLUDED_ESP32_MODMACHINE_H -#define MICROPY_INCLUDED_ESP32_MODMACHINE_H - -#include "py/obj.h" - -typedef enum { - // MACHINE_WAKE_IDLE=0x01, - MACHINE_WAKE_SLEEP=0x02, - MACHINE_WAKE_DEEPSLEEP=0x04 -} wake_type_t; - -extern const mp_obj_type_t machine_timer_type; -extern const mp_obj_type_t machine_wdt_type; -extern const mp_obj_type_t machine_pin_type; -extern const mp_obj_type_t machine_touchpad_type; -extern const mp_obj_type_t machine_adc_type; -extern const mp_obj_type_t machine_adcblock_type; -extern const mp_obj_type_t machine_dac_type; -extern const mp_obj_type_t machine_i2c_type; -extern const mp_obj_type_t machine_spi_type; -extern const mp_obj_type_t machine_i2s_type; -extern const mp_obj_type_t machine_uart_type; -extern const mp_obj_type_t machine_rtc_type; -extern const mp_obj_type_t machine_sdcard_type; - -void machine_init(void); -void machine_deinit(void); -void machine_pins_init(void); -void machine_pins_deinit(void); -void machine_pwm_deinit_all(void); -// TODO: void machine_rmt_deinit_all(void); -void machine_timer_deinit_all(void); -void machine_i2s_init0(); - -#endif // MICROPY_INCLUDED_ESP32_MODMACHINE_H diff --git a/tulip/esp32s3/modnetwork.h b/tulip/esp32s3/modnetwork.h deleted file mode 100644 index b1b3fc368..000000000 --- a/tulip/esp32s3/modnetwork.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 "Eric Poulsen" - * - * 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. - */ -#ifndef MICROPY_INCLUDED_ESP32_MODNETWORK_H -#define MICROPY_INCLUDED_ESP32_MODNETWORK_H - -#include "esp_netif.h" - -enum { PHY_LAN8710, PHY_LAN8720, PHY_IP101, PHY_RTL8201, PHY_DP83848, PHY_KSZ8041, PHY_KSZ8081, PHY_KSZ8851SNL = 100, PHY_DM9051, PHY_W5500 }; -#define IS_SPI_PHY(NUM) (NUM >= 100) -enum { ETH_INITIALIZED, ETH_STARTED, ETH_STOPPED, ETH_CONNECTED, ETH_DISCONNECTED, ETH_GOT_IP }; - -// Cases similar to ESP8266 user_interface.h -// Error cases are referenced from wifi_err_reason_t in ESP-IDF -enum { - STAT_IDLE = 1000, - STAT_CONNECTING = 1001, - STAT_GOT_IP = 1010, -}; - -typedef struct _base_if_obj_t { - mp_obj_base_t base; - esp_interface_t if_id; - esp_netif_t *netif; -} base_if_obj_t; - -extern const mp_obj_type_t esp_network_wlan_type; - -MP_DECLARE_CONST_FUN_OBJ_0(esp_network_initialize_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_get_wlan_obj); -MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_get_lan_obj); -MP_DECLARE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_ifconfig_obj); -MP_DECLARE_CONST_FUN_OBJ_KW(esp_network_config_obj); -MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_phy_mode_obj); - -NORETURN void esp_exceptions_helper(esp_err_t e); - -static inline void esp_exceptions(esp_err_t e) { - if (e != ESP_OK) { - esp_exceptions_helper(e); - } -} - -void socket_events_deinit(void); -void esp_initialise_wifi(void); - -#endif diff --git a/tulip/esp32s3/modnetwork_globals.h b/tulip/esp32s3/modnetwork_globals.h deleted file mode 100644 index 7326d453b..000000000 --- a/tulip/esp32s3/modnetwork_globals.h +++ /dev/null @@ -1,70 +0,0 @@ -{ MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&esp_network_initialize_obj) }, - -#if MICROPY_PY_NETWORK_WLAN -{ MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&esp_network_wlan_type) }, -#endif - -#if MICROPY_PY_NETWORK_LAN -{ MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&esp_network_get_lan_obj) }, -#endif -{ MP_ROM_QSTR(MP_QSTR_PPP), MP_ROM_PTR(&esp_network_ppp_make_new_obj) }, -{ MP_ROM_QSTR(MP_QSTR_phy_mode), MP_ROM_PTR(&esp_network_phy_mode_obj) }, - -#if MICROPY_PY_NETWORK_WLAN -{ MP_ROM_QSTR(MP_QSTR_STA_IF), MP_ROM_INT(WIFI_IF_STA)}, -{ MP_ROM_QSTR(MP_QSTR_AP_IF), MP_ROM_INT(WIFI_IF_AP)}, - -{ MP_ROM_QSTR(MP_QSTR_MODE_11B), MP_ROM_INT(WIFI_PROTOCOL_11B) }, -{ MP_ROM_QSTR(MP_QSTR_MODE_11G), MP_ROM_INT(WIFI_PROTOCOL_11G) }, -{ MP_ROM_QSTR(MP_QSTR_MODE_11N), MP_ROM_INT(WIFI_PROTOCOL_11N) }, -{ MP_ROM_QSTR(MP_QSTR_MODE_LR), MP_ROM_INT(WIFI_PROTOCOL_LR) }, - -{ MP_ROM_QSTR(MP_QSTR_AUTH_OPEN), MP_ROM_INT(WIFI_AUTH_OPEN) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WEP), MP_ROM_INT(WIFI_AUTH_WEP) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WPA_PSK), MP_ROM_INT(WIFI_AUTH_WPA_PSK) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_PSK) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WPA_WPA2_PSK), MP_ROM_INT(WIFI_AUTH_WPA_WPA2_PSK) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_ENTERPRISE), MP_ROM_INT(WIFI_AUTH_WPA2_ENTERPRISE) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA3_PSK) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WPA2_WPA3_PSK), MP_ROM_INT(WIFI_AUTH_WPA2_WPA3_PSK) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_WAPI_PSK), MP_ROM_INT(WIFI_AUTH_WAPI_PSK) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_OWE), MP_ROM_INT(WIFI_AUTH_OWE) }, -{ MP_ROM_QSTR(MP_QSTR_AUTH_MAX), MP_ROM_INT(WIFI_AUTH_MAX) }, -#endif - -#if MICROPY_PY_NETWORK_LAN -{ MP_ROM_QSTR(MP_QSTR_PHY_LAN8710), MP_ROM_INT(PHY_LAN8710) }, -{ MP_ROM_QSTR(MP_QSTR_PHY_LAN8720), MP_ROM_INT(PHY_LAN8720) }, -{ MP_ROM_QSTR(MP_QSTR_PHY_IP101), MP_ROM_INT(PHY_IP101) }, -{ MP_ROM_QSTR(MP_QSTR_PHY_RTL8201), MP_ROM_INT(PHY_RTL8201) }, -{ MP_ROM_QSTR(MP_QSTR_PHY_DP83848), MP_ROM_INT(PHY_DP83848) }, -{ MP_ROM_QSTR(MP_QSTR_PHY_KSZ8041), MP_ROM_INT(PHY_KSZ8041) }, -{ MP_ROM_QSTR(MP_QSTR_PHY_KSZ8081), MP_ROM_INT(PHY_KSZ8081) }, - -#if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL -{ MP_ROM_QSTR(MP_QSTR_PHY_KSZ8851SNL), MP_ROM_INT(PHY_KSZ8851SNL) }, -#endif -#if CONFIG_ETH_SPI_ETHERNET_DM9051 -{ MP_ROM_QSTR(MP_QSTR_PHY_DM9051), MP_ROM_INT(PHY_DM9051) }, -#endif -#if CONFIG_ETH_SPI_ETHERNET_W5500 -{ MP_ROM_QSTR(MP_QSTR_PHY_W5500), MP_ROM_INT(PHY_W5500) }, -#endif - -{ MP_ROM_QSTR(MP_QSTR_ETH_INITIALIZED), MP_ROM_INT(ETH_INITIALIZED)}, -{ MP_ROM_QSTR(MP_QSTR_ETH_STARTED), MP_ROM_INT(ETH_STARTED)}, -{ MP_ROM_QSTR(MP_QSTR_ETH_STOPPED), MP_ROM_INT(ETH_STOPPED)}, -{ MP_ROM_QSTR(MP_QSTR_ETH_CONNECTED), MP_ROM_INT(ETH_CONNECTED)}, -{ MP_ROM_QSTR(MP_QSTR_ETH_DISCONNECTED), MP_ROM_INT(ETH_DISCONNECTED)}, -{ MP_ROM_QSTR(MP_QSTR_ETH_GOT_IP), MP_ROM_INT(ETH_GOT_IP)}, -#endif - -{ MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(STAT_IDLE)}, -{ MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(STAT_CONNECTING)}, -{ MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(STAT_GOT_IP)}, -// Errors from the ESP-IDF -{ MP_ROM_QSTR(MP_QSTR_STAT_NO_AP_FOUND), MP_ROM_INT(WIFI_REASON_NO_AP_FOUND)}, -{ MP_ROM_QSTR(MP_QSTR_STAT_WRONG_PASSWORD), MP_ROM_INT(WIFI_REASON_AUTH_FAIL)}, -{ MP_ROM_QSTR(MP_QSTR_STAT_BEACON_TIMEOUT), MP_ROM_INT(WIFI_REASON_BEACON_TIMEOUT)}, -{ MP_ROM_QSTR(MP_QSTR_STAT_ASSOC_FAIL), MP_ROM_INT(WIFI_REASON_ASSOC_FAIL)}, -{ MP_ROM_QSTR(MP_QSTR_STAT_HANDSHAKE_TIMEOUT), MP_ROM_INT(WIFI_REASON_HANDSHAKE_TIMEOUT)}, diff --git a/tulip/esp32s3/modos.c b/tulip/esp32s3/modos.c deleted file mode 100644 index 287f1d990..000000000 --- a/tulip/esp32s3/modos.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2015 Josef Gajdusek - * Copyright (c) 2016 Damien P. George - * - * 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. - */ - -#include "esp_system.h" - -#include "py/runtime.h" -#include "py/mphal.h" -#include "extmod/misc.h" - -STATIC mp_obj_t mp_os_urandom(mp_obj_t num) { - mp_int_t n = mp_obj_get_int(num); - vstr_t vstr; - vstr_init_len(&vstr, n); - uint32_t r = 0; - for (int i = 0; i < n; i++) { - if ((i & 3) == 0) { - r = esp_random(); // returns 32-bit hardware random number - } - vstr.buf[i] = r; - r >>= 8; - } - return mp_obj_new_bytes_from_vstr(&vstr); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_os_urandom_obj, mp_os_urandom); - -#if MICROPY_PY_OS_DUPTERM_NOTIFY -STATIC mp_obj_t mp_os_dupterm_notify(mp_obj_t obj_in) { - (void)obj_in; - for (;;) { - int c = mp_os_dupterm_rx_chr(); - if (c < 0) { - break; - } - ringbuf_put(&stdin_ringbuf, c); - } - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_os_dupterm_notify_obj, mp_os_dupterm_notify); -#endif diff --git a/tulip/esp32s3/modsocket.c b/tulip/esp32s3/modsocket.c index 7484fce78..934b54596 100644 --- a/tulip/esp32s3/modsocket.c +++ b/tulip/esp32s3/modsocket.c @@ -36,6 +36,7 @@ #include #include +#include "py/gc.h" #include "py/runtime0.h" #include "py/nlr.h" #include "py/objlist.h" @@ -58,6 +59,10 @@ #define MDNS_QUERY_TIMEOUT_MS (5000) #define MDNS_LOCAL_SUFFIX ".local" +#ifndef NO_QSTR +#include "mdns.h" +#endif + enum { SOCKET_STATE_NEW, SOCKET_STATE_CONNECTED, @@ -78,6 +83,8 @@ typedef struct _socket_obj_t { #endif } socket_obj_t; +static const char *TAG = "modsocket"; + void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms); #if MICROPY_PY_SOCKET_EVENTS @@ -86,21 +93,21 @@ void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms); // This divisor is used to reduce the load on the system, so it doesn't poll sockets too often #define USOCKET_EVENTS_DIVISOR (8) -STATIC uint8_t socket_events_divisor; -STATIC socket_obj_t *socket_events_head; +static uint8_t socket_events_divisor; +static socket_obj_t *socket_events_head; void socket_events_deinit(void) { socket_events_head = NULL; } // Assumes the socket is not already in the linked list, and adds it -STATIC void socket_events_add(socket_obj_t *sock) { +static void socket_events_add(socket_obj_t *sock) { sock->events_next = socket_events_head; socket_events_head = sock; } // Assumes the socket is already in the linked list, and removes it -STATIC void socket_events_remove(socket_obj_t *sock) { +static void socket_events_remove(socket_obj_t *sock) { for (socket_obj_t **s = &socket_events_head;; s = &(*s)->events_next) { if (*s == sock) { *s = (*s)->events_next; @@ -149,64 +156,67 @@ static inline void check_for_exceptions(void) { mp_handle_pending(true); } -// This function mimics lwip_getaddrinfo, with added support for mDNS queries -static int _socket_getaddrinfo3(const char *nodename, const char *servname, +#if MICROPY_HW_ENABLE_MDNS_QUERIES +// This function mimics lwip_getaddrinfo, but makes an mDNS query +static int mdns_getaddrinfo(const char *host_str, const char *port_str, const struct addrinfo *hints, struct addrinfo **res) { - - #if MICROPY_HW_ENABLE_MDNS_QUERIES - int nodename_len = strlen(nodename); + int host_len = strlen(host_str); const int local_len = sizeof(MDNS_LOCAL_SUFFIX) - 1; - if (nodename_len > local_len - && strcasecmp(nodename + nodename_len - local_len, MDNS_LOCAL_SUFFIX) == 0) { - // mDNS query - char nodename_no_local[nodename_len - local_len + 1]; - memcpy(nodename_no_local, nodename, nodename_len - local_len); - nodename_no_local[nodename_len - local_len] = '\0'; - - esp_ip4_addr_t addr = {0}; - - esp_err_t err = mdns_query_a(nodename_no_local, MDNS_QUERY_TIMEOUT_MS, &addr); - if (err != ESP_OK) { - if (err == ESP_ERR_NOT_FOUND) { - *res = NULL; - return 0; - } + if (host_len <= local_len || + strcasecmp(host_str + host_len - local_len, MDNS_LOCAL_SUFFIX) != 0) { + return 0; + } + + // mDNS query + char host_no_local[host_len - local_len + 1]; + memcpy(host_no_local, host_str, host_len - local_len); + host_no_local[host_len - local_len] = '\0'; + + esp_ip4_addr_t addr = {0}; + + esp_err_t err = mdns_query_a(host_no_local, MDNS_QUERY_TIMEOUT_MS, &addr); + if (err != ESP_OK) { + if (err == ESP_ERR_NOT_FOUND) { *res = NULL; - return err; + return 0; } + *res = NULL; + return err; + } - struct addrinfo *ai = memp_malloc(MEMP_NETDB); - if (ai == NULL) { - *res = NULL; - return EAI_MEMORY; - } - memset(ai, 0, sizeof(struct addrinfo) + sizeof(struct sockaddr_storage)); - - struct sockaddr_in *sa = (struct sockaddr_in *)((uint8_t *)ai + sizeof(struct addrinfo)); - inet_addr_from_ip4addr(&sa->sin_addr, &addr); - sa->sin_family = AF_INET; - sa->sin_len = sizeof(struct sockaddr_in); - sa->sin_port = lwip_htons((u16_t)atoi(servname)); - ai->ai_family = AF_INET; - ai->ai_canonname = ((char *)sa + sizeof(struct sockaddr_storage)); - memcpy(ai->ai_canonname, nodename, nodename_len + 1); - ai->ai_addrlen = sizeof(struct sockaddr_storage); - ai->ai_addr = (struct sockaddr *)sa; - - *res = ai; - return 0; + struct addrinfo *ai = memp_malloc(MEMP_NETDB); + if (ai == NULL) { + *res = NULL; + return EAI_MEMORY; } - #endif + memset(ai, 0, sizeof(struct addrinfo) + sizeof(struct sockaddr_storage)); - // Normal query - return lwip_getaddrinfo(nodename, servname, hints, res); + struct sockaddr_in *sa = (struct sockaddr_in *)((uint8_t *)ai + sizeof(struct addrinfo)); + inet_addr_from_ip4addr(&sa->sin_addr, &addr); + sa->sin_family = AF_INET; + sa->sin_len = sizeof(struct sockaddr_in); + sa->sin_port = lwip_htons((u16_t)atoi(port_str)); + ai->ai_family = AF_INET; + ai->ai_canonname = ((char *)sa + sizeof(struct sockaddr_storage)); + memcpy(ai->ai_canonname, host_str, host_len + 1); + ai->ai_addrlen = sizeof(struct sockaddr_storage); + ai->ai_addr = (struct sockaddr *)sa; + ai->ai_socktype = SOCK_STREAM; + if (hints) { + ai->ai_socktype = hints->ai_socktype; + ai->ai_protocol = hints->ai_protocol; + } + + *res = ai; + return 0; } +#endif // MICROPY_HW_ENABLE_MDNS_QUERIES -static int _socket_getaddrinfo2(const mp_obj_t host, const mp_obj_t portx, struct addrinfo **resp) { - const struct addrinfo hints = { - .ai_family = AF_INET, - .ai_socktype = SOCK_STREAM, - }; +static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, + const struct addrinfo *hints, struct addrinfo **res) { + int retval = 0; + + *res = NULL; mp_obj_t port = portx; if (mp_obj_is_integer(port)) { @@ -224,34 +234,44 @@ static int _socket_getaddrinfo2(const mp_obj_t host, const mp_obj_t portx, struc } MP_THREAD_GIL_EXIT(); - int res = _socket_getaddrinfo3(host_str, port_str, &hints, resp); + + #if MICROPY_HW_ENABLE_MDNS_QUERIES + retval = mdns_getaddrinfo(host_str, port_str, hints, res); + #endif + + if (retval == 0 && *res == NULL) { + // Normal query + retval = lwip_getaddrinfo(host_str, port_str, hints, res); + } + MP_THREAD_GIL_ENTER(); // Per docs: instead of raising gaierror getaddrinfo raises negative error number - if (res != 0) { - mp_raise_OSError(res > 0 ? -res : res); + if (retval != 0) { + mp_raise_OSError(retval > 0 ? -retval : retval); } // Somehow LwIP returns a resolution of 0.0.0.0 for failed lookups, traced it as far back // as netconn_gethostbyname_addrtype returning OK instead of error. - if (*resp == NULL || - (strcmp(resp[0]->ai_canonname, "0.0.0.0") == 0 && strcmp(host_str, "0.0.0.0") != 0)) { + + if (*res == NULL) { //} || + //(strcmp(res[0]->ai_canonname, "0.0.0.0") == 0 && strcmp(host_str, "0.0.0.0") != 0)) { + lwip_freeaddrinfo(*res); mp_raise_OSError(-2); // name or service not known } - return res; + assert(retval == 0 && *res != NULL); } -STATIC void _socket_getaddrinfo(const mp_obj_t addrtuple, struct addrinfo **resp) { +static void _socket_getaddrinfo(const mp_obj_t addrtuple, struct addrinfo **resp) { mp_obj_t *elem; mp_obj_get_array_fixed_n(addrtuple, 2, &elem); - _socket_getaddrinfo2(elem[0], elem[1], resp); + _getaddrinfo_inner(elem[0], elem[1], NULL, resp); } -STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { +static mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 0, 3, false); - socket_obj_t *sock = m_new_obj_with_finaliser(socket_obj_t); - sock->base.type = type_in; + socket_obj_t *sock = mp_obj_malloc_with_finaliser(socket_obj_t, type_in); sock->domain = AF_INET; sock->type = SOCK_STREAM; sock->proto = 0; @@ -268,6 +288,13 @@ STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, siz sock->state = sock->type == SOCK_STREAM ? SOCKET_STATE_NEW : SOCKET_STATE_CONNECTED; sock->fd = lwip_socket(sock->domain, sock->type, sock->proto); + if (sock->fd < 0 && errno == ENFILE) { + // ESP32 LWIP has a hard socket limit, ENFILE is returned when this is + // reached. Similar to the logic elsewhere for MemoryError, try running + // GC before failing outright. + gc_collect(); + sock->fd = lwip_socket(sock->domain, sock->type, sock->proto); + } if (sock->fd < 0) { mp_raise_OSError(errno); } @@ -276,7 +303,7 @@ STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, siz return MP_OBJ_FROM_PTR(sock); } -STATIC mp_obj_t socket_bind(const mp_obj_t arg0, const mp_obj_t arg1) { +static mp_obj_t socket_bind(const mp_obj_t arg0, const mp_obj_t arg1) { socket_obj_t *self = MP_OBJ_TO_PTR(arg0); struct addrinfo *res; _socket_getaddrinfo(arg1, &res); @@ -288,10 +315,10 @@ STATIC mp_obj_t socket_bind(const mp_obj_t arg0, const mp_obj_t arg1) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_bind_obj, socket_bind); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_bind_obj, socket_bind); // method socket.listen([backlog]) -STATIC mp_obj_t socket_listen(size_t n_args, const mp_obj_t *args) { +static mp_obj_t socket_listen(size_t n_args, const mp_obj_t *args) { socket_obj_t *self = MP_OBJ_TO_PTR(args[0]); int backlog = MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT; @@ -307,9 +334,9 @@ STATIC mp_obj_t socket_listen(size_t n_args, const mp_obj_t *args) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_listen_obj, 1, 2, socket_listen); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_listen_obj, 1, 2, socket_listen); -STATIC mp_obj_t socket_accept(const mp_obj_t arg0) { +static mp_obj_t socket_accept(const mp_obj_t arg0) { socket_obj_t *self = MP_OBJ_TO_PTR(arg0); struct sockaddr addr; @@ -337,8 +364,7 @@ STATIC mp_obj_t socket_accept(const mp_obj_t arg0) { } // create new socket object - socket_obj_t *sock = m_new_obj_with_finaliser(socket_obj_t); - sock->base.type = self->base.type; + socket_obj_t *sock = mp_obj_malloc_with_finaliser(socket_obj_t, self->base.type); sock->fd = new_fd; sock->domain = self->domain; sock->type = self->type; @@ -355,26 +381,106 @@ STATIC mp_obj_t socket_accept(const mp_obj_t arg0) { return client; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_accept_obj, socket_accept); +static MP_DEFINE_CONST_FUN_OBJ_1(socket_accept_obj, socket_accept); -STATIC mp_obj_t socket_connect(const mp_obj_t arg0, const mp_obj_t arg1) { +static mp_obj_t socket_connect(const mp_obj_t arg0, const mp_obj_t arg1) { socket_obj_t *self = MP_OBJ_TO_PTR(arg0); struct addrinfo *res; + bool blocking = false; + int flags; + int raise_err = 0; + _socket_getaddrinfo(arg1, &res); MP_THREAD_GIL_EXIT(); self->state = SOCKET_STATE_CONNECTED; - int r = lwip_connect(self->fd, res->ai_addr, res->ai_addrlen); - MP_THREAD_GIL_ENTER(); + + flags = fcntl(self->fd, F_GETFL); + + blocking = (flags & O_NONBLOCK) == 0; + + if (blocking) { + // For blocking sockets, make the socket temporarily non-blocking and emulate + // blocking using select. + // + // This has two benefits: + // + // - Allows handling external exceptions while waiting for connect. + // + // - Allows emulating a connect timeout, which is not supported by LWIP or + // required by POSIX but is normal behaviour for CPython. + if (fcntl(self->fd, F_SETFL, flags | O_NONBLOCK) < 0) { + ESP_LOGE(TAG, "fcntl set failed %d", errno); // Unexpected internal failure + raise_err = errno; + } + } + + if (raise_err == 0) { + // Try performing the actual connect. Expected to always return immediately. + int r = lwip_connect(self->fd, res->ai_addr, res->ai_addrlen); + if (r != 0) { + raise_err = errno; + } + } + + if (blocking) { + // Set the socket back to blocking. We can still pass it to select() in this state. + int r = fcntl(self->fd, F_SETFL, flags); + if (r != 0 && (raise_err == 0 || raise_err == EINPROGRESS)) { + ESP_LOGE(TAG, "fcntl restore failed %d", errno); // Unexpected internal failure + raise_err = errno; + } + } + lwip_freeaddrinfo(res); - if (r != 0) { - mp_raise_OSError(errno); + + if (blocking && raise_err == EINPROGRESS) { + // Keep calling select() until the socket is marked writable (i.e. connected), + // or an error or a timeout occurs + + // Note: _socket_settimeout() always sets self->retries != 0 on blocking sockets. + + for (unsigned int i = 0; i <= self->retries; i++) { + struct timeval timeout = { + .tv_sec = 0, + .tv_usec = SOCKET_POLL_US, + }; + fd_set wfds; + FD_ZERO(&wfds); + FD_SET(self->fd, &wfds); + + int r = select(self->fd + 1, NULL, &wfds, NULL, &timeout); + if (r < 0) { + // Error condition + raise_err = errno; + break; + } else if (r > 0) { + // Select indicated the socket is writable. Check for any error. + socklen_t socklen = sizeof(raise_err); + r = getsockopt(self->fd, SOL_SOCKET, SO_ERROR, &raise_err, &socklen); + if (r < 0) { + raise_err = errno; + } + break; + } else { + // Select timed out + raise_err = ETIMEDOUT; + + MP_THREAD_GIL_ENTER(); + check_for_exceptions(); + MP_THREAD_GIL_EXIT(); + } + } } + MP_THREAD_GIL_ENTER(); + if (raise_err) { + mp_raise_OSError(raise_err); + } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_connect_obj, socket_connect); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_connect_obj, socket_connect); -STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { +static mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { (void)n_args; // always 4 socket_obj_t *self = MP_OBJ_TO_PTR(args[0]); @@ -382,7 +488,8 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { switch (opt) { // level: SOL_SOCKET - case SO_REUSEADDR: { + case SO_REUSEADDR: + case SO_BROADCAST: { int val = mp_obj_get_int(args[3]); int ret = lwip_setsockopt(self->fd, SOL_SOCKET, opt, &val, sizeof(int)); if (ret != 0) { @@ -391,6 +498,18 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { break; } + case SO_BINDTODEVICE: { + size_t len; + const char *val = mp_obj_str_get_data(args[3], &len); + char ifname[NETIF_NAMESIZE] = {0}; + memcpy(&ifname, val, len); + int ret = lwip_setsockopt(self->fd, SOL_SOCKET, opt, &ifname, NETIF_NAMESIZE); + if (ret != 0) { + mp_raise_OSError(errno); + } + break; + } + #if MICROPY_PY_SOCKET_EVENTS // level: SOL_SOCKET // special "register callback" option @@ -410,6 +529,16 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { } #endif + // level: IPPROTO_TCP + case TCP_NODELAY: { + int val = mp_obj_get_int(args[3]); + int ret = lwip_setsockopt(self->fd, IPPROTO_TCP, opt, &val, sizeof(int)); + if (ret != 0) { + mp_raise_OSError(errno); + } + break; + } + // level: IPPROTO_IP case IP_ADD_MEMBERSHIP: { mp_buffer_info_t bufinfo; @@ -432,7 +561,7 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) { return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_setsockopt_obj, 4, 4, socket_setsockopt); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_setsockopt_obj, 4, 4, socket_setsockopt); void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms) { // Rather than waiting for the entire timeout specified, we wait sock->retries times @@ -450,7 +579,7 @@ void _socket_settimeout(socket_obj_t *sock, uint64_t timeout_ms) { lwip_fcntl(sock->fd, F_SETFL, timeout_ms ? 0 : O_NONBLOCK); } -STATIC mp_obj_t socket_settimeout(const mp_obj_t arg0, const mp_obj_t arg1) { +static mp_obj_t socket_settimeout(const mp_obj_t arg0, const mp_obj_t arg1) { socket_obj_t *self = MP_OBJ_TO_PTR(arg0); if (arg1 == mp_const_none) { _socket_settimeout(self, UINT64_MAX); @@ -463,9 +592,9 @@ STATIC mp_obj_t socket_settimeout(const mp_obj_t arg0, const mp_obj_t arg1) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_settimeout_obj, socket_settimeout); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_settimeout_obj, socket_settimeout); -STATIC mp_obj_t socket_setblocking(const mp_obj_t arg0, const mp_obj_t arg1) { +static mp_obj_t socket_setblocking(const mp_obj_t arg0, const mp_obj_t arg1) { socket_obj_t *self = MP_OBJ_TO_PTR(arg0); if (mp_obj_is_true(arg1)) { _socket_settimeout(self, UINT64_MAX); @@ -474,12 +603,12 @@ STATIC mp_obj_t socket_setblocking(const mp_obj_t arg0, const mp_obj_t arg1) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_setblocking_obj, socket_setblocking); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_setblocking_obj, socket_setblocking); // XXX this can end up waiting a very long time if the content is dribbled in one character // at a time, as the timeout resets each time a recvfrom succeeds ... this is probably not // good behaviour. -STATIC mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, +static mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, struct sockaddr *from, socklen_t *from_len, int *errcode) { socket_obj_t *sock = MP_OBJ_TO_PTR(self_in); @@ -550,12 +679,12 @@ mp_obj_t _socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in, return mp_obj_new_bytes_from_vstr(&vstr); } -STATIC mp_obj_t socket_recv(mp_obj_t self_in, mp_obj_t len_in) { +static mp_obj_t socket_recv(mp_obj_t self_in, mp_obj_t len_in) { return _socket_recvfrom(self_in, len_in, NULL, NULL); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_recv_obj, socket_recv); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_recv_obj, socket_recv); -STATIC mp_obj_t socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { +static mp_obj_t socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { struct sockaddr from; socklen_t fromlen = sizeof(from); @@ -568,7 +697,7 @@ STATIC mp_obj_t socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { return mp_obj_new_tuple(2, tuple); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_recvfrom_obj, socket_recvfrom); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_recvfrom_obj, socket_recvfrom); int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) { int sentlen = 0; @@ -591,16 +720,16 @@ int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) { return sentlen; } -STATIC mp_obj_t socket_send(const mp_obj_t arg0, const mp_obj_t arg1) { +static mp_obj_t socket_send(const mp_obj_t arg0, const mp_obj_t arg1) { socket_obj_t *sock = MP_OBJ_TO_PTR(arg0); mp_buffer_info_t bufinfo; mp_get_buffer_raise(arg1, &bufinfo, MP_BUFFER_READ); int r = _socket_send(sock, bufinfo.buf, bufinfo.len); return mp_obj_new_int(r); } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_send_obj, socket_send); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_send_obj, socket_send); -STATIC mp_obj_t socket_sendall(const mp_obj_t arg0, const mp_obj_t arg1) { +static mp_obj_t socket_sendall(const mp_obj_t arg0, const mp_obj_t arg1) { // XXX behaviour when nonblocking (see extmod/modlwip.c) // XXX also timeout behaviour. socket_obj_t *sock = MP_OBJ_TO_PTR(arg0); @@ -612,9 +741,9 @@ STATIC mp_obj_t socket_sendall(const mp_obj_t arg0, const mp_obj_t arg1) { } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(socket_sendall_obj, socket_sendall); +static MP_DEFINE_CONST_FUN_OBJ_2(socket_sendall_obj, socket_sendall); -STATIC mp_obj_t socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_in) { +static mp_obj_t socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_in) { socket_obj_t *self = MP_OBJ_TO_PTR(self_in); // get the buffer to send @@ -642,25 +771,25 @@ STATIC mp_obj_t socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_ } mp_raise_OSError(MP_ETIMEDOUT); } -STATIC MP_DEFINE_CONST_FUN_OBJ_3(socket_sendto_obj, socket_sendto); +static MP_DEFINE_CONST_FUN_OBJ_3(socket_sendto_obj, socket_sendto); -STATIC mp_obj_t socket_fileno(const mp_obj_t arg0) { +static mp_obj_t socket_fileno(const mp_obj_t arg0) { socket_obj_t *self = MP_OBJ_TO_PTR(arg0); return mp_obj_new_int(self->fd); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(socket_fileno_obj, socket_fileno); +static MP_DEFINE_CONST_FUN_OBJ_1(socket_fileno_obj, socket_fileno); -STATIC mp_obj_t socket_makefile(size_t n_args, const mp_obj_t *args) { +static mp_obj_t socket_makefile(size_t n_args, const mp_obj_t *args) { (void)n_args; return args[0]; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_makefile_obj, 1, 3, socket_makefile); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_makefile_obj, 1, 3, socket_makefile); -STATIC mp_uint_t socket_stream_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { +static mp_uint_t socket_stream_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { return _socket_read_data(self_in, buf, size, NULL, NULL, errcode); } -STATIC mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { +static mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { socket_obj_t *sock = self_in; for (int i = 0; i <= sock->retries; i++) { MP_THREAD_GIL_EXIT(); @@ -680,7 +809,7 @@ STATIC mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_ return MP_STREAM_ERROR; } -STATIC mp_uint_t socket_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { +static mp_uint_t socket_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, int *errcode) { socket_obj_t *socket = self_in; if (request == MP_STREAM_POLL) { if (socket->fd == -1) { @@ -749,7 +878,7 @@ STATIC mp_uint_t socket_stream_ioctl(mp_obj_t self_in, mp_uint_t request, uintpt return MP_STREAM_ERROR; } -STATIC const mp_rom_map_elem_t socket_locals_dict_table[] = { +static const mp_rom_map_elem_t socket_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) }, { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, { MP_ROM_QSTR(MP_QSTR_bind), MP_ROM_PTR(&socket_bind_obj) }, @@ -772,15 +901,15 @@ STATIC const mp_rom_map_elem_t socket_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, }; -STATIC MP_DEFINE_CONST_DICT(socket_locals_dict, socket_locals_dict_table); +static MP_DEFINE_CONST_DICT(socket_locals_dict, socket_locals_dict_table); -STATIC const mp_stream_p_t socket_stream_p = { +static const mp_stream_p_t socket_stream_p = { .read = socket_stream_read, .write = socket_stream_write, .ioctl = socket_stream_ioctl }; -STATIC MP_DEFINE_CONST_OBJ_TYPE( +static MP_DEFINE_CONST_OBJ_TYPE( socket_type, MP_QSTR_socket, MP_TYPE_FLAG_NONE, @@ -789,11 +918,34 @@ STATIC MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &socket_locals_dict ); -STATIC mp_obj_t esp_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { - // TODO support additional args beyond the first two - +static mp_obj_t esp_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { + struct addrinfo hints = { }; struct addrinfo *res = NULL; - _socket_getaddrinfo2(args[0], args[1], &res); + + // Optional args: family=0, type=0, proto=0, flags=0, where 0 is "least narrow" + if (n_args > 2) { + hints.ai_family = mp_obj_get_int(args[2]); + } + if (n_args > 3) { + hints.ai_socktype = mp_obj_get_int(args[3]); + } + if (hints.ai_socktype == 0) { + // This is slightly different to CPython with POSIX getaddrinfo. In + // CPython, calling socket.getaddrinfo() with socktype=0 returns any/all + // supported SocketKind values. Here, lwip_getaddrinfo() will echo + // whatever socktype was supplied to the caller. Rather than returning 0 + // (invalid in a result), make it SOCK_STREAM. + hints.ai_socktype = SOCK_STREAM; + } + if (n_args > 4) { + hints.ai_protocol = mp_obj_get_int(args[4]); + } + if (n_args > 5) { + hints.ai_flags = mp_obj_get_int(args[5]); + } + hints.ai_flags = hints.ai_flags | AI_CANONNAME; + + _getaddrinfo_inner(args[0], args[1], &hints, &res); mp_obj_t ret_list = mp_obj_new_list(0, NULL); for (struct addrinfo *resi = res; resi; resi = resi->ai_next) { @@ -801,7 +953,7 @@ STATIC mp_obj_t esp_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { mp_obj_new_int(resi->ai_family), mp_obj_new_int(resi->ai_socktype), mp_obj_new_int(resi->ai_protocol), - mp_obj_new_str(resi->ai_canonname, strlen(resi->ai_canonname)), + mp_obj_new_str_from_cstr(resi->ai_canonname), mp_const_none }; @@ -812,7 +964,7 @@ STATIC mp_obj_t esp_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { char buf[16]; ip4addr_ntoa_r(&ip4_addr, buf, sizeof(buf)); mp_obj_t inaddr_objs[2] = { - mp_obj_new_str(buf, strlen(buf)), + mp_obj_new_str_from_cstr(buf), mp_obj_new_int(ntohs(addr->sin_port)) }; addrinfo_objs[4] = mp_obj_new_tuple(2, inaddr_objs); @@ -820,25 +972,23 @@ STATIC mp_obj_t esp_socket_getaddrinfo(size_t n_args, const mp_obj_t *args) { mp_obj_list_append(ret_list, mp_obj_new_tuple(5, addrinfo_objs)); } - if (res) { - lwip_freeaddrinfo(res); - } + lwip_freeaddrinfo(res); return ret_list; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_socket_getaddrinfo_obj, 2, 6, esp_socket_getaddrinfo); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_socket_getaddrinfo_obj, 2, 6, esp_socket_getaddrinfo); -STATIC mp_obj_t esp_socket_initialize() { +static mp_obj_t esp_socket_initialize() { static int initialized = 0; if (!initialized) { - ESP_LOGI("modsocket", "Initializing"); + ESP_LOGI(TAG, "Initializing"); esp_netif_init(); initialized = 1; } return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(esp_socket_initialize_obj, esp_socket_initialize); +static MP_DEFINE_CONST_FUN_OBJ_0(esp_socket_initialize_obj, esp_socket_initialize); -STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = { +static const mp_rom_map_elem_t mp_module_socket_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_socket) }, { MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&esp_socket_initialize_obj) }, { MP_ROM_QSTR(MP_QSTR_socket), MP_ROM_PTR(&socket_type) }, @@ -854,10 +1004,13 @@ STATIC const mp_rom_map_elem_t mp_module_socket_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_IPPROTO_IP), MP_ROM_INT(IPPROTO_IP) }, { MP_ROM_QSTR(MP_QSTR_SOL_SOCKET), MP_ROM_INT(SOL_SOCKET) }, { MP_ROM_QSTR(MP_QSTR_SO_REUSEADDR), MP_ROM_INT(SO_REUSEADDR) }, + { MP_ROM_QSTR(MP_QSTR_SO_BROADCAST), MP_ROM_INT(SO_BROADCAST) }, + { MP_ROM_QSTR(MP_QSTR_SO_BINDTODEVICE), MP_ROM_INT(SO_BINDTODEVICE) }, { MP_ROM_QSTR(MP_QSTR_IP_ADD_MEMBERSHIP), MP_ROM_INT(IP_ADD_MEMBERSHIP) }, + { MP_ROM_QSTR(MP_QSTR_TCP_NODELAY), MP_ROM_INT(TCP_NODELAY) }, }; -STATIC MP_DEFINE_CONST_DICT(mp_module_socket_globals, mp_module_socket_globals_table); +static MP_DEFINE_CONST_DICT(mp_module_socket_globals, mp_module_socket_globals_table); const mp_obj_module_t mp_module_socket = { .base = { &mp_type_module }, diff --git a/tulip/esp32s3/modtime.c b/tulip/esp32s3/modtime.c deleted file mode 100644 index 7a2b21508..000000000 --- a/tulip/esp32s3/modtime.c +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2016-2023 Damien P. George - * - * 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. - */ - -#include - -#include "py/obj.h" -#include "shared/timeutils/timeutils.h" - -// Return the localtime as an 8-tuple. -STATIC mp_obj_t mp_time_localtime_get(void) { - struct timeval tv; - gettimeofday(&tv, NULL); - timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(tv.tv_sec, &tm); - mp_obj_t tuple[8] = { - tuple[0] = mp_obj_new_int(tm.tm_year), - tuple[1] = mp_obj_new_int(tm.tm_mon), - tuple[2] = mp_obj_new_int(tm.tm_mday), - tuple[3] = mp_obj_new_int(tm.tm_hour), - tuple[4] = mp_obj_new_int(tm.tm_min), - tuple[5] = mp_obj_new_int(tm.tm_sec), - tuple[6] = mp_obj_new_int(tm.tm_wday), - tuple[7] = mp_obj_new_int(tm.tm_yday), - }; - return mp_obj_new_tuple(8, tuple); -} - -// Return the number of seconds since the Epoch. -STATIC mp_obj_t mp_time_time_get(void) { - struct timeval tv; - gettimeofday(&tv, NULL); - return mp_obj_new_int(tv.tv_sec); -} diff --git a/tulip/esp32s3/mpconfigport.h b/tulip/esp32s3/mpconfigport.h deleted file mode 100644 index d87111ea9..000000000 --- a/tulip/esp32s3/mpconfigport.h +++ /dev/null @@ -1,253 +0,0 @@ -// Options to control how MicroPython is built for this port, -// overriding defaults in py/mpconfig.h. - -// Board-specific definitions -#include "mpconfigboard.h" - -#include -#include -#include "esp_random.h" -#include "esp_system.h" -#include "freertos/FreeRTOS.h" -#include "driver/i2s.h" -#include "esp_wifi_types.h" - -#ifndef MICROPY_CONFIG_ROM_LEVEL -#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES) -#endif - -// object representation and NLR handling -#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A) -#define MICROPY_NLR_SETJMP (1) -#if CONFIG_IDF_TARGET_ESP32C3 -#define MICROPY_GCREGS_SETJMP (1) -#endif - -// memory allocation policies -#define MICROPY_ALLOC_PATH_MAX (128) - -// emitters -#define MICROPY_PERSISTENT_CODE_LOAD (1) -#if !CONFIG_IDF_TARGET_ESP32C3 -#define MICROPY_EMIT_XTENSAWIN (1) -#endif - -// workaround for xtensa-esp32-elf-gcc esp-2020r3, which can generate wrong code for loops -// see https://github.com/espressif/esp-idf/issues/9130 -// this was fixed in newer versions of the compiler by: -// "gas: use literals/const16 for xtensa loop relaxation" -// https://github.com/jcmvbkbc/binutils-gdb-xtensa/commit/403b0b61f6d4358aee8493cb1d11814e368942c9 -#define MICROPY_COMP_CONST_FOLDING_COMPILER_WORKAROUND (1) - -// optimisations -#define MICROPY_OPT_COMPUTED_GOTO (1) - -// Python internal features -#define MICROPY_READER_VFS (1) -#define MICROPY_ENABLE_GC (1) -#define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) -#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) -#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) -#define MICROPY_WARNINGS (1) -#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) -#define MICROPY_STREAMS_POSIX_API (1) -#define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys -#define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf -#define MICROPY_SCHEDULER_DEPTH (128) -#define MICROPY_VFS (1) - -// control over Python builtins -#define MICROPY_PY_STR_BYTES_CMP_WARN (1) -#define MICROPY_PY_ALL_INPLACE_SPECIAL_METHODS (1) -#define MICROPY_PY_BUILTINS_HELP_TEXT esp32_help_text -#define MICROPY_PY_IO_BUFFEREDWRITER (1) -#define MICROPY_PY_TIME_GMTIME_LOCALTIME_MKTIME (1) -#define MICROPY_PY_TIME_TIME_TIME_NS (1) -#define MICROPY_PY_TIME_INCLUDEFILE "ports/esp32/modtime.c" -#define MICROPY_PY_THREAD (1) -#define MICROPY_PY_THREAD_GIL (1) -#define MICROPY_PY_THREAD_GIL_VM_DIVISOR (32) - -// extended modules -#ifndef MICROPY_ESPNOW -#define MICROPY_ESPNOW (1) -#endif -#ifndef MICROPY_PY_BLUETOOTH -#define MICROPY_PY_BLUETOOTH (0) -#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS (0) -#define MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS_WITH_INTERLOCK (0) -#define MICROPY_PY_BLUETOOTH_SYNC_EVENT_STACK_SIZE (CONFIG_BT_NIMBLE_TASK_STACK_SIZE) -#define MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE (0) -#define MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING (0) -#define MICROPY_BLUETOOTH_NIMBLE (0) -#define MICROPY_BLUETOOTH_NIMBLE_BINDINGS_ONLY (0) -#endif -#define MICROPY_PY_HASHLIB_SHA1 (1) -#define MICROPY_PY_HASHLIB_SHA256 (1) -#define MICROPY_PY_CRYPTOLIB (1) -#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (esp_random()) -#define MICROPY_PY_OS_INCLUDEFILE "ports/esp32/modos.c" -#define MICROPY_PY_OS_DUPTERM (1) -#define MICROPY_PY_OS_DUPTERM_NOTIFY (1) -#define MICROPY_PY_OS_SYNC (1) -#define MICROPY_PY_OS_UNAME (1) -#define MICROPY_PY_OS_URANDOM (1) -#define MICROPY_PY_MACHINE (1) -#define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new -#define MICROPY_PY_MACHINE_BITSTREAM (1) -#define MICROPY_PY_MACHINE_PULSE (1) -#define MICROPY_PY_MACHINE_PWM (1) -#define MICROPY_PY_MACHINE_PWM_DUTY (1) -#define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/esp32/machine_pwm.c" -#define MICROPY_PY_MACHINE_I2C (1) -#define MICROPY_PY_MACHINE_I2C_TRANSFER_WRITE1 (1) -#define MICROPY_PY_MACHINE_SOFTI2C (1) -#define MICROPY_PY_MACHINE_SPI (1) -#define MICROPY_PY_MACHINE_SPI_MSB (0) -#define MICROPY_PY_MACHINE_SPI_LSB (1) -#define MICROPY_PY_MACHINE_SOFTSPI (1) -#ifndef MICROPY_PY_MACHINE_DAC -#define MICROPY_PY_MACHINE_DAC (1) -#endif -#ifndef MICROPY_PY_MACHINE_I2S -#define MICROPY_PY_MACHINE_I2S (0) -#endif -#define MICROPY_PY_NETWORK (1) -#ifndef MICROPY_PY_NETWORK_HOSTNAME_DEFAULT -#if CONFIG_IDF_TARGET_ESP32 -#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32" -#elif CONFIG_IDF_TARGET_ESP32S2 -#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32s2" -#elif CONFIG_IDF_TARGET_ESP32S3 -#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32s3" -#elif CONFIG_IDF_TARGET_ESP32C3 -#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "mpy-esp32c3" -#endif -#endif -#define MICROPY_PY_NETWORK_INCLUDEFILE "ports/esp32/modnetwork.h" -#define MICROPY_PY_NETWORK_MODULE_GLOBALS_INCLUDEFILE "ports/esp32/modnetwork_globals.h" -#ifndef MICROPY_PY_NETWORK_WLAN -#define MICROPY_PY_NETWORK_WLAN (1) -#endif -#ifndef MICROPY_HW_ENABLE_SDCARD -#define MICROPY_HW_ENABLE_SDCARD (1) -#endif -#define MICROPY_HW_SOFTSPI_MIN_DELAY (0) -#define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (esp_rom_get_cpu_ticks_per_us() * 1000000 / 200) // roughly -#define MICROPY_PY_SSL (1) -#define MICROPY_SSL_MBEDTLS (1) -#define MICROPY_PY_SSL_FINALISER (1) -#define MICROPY_PY_WEBSOCKET (1) -#define MICROPY_PY_WEBREPL (1) -#define MICROPY_PY_ONEWIRE (1) -#define MICROPY_PY_PLATFORM (1) -#define MICROPY_PY_SOCKET_EVENTS (MICROPY_PY_WEBREPL) -#define MICROPY_PY_BLUETOOTH_RANDOM_ADDR (1) -#define MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME ("ESP32") - -// fatfs configuration -#define MICROPY_FATFS_ENABLE_LFN (1) -#define MICROPY_FATFS_RPATH (2) -#define MICROPY_FATFS_MAX_SS (4096) -#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ - -#define MP_STATE_PORT MP_STATE_VM - -// type definitions for the specific machine - -#define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p))) -void *esp_native_code_commit(void *, size_t, void *); -#define MP_PLAT_COMMIT_EXEC(buf, len, reloc) esp_native_code_commit(buf, len, reloc) -#define MP_SSIZE_MAX (0x7fffffff) - -// Note: these "critical nested" macros do not ensure cross-CPU exclusion, -// the only disable interrupts on the current CPU. To full manage exclusion -// one should use portENTER_CRITICAL/portEXIT_CRITICAL instead. -#include "freertos/FreeRTOS.h" -#define MICROPY_BEGIN_ATOMIC_SECTION() portSET_INTERRUPT_MASK_FROM_ISR() -#define MICROPY_END_ATOMIC_SECTION(state) portCLEAR_INTERRUPT_MASK_FROM_ISR(state) - -#if MICROPY_PY_SOCKET_EVENTS -#define MICROPY_PY_SOCKET_EVENTS_HANDLER extern void socket_events_handler(void); socket_events_handler(); -#else -#define MICROPY_PY_SOCKET_EVENTS_HANDLER -#endif - -#if MICROPY_PY_THREAD -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ - MICROPY_PY_SOCKET_EVENTS_HANDLER \ - MP_THREAD_GIL_EXIT(); \ - ulTaskNotifyTake(pdFALSE, 1); \ - MP_THREAD_GIL_ENTER(); \ - } while (0); -#else -#define MICROPY_EVENT_POLL_HOOK \ - do { \ - extern void mp_handle_pending(bool); \ - mp_handle_pending(true); \ - MICROPY_PY_SOCKET_EVENTS_HANDLER \ - asm ("waiti 0"); \ - } while (0); -#endif - -// Functions that should go in IRAM -#define MICROPY_WRAP_MP_BINARY_OP(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_EXECUTE_BYTECODE(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_LOAD_GLOBAL(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_LOAD_NAME(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_MAP_LOOKUP(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_OBJ_GET_TYPE(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) IRAM_ATTR f -#define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) IRAM_ATTR f - -#define UINT_FMT "%u" -#define INT_FMT "%d" - -typedef int32_t mp_int_t; // must be pointer size -typedef uint32_t mp_uint_t; // must be pointer size -typedef long mp_off_t; -// ssize_t, off_t as required by POSIX-signatured functions in stream.h -#include - -// board specifics -#define MICROPY_PY_SYS_PLATFORM "esp32" - -// ESP32-S3 extended IO for 47 & 48 -#ifndef MICROPY_HW_ESP32S3_EXTENDED_IO -#define MICROPY_HW_ESP32S3_EXTENDED_IO (1) -#endif - -#ifndef MICROPY_HW_ENABLE_MDNS_QUERIES -#define MICROPY_HW_ENABLE_MDNS_QUERIES (0) -#endif - -#ifndef MICROPY_HW_ENABLE_MDNS_RESPONDER -#define MICROPY_HW_ENABLE_MDNS_RESPONDER (0) -#endif - -#ifndef MICROPY_BOARD_STARTUP -#define MICROPY_BOARD_STARTUP boardctrl_startup -#endif - -void boardctrl_startup(void); - -#ifndef MICROPY_PY_NETWORK_LAN -#if CONFIG_IDF_TARGET_ESP32 || (CONFIG_ETH_USE_SPI_ETHERNET && (CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL || CONFIG_ETH_SPI_ETHERNET_DM9051 || CONFIG_ETH_SPI_ETHERNET_W5500)) -#define MICROPY_PY_NETWORK_LAN (1) -#else -#define MICROPY_PY_NETWORK_LAN (0) -#endif -#endif - -#if MICROPY_PY_NETWORK_LAN && CONFIG_ETH_USE_SPI_ETHERNET -#ifndef MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 -#define MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ (12) -#else -#define MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ (36) -#endif -#endif -#endif diff --git a/tulip/esp32s3/mphalport.c b/tulip/esp32s3/mphalport.c deleted file mode 100644 index 8517cd432..000000000 --- a/tulip/esp32s3/mphalport.c +++ /dev/null @@ -1,208 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Damien P. George - * - * 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. - */ - -#include -#include -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_timer.h" - -#include "py/obj.h" -#include "py/objstr.h" -#include "py/stream.h" -#include "py/mpstate.h" -#include "py/mphal.h" -#include "extmod/misc.h" -#include "shared/timeutils/timeutils.h" -#include "shared/runtime/pyexec.h" -#include "mphalport.h" -#include "usb.h" -#include "usb_serial_jtag.h" -#include "uart.h" - -TaskHandle_t mp_main_task_handle; - -STATIC uint8_t stdin_ringbuf_array[260]; -//STATIC uint8_t midi_ringbuf_array[48]; -ringbuf_t stdin_ringbuf = {stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, 0}; -//ringbuf_t midi_ringbuf = {midi_ringbuf_array, sizeof(midi_ringbuf_array), 0, 0}; - -// Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. -void check_esp_err(esp_err_t code) { - if (code != ESP_OK) { - // map esp-idf error code to posix error code - uint32_t pcode = -code; - switch (code) { - case ESP_ERR_NO_MEM: - pcode = MP_ENOMEM; - break; - case ESP_ERR_TIMEOUT: - pcode = MP_ETIMEDOUT; - break; - case ESP_ERR_NOT_SUPPORTED: - pcode = MP_EOPNOTSUPP; - break; - } - // construct string object - mp_obj_str_t *o_str = m_new_obj_maybe(mp_obj_str_t); - if (o_str == NULL) { - mp_raise_OSError(pcode); - return; - } - o_str->base.type = &mp_type_str; - o_str->data = (const byte *)esp_err_to_name(code); // esp_err_to_name ret's ptr to const str - o_str->len = strlen((char *)o_str->data); - o_str->hash = qstr_compute_hash(o_str->data, o_str->len); - // raise - mp_obj_t args[2] = { MP_OBJ_NEW_SMALL_INT(pcode), MP_OBJ_FROM_PTR(o_str)}; - nlr_raise(mp_obj_exception_make_new(&mp_type_OSError, 2, 0, args)); - } -} - -uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { - uintptr_t ret = 0; - if ((poll_flags & MP_STREAM_POLL_RD) && stdin_ringbuf.iget != stdin_ringbuf.iput) { - ret |= MP_STREAM_POLL_RD; - } - if (poll_flags & MP_STREAM_POLL_WR) { - ret |= MP_STREAM_POLL_WR; - } - return ret; -} - -int mp_hal_stdin_rx_chr(void) { - for (;;) { - int c = ringbuf_get(&stdin_ringbuf); - if (c != -1) { - return c; - } - MICROPY_EVENT_POLL_HOOK - } -} - -void mp_hal_stdout_tx_strn(const char *str, size_t len) { - // Only release the GIL if many characters are being sent - bool release_gil = len > 20; - if (release_gil) { - MP_THREAD_GIL_EXIT(); - } - #if CONFIG_USB_OTG_SUPPORTED - usb_tx_strn(str, len); - #elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - usb_serial_jtag_tx_strn(str, len); - #endif - #if MICROPY_HW_ENABLE_UART_REPL - uart_stdout_tx_strn(str, len); - #endif - if (release_gil) { - MP_THREAD_GIL_ENTER(); - } - mp_os_dupterm_tx_strn(str, len); -} - -uint32_t mp_hal_ticks_ms(void) { - return esp_timer_get_time() / 1000; -} - -uint32_t mp_hal_ticks_us(void) { - return esp_timer_get_time(); -} - -void mp_hal_delay_ms(uint32_t ms) { - uint64_t us = (uint64_t)ms * 1000ULL; - uint64_t dt; - uint64_t t0 = esp_timer_get_time(); - for (;;) { - mp_handle_pending(true); - MICROPY_PY_SOCKET_EVENTS_HANDLER - MP_THREAD_GIL_EXIT(); - uint64_t t1 = esp_timer_get_time(); - dt = t1 - t0; - if (dt + portTICK_PERIOD_MS * 1000ULL >= us) { - // doing a vTaskDelay would take us beyond requested delay time - taskYIELD(); - MP_THREAD_GIL_ENTER(); - t1 = esp_timer_get_time(); - dt = t1 - t0; - break; - } else { - ulTaskNotifyTake(pdFALSE, 1); - MP_THREAD_GIL_ENTER(); - } - } - if (dt < us) { - // do the remaining delay accurately - mp_hal_delay_us(us - dt); - } -} - -void mp_hal_delay_us(uint32_t us) { - // these constants are tested for a 240MHz clock - const uint32_t this_overhead = 5; - const uint32_t pend_overhead = 150; - - // return if requested delay is less than calling overhead - if (us < this_overhead) { - return; - } - us -= this_overhead; - - uint64_t t0 = esp_timer_get_time(); - for (;;) { - uint64_t dt = esp_timer_get_time() - t0; - if (dt >= us) { - return; - } - if (dt + pend_overhead < us) { - // we have enough time to service pending events - // (don't use MICROPY_EVENT_POLL_HOOK because it also yields) - mp_handle_pending(true); - } - } -} - -uint64_t mp_hal_time_ns(void) { - struct timeval tv; - gettimeofday(&tv, NULL); - uint64_t ns = tv.tv_sec * 1000000000ULL; - ns += (uint64_t)tv.tv_usec * 1000ULL; - return ns; -} - -// Wake up the main task if it is sleeping -void mp_hal_wake_main_task_from_isr(void) { - #if !defined TDECK && !defined TLONG - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - vTaskNotifyGiveFromISR(mp_main_task_handle, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); - } - #endif -} diff --git a/tulip/esp32s3/mphalport.h b/tulip/esp32s3/mphalport.h deleted file mode 100644 index 4f25b3d4f..000000000 --- a/tulip/esp32s3/mphalport.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Damien P. George - * - * 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. - */ - -#ifndef INCLUDED_MPHALPORT_H -#define INCLUDED_MPHALPORT_H - -#include "py/ringbuf.h" -#include "shared/runtime/interrupt_char.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#include "driver/spi_master.h" - -#define MICROPY_PLATFORM_VERSION "IDF" IDF_VER - -// The core that the MicroPython task(s) are pinned to. -// Now that we have IDF 4.2.0+, we are once again able to pin to core 1 -// and avoid the Wifi/BLE timing problems on the same core. -// Best effort here to remain backwards compatible in rare version edge cases... -// See https://github.com/micropython/micropython/issues/5489 for history -#if CONFIG_FREERTOS_UNICORE -#define MP_TASK_COREID (0) -#else -#define MP_TASK_COREID (1) -#endif - -extern TaskHandle_t mp_main_task_handle; - -extern ringbuf_t stdin_ringbuf; - -// Check the ESP-IDF error code and raise an OSError if it's not ESP_OK. -void check_esp_err(esp_err_t code); - -uint32_t mp_hal_ticks_us(void); -__attribute__((always_inline)) static inline uint32_t mp_hal_ticks_cpu(void) { - uint32_t ccount; - #if CONFIG_IDF_TARGET_ESP32C3 - __asm__ __volatile__ ("csrr %0, 0x7E2" : "=r" (ccount)); // Machine Performance Counter Value - #else - __asm__ __volatile__ ("rsr %0,ccount" : "=a" (ccount)); - #endif - return ccount; -} - -void mp_hal_delay_us(uint32_t); -#define mp_hal_delay_us_fast(us) esp_rom_delay_us(us) -void mp_hal_set_interrupt_char(int c); -uint32_t mp_hal_get_cpu_freq(void); - -#define mp_hal_quiet_timing_enter() MICROPY_BEGIN_ATOMIC_SECTION() -#define mp_hal_quiet_timing_exit(irq_state) MICROPY_END_ATOMIC_SECTION(irq_state) - -// Wake up the main task if it is sleeping -void mp_hal_wake_main_task_from_isr(void); - -// C-level pin HAL -#include "py/obj.h" -#include "driver/gpio.h" -#define MP_HAL_PIN_FMT "%u" -#define mp_hal_pin_obj_t gpio_num_t -mp_hal_pin_obj_t machine_pin_get_id(mp_obj_t pin_in); -#define mp_hal_get_pin_obj(o) machine_pin_get_id(o) -#define mp_obj_get_pin(o) machine_pin_get_id(o) // legacy name; only to support esp8266/modonewire -#define mp_hal_pin_name(p) (p) -static inline void mp_hal_pin_input(mp_hal_pin_obj_t pin) { - esp_rom_gpio_pad_select_gpio(pin); - gpio_set_direction(pin, GPIO_MODE_INPUT); -} -static inline void mp_hal_pin_output(mp_hal_pin_obj_t pin) { - esp_rom_gpio_pad_select_gpio(pin); - gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT); -} -static inline void mp_hal_pin_open_drain(mp_hal_pin_obj_t pin) { - esp_rom_gpio_pad_select_gpio(pin); - gpio_set_direction(pin, GPIO_MODE_INPUT_OUTPUT_OD); -} -static inline void mp_hal_pin_od_low(mp_hal_pin_obj_t pin) { - gpio_set_level(pin, 0); -} -static inline void mp_hal_pin_od_high(mp_hal_pin_obj_t pin) { - gpio_set_level(pin, 1); -} -static inline int mp_hal_pin_read(mp_hal_pin_obj_t pin) { - return gpio_get_level(pin); -} -static inline void mp_hal_pin_write(mp_hal_pin_obj_t pin, int v) { - gpio_set_level(pin, v); -} - -spi_host_device_t machine_hw_spi_get_host(mp_obj_t in); - -#endif // INCLUDED_MPHALPORT_H diff --git a/tulip/esp32s3/mpnimbleport.c b/tulip/esp32s3/mpnimbleport.c deleted file mode 100644 index 8235275be..000000000 --- a/tulip/esp32s3/mpnimbleport.c +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2019 Jim Mussared - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mperrno.h" -#include "py/mphal.h" - -#if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_NIMBLE - -#define DEBUG_printf(...) // printf("nimble (esp32): " __VA_ARGS__) - -#include "esp_nimble_hci.h" -#include "nimble/nimble_port.h" -#include "nimble/nimble_port_freertos.h" - -#include "extmod/nimble/modbluetooth_nimble.h" - -STATIC void ble_host_task(void *param) { - DEBUG_printf("ble_host_task\n"); - nimble_port_run(); // This function will return only when nimble_port_stop() is executed. - nimble_port_freertos_deinit(); -} - -void mp_bluetooth_nimble_port_hci_init(void) { - DEBUG_printf("mp_bluetooth_nimble_port_hci_init\n"); - esp_nimble_hci_init(); -} - -void mp_bluetooth_nimble_port_hci_deinit(void) { - DEBUG_printf("mp_bluetooth_nimble_port_hci_deinit\n"); - - esp_nimble_hci_deinit(); -} - -void mp_bluetooth_nimble_port_start(void) { - DEBUG_printf("mp_bluetooth_nimble_port_start\n"); - nimble_port_freertos_init(ble_host_task); -} - -void mp_bluetooth_nimble_port_shutdown(void) { - DEBUG_printf("mp_bluetooth_nimble_port_shutdown\n"); - - // Despite the name, these is an ESP32-specific (no other NimBLE ports have these functions). - // Calls ble_hs_stop() and waits for stack shutdown. - nimble_port_stop(); - - // Shuts down the event queue. - nimble_port_deinit(); - - // Mark stack as shutdown. - mp_bluetooth_nimble_ble_state = MP_BLUETOOTH_NIMBLE_BLE_STATE_OFF; -} - -#endif diff --git a/tulip/esp32s3/mpthreadport.c b/tulip/esp32s3/mpthreadport.c deleted file mode 100644 index e6c7e9bc8..000000000 --- a/tulip/esp32s3/mpthreadport.c +++ /dev/null @@ -1,243 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd - * Copyright (c) 2017 Pycom Limited - * - * 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. - */ - -#include "stdio.h" - -#include "py/runtime.h" -#include "py/gc.h" -#include "py/mpthread.h" -#include "py/mphal.h" -#include "mpthreadport.h" - -#include "esp_task.h" - -#if MICROPY_PY_THREAD - -#define MP_THREAD_MIN_STACK_SIZE (4 * 1024) -#define MP_THREAD_DEFAULT_STACK_SIZE (MP_THREAD_MIN_STACK_SIZE + 1024) -#define MP_THREAD_PRIORITY (ESP_TASK_PRIO_MIN + 1) - -// this structure forms a linked list, one node per active thread -typedef struct _mp_thread_t { - TaskHandle_t id; // system id of thread - int ready; // whether the thread is ready and running - void *arg; // thread Python args, a GC root pointer - void *stack; // pointer to the stack - size_t stack_len; // number of words in the stack - struct _mp_thread_t *next; -} mp_thread_t; - -// the mutex controls access to the linked list -STATIC mp_thread_mutex_t thread_mutex; -STATIC mp_thread_t thread_entry0; -STATIC mp_thread_t *thread = NULL; // root pointer, handled by mp_thread_gc_others - -void mp_thread_init(void *stack, uint32_t stack_len) { - mp_thread_set_state(&mp_state_ctx.thread); - // create the first entry in the linked list of all threads - thread_entry0.id = xTaskGetCurrentTaskHandle(); - thread_entry0.ready = 1; - thread_entry0.arg = NULL; - thread_entry0.stack = stack; - thread_entry0.stack_len = stack_len; - thread_entry0.next = NULL; - mp_thread_mutex_init(&thread_mutex); - - // memory barrier to ensure above data is committed - __sync_synchronize(); - - // vPortCleanUpTCB needs the thread ready after thread_mutex is ready - thread = &thread_entry0; -} - -void mp_thread_gc_others(void) { - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; th = th->next) { - gc_collect_root((void **)&th, 1); - gc_collect_root(&th->arg, 1); // probably not needed - if (th->id == xTaskGetCurrentTaskHandle()) { - continue; - } - if (!th->ready) { - continue; - } - gc_collect_root(th->stack, th->stack_len); - } - mp_thread_mutex_unlock(&thread_mutex); -} - -mp_state_thread_t *mp_thread_get_state(void) { - return pvTaskGetThreadLocalStoragePointer(NULL, 1); -} - -void mp_thread_set_state(mp_state_thread_t *state) { - vTaskSetThreadLocalStoragePointer(NULL, 1, state); -} - -void mp_thread_start(void) { - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; th = th->next) { - if (th->id == xTaskGetCurrentTaskHandle()) { - th->ready = 1; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); -} - -STATIC void *(*ext_thread_entry)(void *) = NULL; - -STATIC void freertos_entry(void *arg) { - if (ext_thread_entry) { - ext_thread_entry(arg); - } - vTaskDelete(NULL); - for (;;) {; - } -} - -void mp_thread_create_ex(void *(*entry)(void *), void *arg, size_t *stack_size, int priority, char *name) { - // store thread entry function into a global variable so we can access it - ext_thread_entry = entry; - - if (*stack_size == 0) { - *stack_size = MP_THREAD_DEFAULT_STACK_SIZE; // default stack size - } else if (*stack_size < MP_THREAD_MIN_STACK_SIZE) { - *stack_size = MP_THREAD_MIN_STACK_SIZE; // minimum stack size - } - - // Allocate linked-list node (must be outside thread_mutex lock) - mp_thread_t *th = m_new_obj(mp_thread_t); - - mp_thread_mutex_lock(&thread_mutex, 1); - - // create thread - BaseType_t result = xTaskCreatePinnedToCore(freertos_entry, name, *stack_size / sizeof(StackType_t), arg, priority, &th->id, MP_TASK_COREID); - if (result != pdPASS) { - mp_thread_mutex_unlock(&thread_mutex); - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("can't create thread")); - } - - // add thread to linked list of all threads - th->ready = 0; - th->arg = arg; - th->stack = pxTaskGetStackStart(th->id); - th->stack_len = *stack_size / sizeof(uintptr_t); - th->next = thread; - thread = th; - - // adjust the stack_size to provide room to recover from hitting the limit - *stack_size -= 1024; - - mp_thread_mutex_unlock(&thread_mutex); -} - -void mp_thread_create(void *(*entry)(void *), void *arg, size_t *stack_size) { - mp_thread_create_ex(entry, arg, stack_size, MP_THREAD_PRIORITY, "mp_thread"); -} - -void mp_thread_finish(void) { - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; th = th->next) { - if (th->id == xTaskGetCurrentTaskHandle()) { - th->ready = 0; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); -} - -// This is called from the FreeRTOS idle task and is not within Python context, -// so MP_STATE_THREAD is not valid and it does not have the GIL. -void vPortCleanUpTCB(void *tcb) { - if (thread == NULL) { - // threading not yet initialised - return; - } - mp_thread_t *prev = NULL; - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; prev = th, th = th->next) { - // unlink the node from the list - if ((void *)th->id == tcb) { - if (prev != NULL) { - prev->next = th->next; - } else { - // move the start pointer - thread = th->next; - } - // The "th" memory will eventually be reclaimed by the GC. - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); -} - -void mp_thread_mutex_init(mp_thread_mutex_t *mutex) { - // Need a binary semaphore so a lock can be acquired on one Python thread - // and then released on another. - mutex->handle = xSemaphoreCreateBinaryStatic(&mutex->buffer); - xSemaphoreGive(mutex->handle); -} - -int mp_thread_mutex_lock(mp_thread_mutex_t *mutex, int wait) { - return pdTRUE == xSemaphoreTake(mutex->handle, wait ? portMAX_DELAY : 0); -} - -void mp_thread_mutex_unlock(mp_thread_mutex_t *mutex) { - xSemaphoreGive(mutex->handle); -} - -void mp_thread_deinit(void) { - for (;;) { - // Find a task to delete - TaskHandle_t id = NULL; - mp_thread_mutex_lock(&thread_mutex, 1); - for (mp_thread_t *th = thread; th != NULL; th = th->next) { - // Don't delete the current task - if (th->id != xTaskGetCurrentTaskHandle()) { - id = th->id; - break; - } - } - mp_thread_mutex_unlock(&thread_mutex); - - if (id == NULL) { - // No tasks left to delete - break; - } else { - // Call FreeRTOS to delete the task (it will call vPortCleanUpTCB) - vTaskDelete(id); - } - } -} - -#else - -void vPortCleanUpTCB(void *tcb) { -} - -#endif // MICROPY_PY_THREAD diff --git a/tulip/esp32s3/mpthreadport.h b/tulip/esp32s3/mpthreadport.h deleted file mode 100644 index 54e35d61c..000000000 --- a/tulip/esp32s3/mpthreadport.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016 Damien P. George on behalf of Pycom Ltd - * Copyright (c) 2017 Pycom Limited - * - * 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. - */ - -#ifndef MICROPY_INCLUDED_ESP32_MPTHREADPORT_H -#define MICROPY_INCLUDED_ESP32_MPTHREADPORT_H - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "freertos/queue.h" - -typedef struct _mp_thread_mutex_t { - SemaphoreHandle_t handle; - StaticSemaphore_t buffer; -} mp_thread_mutex_t; - -void mp_thread_init(void *stack, uint32_t stack_len); -void mp_thread_gc_others(void); -void mp_thread_deinit(void); - -#endif // MICROPY_INCLUDED_ESP32_MPTHREADPORT_H diff --git a/tulip/esp32s3/network_common.c b/tulip/esp32s3/network_common.c index cc792dbdf..9204fbd81 100644 --- a/tulip/esp32s3/network_common.c +++ b/tulip/esp32s3/network_common.c @@ -34,6 +34,7 @@ #include #include "py/runtime.h" +#include "py/parsenum.h" #include "py/mperrno.h" #include "shared/netutils/netutils.h" #include "modnetwork.h" @@ -41,7 +42,8 @@ #include "esp_log.h" #include "esp_netif.h" #include "esp_wifi.h" -// #include "lwip/dns.h" +#include "lwip/sockets.h" +#include "lwip/dns.h" NORETURN void esp_exceptions_helper(esp_err_t e) { switch (e) { @@ -80,7 +82,7 @@ NORETURN void esp_exceptions_helper(esp_err_t e) { } } -STATIC mp_obj_t esp_initialize() { +static mp_obj_t esp_initialize() { static int initialized = 0; if (!initialized) { esp_exceptions(esp_netif_init()); @@ -90,7 +92,7 @@ STATIC mp_obj_t esp_initialize() { } MP_DEFINE_CONST_FUN_OBJ_0(esp_network_initialize_obj, esp_initialize); -STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { +static mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { base_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); esp_netif_ip_info_t info; esp_netif_dns_info_t dns_info; @@ -153,9 +155,193 @@ STATIC mp_obj_t esp_ifconfig(size_t n_args, const mp_obj_t *args) { } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_ifconfig_obj, 1, 2, esp_ifconfig); -STATIC mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { +static mp_obj_t esp_network_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + if (kwargs->used == 0) { + // Get config value + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[0])) { + case MP_QSTR_dns: { + char addr_str[IPADDR_STRLEN_MAX]; + ipaddr_ntoa_r(dns_getserver(0), addr_str, sizeof(addr_str)); + return mp_obj_new_str_from_cstr(addr_str); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } else { + // Set config value(s) + if (n_args != 0) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_dns: { + ip_addr_t dns; + size_t addr_len; + const char *addr_str = mp_obj_str_get_data(e->value, &addr_len); + if (!ipaddr_aton(addr_str, &dns)) { + mp_raise_ValueError(MP_ERROR_TEXT("invalid arguments as dns server")); + } + dns_setserver(0, &dns); + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(esp_network_ipconfig_obj, 0, esp_network_ipconfig); + +static mp_obj_t esp_ipconfig(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + base_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + esp_netif_ip_info_t info; + esp_netif_get_ip_info(self->netif, &info); + + if (kwargs->used == 0) { + // Get config value + if (n_args != 2) { + mp_raise_TypeError(MP_ERROR_TEXT("must query one param")); + } + + switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_dhcp4: { + if (self->if_id == ESP_IF_WIFI_STA || self->if_id == ESP_IF_ETH) { + esp_netif_dhcp_status_t status; + esp_exceptions(esp_netif_dhcpc_get_status(self->netif, &status)); + return mp_obj_new_bool(status == ESP_NETIF_DHCP_STARTED); + } else { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + case MP_QSTR_addr4: { + mp_obj_t tuple[2] = { + netutils_format_ipv4_addr((uint8_t *)&info.ip, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&info.netmask, NETUTILS_BIG), + }; + return mp_obj_new_tuple(2, tuple); + } + case MP_QSTR_gw4: { + return netutils_format_ipv4_addr((uint8_t *)&info.gw, NETUTILS_BIG); + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + return mp_const_none; + } else { + // Set config value(s) + if (n_args != 1) { + mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); + } + int touched_ip_info = 0; + for (size_t i = 0; i < kwargs->alloc; ++i) { + if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { + mp_map_elem_t *e = &kwargs->table[i]; + switch (mp_obj_str_get_qstr(e->key)) { + case MP_QSTR_dhcp4: { + esp_netif_dhcp_status_t status; + if (self->if_id == ESP_IF_WIFI_STA || self->if_id == ESP_IF_ETH) { + esp_exceptions(esp_netif_dhcpc_get_status(self->netif, &status)); + if (mp_obj_is_true(e->value) && status != ESP_NETIF_DHCP_STARTED) { + esp_exceptions(esp_netif_dhcpc_start(self->netif)); + } else if (!mp_obj_is_true(e->value) && status == ESP_NETIF_DHCP_STARTED) { + esp_exceptions(esp_netif_dhcpc_stop(self->netif)); + } + } else { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + break; + } + case MP_QSTR_addr4: { + if (e->value != mp_const_none && mp_obj_is_str(e->value)) { + size_t addr_len; + const char *input_str = mp_obj_str_get_data(e->value, &addr_len); + char *split = strchr(input_str, '/'); + if (split) { + mp_obj_t prefix_obj = mp_parse_num_integer(split + 1, strlen(split + 1), 10, NULL); + int prefix_bits = mp_obj_get_int(prefix_obj); + uint32_t mask = -(1u << (32 - prefix_bits)); + uint32_t *m = (uint32_t *)&info.netmask; + *m = esp_netif_htonl(mask); + } + netutils_parse_ipv4_addr(e->value, (void *)&info.ip, NETUTILS_BIG); + } else if (e->value != mp_const_none) { + mp_obj_t *items; + mp_obj_get_array_fixed_n(e->value, 2, &items); + netutils_parse_ipv4_addr(items[0], (void *)&info.ip, NETUTILS_BIG); + netutils_parse_ipv4_addr(items[1], (void *)&info.netmask, NETUTILS_BIG); + } + touched_ip_info = 1; + break; + } + case MP_QSTR_gw4: { + netutils_parse_ipv4_addr(e->value, (void *)&info.gw, NETUTILS_BIG); + touched_ip_info = 1; + break; + } + default: { + mp_raise_ValueError(MP_ERROR_TEXT("unexpected key")); + break; + } + } + } + } + if (self->if_id == ESP_IF_WIFI_STA || self->if_id == ESP_IF_ETH) { + if (touched_ip_info) { + esp_err_t e = esp_netif_dhcpc_stop(self->netif); + if (e != ESP_OK && e != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { + esp_exceptions_helper(e); + } + esp_exceptions(esp_netif_set_ip_info(self->netif, &info)); + } + } else if (self->if_id == ESP_IF_WIFI_AP) { + esp_err_t e = esp_netif_dhcps_stop(self->netif); + if (e != ESP_OK && e != ESP_ERR_ESP_NETIF_DHCP_ALREADY_STOPPED) { + esp_exceptions_helper(e); + } + esp_exceptions(esp_netif_set_ip_info(self->netif, &info)); + esp_exceptions(esp_netif_dhcps_start(self->netif)); + } + + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(esp_nic_ipconfig_obj, 1, esp_ipconfig); + +mp_obj_t esp_ifname(esp_netif_t *netif) { + char ifname[NETIF_NAMESIZE + 1] = {0}; + mp_obj_t ret = mp_const_none; + if (esp_netif_get_netif_impl_name(netif, ifname) == ESP_OK && ifname[0] != 0) { + ret = mp_obj_new_str_from_cstr((char *)ifname); + } + return ret; +} + +static mp_obj_t esp_phy_mode(size_t n_args, const mp_obj_t *args) { return mp_const_none; } MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp_network_phy_mode_obj, 0, 1, esp_phy_mode); -//_Static_assert(WIFI_AUTH_MAX == 12, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +_Static_assert(WIFI_AUTH_MAX == 14, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 5) && ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2) +_Static_assert(WIFI_AUTH_MAX == 11, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#else +_Static_assert(WIFI_AUTH_MAX == 10, "Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h"); +#endif diff --git a/tulip/esp32s3/network_lan.c b/tulip/esp32s3/network_lan.c deleted file mode 100644 index 8128eb5e1..000000000 --- a/tulip/esp32s3/network_lan.c +++ /dev/null @@ -1,422 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2017 "Eric Poulsen" - * Copyright (c) 2021 "Tobias Eydam" - * - * Based on the ESP IDF example code which is Public Domain / CC0 - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mphal.h" - -#include "esp_idf_version.h" - -#if MICROPY_PY_NETWORK_LAN - -#include "esp_eth.h" -#include "esp_eth_mac.h" -#include "esp_event.h" -#include "esp_log.h" -#include "esp_netif.h" -#if CONFIG_ETH_USE_SPI_ETHERNET -#include "driver/spi_master.h" -#endif - -#include "modnetwork.h" - -typedef struct _lan_if_obj_t { - base_if_obj_t base; - bool initialized; - bool active; - int8_t mdc_pin; - int8_t mdio_pin; - int8_t phy_power_pin; - int8_t phy_cs_pin; - int8_t phy_int_pin; - uint8_t phy_addr; - uint8_t phy_type; - esp_eth_phy_t *phy; - esp_eth_handle_t eth_handle; -} lan_if_obj_t; - -const mp_obj_type_t lan_if_type; -STATIC lan_if_obj_t lan_obj = {{{&lan_if_type}, ESP_IF_ETH, NULL}, false, false}; -STATIC uint8_t eth_status = 0; - -static void eth_event_handler(void *arg, esp_event_base_t event_base, - int32_t event_id, void *event_data) { - switch (event_id) { - case ETHERNET_EVENT_CONNECTED: - eth_status = ETH_CONNECTED; - ESP_LOGI("ethernet", "Ethernet Link Up"); - break; - case ETHERNET_EVENT_DISCONNECTED: - eth_status = ETH_DISCONNECTED; - ESP_LOGI("ethernet", "Ethernet Link Down"); - break; - case ETHERNET_EVENT_START: - eth_status = ETH_STARTED; - ESP_LOGI("ethernet", "Ethernet Started"); - break; - case ETHERNET_EVENT_STOP: - eth_status = ETH_STOPPED; - ESP_LOGI("ethernet", "Ethernet Stopped"); - break; - case IP_EVENT_ETH_GOT_IP: - eth_status = ETH_GOT_IP; - ESP_LOGI("ethernet", "Ethernet Got IP"); - break; - default: - break; - } -} - -STATIC mp_obj_t get_lan(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - lan_if_obj_t *self = &lan_obj; - - if (self->initialized) { - return MP_OBJ_FROM_PTR(&lan_obj); - } - - enum { ARG_id, ARG_mdc, ARG_mdio, ARG_power, ARG_phy_addr, ARG_phy_type, - ARG_spi, ARG_cs, ARG_int, ARG_ref_clk_mode, ARG_ref_clk }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_id, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_mdc, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_mdio, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_power, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_phy_addr, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_phy_type, MP_ARG_KW_ONLY | MP_ARG_REQUIRED | MP_ARG_INT }, - { MP_QSTR_spi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_int, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_ref_clk_mode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, - { MP_QSTR_ref_clk, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - }; - - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - if (args[ARG_id].u_obj != mp_const_none) { - if (mp_obj_get_int(args[ARG_id].u_obj) != 0) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid LAN interface identifier")); - } - } - - #define GET_PIN(XXX) args[XXX].u_obj == mp_const_none ? -1 : machine_pin_get_id(args[XXX].u_obj); - - self->mdc_pin = GET_PIN(ARG_mdc); - self->mdio_pin = GET_PIN(ARG_mdio); - self->phy_power_pin = GET_PIN(ARG_power); - self->phy_cs_pin = GET_PIN(ARG_cs); - self->phy_int_pin = GET_PIN(ARG_int); - - if (args[ARG_phy_addr].u_int < 0x00 || args[ARG_phy_addr].u_int > 0x1f) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid phy address")); - } - self->phy_addr = args[ARG_phy_addr].u_int; - - if (args[ARG_phy_type].u_int != PHY_LAN8710 && - args[ARG_phy_type].u_int != PHY_LAN8720 && - args[ARG_phy_type].u_int != PHY_IP101 && - args[ARG_phy_type].u_int != PHY_RTL8201 && - args[ARG_phy_type].u_int != PHY_KSZ8041 && - args[ARG_phy_type].u_int != PHY_KSZ8081 && - #if CONFIG_ETH_USE_SPI_ETHERNET - #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL - args[ARG_phy_type].u_int != PHY_KSZ8851SNL && - #endif - #if CONFIG_ETH_SPI_ETHERNET_DM9051 - args[ARG_phy_type].u_int != PHY_DM9051 && - #endif - #if CONFIG_ETH_SPI_ETHERNET_W5500 - args[ARG_phy_type].u_int != PHY_W5500 && - #endif - #endif - args[ARG_phy_type].u_int != PHY_DP83848) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid phy type")); - } - - eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); - #if CONFIG_IDF_TARGET_ESP32 - eth_esp32_emac_config_t esp32_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); - #endif - - esp_eth_mac_t *mac = NULL; - - #if CONFIG_IDF_TARGET_ESP32 - // Dynamic ref_clk configuration. - if (args[ARG_ref_clk_mode].u_int != -1) { - // Map the GPIO_MODE constants to EMAC_CLK constants. - esp32_config.clock_config.rmii.clock_mode = - args[ARG_ref_clk_mode].u_int == GPIO_MODE_INPUT ? EMAC_CLK_EXT_IN : EMAC_CLK_OUT; - } - if (args[ARG_ref_clk].u_obj != mp_const_none) { - esp32_config.clock_config.rmii.clock_gpio = machine_pin_get_id(args[ARG_ref_clk].u_obj); - } - #endif - - eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); - phy_config.phy_addr = self->phy_addr; - phy_config.reset_gpio_num = self->phy_power_pin; - self->phy = NULL; - - #if CONFIG_ETH_USE_SPI_ETHERNET - spi_device_handle_t spi_handle = NULL; - if (IS_SPI_PHY(args[ARG_phy_type].u_int)) { - spi_device_interface_config_t devcfg = { - .mode = 0, - .clock_speed_hz = MICROPY_PY_NETWORK_LAN_SPI_CLOCK_SPEED_MZ * 1000 * 1000, - .queue_size = 20, - .spics_io_num = self->phy_cs_pin, - }; - switch (args[ARG_phy_type].u_int) { - #if CONFIG_ETH_SPI_ETHERNET_DM9051 - case PHY_DM9051: { - devcfg.command_bits = 1; - devcfg.address_bits = 7; - break; - } - #endif - #if CONFIG_ETH_SPI_ETHERNET_W5500 - case PHY_W5500: { - devcfg.command_bits = 16; - devcfg.address_bits = 8; - break; - } - #endif - } - spi_host_device_t host = machine_hw_spi_get_host(args[ARG_spi].u_obj); - if (spi_bus_add_device(host, &devcfg, &spi_handle) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("spi_bus_add_device failed")); - } - } - #endif - - switch (args[ARG_phy_type].u_int) { - #if CONFIG_IDF_TARGET_ESP32 - case PHY_LAN8710: - case PHY_LAN8720: - self->phy = esp_eth_phy_new_lan87xx(&phy_config); - break; - case PHY_IP101: - self->phy = esp_eth_phy_new_ip101(&phy_config); - break; - case PHY_RTL8201: - self->phy = esp_eth_phy_new_rtl8201(&phy_config); - break; - case PHY_DP83848: - self->phy = esp_eth_phy_new_dp83848(&phy_config); - break; - case PHY_KSZ8041: - case PHY_KSZ8081: - self->phy = esp_eth_phy_new_ksz80xx(&phy_config); - break; - #endif - #if CONFIG_ETH_USE_SPI_ETHERNET - #if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL - case PHY_KSZ8851SNL: { - eth_ksz8851snl_config_t chip_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_handle); - chip_config.int_gpio_num = self->phy_int_pin; - mac = esp_eth_mac_new_ksz8851snl(&chip_config, &mac_config); - self->phy = esp_eth_phy_new_ksz8851snl(&phy_config); - break; - } - #endif - #if CONFIG_ETH_SPI_ETHERNET_DM9051 - case PHY_DM9051: { - eth_dm9051_config_t chip_config = ETH_DM9051_DEFAULT_CONFIG(spi_handle); - chip_config.int_gpio_num = self->phy_int_pin; - mac = esp_eth_mac_new_dm9051(&chip_config, &mac_config); - self->phy = esp_eth_phy_new_dm9051(&phy_config); - break; - } - #endif - #if CONFIG_ETH_SPI_ETHERNET_W5500 - case PHY_W5500: { - eth_w5500_config_t chip_config = ETH_W5500_DEFAULT_CONFIG(spi_handle); - chip_config.int_gpio_num = self->phy_int_pin; - mac = esp_eth_mac_new_w5500(&chip_config, &mac_config); - self->phy = esp_eth_phy_new_w5500(&phy_config); - break; - } - #endif - #endif - } - - #if CONFIG_IDF_TARGET_ESP32 - if (!IS_SPI_PHY(args[ARG_phy_type].u_int)) { - if (self->mdc_pin == -1 || self->mdio_pin == -1) { - mp_raise_ValueError(MP_ERROR_TEXT("mdc and mdio must be specified")); - } - esp32_config.smi_mdc_gpio_num = self->mdc_pin; - esp32_config.smi_mdio_gpio_num = self->mdio_pin; - mac = esp_eth_mac_new_esp32(&esp32_config, &mac_config); - } - #endif - - if (esp_netif_init() != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_netif_init failed")); - } - - esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); - self->base.netif = esp_netif_new(&cfg); - - if (esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_event_handler_register failed")); - } - - if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_event_handler_register failed")); - } - - esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, self->phy); - - esp_err_t esp_err = esp_eth_driver_install(&config, &self->eth_handle); - if (esp_err == ESP_OK) { - self->active = false; - self->initialized = true; - } else { - if (esp_err == ESP_ERR_INVALID_ARG) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed with invalid argument")); - } else if (esp_err == ESP_ERR_NO_MEM) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed with no memory for driver")); - } else { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_eth_driver_install failed")); - } - } - - if (esp_netif_attach(self->base.netif, esp_eth_new_netif_glue(self->eth_handle)) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("esp_netif_attach failed")); - } - - eth_status = ETH_INITIALIZED; - - return MP_OBJ_FROM_PTR(&lan_obj); -} -MP_DEFINE_CONST_FUN_OBJ_KW(esp_network_get_lan_obj, 0, get_lan); - -STATIC mp_obj_t lan_active(size_t n_args, const mp_obj_t *args) { - lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - if (n_args > 1) { - if (mp_obj_is_true(args[1])) { - self->active = (esp_eth_start(self->eth_handle) == ESP_OK); - if (!self->active) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet enable failed")); - } - } else { - self->active = !(esp_eth_stop(self->eth_handle) == ESP_OK); - if (self->active) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ethernet disable failed")); - } - } - } - - return mp_obj_new_bool(self->active); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(lan_active_obj, 1, 2, lan_active); - -STATIC mp_obj_t lan_status(mp_obj_t self_in) { - return MP_OBJ_NEW_SMALL_INT(eth_status); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(lan_status_obj, lan_status); - -STATIC mp_obj_t lan_isconnected(mp_obj_t self_in) { - lan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - return self->active ? mp_obj_new_bool(self->phy->get_link(self->phy) == ETH_LINK_UP) : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(lan_isconnected_obj, lan_isconnected); - -STATIC mp_obj_t lan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - if (n_args != 1 && kwargs->used != 0) { - mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); - } - lan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - if (kwargs->used != 0) { - - for (size_t i = 0; i < kwargs->alloc; i++) { - if (mp_map_slot_is_filled(kwargs, i)) { - switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { - case MP_QSTR_mac: { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); - if (bufinfo.len != 6) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); - } - if ( - (esp_eth_ioctl(self->eth_handle, ETH_CMD_S_MAC_ADDR, bufinfo.buf) != ESP_OK) || - (esp_netif_set_mac(self->base.netif, bufinfo.buf) != ESP_OK) - ) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("failed setting MAC address")); - } - break; - } - default: - break; - } - } - } - return mp_const_none; - } - - if (n_args != 2) { - mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); - } - - mp_obj_t val = mp_const_none; - - switch (mp_obj_str_get_qstr(args[1])) { - case MP_QSTR_mac: { - uint8_t mac[6]; - esp_eth_ioctl(self->eth_handle, ETH_CMD_G_MAC_ADDR, mac); - return mp_obj_new_bytes(mac, sizeof(mac)); - } - default: - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); - } - - return val; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(lan_config_obj, 1, lan_config); - -STATIC const mp_rom_map_elem_t lan_if_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&lan_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&lan_isconnected_obj) }, - { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&lan_status_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&lan_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_network_ifconfig_obj) }, -}; - -STATIC MP_DEFINE_CONST_DICT(lan_if_locals_dict, lan_if_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - lan_if_type, - MP_QSTR_LAN, - MP_TYPE_FLAG_NONE, - locals_dict, &lan_if_locals_dict - ); - -#endif diff --git a/tulip/esp32s3/network_ppp.c b/tulip/esp32s3/network_ppp.c deleted file mode 100644 index caad7eb48..000000000 --- a/tulip/esp32s3/network_ppp.c +++ /dev/null @@ -1,287 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2018 "Eric Poulsen" - * - * Based on the ESP IDF example code which is Public Domain / CC0 - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mphal.h" -#include "py/objtype.h" -#include "py/stream.h" -#include "shared/netutils/netutils.h" -#include "modmachine.h" -#include "ppp_set_auth.h" - -#include "netif/ppp/ppp.h" -#include "netif/ppp/pppos.h" -#include "lwip/err.h" -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/netdb.h" -#include "lwip/dns.h" -#include "netif/ppp/pppapi.h" - -#define PPP_CLOSE_TIMEOUT_MS (4000) - -typedef struct _ppp_if_obj_t { - mp_obj_base_t base; - bool active; - bool connected; - volatile bool clean_close; - ppp_pcb *pcb; - mp_obj_t stream; - SemaphoreHandle_t inactiveWaitSem; - volatile TaskHandle_t client_task_handle; - struct netif pppif; -} ppp_if_obj_t; - -const mp_obj_type_t ppp_if_type; - -static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { - ppp_if_obj_t *self = ctx; - struct netif *pppif = ppp_netif(self->pcb); - - switch (err_code) { - case PPPERR_NONE: - self->connected = (pppif->ip_addr.u_addr.ip4.addr != 0); - break; - case PPPERR_USER: - self->clean_close = true; - break; - case PPPERR_CONNECT: - self->connected = false; - break; - default: - break; - } -} - -STATIC mp_obj_t ppp_make_new(mp_obj_t stream) { - mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); - - ppp_if_obj_t *self = m_new_obj_with_finaliser(ppp_if_obj_t); - - self->base.type = &ppp_if_type; - self->stream = stream; - self->active = false; - self->connected = false; - self->clean_close = false; - self->client_task_handle = NULL; - - return MP_OBJ_FROM_PTR(self); -} -MP_DEFINE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj, ppp_make_new); - -static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) { - ppp_if_obj_t *self = ctx; - int err; - return mp_stream_rw(self->stream, data, len, &err, MP_STREAM_RW_WRITE); -} - -static void pppos_client_task(void *self_in) { - ppp_if_obj_t *self = (ppp_if_obj_t *)self_in; - uint8_t buf[256]; - - while (ulTaskNotifyTake(pdTRUE, 0) == 0) { - int err; - int len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); - if (len > 0) { - pppos_input_tcpip(self->pcb, (u8_t *)buf, len); - } - } - - self->client_task_handle = NULL; - vTaskDelete(NULL); -} - -STATIC mp_obj_t ppp_active(size_t n_args, const mp_obj_t *args) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - if (n_args > 1) { - if (mp_obj_is_true(args[1])) { - if (self->active) { - return mp_const_true; - } - - self->pcb = pppapi_pppos_create(&self->pppif, ppp_output_callback, ppp_status_cb, self); - - if (self->pcb == NULL) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("init failed")); - } - self->active = true; - } else { - if (!self->active) { - return mp_const_false; - } - - if (self->client_task_handle != NULL) { // is connecting or connected? - // Wait for PPPERR_USER, with timeout - pppapi_close(self->pcb, 0); - uint32_t t0 = mp_hal_ticks_ms(); - while (!self->clean_close && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { - mp_hal_delay_ms(10); - } - - // Shutdown task - xTaskNotifyGive(self->client_task_handle); - t0 = mp_hal_ticks_ms(); - while (self->client_task_handle != NULL && mp_hal_ticks_ms() - t0 < PPP_CLOSE_TIMEOUT_MS) { - mp_hal_delay_ms(10); - } - } - - // Release PPP - pppapi_free(self->pcb); - self->pcb = NULL; - self->active = false; - self->connected = false; - self->clean_close = false; - } - } - return mp_obj_new_bool(self->active); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_active_obj, 1, 2, ppp_active); - -STATIC mp_obj_t ppp_connect_py(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - enum { ARG_authmode, ARG_username, ARG_password }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_authmode, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = PPPAUTHTYPE_NONE} }, - { MP_QSTR_username, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, - { MP_QSTR_password, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, - }; - - mp_arg_val_t parsed_args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, parsed_args); - - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - if (!self->active) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("must be active")); - } - - if (self->client_task_handle != NULL) { - mp_raise_OSError(MP_EALREADY); - } - - switch (parsed_args[ARG_authmode].u_int) { - case PPPAUTHTYPE_NONE: - case PPPAUTHTYPE_PAP: - case PPPAUTHTYPE_CHAP: - break; - default: - mp_raise_ValueError(MP_ERROR_TEXT("invalid auth")); - } - - if (parsed_args[ARG_authmode].u_int != PPPAUTHTYPE_NONE) { - const char *username_str = mp_obj_str_get_str(parsed_args[ARG_username].u_obj); - const char *password_str = mp_obj_str_get_str(parsed_args[ARG_password].u_obj); - pppapi_set_auth(self->pcb, parsed_args[ARG_authmode].u_int, username_str, password_str); - } - if (pppapi_set_default(self->pcb) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("set default failed")); - } - - ppp_set_usepeerdns(self->pcb, true); - - if (pppapi_connect(self->pcb, 0) != ESP_OK) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("connect failed")); - } - - if (xTaskCreatePinnedToCore(pppos_client_task, "ppp", 2048, self, 1, (TaskHandle_t *)&self->client_task_handle, MP_TASK_COREID) != pdPASS) { - mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create worker task")); - } - - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_KW(ppp_connect_obj, 1, ppp_connect_py); - -STATIC mp_obj_t ppp_delete(mp_obj_t self_in) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_obj_t args[] = {self, mp_const_false}; - ppp_active(2, args); - return mp_const_none; -} -MP_DEFINE_CONST_FUN_OBJ_1(ppp_delete_obj, ppp_delete); - -STATIC mp_obj_t ppp_ifconfig(size_t n_args, const mp_obj_t *args) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - if (n_args == 1) { - // get - const ip_addr_t *dns; - if (self->pcb != NULL) { - dns = dns_getserver(0); - struct netif *pppif = ppp_netif(self->pcb); - mp_obj_t tuple[4] = { - netutils_format_ipv4_addr((uint8_t *)&pppif->ip_addr, NETUTILS_BIG), - netutils_format_ipv4_addr((uint8_t *)&pppif->gw, NETUTILS_BIG), - netutils_format_ipv4_addr((uint8_t *)&pppif->netmask, NETUTILS_BIG), - netutils_format_ipv4_addr((uint8_t *)dns, NETUTILS_BIG), - }; - return mp_obj_new_tuple(4, tuple); - } else { - mp_obj_t tuple[4] = { mp_const_none, mp_const_none, mp_const_none, mp_const_none }; - return mp_obj_new_tuple(4, tuple); - } - } else { - ip_addr_t dns; - mp_obj_t *items; - mp_obj_get_array_fixed_n(args[1], 4, &items); - netutils_parse_ipv4_addr(items[3], (uint8_t *)&dns.u_addr.ip4, NETUTILS_BIG); - dns_setserver(0, &dns); - return mp_const_none; - } -} -MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ppp_ifconfig_obj, 1, 2, ppp_ifconfig); - -STATIC mp_obj_t ppp_status(mp_obj_t self_in) { - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(ppp_status_obj, ppp_status); - -STATIC mp_obj_t ppp_isconnected(mp_obj_t self_in) { - ppp_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(self->connected); -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(ppp_isconnected_obj, ppp_isconnected); - -STATIC const mp_rom_map_elem_t ppp_if_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&ppp_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&ppp_connect_obj) }, - { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&ppp_isconnected_obj) }, - { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&ppp_status_obj) }, - { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&ppp_ifconfig_obj) }, - { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ppp_delete_obj) }, - { MP_ROM_QSTR(MP_QSTR_AUTH_NONE), MP_ROM_INT(PPPAUTHTYPE_NONE) }, - { MP_ROM_QSTR(MP_QSTR_AUTH_PAP), MP_ROM_INT(PPPAUTHTYPE_PAP) }, - { MP_ROM_QSTR(MP_QSTR_AUTH_CHAP), MP_ROM_INT(PPPAUTHTYPE_CHAP) }, -}; -STATIC MP_DEFINE_CONST_DICT(ppp_if_locals_dict, ppp_if_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - ppp_if_type, - MP_QSTR_PPP, - MP_TYPE_FLAG_NONE, - locals_dict, &ppp_if_locals_dict - ); diff --git a/tulip/esp32s3/network_wlan.c b/tulip/esp32s3/network_wlan.c deleted file mode 100644 index cfedd898c..000000000 --- a/tulip/esp32s3/network_wlan.c +++ /dev/null @@ -1,681 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * Development of the code in this file was sponsored by Microbric Pty Ltd - * and Mnemote Pty Ltd - * - * The MIT License (MIT) - * - * Copyright (c) 2016, 2017 Nick Moore @mnemote - * Copyright (c) 2017 "Eric Poulsen" - * - * Based on esp8266/modnetwork.c which is Copyright (c) 2015 Paul Sokolovsky - * And the ESP IDF example code which is Public Domain / CC0 - * - * 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. - */ - -#include - -#include "py/objlist.h" -#include "py/runtime.h" -#include "py/mphal.h" -#include "extmod/modnetwork.h" -#include "modnetwork.h" - -#include "esp_wifi.h" -#include "esp_log.h" - -#if MICROPY_PY_NETWORK_WLAN - -#if (WIFI_MODE_STA & WIFI_MODE_AP != WIFI_MODE_NULL || WIFI_MODE_STA | WIFI_MODE_AP != WIFI_MODE_APSTA) -#error WIFI_MODE_STA and WIFI_MODE_AP are supposed to be bitfields! -#endif - -typedef base_if_obj_t wlan_if_obj_t; - -STATIC wlan_if_obj_t wlan_sta_obj; -STATIC wlan_if_obj_t wlan_ap_obj; - -// Set to "true" if esp_wifi_start() was called -static bool wifi_started = false; - -// Set to "true" if the STA interface is requested to be connected by the -// user, used for automatic reassociation. -static bool wifi_sta_connect_requested = false; - -// Set to "true" if the STA interface is connected to wifi and has IP address. -static bool wifi_sta_connected = false; - -// Store the current status. 0 means None here, safe to do so as first enum value is WIFI_REASON_UNSPECIFIED=1. -static uint8_t wifi_sta_disconn_reason = 0; - -#if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER -// Whether mDNS has been initialised or not -static bool mdns_initialised = false; -#endif - -static uint8_t conf_wifi_sta_reconnects = 0; -static volatile uint8_t wifi_sta_reconnects; - -// This function is called by the system-event task and so runs in a different -// thread to the main MicroPython task. It must not raise any Python exceptions. -static void network_wlan_wifi_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - switch (event_id) { - case WIFI_EVENT_STA_START: - ESP_LOGI("wifi", "STA_START"); - wifi_sta_reconnects = 0; - break; - - case WIFI_EVENT_STA_CONNECTED: - ESP_LOGI("network", "CONNECTED"); - break; - - case WIFI_EVENT_STA_DISCONNECTED: { - // This is a workaround as ESP32 WiFi libs don't currently - // auto-reassociate. - - wifi_event_sta_disconnected_t *disconn = event_data; - char *message = ""; - wifi_sta_disconn_reason = disconn->reason; - switch (disconn->reason) { - case WIFI_REASON_BEACON_TIMEOUT: - // AP has dropped out; try to reconnect. - message = "\nbeacon timeout"; - break; - case WIFI_REASON_NO_AP_FOUND: - // AP may not exist, or it may have momentarily dropped out; try to reconnect. - message = "\nno AP found"; - break; - case WIFI_REASON_AUTH_FAIL: - // Password may be wrong, or it just failed to connect; try to reconnect. - message = "\nauthentication failed"; - break; - default: - // Let other errors through and try to reconnect. - break; - } - ESP_LOGI("wifi", "STA_DISCONNECTED, reason:%d%s", disconn->reason, message); - - wifi_sta_connected = false; - if (wifi_sta_connect_requested) { - wifi_mode_t mode; - if (esp_wifi_get_mode(&mode) != ESP_OK) { - break; - } - if (!(mode & WIFI_MODE_STA)) { - break; - } - if (conf_wifi_sta_reconnects) { - ESP_LOGI("wifi", "reconnect counter=%d, max=%d", - wifi_sta_reconnects, conf_wifi_sta_reconnects); - if (++wifi_sta_reconnects >= conf_wifi_sta_reconnects) { - break; - } - } - esp_err_t e = esp_wifi_connect(); - if (e != ESP_OK) { - ESP_LOGI("wifi", "error attempting to reconnect: 0x%04x", e); - } - } - break; - } - default: - break; - } -} - -static void network_wlan_ip_event_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { - switch (event_id) { - case IP_EVENT_STA_GOT_IP: - ESP_LOGI("network", "GOT_IP"); - wifi_sta_connected = true; - wifi_sta_disconn_reason = 0; // Success so clear error. (in case of new error will be replaced anyway) - #if MICROPY_HW_ENABLE_MDNS_QUERIES || MICROPY_HW_ENABLE_MDNS_RESPONDER - if (!mdns_initialised) { - mdns_init(); - #if MICROPY_HW_ENABLE_MDNS_RESPONDER - mdns_hostname_set(mod_network_hostname); - mdns_instance_name_set(mod_network_hostname); - #endif - mdns_initialised = true; - } - #endif - break; - - default: - break; - } -} - -STATIC void require_if(mp_obj_t wlan_if, int if_no) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(wlan_if); - if (self->if_id != if_no) { - mp_raise_msg(&mp_type_OSError, if_no == ESP_IF_WIFI_STA ? MP_ERROR_TEXT("STA required") : MP_ERROR_TEXT("AP required")); - } -} - -void esp_initialise_wifi(void) { - static int wifi_initialized = 0; - if (!wifi_initialized) { - esp_exceptions(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, network_wlan_wifi_event_handler, NULL, NULL)); - esp_exceptions(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, network_wlan_ip_event_handler, NULL, NULL)); - - wlan_sta_obj.base.type = &esp_network_wlan_type; - wlan_sta_obj.if_id = ESP_IF_WIFI_STA; - wlan_sta_obj.netif = esp_netif_create_default_wifi_sta(); - - wlan_ap_obj.base.type = &esp_network_wlan_type; - wlan_ap_obj.if_id = ESP_IF_WIFI_AP; - wlan_ap_obj.netif = esp_netif_create_default_wifi_ap(); - - wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); - ESP_LOGD("modnetwork", "Initializing WiFi"); - esp_exceptions(esp_wifi_init(&cfg)); - esp_exceptions(esp_wifi_set_storage(WIFI_STORAGE_RAM)); - - ESP_LOGD("modnetwork", "Initialized"); - wifi_initialized = 1; - } -} - -STATIC mp_obj_t network_wlan_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 0, 1, false); - - esp_initialise_wifi(); - - int idx = (n_args > 0) ? mp_obj_get_int(args[0]) : ESP_IF_WIFI_STA; - if (idx == ESP_IF_WIFI_STA) { - return MP_OBJ_FROM_PTR(&wlan_sta_obj); - } else if (idx == ESP_IF_WIFI_AP) { - return MP_OBJ_FROM_PTR(&wlan_ap_obj); - } else { - mp_raise_ValueError(MP_ERROR_TEXT("invalid WLAN interface identifier")); - } -} - -STATIC mp_obj_t network_wlan_active(size_t n_args, const mp_obj_t *args) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - wifi_mode_t mode; - if (!wifi_started) { - mode = WIFI_MODE_NULL; - } else { - esp_exceptions(esp_wifi_get_mode(&mode)); - } - - int bit = (self->if_id == ESP_IF_WIFI_STA) ? WIFI_MODE_STA : WIFI_MODE_AP; - - if (n_args > 1) { - bool active = mp_obj_is_true(args[1]); - mode = active ? (mode | bit) : (mode & ~bit); - if (mode == WIFI_MODE_NULL) { - if (wifi_started) { - esp_exceptions(esp_wifi_stop()); - wifi_started = false; - } - } else { - esp_exceptions(esp_wifi_set_mode(mode)); - if (!wifi_started) { - // WIFI_EVENT_STA_START must be received before esp_wifi_connect() can be called. - // Use the `wifi_sta_reconnects` variable to detect that event. - wifi_sta_reconnects = 1; - esp_exceptions(esp_wifi_start()); - wifi_started = true; - while (wifi_sta_reconnects != 0) { - MICROPY_EVENT_POLL_HOOK; - } - } - } - } - - return (mode & bit) ? mp_const_true : mp_const_false; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_active_obj, 1, 2, network_wlan_active); - -STATIC mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_ssid, ARG_key, ARG_bssid }; - static const mp_arg_t allowed_args[] = { - { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_, MP_ARG_OBJ, {.u_obj = mp_const_none} }, - { MP_QSTR_bssid, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} }, - }; - - // parse args - mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; - mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - - wifi_config_t wifi_sta_config = {0}; - - // configure any parameters that are given - if (n_args > 1) { - size_t len; - const char *p; - if (args[ARG_ssid].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_ssid].u_obj, &len); - memcpy(wifi_sta_config.sta.ssid, p, MIN(len, sizeof(wifi_sta_config.sta.ssid))); - } - if (args[ARG_key].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_key].u_obj, &len); - memcpy(wifi_sta_config.sta.password, p, MIN(len, sizeof(wifi_sta_config.sta.password))); - } - if (args[ARG_bssid].u_obj != mp_const_none) { - p = mp_obj_str_get_data(args[ARG_bssid].u_obj, &len); - if (len != sizeof(wifi_sta_config.sta.bssid)) { - mp_raise_ValueError(NULL); - } - wifi_sta_config.sta.bssid_set = 1; - memcpy(wifi_sta_config.sta.bssid, p, sizeof(wifi_sta_config.sta.bssid)); - } - esp_exceptions(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_sta_config)); - } - - esp_exceptions(esp_netif_set_hostname(wlan_sta_obj.netif, mod_network_hostname)); - - wifi_sta_reconnects = 0; - // connect to the WiFi AP - MP_THREAD_GIL_EXIT(); - esp_exceptions(esp_wifi_connect()); - MP_THREAD_GIL_ENTER(); - wifi_sta_connect_requested = true; - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_connect_obj, 1, network_wlan_connect); - -STATIC mp_obj_t network_wlan_disconnect(mp_obj_t self_in) { - wifi_sta_connect_requested = false; - esp_exceptions(esp_wifi_disconnect()); - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_disconnect_obj, network_wlan_disconnect); - -STATIC mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - if (n_args == 1) { - if (self->if_id == ESP_IF_WIFI_STA) { - // Case of no arg is only for the STA interface - if (wifi_sta_connected) { - // Happy path, connected with IP - return MP_OBJ_NEW_SMALL_INT(STAT_GOT_IP); - } else if (wifi_sta_connect_requested - && (conf_wifi_sta_reconnects == 0 - || wifi_sta_reconnects < conf_wifi_sta_reconnects)) { - // No connection or error, but is requested = Still connecting - return MP_OBJ_NEW_SMALL_INT(STAT_CONNECTING); - } else if (wifi_sta_disconn_reason == 0) { - // No activity, No error = Idle - return MP_OBJ_NEW_SMALL_INT(STAT_IDLE); - } else { - // Simply pass the error through from ESP-identifier - return MP_OBJ_NEW_SMALL_INT(wifi_sta_disconn_reason); - } - } - return mp_const_none; - } - - // one argument: return status based on query parameter - switch ((uintptr_t)args[1]) { - case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_stations): { - // return list of connected stations, only if in soft-AP mode - require_if(args[0], ESP_IF_WIFI_AP); - wifi_sta_list_t station_list; - esp_exceptions(esp_wifi_ap_get_sta_list(&station_list)); - wifi_sta_info_t *stations = (wifi_sta_info_t *)station_list.sta; - mp_obj_t list = mp_obj_new_list(0, NULL); - for (int i = 0; i < station_list.num; ++i) { - mp_obj_tuple_t *t = mp_obj_new_tuple(1, NULL); - t->items[0] = mp_obj_new_bytes(stations[i].mac, sizeof(stations[i].mac)); - mp_obj_list_append(list, t); - } - return list; - } - case (uintptr_t)MP_OBJ_NEW_QSTR(MP_QSTR_rssi): { - // return signal of AP, only in STA mode - require_if(args[0], ESP_IF_WIFI_STA); - - wifi_ap_record_t info; - esp_exceptions(esp_wifi_sta_get_ap_info(&info)); - return MP_OBJ_NEW_SMALL_INT(info.rssi); - } - default: - mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); - } - - return mp_const_none; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_status_obj, 1, 2, network_wlan_status); - -STATIC mp_obj_t network_wlan_scan(mp_obj_t self_in) { - // check that STA mode is active - wifi_mode_t mode; - esp_exceptions(esp_wifi_get_mode(&mode)); - if ((mode & WIFI_MODE_STA) == 0) { - mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("STA must be active")); - } - - mp_obj_t list = mp_obj_new_list(0, NULL); - wifi_scan_config_t config = { 0 }; - config.show_hidden = true; - MP_THREAD_GIL_EXIT(); - esp_err_t status = esp_wifi_scan_start(&config, 1); - MP_THREAD_GIL_ENTER(); - if (status == 0) { - uint16_t count = 0; - esp_exceptions(esp_wifi_scan_get_ap_num(&count)); - if (count == 0) { - // esp_wifi_scan_get_ap_records must be called to free internal buffers from the scan. - // But it returns an error if wifi_ap_records==NULL. So allocate at least 1 AP entry. - // esp_wifi_scan_get_ap_records will then return the actual number of APs in count. - count = 1; - } - wifi_ap_record_t *wifi_ap_records = calloc(count, sizeof(wifi_ap_record_t)); - esp_exceptions(esp_wifi_scan_get_ap_records(&count, wifi_ap_records)); - for (uint16_t i = 0; i < count; i++) { - mp_obj_tuple_t *t = mp_obj_new_tuple(6, NULL); - uint8_t *x = memchr(wifi_ap_records[i].ssid, 0, sizeof(wifi_ap_records[i].ssid)); - int ssid_len = x ? x - wifi_ap_records[i].ssid : sizeof(wifi_ap_records[i].ssid); - t->items[0] = mp_obj_new_bytes(wifi_ap_records[i].ssid, ssid_len); - t->items[1] = mp_obj_new_bytes(wifi_ap_records[i].bssid, sizeof(wifi_ap_records[i].bssid)); - t->items[2] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].primary); - t->items[3] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].rssi); - t->items[4] = MP_OBJ_NEW_SMALL_INT(wifi_ap_records[i].authmode); - t->items[5] = mp_const_false; // XXX hidden? - mp_obj_list_append(list, MP_OBJ_FROM_PTR(t)); - } - free(wifi_ap_records); - } - return list; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_scan_obj, network_wlan_scan); - -STATIC mp_obj_t network_wlan_isconnected(mp_obj_t self_in) { - wlan_if_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (self->if_id == ESP_IF_WIFI_STA) { - return mp_obj_new_bool(wifi_sta_connected); - } else { - wifi_sta_list_t sta; - esp_wifi_ap_get_sta_list(&sta); - return mp_obj_new_bool(sta.num != 0); - } -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_isconnected_obj, network_wlan_isconnected); - -STATIC mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { - if (n_args != 1 && kwargs->used != 0) { - mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); - } - - wlan_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); - - bool is_wifi = self->if_id == ESP_IF_WIFI_AP || self->if_id == ESP_IF_WIFI_STA; - - wifi_config_t cfg; - if (is_wifi) { - esp_exceptions(esp_wifi_get_config(self->if_id, &cfg)); - } - - if (kwargs->used != 0) { - if (!is_wifi) { - goto unknown; - } - - for (size_t i = 0; i < kwargs->alloc; i++) { - if (mp_map_slot_is_filled(kwargs, i)) { - int req_if = -1; - - switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { - case MP_QSTR_mac: { - mp_buffer_info_t bufinfo; - mp_get_buffer_raise(kwargs->table[i].value, &bufinfo, MP_BUFFER_READ); - if (bufinfo.len != 6) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid buffer length")); - } - esp_exceptions(esp_wifi_set_mac(self->if_id, bufinfo.buf)); - break; - } - case MP_QSTR_ssid: - case MP_QSTR_essid: { - req_if = ESP_IF_WIFI_AP; - size_t len; - const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); - len = MIN(len, sizeof(cfg.ap.ssid)); - memcpy(cfg.ap.ssid, s, len); - cfg.ap.ssid_len = len; - break; - } - case MP_QSTR_hidden: { - req_if = ESP_IF_WIFI_AP; - cfg.ap.ssid_hidden = mp_obj_is_true(kwargs->table[i].value); - break; - } - case MP_QSTR_security: - case MP_QSTR_authmode: { - req_if = ESP_IF_WIFI_AP; - cfg.ap.authmode = mp_obj_get_int(kwargs->table[i].value); - break; - } - case MP_QSTR_key: - case MP_QSTR_password: { - req_if = ESP_IF_WIFI_AP; - size_t len; - const char *s = mp_obj_str_get_data(kwargs->table[i].value, &len); - len = MIN(len, sizeof(cfg.ap.password) - 1); - memcpy(cfg.ap.password, s, len); - cfg.ap.password[len] = 0; - break; - } - case MP_QSTR_channel: { - uint8_t primary; - wifi_second_chan_t secondary; - // Get the current value of secondary - esp_exceptions(esp_wifi_get_channel(&primary, &secondary)); - primary = mp_obj_get_int(kwargs->table[i].value); - esp_err_t err = esp_wifi_set_channel(primary, secondary); - if (err == ESP_ERR_INVALID_ARG) { - // May need to swap secondary channel above to below or below to above - secondary = ( - (secondary == WIFI_SECOND_CHAN_ABOVE) - ? WIFI_SECOND_CHAN_BELOW - : (secondary == WIFI_SECOND_CHAN_BELOW) - ? WIFI_SECOND_CHAN_ABOVE - : WIFI_SECOND_CHAN_NONE); - esp_exceptions(esp_wifi_set_channel(primary, secondary)); - } - break; - } - case MP_QSTR_hostname: - case MP_QSTR_dhcp_hostname: { - // TODO: Deprecated. Use network.hostname(name) instead. - size_t len; - const char *str = mp_obj_str_get_data(kwargs->table[i].value, &len); - if (len >= MICROPY_PY_NETWORK_HOSTNAME_MAX_LEN) { - mp_raise_ValueError(NULL); - } - strcpy(mod_network_hostname, str); - break; - } - case MP_QSTR_max_clients: { - req_if = ESP_IF_WIFI_AP; - cfg.ap.max_connection = mp_obj_get_int(kwargs->table[i].value); - break; - } - case MP_QSTR_reconnects: { - int reconnects = mp_obj_get_int(kwargs->table[i].value); - req_if = ESP_IF_WIFI_STA; - // parameter reconnects == -1 means to retry forever. - // here means conf_wifi_sta_reconnects == 0 to retry forever. - conf_wifi_sta_reconnects = (reconnects == -1) ? 0 : reconnects + 1; - break; - } - case MP_QSTR_txpower: { - int8_t power = (mp_obj_get_float(kwargs->table[i].value) * 4); - esp_exceptions(esp_wifi_set_max_tx_power(power)); - break; - } - case MP_QSTR_protocol: { - esp_exceptions(esp_wifi_set_protocol(self->if_id, mp_obj_get_int(kwargs->table[i].value))); - break; - } - case MP_QSTR_pm: { - esp_exceptions(esp_wifi_set_ps(mp_obj_get_int(kwargs->table[i].value))); - break; - } - default: - goto unknown; - } - - // We post-check interface requirements to save on code size - if (req_if >= 0) { - require_if(args[0], req_if); - } - } - } - - esp_exceptions(esp_wifi_set_config(self->if_id, &cfg)); - - return mp_const_none; - } - - // Get config - - if (n_args != 2) { - mp_raise_TypeError(MP_ERROR_TEXT("can query only one param")); - } - - int req_if = -1; - mp_obj_t val = mp_const_none; - - switch (mp_obj_str_get_qstr(args[1])) { - case MP_QSTR_mac: { - uint8_t mac[6]; - switch (self->if_id) { - case ESP_IF_WIFI_AP: // fallthrough intentional - case ESP_IF_WIFI_STA: - esp_exceptions(esp_wifi_get_mac(self->if_id, mac)); - return mp_obj_new_bytes(mac, sizeof(mac)); - default: - goto unknown; - } - } - case MP_QSTR_ssid: - case MP_QSTR_essid: - switch (self->if_id) { - case ESP_IF_WIFI_STA: - val = mp_obj_new_str((char *)cfg.sta.ssid, strlen((char *)cfg.sta.ssid)); - break; - case ESP_IF_WIFI_AP: - val = mp_obj_new_str((char *)cfg.ap.ssid, cfg.ap.ssid_len); - break; - default: - req_if = ESP_IF_WIFI_AP; - } - break; - case MP_QSTR_hidden: - req_if = ESP_IF_WIFI_AP; - val = mp_obj_new_bool(cfg.ap.ssid_hidden); - break; - case MP_QSTR_security: - case MP_QSTR_authmode: - req_if = ESP_IF_WIFI_AP; - val = MP_OBJ_NEW_SMALL_INT(cfg.ap.authmode); - break; - case MP_QSTR_channel: { - uint8_t channel; - wifi_second_chan_t second; - esp_exceptions(esp_wifi_get_channel(&channel, &second)); - val = MP_OBJ_NEW_SMALL_INT(channel); - break; - } - case MP_QSTR_hostname: - case MP_QSTR_dhcp_hostname: { - // TODO: Deprecated. Use network.hostname() instead. - req_if = ESP_IF_WIFI_STA; - val = mp_obj_new_str(mod_network_hostname, strlen(mod_network_hostname)); - break; - } - case MP_QSTR_max_clients: { - val = MP_OBJ_NEW_SMALL_INT(cfg.ap.max_connection); - break; - } - case MP_QSTR_reconnects: - req_if = ESP_IF_WIFI_STA; - int rec = conf_wifi_sta_reconnects - 1; - val = MP_OBJ_NEW_SMALL_INT(rec); - break; - case MP_QSTR_txpower: { - int8_t power; - esp_exceptions(esp_wifi_get_max_tx_power(&power)); - val = mp_obj_new_float(power * 0.25); - break; - } - case MP_QSTR_protocol: { - uint8_t protocol_bitmap; - esp_exceptions(esp_wifi_get_protocol(self->if_id, &protocol_bitmap)); - val = MP_OBJ_NEW_SMALL_INT(protocol_bitmap); - break; - } - case MP_QSTR_pm: { - wifi_ps_type_t ps_type; - esp_exceptions(esp_wifi_get_ps(&ps_type)); - val = MP_OBJ_NEW_SMALL_INT(ps_type); - break; - } - default: - goto unknown; - } - - // We post-check interface requirements to save on code size - if (req_if >= 0) { - require_if(args[0], req_if); - } - - return val; - -unknown: - mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); -} -MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_config_obj, 1, network_wlan_config); - -STATIC const mp_rom_map_elem_t wlan_if_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_wlan_active_obj) }, - { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_wlan_connect_obj) }, - { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_wlan_disconnect_obj) }, - { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_wlan_status_obj) }, - { MP_ROM_QSTR(MP_QSTR_scan), MP_ROM_PTR(&network_wlan_scan_obj) }, - { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_wlan_isconnected_obj) }, - { MP_ROM_QSTR(MP_QSTR_config), MP_ROM_PTR(&network_wlan_config_obj) }, - { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&esp_network_ifconfig_obj) }, - - // Constants - { MP_ROM_QSTR(MP_QSTR_PM_NONE), MP_ROM_INT(WIFI_PS_NONE) }, - { MP_ROM_QSTR(MP_QSTR_PM_PERFORMANCE), MP_ROM_INT(WIFI_PS_MIN_MODEM) }, - { MP_ROM_QSTR(MP_QSTR_PM_POWERSAVE), MP_ROM_INT(WIFI_PS_MAX_MODEM) }, -}; -STATIC MP_DEFINE_CONST_DICT(wlan_if_locals_dict, wlan_if_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - esp_network_wlan_type, - MP_QSTR_WLAN, - MP_TYPE_FLAG_NONE, - make_new, network_wlan_make_new, - locals_dict, &wlan_if_locals_dict - ); - -#endif // MICROPY_PY_NETWORK_WLAN diff --git a/tulip/esp32s3/ppp_set_auth.c b/tulip/esp32s3/ppp_set_auth.c deleted file mode 100644 index 88ab668d4..000000000 --- a/tulip/esp32s3/ppp_set_auth.c +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// The pppapi_set_auth function was made static in the ESP-IDF, so it's re-added here. -// See ESP-IDF commit c67f4c2b4c2bb4b7740f988fc0f8a3e911e56afe - -#include "ppp_set_auth.h" - -#ifdef CONFIG_ESP_NETIF_TCPIP_LWIP - -#include "netif/ppp/pppapi.h" - -typedef struct { - struct tcpip_api_call_data call; - ppp_pcb *ppp; - u8_t authtype; - const char *user; - const char *passwd; -} set_auth_msg_t; - -static err_t pppapi_do_ppp_set_auth(struct tcpip_api_call_data *m) { - set_auth_msg_t *msg = (set_auth_msg_t *)m; - ppp_set_auth(msg->ppp, msg->authtype, msg->user, msg->passwd); - return ERR_OK; -} - -void pppapi_set_auth(ppp_pcb *pcb, u8_t authtype, const char *user, const char *passwd) { - set_auth_msg_t msg = { .ppp = pcb, .authtype = authtype, .user = user, .passwd = passwd}; - tcpip_api_call(pppapi_do_ppp_set_auth, &msg.call); -} - -#endif diff --git a/tulip/esp32s3/ppp_set_auth.h b/tulip/esp32s3/ppp_set_auth.h deleted file mode 100644 index 67676ef4d..000000000 --- a/tulip/esp32s3/ppp_set_auth.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// The pppapi_set_auth function was made static in the ESP-IDF, so it's re-added here. -// See ESP-IDF commit c67f4c2b4c2bb4b7740f988fc0f8a3e911e56afe - -#pragma once - -#include "esp_netif.h" - -#ifdef CONFIG_ESP_NETIF_TCPIP_LWIP - -#include "lwip/netif.h" - -typedef struct ppp_pcb_s ppp_pcb; - -void pppapi_set_auth(ppp_pcb *pcb, u8_t authtype, const char *user, const char *passwd); - -#endif diff --git a/tulip/esp32s3/tdeck_keyboard.c b/tulip/esp32s3/tdeck_keyboard.c index c84e5b525..7ec1d5ec9 100644 --- a/tulip/esp32s3/tdeck_keyboard.c +++ b/tulip/esp32s3/tdeck_keyboard.c @@ -11,7 +11,7 @@ #include "freertos/task.h" #include "freertos/queue.h" #include "driver/gpio.h" -#include "usb_keyboard.h" +#include "usb_host.h" #define TIMEOUT_MS 10 diff --git a/tulip/esp32s3/uart.c b/tulip/esp32s3/uart.c index 45a8d8e13..d32b50948 100644 --- a/tulip/esp32s3/uart.c +++ b/tulip/esp32s3/uart.c @@ -1,4 +1,3 @@ - /* * This file is part of the MicroPython project, http://micropython.org/ * @@ -27,18 +26,20 @@ * THE SOFTWARE. */ + #include "py/runtime.h" #include "py/mphal.h" #include "uart.h" -#include "soc/uart_periph.h" -#include "display.h" -//#if MICROPY_HW_ENABLE_UART_REPL + +#if MICROPY_HW_ENABLE_UART_REPL #include #include "driver/uart.h" // For uart_get_sclk_freq() #include "hal/uart_hal.h" +#include "soc/uart_periph.h" +#include "display.h" -STATIC void uart_irq_handler(void *arg); +static void uart_irq_handler(void *arg); // Declaring the HAL structure on the stack saves a tiny amount of static RAM #define REPL_HAL_DEFN() { .dev = UART_LL_GET_HW(MICROPY_HW_UART_REPL) } @@ -51,7 +52,7 @@ STATIC void uart_irq_handler(void *arg); void uart_stdout_init(void) { uart_hal_context_t repl_hal = REPL_HAL_DEFN(); - #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) uart_sclk_t sclk; #else soc_module_clk_t sclk; @@ -82,8 +83,33 @@ void uart_stdout_init(void) { } +int uart_stdout_tx_strn(const char *str, size_t len) { + if(len) { + display_tfb_str((unsigned char*)str, len, 0, tfb_fg_pal_color, tfb_bg_pal_color); + } + uart_hal_context_t repl_hal = REPL_HAL_DEFN(); + size_t remaining = len; + uint32_t written = 0; + // TODO add a timeout + for (;;) { + uart_hal_write_txfifo(&repl_hal, (const void *)str, remaining, &written); + + if (written >= remaining) { + break; + } + remaining -= written; + str += written; + ulTaskNotifyTake(pdFALSE, 1); + } + //if(len) { + // display_tfb_str((unsigned char*)str, len, 0, tfb_fg_pal_color, tfb_bg_pal_color); + //} + return len; +} + + // all code executed in ISR must be in IRAM, and any const data must be in DRAM -STATIC void IRAM_ATTR uart_irq_handler(void *arg) { +static void IRAM_ATTR uart_irq_handler(void *arg) { uint8_t rbuf[SOC_UART_FIFO_LEN]; int len; uart_hal_context_t repl_hal = REPL_HAL_DEFN(); @@ -104,89 +130,7 @@ STATIC void IRAM_ATTR uart_irq_handler(void *arg) { } } -/* +#endif // MICROPY_HW_ENABLE_UART_REPL -void uart_midi_init(void) { - uart_hal_context_t midi_hal = MIDI_HAL_DEFN(); - uint32_t sclk_freq; - - #if UART_SCLK_DEFAULT == SOC_MOD_CLK_APB - sclk_freq = APB_CLK_FREQ; // Assumes no frequency scaling - #else - // ESP32-H2 and ESP32-C2, I think - #error "This SoC uses a different default UART SCLK source, code needs updating." - #endif - - uart_hal_init(&midi_hal, MICROPY_HW_UART_MIDI); // Sets defaults: 8n1, no flow control - uart_hal_set_baudrate(&midi_hal, MICROPY_HW_UART_MIDI_BAUD, sclk_freq); - uart_hal_rxfifo_rst(&midi_hal); - uart_hal_txfifo_rst(&midi_hal); - - ESP_ERROR_CHECK( - esp_intr_alloc(uart_periph_signal[MICROPY_HW_UART_MIDI].irq, - ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, - uart_midi_irq_handler, - NULL, - NULL) - ); - - // Enable RX interrupts - uart_hal_set_rxfifo_full_thr(&midi_hal, RXFIFO_FULL_THR); - uart_hal_set_rx_timeout(&midi_hal, RXFIFO_RX_TIMEOUT); - uart_hal_ena_intr_mask(&midi_hal, UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT); -} - - -#include "midi.h" -// all code executed in ISR must be in IRAM, and any const data must be in DRAM -STATIC void IRAM_ATTR uart_midi_irq_handler(void *arg) { - uint8_t rbuf[SOC_UART_FIFO_LEN]; - int len; - uart_hal_context_t midi_hal = MIDI_HAL_DEFN(); - - uart_hal_clr_intsts_mask(&midi_hal, UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT | UART_INTR_FRAM_ERR); - - len = uart_hal_get_rxfifo_len(&midi_hal); - - uart_hal_read_rxfifo(&midi_hal, rbuf, &len); - - for (int i = 0; i < len; i++) { - ringbuf_put(&midi_ringbuf, rbuf[i]); - } - -} -*/ - - -int uart_stdout_tx_strn(const char *str, size_t len) { - uart_hal_context_t repl_hal = REPL_HAL_DEFN(); - size_t remaining = len; - uint32_t written = 0; - // TODO add a timeout - for (;;) { - uart_hal_write_txfifo(&repl_hal, (const void *)str, remaining, &written); - - if (written >= remaining) { - break; - } - remaining -= written; - str += written; - ulTaskNotifyTake(pdFALSE, 1); - } - if(len) { - display_tfb_str((unsigned char*)str, len, 0, tfb_fg_pal_color, tfb_bg_pal_color); - } - return len; -} -/* - -int uart_stdout_tx_strn(const char *str, size_t len) { - if(len) { - display_tfb_str((unsigned char*)str, len, 0, tfb_fg_pal_color, tfb_bg_pal_color); - } - ulTaskNotifyTake(pdFALSE, 1); - return 0; -} -*/ diff --git a/tulip/esp32s3/usb.c b/tulip/esp32s3/usb.c deleted file mode 100644 index 83ba9533d..000000000 --- a/tulip/esp32s3/usb.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Damien P. George - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mphal.h" -#include "usb.h" - -#if CONFIG_USB_OTG_SUPPORTED - -#include "esp_timer.h" -#ifndef NO_QSTR -#include "tinyusb.h" -#include "tusb_cdc_acm.h" -#endif - -#define CDC_ITF TINYUSB_CDC_ACM_0 - -static uint8_t usb_rx_buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE]; - -static void usb_callback_rx(int itf, cdcacm_event_t *event) { - // TODO: what happens if more chars come in during this function, are they lost? - for (;;) { - size_t len = 0; - esp_err_t ret = tinyusb_cdcacm_read(itf, usb_rx_buf, sizeof(usb_rx_buf), &len); - if (ret != ESP_OK) { - break; - } - if (len == 0) { - break; - } - for (size_t i = 0; i < len; ++i) { - if (usb_rx_buf[i] == mp_interrupt_char) { - mp_sched_keyboard_interrupt(); - } else { - ringbuf_put(&stdin_ringbuf, usb_rx_buf[i]); - } - } - } -} - -void usb_init(void) { - // Initialise the USB with defaults. - tinyusb_config_t tusb_cfg = {0}; - ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); - - // Initialise the USB serial interface. - tinyusb_config_cdcacm_t amc_cfg = { - .usb_dev = TINYUSB_USBDEV_0, - .cdc_port = CDC_ITF, - .rx_unread_buf_sz = 256, - .callback_rx = &usb_callback_rx, - .callback_rx_wanted_char = NULL, - .callback_line_state_changed = NULL, - .callback_line_coding_changed = NULL - }; - ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); - -} - -void usb_tx_strn(const char *str, size_t len) { - // Write out the data to the CDC interface, but only while the USB host is connected. - uint64_t timeout = esp_timer_get_time() + (uint64_t)(MICROPY_HW_USB_CDC_TX_TIMEOUT_MS * 1000); - while (tud_cdc_n_connected(CDC_ITF) && len && esp_timer_get_time() < timeout) { - size_t l = tinyusb_cdcacm_write_queue(CDC_ITF, (uint8_t *)str, len); - str += l; - len -= l; - tud_cdc_n_write_flush(CDC_ITF); - } -} - -#endif // CONFIG_USB_OTG_SUPPORTED diff --git a/tulip/esp32s3/usb.h b/tulip/esp32s3/usb.h deleted file mode 100644 index a4c7d4070..000000000 --- a/tulip/esp32s3/usb.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Damien P. George - * - * 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. - */ -#ifndef MICROPY_INCLUDED_ESP32_USB_H -#define MICROPY_INCLUDED_ESP32_USB_H - -#define MICROPY_HW_USB_CDC_TX_TIMEOUT_MS (500) - -void usb_init(void); -void usb_tx_strn(const char *str, size_t len); - -#endif // MICROPY_INCLUDED_ESP32_USB_H diff --git a/tulip/esp32s3/usb_host.c b/tulip/esp32s3/usb_host.c new file mode 100644 index 000000000..96684ef84 --- /dev/null +++ b/tulip/esp32s3/usb_host.c @@ -0,0 +1,896 @@ +// usb_host.c +#include "usb_host.h" + +// Turn this on if you want more USB debug info +#define DEBUG_USB + +typedef union { + struct { + uint8_t bLength; /**< Size of the descriptor in bytes */ + uint8_t bDescriptorType; /**< Constant name specifying type of HID descriptor. */ + uint16_t bcdHID; /**< USB HID Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H) */ + uint8_t bCountryCode; /**< Numeric expression identifying country code of the localized hardware. */ + uint8_t bNumDescriptor; /**< Numeric expression specifying the number of class descriptors. */ + uint8_t bHIDDescriptorType; /**< Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants. */ + uint16_t wHIDDescriptorLength; /**< Numeric expression that is the total size of the Report descriptor. */ + uint8_t bHIDDescriptorTypeOpt; /**< Optional constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants. */ + uint16_t wHIDDescriptorLengthOpt; /**< Optional numeric expression that is the total size of the Report descriptor. */ + } USB_DESC_ATTR; + uint8_t val[9]; +} usb_hid_desc_t; + + + +#ifdef DEBUG_USB +#define DBGPRINTF(s) printf(s) +#define DBGPRINTF1(s, a) printf(s, a) +#define DBGPRINTF2(s, a, b) printf(s, a, b) +#define DBGPRINTF3(s, a, b, c) printf(s, a, b, c) +#define DBGPRINTF4(s, a, b, c, d) printf(s, a, b, c, d) + +void show_config_desc(const void *p, int indent) +{ + char prefix[8]; + for (int i=0; i < indent; ++i) prefix[i] = '*'; + prefix[indent] = '\0'; + const usb_config_desc_t *config_desc = (const usb_config_desc_t *)p; + fprintf(stderr, "%s CONFIG_DESC(size=%d)\n", prefix, config_desc->bLength); + //fprintf(stderr, "bDescriptorType(config): %d\n", config_desc->bDescriptorType); + fprintf(stderr, "%s wTotalLength: %d\n", prefix, config_desc->wTotalLength); + fprintf(stderr, "%s bNumInterfaces: %d\n", prefix, config_desc->bNumInterfaces); + fprintf(stderr, "%s bConfigurationValue: %d\n", prefix, config_desc->bConfigurationValue); + fprintf(stderr, "%s iConfiguration: %d\n", prefix, config_desc->iConfiguration); + fprintf(stderr, "%s bmAttributes: 0x%02x (%s%s%s)\n", prefix, + config_desc->bmAttributes, + (config_desc->bmAttributes & USB_BM_ATTRIBUTES_SELFPOWER)?"Self Powered, ":"", + (config_desc->bmAttributes & USB_BM_ATTRIBUTES_WAKEUP)?"Remote Wakeup, ":"", + (config_desc->bmAttributes & USB_BM_ATTRIBUTES_BATTERY)?"Battery Powered":""); + fprintf(stderr,"%s bMaxPower: %d (%d mA)\n", prefix, config_desc->bMaxPower, config_desc->bMaxPower*2); +} + +uint8_t show_interface_desc(const void *p, int indent) +{ + char prefix[8]; + for (int i=0; i < indent; ++i) prefix[i] = '*'; + prefix[indent] = '\0'; + const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; + + fprintf(stderr, "%s INTERFACE_DESC(size=%d)\n", prefix, intf->bLength); + //fprintf(stderr, "%s bDescriptorType (interface): %d\n", intf->bDescriptorType); + fprintf(stderr, "%s bInterfaceNumber: %d\n", prefix, intf->bInterfaceNumber); + fprintf(stderr, "%s bAlternateSetting: %d\n", prefix, intf->bAlternateSetting); + fprintf(stderr, "%s bNumEndpoints: %d\n", prefix, intf->bNumEndpoints); + fprintf(stderr, "%s bInterfaceClass: 0x%02x\n", prefix, intf->bInterfaceClass); + fprintf(stderr, "%s bInterfaceSubClass: 0x%02x\n", prefix, intf->bInterfaceSubClass); + fprintf(stderr, "%s bInterfaceProtocol: 0x%02x\n", prefix, intf->bInterfaceProtocol); + fprintf(stderr, "%s iInterface: %d\n", prefix, intf->iInterface); + return intf->bInterfaceClass; +} + +void show_endpoint_desc(const void *p, int indent) +{ + char prefix[8]; + for (int i=0; i < indent; ++i) prefix[i] = '*'; + prefix[indent] = '\0'; + const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; + const char *XFER_TYPE_NAMES[] = { + "Control", "Isochronous", "Bulk", "Interrupt" + }; + fprintf(stderr, "%s ENDPOINT_DESC(size=%d)\n", prefix, endpoint->bLength); + //fprintf(stderr, "%s bDescriptorType (endpoint): %d\n", prefix, endpoint->bDescriptorType); + fprintf(stderr, "%s bEndpointAddress: 0x%02x (%s)\n", prefix, + endpoint->bEndpointAddress, + (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK)?"In":"Out"); + fprintf(stderr, "%s bmAttributes: 0x%02x (%s)\n", prefix, + endpoint->bmAttributes, + XFER_TYPE_NAMES[endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK]); + fprintf(stderr, "%s wMaxPacketSize: %d\n", prefix, endpoint->wMaxPacketSize); + fprintf(stderr, "%s bInterval: %d\n", prefix, endpoint->bInterval); +} + +// end of show_desc.hpp +#else // !DEBUG_USB + +#define DBGPRINTF(s) /* nothing */ +#define DBGPRINTF1(s, a) /* nothing */ +#define DBGPRINTF2(s, a, b) /* nothing */ +#define DBGPRINTF3(s, a, b, c) /* nothing */ +#define DBGPRINTF4(s, a, b, c, d) /* nothing */ + +#define show_config_desc(p, i) /* nothing */ +#define show_interface_desc(p, i) /* nothing */ +#define show_endpoint_desc(p, i) /* nothing */ + +#endif // DEBUG_USB + +#define KEYBOARD_BUFFER_SIZE 64 +#define KEYBOARD_BYTES 8 +#define MOUSE_BYTES 8 + +uint16_t keyboard_bytes = KEYBOARD_BYTES; +uint16_t mouse_bytes = MOUSE_BYTES; + +// How long you hold down a key before it starts repeating +#define KEY_REPEAT_TRIGGER_MS 500 +// How often (in ms) to repeat a key once held +#define KEY_REPEAT_INTER_MS 90 + + +const TickType_t HOST_EVENT_TIMEOUT = 1; +const TickType_t CLIENT_EVENT_TIMEOUT = 1; + +const size_t USB_HID_DESC_SIZE = 9; + +usb_host_client_handle_t Client_Handle; +usb_device_handle_t Device_Handle_kb; +usb_device_handle_t Device_Handle_midi; +usb_device_handle_t Device_Handle_mouse; + +usb_device_handle_t Device_Handle_unknown; // used to reassign after a claim + +uint8_t Interface_Number_kb; +uint8_t Interface_Number_midi; +uint8_t Interface_Number_mouse; + +bool keyboard_claimed = false; +bool keyboard_ready = false; + +bool midi_claimed = false; +bool midi_has_out = false; +bool midi_has_in = false; +bool midi_ready = false; + +bool mouse_claimed = false; +bool mouse_ready = false; + +bool isKeyboardPolling = false; +int64_t KeyboardTimer=0; +uint8_t KeyboardInterval; +usb_transfer_t *KeyboardIn = NULL; + +bool isMousePolling = false; +int64_t MouseTimer=0; +uint8_t MouseInterval; +usb_transfer_t *MouseIn = NULL; + + +// Keep track of which keys / scan codes are being held +int64_t KeyRepeatTimer=0; +uint16_t current_held = 0; +int64_t current_held_ms = 0; +int64_t last_inter_trigger_ms = 0; + + +#define MIDI_IN_BUFFERS 8 +#define MIDI_OUT_BUFFERS 8 + +usb_transfer_t *MIDIOut = NULL; +usb_transfer_t *MIDIIn[MIDI_IN_BUFFERS] = { NULL }; + + +// This identifies a mouse HID report packet for a standard "boot" mouse. +typedef struct { + union { + struct { + uint8_t button1: 1; + uint8_t button2: 1; + uint8_t button3: 1; + uint8_t reserved: 5; + }; + uint8_t val; + } buttons; + int8_t x_displacement; + int8_t y_displacement; +} __attribute__((packed)) hid_mouse_input_report_boot_t; + +int16_t mouse_x_pos = 0; +int16_t mouse_y_pos = 0; +uint8_t mouse_buttons[3]; + + + +// USB MIDI Event Packet Format (always 4 bytes) +// +// Byte 0 |Byte 1 |Byte 2 |Byte 3 +// -------|-------|-------|------ +// CN+CIN |MIDI_0 |MIDI_1 |MIDI_2 +// +// CN = Cable Number ((0x0..0xf)<<4) specifies virtual MIDI jack/cable +// CIN = Code Index Number (0x0..0xf) classifies the 3 MIDI bytes. +// See Table 4-1 in the MIDI 1.0 spec at usb.org. +// + +static void midi_transfer_cb(usb_transfer_t *transfer) { + #ifdef DEBUG_USB + //fprintf(stderr, "**midi_transfer_cb context: %p, num_bytes %d\n", transfer->context, transfer->actual_num_bytes); + #endif + if (Device_Handle_midi == transfer->device_handle) { + int in_xfer = transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; + if ((transfer->status == 0) && in_xfer) { + uint8_t *const p = transfer->data_buffer; + for (int i = 0; i < transfer->actual_num_bytes; i += 4) { + if ((p[i] + p[i + 1] + p[i + 2] + p[i + 3]) == 0) break; + DBGPRINTF4("midi: %02x %02x %02x %02x\n", + p[i], p[i + 1], p[i + 2], p[i + 3]); + // Strip the first byte which is the USB-MIDI virtual wire ID, + // rest is MIDI message (at least for 3-byte messages). + convert_midi_bytes_to_messages(p + i + 1, 3,1); + } + esp_err_t err = usb_host_transfer_submit(transfer); + if (err != ESP_OK) { + fprintf(stderr, "midi usb_host_transfer_submit err: 0x%x\n", err); + } + } else { + //fprintf(stderr, "midi transfer->status %d\n", transfer->status); + } + } +} + + + +bool check_interface_desc_MIDI(const void *p) { + const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; + + // USB MIDI + if ((intf->bInterfaceClass == USB_CLASS_AUDIO) && + (intf->bInterfaceSubClass == 3) && + (intf->bInterfaceProtocol == 0)) { + midi_claimed = true; + fprintf(stderr, "Claiming a MIDI device!\n"); + Interface_Number_midi = intf->bInterfaceNumber; + esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle_unknown, + Interface_Number_midi, intf->bAlternateSetting); + if (err != ESP_OK) fprintf(stderr, "midi usb_host_interface_claim failed: %x\n", err); + return true; + } + return false; +} + + +void send_single_midi_out_packet(uint8_t * data) { // 4 bytes + MIDIOut->data_buffer[0] = data[0]; + MIDIOut->data_buffer[1] = data[1]; + MIDIOut->data_buffer[2] = data[2]; + MIDIOut->data_buffer[3] = data[3]; + MIDIOut->num_bytes = 4; + //fprintf(stderr, "sending midi out packet %02x %02x %02x %02x\n", data[0], data[1], data[2], data[3]); + esp_err_t err = usb_host_transfer_submit(MIDIOut); + if (err != ESP_OK) { + fprintf(stderr, "midi OUT usb_host_transfer_submit err: 0x%x\n", err); + } +} +uint8_t usb_sysex_flag=0; + +void send_usb_midi_out(uint8_t * data, uint16_t len) { + // we have to discern code_index from data. basically a reverse version of our MIDI input parser. + uint8_t usb_midi_packet[4] = {0,0,0,0}; + uint8_t usb_midi_message_slot = 0; + for(size_t i=0;i> 4); // status is code_index for these guys + if(usb_midi_packet[1]==0xF2) usb_midi_packet[0] = 0x03; // special + if(usb_midi_message_slot == 0) { + usb_midi_packet[2] = byte; + usb_midi_message_slot = 1; + } else { + usb_midi_packet[3] = byte; + usb_midi_message_slot = 0; + send_single_midi_out_packet(usb_midi_packet); + } + // a 1 byte data message + } else if (status == 0xC0 || status == 0xD0) { + usb_midi_packet[0] = (status >> 4); + usb_midi_packet[2] = byte; + send_single_midi_out_packet(usb_midi_packet); + i = len+1; + } else if (usb_midi_packet[1] == 0xF3 || usb_midi_packet[1] == 0xF1) { + usb_midi_packet[0] = 0x02; // special + send_single_midi_out_packet(usb_midi_packet); + i = len+1; + } + } + } + } + +} + + +void prepare_endpoint_midi(const void *p) { + const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; + esp_err_t err; + + // must be bulk for MIDI + if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_BULK) { + fprintf(stderr, "USB MIDI: Not bulk endpoint: 0x%02x\n", endpoint->bmAttributes); + return; + } + if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { + // MIDI-IN endpoint. + for (int i = 0; i < MIDI_IN_BUFFERS; i++) { + if (MIDIIn[i] == NULL) { + err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIIn[i]); + if (err != ESP_OK) { + MIDIIn[i] = NULL; + fprintf(stderr, "midi usb_host_transfer_alloc/In err: 0x%x\n", err); + midi_has_out = false; + return; + } + } + if (MIDIIn[i] != NULL) { + MIDIIn[i]->device_handle = Device_Handle_midi; + MIDIIn[i]->bEndpointAddress = endpoint->bEndpointAddress; + MIDIIn[i]->callback = midi_transfer_cb; + MIDIIn[i]->context = (void *)i; + MIDIIn[i]->num_bytes = endpoint->wMaxPacketSize; + esp_err_t err = usb_host_transfer_submit(MIDIIn[i]); + if (err != ESP_OK) { + fprintf(stderr, "midi usb_host_transfer_submit/In err: 0x%x\n", err); + } + midi_has_in = true; + } + } + } else { + // MIDI-OUT endpoint + if (MIDIOut == NULL) { + err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIOut); + if (err != ESP_OK) { + MIDIOut = NULL; + fprintf(stderr, "midi usb_host_transfer_alloc/Out err: 0x%x\n", err); + midi_has_out = false; + return; + } + } + if (MIDIOut != NULL) { + DBGPRINTF1("MIDI USB Out data_buffer_size: %d\n", MIDIOut->data_buffer_size); + MIDIOut->device_handle = Device_Handle_midi; + MIDIOut->bEndpointAddress = endpoint->bEndpointAddress; + MIDIOut->callback = midi_transfer_cb; + MIDIOut->context = NULL; + // MIDIOut->flags |= USB_TRANSFER_FLAG_ZERO_PACK; + midi_has_out = true; + } + } + // MIDI is ready when both input and output endpoints are initialized. + midi_ready = midi_has_in && midi_has_out; +} + +void new_enumeration_config_fn(const usb_config_desc_t *config_desc); + + +void _client_event_callback(const usb_host_client_event_msg_t *event_msg, void *arg) +{ + esp_err_t err; + switch (event_msg->event) + { + /**< A new device has been enumerated and added to the USB Host Library */ + case USB_HOST_CLIENT_EVENT_NEW_DEV: + DBGPRINTF1("New device address: %d\n", event_msg->new_dev.address); + err = usb_host_device_open(Client_Handle, event_msg->new_dev.address, &Device_Handle_unknown); + if (err != ESP_OK) fprintf(stderr, "usb_host_device_open: 0x%x\n", err); + + usb_device_info_t dev_info; + err = usb_host_device_info(Device_Handle_unknown, &dev_info); + if (err != ESP_OK) fprintf(stderr, "usb_host_device_info: 0x%x\n", err); + //printf("speed: %d dev_addr %d vMaxPacketSize0 %d bConfigurationValue %d\n", + // dev_info.speed, dev_info.dev_addr, dev_info.bMaxPacketSize0, + // dev_info.bConfigurationValue); + + const usb_device_desc_t *dev_desc; + err = usb_host_get_device_descriptor(Device_Handle_unknown, &dev_desc); + if (err != ESP_OK) fprintf(stderr, "usb_host_get_device_desc: 0x%x\n", err); + + const usb_config_desc_t *config_desc; + err = usb_host_get_active_config_descriptor(Device_Handle_unknown, &config_desc); + if (err != ESP_OK) fprintf(stderr,"usb_host_get_config_desc: 0x%x\n", err); + // Finally, we get to inspect the new device and maybe connect to it. + new_enumeration_config_fn(config_desc); + break; + + /**< A device opened by the client is now gone */ + case USB_HOST_CLIENT_EVENT_DEV_GONE: + fprintf(stderr,"Device Gone handle: 0x%lx\n", (uint32_t)event_msg->dev_gone.dev_hdl); + // Mark everything de-initialized so it will re-initialized on another connect. + esp_err_t err; + if (midi_claimed && (uint32_t)Device_Handle_midi == (uint32_t)event_msg->dev_gone.dev_hdl) { + err = usb_host_interface_release(Client_Handle, Device_Handle_midi, Interface_Number_midi); + if (err != ESP_OK) fprintf(stderr,"usb_host_interface_release err: 0x%x\n", err); + midi_claimed = false; + midi_ready = false; + midi_has_in = false; + midi_has_out = false; + } + if (keyboard_claimed && (uint32_t)Device_Handle_kb == (uint32_t)event_msg->dev_gone.dev_hdl) { + err = usb_host_interface_release(Client_Handle, Device_Handle_kb, Interface_Number_kb); + if (err != ESP_OK) fprintf(stderr,"usb_host_interface_release err: 0x%x\n", err); + keyboard_claimed = false; + keyboard_ready = false; + } + if (mouse_claimed && (uint32_t)Device_Handle_mouse == (uint32_t)event_msg->dev_gone.dev_hdl) { + err = usb_host_interface_release(Client_Handle, Device_Handle_mouse, Interface_Number_mouse); + if (err != ESP_OK) fprintf(stderr,"usb_host_interface_release err: 0x%x\n", err); + mouse_claimed = false; + mouse_ready = false; + disable_mouse_pointer(); + } + err = usb_host_device_close(Client_Handle, Device_Handle_unknown); + if (err != ESP_OK) fprintf(stderr,"usb_host_device_close err: 0x%x\n", err); + break; + default: + fprintf(stderr,"Unknown USB event: %d\n", event_msg->event); + break; + } +} + +// Reference: esp-idf/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c + +void usbh_setup() +{ + const usb_host_config_t config = { + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + esp_err_t err = usb_host_install(&config); + (void)err; + DBGPRINTF1("usb_host_install: 0x%x\n", err); + + const usb_host_client_config_t client_config = { + .is_synchronous = false, + .max_num_event_msg = 5, + .async = { + .client_event_callback = _client_event_callback, + .callback_arg = Client_Handle + } + }; + err = usb_host_client_register(&client_config, &Client_Handle); + DBGPRINTF1("usb_host_client_register: 0x%x\n", err); +} + +void usbh_task(void) +{ + uint32_t event_flags; + + esp_err_t err = usb_host_lib_handle_events(HOST_EVENT_TIMEOUT, &event_flags); + if (err == ESP_OK) { + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + DBGPRINTF("No more clients\n"); + } + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + DBGPRINTF("No more devices\n"); + } + } else { + if (err != ESP_ERR_TIMEOUT) { + fprintf(stderr,"usb_host_lib_handle_events: 0x%x flags: %lx\n", err, event_flags); + } + } + + err = usb_host_client_handle_events(Client_Handle, CLIENT_EVENT_TIMEOUT); + if ((err != ESP_OK) && (err != ESP_ERR_TIMEOUT)) { + fprintf(stderr,"usb_host_client_handle_events: 0x%x\n", err); + } +} + + + +static char lvgl_kb_buf[KEYBOARD_BUFFER_SIZE]; + +void lvgl_keyboard_read(lv_indev_t * indev_drv, lv_indev_data_t * data) { + (void) indev_drv; // unused + static bool dummy_read = false; + const size_t len = strlen(lvgl_kb_buf); + + // Send a release manually + if (dummy_read) { + dummy_read = false; + data->state = LV_INDEV_STATE_RELEASED; + data->continue_reading = len > 0; + } + // Send the pressed character + else if (len > 0) { + dummy_read = true; + data->state = LV_INDEV_STATE_PRESSED; + data->key = lvgl_kb_buf[0]; + memmove(lvgl_kb_buf, lvgl_kb_buf + 1, len); + data->continue_reading = true; + } +} + + +void decode_keyboard_report(uint8_t *p) { + // First byte, modifier mask + uint8_t modifier = p[0]; + uint8_t new_key = 0; + uint8_t key_held_this_session = 0; + // Second byte, reserved + // next 6 bytes, scan codes (for rollover) + + #ifdef DEBUG_USB + //fprintf(stderr,"decode report %d %d %d %d %d %d\n", p[2],p[3],p[4],p[5],p[6],p[7]); + #endif + for(uint8_t i=2;i<8;i++) { + if(p[i]!=0) { + key_held_this_session = 1; + uint8_t skip = 0; + for(uint8_t j=2;j<8;j++) { + if(last_scan[j] == p[i]) skip = 1; + } + if(!skip) { // only process new keys + uint16_t c = scan_ascii(p[i], modifier); + if(c) { + if(keycode_to_ctrl_key(c) != '\0') { + const size_t len = strlen(lvgl_kb_buf); + if (len < KEYBOARD_BUFFER_SIZE - 1) { + lvgl_kb_buf[len] = keycode_to_ctrl_key(c); + lvgl_kb_buf[len + 1] = '\0'; + } + } else { + // put it in lvgl_kb_buf for lvgl + const size_t len = strlen(lvgl_kb_buf); + if (len < KEYBOARD_BUFFER_SIZE - 1) { + lvgl_kb_buf[len] = c; + lvgl_kb_buf[len+1] = '\0'; + } + } + new_key = 1; + current_held_ms = esp_timer_get_time()/1000; + current_held = c; + send_key_to_micropython(c); + } + } + } + } + if(!new_key && !key_held_this_session) { + // we got a message but no new keys. so is a release + current_held_ms = 0; + current_held = 0; + last_inter_trigger_ms = 0; + } + for(uint8_t i=0;i<8;i++) last_scan[i] = p[i]; +} + +#define CHECK_BIT(var,pos) ((var) & (1<<(pos))) +void keyboard_transfer_cb(usb_transfer_t *transfer) +{ + if (Device_Handle_kb == transfer->device_handle) { + isKeyboardPolling = false; + if (transfer->status == 0) { + #ifdef DEBUG_USB + //fprintf(stderr, "nb is %d\n", transfer->actual_num_bytes); + #endif + if (transfer->actual_num_bytes == 8 || transfer->actual_num_bytes == 16) { + uint8_t *const p = transfer->data_buffer; + decode_keyboard_report(p); + } else if (transfer->actual_num_bytes == 10) { + uint8_t *const p = transfer->data_buffer; + decode_keyboard_report(p+1); + } else if (transfer->actual_num_bytes == 17 || transfer->actual_num_bytes == 32) { + // This is a weird keyboard (8bitdo retro, custom 40 key ortho) that uses USB FS HID and sends keys as a bitmask. + // I found people talking about this here but no code, so here i am https://stackoverflow.com/questions/57793525/unusual-usb-hid-reports + uint8_t *const p = transfer->data_buffer; + // We treat each bit on position as a index into a scan code, and will add it to a new buffer for decode_report to deal with + uint8_t new_decode[8]; + for(uint8_t i=0;i<8;i++) new_decode[i] = 0; + new_decode[0] = p[1]; // modifier goes here + uint8_t bit_count = 0; + uint8_t rollover_count = 2; // start writing as pos 2 + // p[0] has the length (starting from the modifier) + for(uint8_t i=2;iactual_num_bytes); + } + } + else { + fprintf(stderr, "kbd transfer->status %d\n", transfer->status); + } + } +} + + +void mouse_transfer_cb(usb_transfer_t *transfer) +{ + //fprintf(stderr, "mouse nb is %d\n", transfer->actual_num_bytes); + //uint8_t *p = transfer->data_buffer; + //fprintf(stderr,"mouse decode report %d %d %d %d %d %d %d %d\n", p[0], p[1], p[2],p[3],p[4],p[5],p[6],p[7]); + + + if (Device_Handle_mouse == transfer->device_handle) { + isMousePolling = false; + if (transfer->status == 0) { + hid_mouse_input_report_boot_t *mouse_report = (hid_mouse_input_report_boot_t *)(transfer->data_buffer+1); + mouse_x_pos += mouse_report->x_displacement; + mouse_y_pos += mouse_report->y_displacement; + if(mouse_x_pos >= H_RES) mouse_x_pos = H_RES-1; + if(mouse_y_pos >= V_RES) mouse_y_pos = V_RES-1; + if(mouse_x_pos < 0) mouse_x_pos = 0; + if(mouse_y_pos < 0) mouse_y_pos = 0; + mouse_buttons[0] = mouse_report->buttons.button1; + mouse_buttons[1] = mouse_report->buttons.button2; + mouse_buttons[2] = mouse_report->buttons.button3; + last_touch_x[0] = mouse_x_pos; + last_touch_y[0] = mouse_y_pos; + enable_mouse_pointer();// this is a no-op if already installed + if(mouse_buttons[0]) { + send_touch_to_micropython(mouse_x_pos, mouse_y_pos, 0); + } else { + send_touch_to_micropython(mouse_x_pos, mouse_y_pos, 1); + } + } + else { + fprintf(stderr, "mouse transfer->status %d\n", transfer->status); + } + } +} + +bool check_interface_desc_boot_keyboard(const void *p) { + const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; + if ((intf->bInterfaceClass == USB_CLASS_HID) && + (intf->bInterfaceSubClass == 1) && + (intf->bInterfaceProtocol == 1)) { + keyboard_claimed = true; + Interface_Number_kb = intf->bInterfaceNumber; + esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle_unknown, + Interface_Number_kb, intf->bAlternateSetting); + if (err != ESP_OK) fprintf(stderr, "usb_host_interface_claim failed: 0x%x\n", err); + return true; + } + return false; +} + +bool check_interface_desc_boot_mouse(const void *p) { + const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; + if ((intf->bInterfaceClass == USB_CLASS_HID) && + (intf->bInterfaceSubClass == 1) && + (intf->bInterfaceProtocol == 2)) { + mouse_claimed = true; + Interface_Number_mouse = intf->bInterfaceNumber; + esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle_unknown, + Interface_Number_mouse, intf->bAlternateSetting); + if (err != ESP_OK) fprintf(stderr, "usb_host_interface_claim failed: 0x%x\n", err); + return true; + } else if ((intf->bInterfaceClass == USB_CLASS_HID) && + (intf->bInterfaceSubClass == 0) && + (intf->bInterfaceProtocol == 0)) { + fprintf(stderr, "We are using a different type of mouse claim\n"); + mouse_claimed = true; + Interface_Number_mouse = intf->bInterfaceNumber; + esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle_unknown, + Interface_Number_mouse, intf->bAlternateSetting); + if (err != ESP_OK) fprintf(stderr, "usb_host_interface_claim failed: 0x%x\n", err); + return true; + } + return false; +} + +void prepare_endpoint_hid_kb(const void *p) +{ + const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; + esp_err_t err; + keyboard_bytes = usb_round_up_to_mps(KEYBOARD_BYTES, endpoint->wMaxPacketSize); + DBGPRINTF2("Setting KB to %d from MPS %d\n", keyboard_bytes, endpoint->wMaxPacketSize); + + // must be interrupt for HID + if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) { + fprintf(stderr, "Kbd: Not interrupt endpoint: 0x%02x\n", endpoint->bmAttributes); + return; + } + if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { + if (KeyboardIn == NULL) { + err = usb_host_transfer_alloc(keyboard_bytes, 0, &KeyboardIn); + if (err != ESP_OK) { + KeyboardIn = NULL; + fprintf(stderr, "kbd usb_host_transfer_alloc/In err: 0x%x\n", err); + return; + } + } + if (KeyboardIn != NULL) { + KeyboardIn->device_handle = Device_Handle_kb; + KeyboardIn->bEndpointAddress = endpoint->bEndpointAddress; + KeyboardIn->callback = keyboard_transfer_cb; + KeyboardIn->context = NULL; + keyboard_ready = true; + KeyboardInterval = endpoint->bInterval; + DBGPRINTF("USB boot keyboard ready\n"); + } + } else { + DBGPRINTF("Ignoring kbd interrupt Out endpoint\n"); + } +} +void prepare_endpoint_hid_mouse(const void *p) +{ + const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; + esp_err_t err; + mouse_bytes = usb_round_up_to_mps(MOUSE_BYTES, endpoint->wMaxPacketSize); + DBGPRINTF2("Setting mouse or 0x03/0/0 to %d from MPS %d\n", mouse_bytes, endpoint->wMaxPacketSize); + if(mouse_bytes==32) { + fprintf(stderr, "Redirecting this endpoint to KB (0x03/0/0)\n"); + KeyboardIn = NULL; + MouseIn = NULL; + prepare_endpoint_hid_kb(p); + mouse_claimed = false; + } else { + // must be interrupt for HID + if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) { + fprintf(stderr, "Mouse: Not interrupt endpoint: 0x%02x\n", endpoint->bmAttributes); + return; + } + if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { + if (MouseIn == NULL) { + err = usb_host_transfer_alloc(mouse_bytes, 0, &MouseIn); + if (err != ESP_OK) { + MouseIn = NULL; + fprintf(stderr, "mouse usb_host_transfer_alloc/In err: 0x%x\n", err); + return; + } + } + if (MouseIn != NULL) { + MouseIn->device_handle = Device_Handle_mouse; + MouseIn->bEndpointAddress = endpoint->bEndpointAddress; + MouseIn->callback = mouse_transfer_cb; + MouseIn->context = NULL; + mouse_ready = true; + mouse_x_pos = H_RES/2; + mouse_y_pos = V_RES/2; + MouseInterval = endpoint->bInterval; + DBGPRINTF("USB boot mouse ready\n"); + } + } else { + DBGPRINTF("Ignoring mouse interrupt Out endpoint\n"); + } + } +} + +void new_enumeration_config_fn(const usb_config_desc_t *config_desc) { + // We just retrieved the config of a newly-connected device. + // Read through it and see if we can claim a recognized device. + // + // &config_desc->val[0] is the same as config_desc + // so the first "descriptor type" found is actually TYPE_CONFIGURATION + // and the call to show_config_desc(p) is equivalent to doing + // show_config_desc(config_desc) here. + + + const uint8_t *p = &config_desc->val[0]; + uint8_t bLength; + int indent = 1; + int last_descriptor = -1; + for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) { + bLength = *p; + if ((i + bLength) <= config_desc->wTotalLength) { + const uint8_t bDescriptorType = *(p + 1); + DBGPRINTF3("config_desc->val[%d]: type=%d length=%d\n", i, bDescriptorType, bLength); + switch (bDescriptorType) { + case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: + // The first record in val[] is by definition the config description. + show_config_desc(p, indent++); + last_descriptor = bDescriptorType; + break; + case USB_B_DESCRIPTOR_TYPE_DEVICE: + DBGPRINTF("USB Device Descriptor should not appear in config\n"); + break; + case USB_B_DESCRIPTOR_TYPE_STRING: + DBGPRINTF("USB string desc TBD\n"); + break; + case USB_B_DESCRIPTOR_TYPE_INTERFACE: + if ((last_descriptor == USB_B_DESCRIPTOR_TYPE_INTERFACE) + || (last_descriptor == USB_B_DESCRIPTOR_TYPE_ENDPOINT)) --indent; + show_interface_desc(p, indent++); + if(!midi_claimed) { check_interface_desc_MIDI(p); if(midi_claimed) Device_Handle_midi = Device_Handle_unknown; } + if(!keyboard_claimed) { check_interface_desc_boot_keyboard(p);if(keyboard_claimed) Device_Handle_kb = Device_Handle_unknown;} + if(!mouse_claimed) { check_interface_desc_boot_mouse(p);if(mouse_claimed) Device_Handle_mouse = Device_Handle_unknown;} + last_descriptor = bDescriptorType; + break; + case USB_B_DESCRIPTOR_TYPE_ENDPOINT: + show_endpoint_desc(p, indent); + if (keyboard_claimed && !keyboard_ready) { + prepare_endpoint_hid_kb(p); + } + if (mouse_claimed && !mouse_ready) { + prepare_endpoint_hid_mouse(p); + } + if (midi_claimed && !midi_ready) { + prepare_endpoint_midi(p); + } + last_descriptor = bDescriptorType; + break; + case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: + DBGPRINTF("USB device qual desc TBD\n"); + break; + case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION: + DBGPRINTF("USB Other Speed TBD\n"); + break; + case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER: + DBGPRINTF("USB Interface Power TBD\n"); + break; + case 0x21: + // HID + break; + case 0x24: + // UAS PIPE mode - see this for MIDI keyboard. + break; + case 0x25: + // Also see this for MIDI keyboard. + break; + default: + fprintf(stderr, "Unknown USB Descriptor Type: 0x%x\n", bDescriptorType); + break; + } + } + else { + fprintf(stderr, "USB Descriptor invalid\n"); + return; + } + } +} + + +void run_usb() +{ + // Reset key maps + for(uint8_t i=0;i 0) { + if(KeyRepeatTimer - current_held_ms > KEY_REPEAT_TRIGGER_MS) { + if(KeyRepeatTimer - last_inter_trigger_ms > KEY_REPEAT_INTER_MS) { + send_key_to_micropython(current_held); + last_inter_trigger_ms = KeyRepeatTimer; + } + } + } + + if (keyboard_ready && !isKeyboardPolling && (KeyboardTimer > KeyboardInterval)) { + KeyboardIn->num_bytes = keyboard_bytes; + esp_err_t err = usb_host_transfer_submit(KeyboardIn); + if (err != ESP_OK) { + fprintf(stderr,"kbd usb_host_transfer_submit/In err: 0x%x\n", err); + } + isKeyboardPolling = true; + KeyboardTimer = 0; + } + if (mouse_ready && !isMousePolling && (MouseTimer > MouseInterval)) { + MouseIn->num_bytes = mouse_bytes; + esp_err_t err = usb_host_transfer_submit(MouseIn); + if (err != ESP_OK) { + fprintf(stderr, "mouse usb_host_transfer_submit/In err: 0x%x\n", err); + } + isMousePolling = true; + MouseTimer = 0; + } + } +} diff --git a/tulip/esp32s3/usb_keyboard.h b/tulip/esp32s3/usb_host.h similarity index 56% rename from tulip/esp32s3/usb_keyboard.h rename to tulip/esp32s3/usb_host.h index 52cd213d0..73c1a5ce3 100644 --- a/tulip/esp32s3/usb_keyboard.h +++ b/tulip/esp32s3/usb_host.h @@ -1,4 +1,4 @@ -// usb_keyboard.h +// usb_host.h #ifndef __USB_KEYBOARD_H__ #define __USB_KEYBOARD_H__ @@ -12,19 +12,8 @@ #include "keyscan.h" #include "tulip_helpers.h" #include "esp_timer.h" +#include "display.h" #include "midi.h" // from extmod/tulip/ -#include "lvgl.h" - - -// vortex 8 -// keychron 16 -#define KEYBOARD_BYTES 8 -extern uint16_t keyboard_bytes; - -// How long you hold down a key before it starts repeating -#define KEY_REPEAT_TRIGGER_MS 500 -// How often (in ms) to repeat a key once held -#define KEY_REPEAT_INTER_MS 90 void usbh_setup(); void run_usb(); diff --git a/tulip/esp32s3/usb_keyboard.c b/tulip/esp32s3/usb_keyboard.c deleted file mode 100644 index 738e52555..000000000 --- a/tulip/esp32s3/usb_keyboard.c +++ /dev/null @@ -1,689 +0,0 @@ -// usb_keyboard.c -#include "usb_keyboard.h" - -#define KEYBOARD_BUFFER_SIZE 32 - -uint16_t keyboard_bytes = KEYBOARD_BYTES; - -typedef union { - struct { - uint8_t bLength; /**< Size of the descriptor in bytes */ - uint8_t bDescriptorType; /**< Constant name specifying type of HID descriptor. */ - uint16_t bcdHID; /**< USB HID Specification Release Number in Binary-Coded Decimal (i.e., 2.10 is 210H) */ - uint8_t bCountryCode; /**< Numeric expression identifying country code of the localized hardware. */ - uint8_t bNumDescriptor; /**< Numeric expression specifying the number of class descriptors. */ - uint8_t bHIDDescriptorType; /**< Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants. */ - uint16_t wHIDDescriptorLength; /**< Numeric expression that is the total size of the Report descriptor. */ - uint8_t bHIDDescriptorTypeOpt; /**< Optional constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants. */ - uint16_t wHIDDescriptorLengthOpt; /**< Optional numeric expression that is the total size of the Report descriptor. */ - } USB_DESC_ATTR; - uint8_t val[9]; -} usb_hid_desc_t; - -//#define DEBUG_USB -#ifdef DEBUG_USB -// from show_desc.hpp -// from https://github.com/touchgadget/esp32-usb-host-demos - -#define DBGPRINTF(s) printf(s) -#define DBGPRINTF1(s, a) printf(s, a) -#define DBGPRINTF2(s, a, b) printf(s, a, b) -#define DBGPRINTF3(s, a, b, c) printf(s, a, b, c) -#define DBGPRINTF4(s, a, b, c, d) printf(s, a, b, c, d) - -void show_config_desc(const void *p, int indent) -{ - char prefix[8]; - for (int i=0; i < indent; ++i) prefix[i] = '*'; - prefix[indent] = '\0'; - const usb_config_desc_t *config_desc = (const usb_config_desc_t *)p; - fprintf(stderr, "%s CONFIG_DESC(size=%d)\n", prefix, config_desc->bLength); - //fprintf(stderr, "bDescriptorType(config): %d\n", config_desc->bDescriptorType); - fprintf(stderr, "%s wTotalLength: %d\n", prefix, config_desc->wTotalLength); - fprintf(stderr, "%s bNumInterfaces: %d\n", prefix, config_desc->bNumInterfaces); - fprintf(stderr, "%s bConfigurationValue: %d\n", prefix, config_desc->bConfigurationValue); - fprintf(stderr, "%s iConfiguration: %d\n", prefix, config_desc->iConfiguration); - fprintf(stderr, "%s bmAttributes: 0x%02x (%s%s%s)\n", prefix, - config_desc->bmAttributes, - (config_desc->bmAttributes & USB_BM_ATTRIBUTES_SELFPOWER)?"Self Powered, ":"", - (config_desc->bmAttributes & USB_BM_ATTRIBUTES_WAKEUP)?"Remote Wakeup, ":"", - (config_desc->bmAttributes & USB_BM_ATTRIBUTES_BATTERY)?"Battery Powered":""); - fprintf(stderr,"%s bMaxPower: %d (%d mA)\n", prefix, config_desc->bMaxPower, config_desc->bMaxPower*2); -} - -uint8_t show_interface_desc(const void *p, int indent) -{ - char prefix[8]; - for (int i=0; i < indent; ++i) prefix[i] = '*'; - prefix[indent] = '\0'; - const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; - - fprintf(stderr, "%s INTERFACE_DESC(size=%d)\n", prefix, intf->bLength); - //fprintf(stderr, "%s bDescriptorType (interface): %d\n", intf->bDescriptorType); - fprintf(stderr, "%s bInterfaceNumber: %d\n", prefix, intf->bInterfaceNumber); - fprintf(stderr, "%s bAlternateSetting: %d\n", prefix, intf->bAlternateSetting); - fprintf(stderr, "%s bNumEndpoints: %d\n", prefix, intf->bNumEndpoints); - fprintf(stderr, "%s bInterfaceClass: 0x%02x\n", prefix, intf->bInterfaceClass); - fprintf(stderr, "%s bInterfaceSubClass: 0x%02x\n", prefix, intf->bInterfaceSubClass); - fprintf(stderr, "%s bInterfaceProtocol: 0x%02x\n", prefix, intf->bInterfaceProtocol); - fprintf(stderr, "%s iInterface: %d\n", prefix, intf->iInterface); - return intf->bInterfaceClass; -} - -void show_endpoint_desc(const void *p, int indent) -{ - char prefix[8]; - for (int i=0; i < indent; ++i) prefix[i] = '*'; - prefix[indent] = '\0'; - const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; - const char *XFER_TYPE_NAMES[] = { - "Control", "Isochronous", "Bulk", "Interrupt" - }; - fprintf(stderr, "%s ENDPOINT_DESC(size=%d)\n", prefix, endpoint->bLength); - //fprintf(stderr, "%s bDescriptorType (endpoint): %d\n", prefix, endpoint->bDescriptorType); - fprintf(stderr, "%s bEndpointAddress: 0x%02x (%s)\n", prefix, - endpoint->bEndpointAddress, - (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK)?"In":"Out"); - fprintf(stderr, "%s bmAttributes: 0x%02x (%s)\n", prefix, - endpoint->bmAttributes, - XFER_TYPE_NAMES[endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK]); - fprintf(stderr, "%s wMaxPacketSize: %d\n", prefix, endpoint->wMaxPacketSize); - fprintf(stderr, "%s bInterval: %d\n", prefix, endpoint->bInterval); -} - -// end of show_desc.hpp -#else // !DEBUG_USB - -#define DBGPRINTF(s) /* nothing */ -#define DBGPRINTF1(s, a) /* nothing */ -#define DBGPRINTF2(s, a, b) /* nothing */ -#define DBGPRINTF3(s, a, b, c) /* nothing */ -#define DBGPRINTF4(s, a, b, c, d) /* nothing */ - -#define show_config_desc(p, i) /* nothing */ -#define show_interface_desc(p, i) /* nothing */ -#define show_endpoint_desc(p, i) /* nothing */ - -#endif // DEBUG_USB - -const TickType_t HOST_EVENT_TIMEOUT = 1; -const TickType_t CLIENT_EVENT_TIMEOUT = 1; - -const size_t USB_HID_DESC_SIZE = 9; - -usb_host_client_handle_t Client_Handle; -usb_device_handle_t Device_Handle; -uint8_t Interface_Number; - -bool isKeyboard = false; -bool isKeyboardReady = false; -uint8_t KeyboardInterval; -bool isKeyboardPolling = false; -int64_t KeyboardTimer=0; -int64_t KeyRepeatTimer=0; - - - -//const size_t KEYBOARD_IN_BUFFER_SIZE = KEYBOARD_BYTES; -usb_transfer_t *KeyboardIn = NULL; - -// --------------------------------- -// from usbhmidi.ino -bool isMIDI = false; -bool haveMIDIin = false; -bool haveMIDIout = false; -bool isMIDIReady = false; - -//const size_t MIDI_IN_BUFFERS = 8; -//const size_t MIDI_OUT_BUFFERS = 8; -#define MIDI_IN_BUFFERS 8 -//#define MIDI_OUT_BUFFERS 8 -usb_transfer_t *MIDIOut = NULL; -usb_transfer_t *MIDIIn[MIDI_IN_BUFFERS] = { NULL }; - - -// USB MIDI Event Packet Format (always 4 bytes) -// -// Byte 0 |Byte 1 |Byte 2 |Byte 3 -// -------|-------|-------|------ -// CN+CIN |MIDI_0 |MIDI_1 |MIDI_2 -// -// CN = Cable Number ((0x0..0xf)<<4) specifies virtual MIDI jack/cable -// CIN = Code Index Number (0x0..0xf) classifies the 3 MIDI bytes. -// See Table 4-1 in the MIDI 1.0 spec at usb.org. -// - -static void midi_transfer_cb(usb_transfer_t *transfer) { - //printf("**midi_transfer_cb context: %p\n", transfer->context); - if (Device_Handle == transfer->device_handle) { - int in_xfer = transfer->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK; - if ((transfer->status == 0) && in_xfer) { - uint8_t *const p = transfer->data_buffer; - for (int i = 0; i < transfer->actual_num_bytes; i += 4) { - if ((p[i] + p[i + 1] + p[i + 2] + p[i + 3]) == 0) break; - DBGPRINTF4("midi: %02x %02x %02x %02x\n", - p[i], p[i + 1], p[i + 2], p[i + 3]); - // Strip the first byte which is the USB-MIDI virtual wire ID, - // rest is MIDI message (at least for 3-byte messages). - convert_midi_bytes_to_messages(p + i + 1, 3, 1); - } - esp_err_t err = usb_host_transfer_submit(transfer); - if (err != ESP_OK) { - printf("midi usb_host_transfer_submit err: 0x%x\n", err); - } - } else { - printf("midi transfer->status %d\n", transfer->status); - } - } -} - -bool check_interface_desc_MIDI(const void *p) { - const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; - - // USB MIDI - if ((intf->bInterfaceClass == USB_CLASS_AUDIO) && - (intf->bInterfaceSubClass == 3) && - (intf->bInterfaceProtocol == 0)) { - isMIDI = true; - printf("Claiming a MIDI device!\n"); - Interface_Number = intf->bInterfaceNumber; - esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle, - Interface_Number, intf->bAlternateSetting); - if (err != ESP_OK) printf("midi usb_host_interface_claim failed: %x\n", err); - return true; - } - return false; -} - -void prepare_endpoint_midi(const void *p) { - const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; - esp_err_t err; - - // must be bulk for MIDI - if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_BULK) { - printf("MIDI: Not bulk endpoint: 0x%02x\n", endpoint->bmAttributes); - return; - } - if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { - // MIDI-IN endpoint. - for (int i = 0; i < MIDI_IN_BUFFERS; i++) { - if (MIDIIn[i] == NULL) { - err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIIn[i]); - if (err != ESP_OK) { - MIDIIn[i] = NULL; - printf("midi usb_host_transfer_alloc/In err: 0x%x\n", err); - haveMIDIout = false; - return; - } - } - if (MIDIIn[i] != NULL) { - MIDIIn[i]->device_handle = Device_Handle; - MIDIIn[i]->bEndpointAddress = endpoint->bEndpointAddress; - MIDIIn[i]->callback = midi_transfer_cb; - MIDIIn[i]->context = (void *)i; - MIDIIn[i]->num_bytes = endpoint->wMaxPacketSize; - esp_err_t err = usb_host_transfer_submit(MIDIIn[i]); - if (err != ESP_OK) { - printf("midi usb_host_transfer_submit/In err: 0x%x\n", err); - } - haveMIDIout = true; - } - } - } else { - // MIDI-OUT endpoint - if (MIDIOut == NULL) { - err = usb_host_transfer_alloc(endpoint->wMaxPacketSize, 0, &MIDIOut); - if (err != ESP_OK) { - MIDIOut = NULL; - printf("midi usb_host_transfer_alloc/Out err: 0x%x\n", err); - haveMIDIin = false; - return; - } - } - if (MIDIOut != NULL) { - DBGPRINTF1("Out data_buffer_size: %d\n", MIDIOut->data_buffer_size); - MIDIOut->device_handle = Device_Handle; - MIDIOut->bEndpointAddress = endpoint->bEndpointAddress; - MIDIOut->callback = midi_transfer_cb; - MIDIOut->context = NULL; - // MIDIOut->flags |= USB_TRANSFER_FLAG_ZERO_PACK; - haveMIDIin = true; - } - } - // MIDI is ready when both input and output endpoints are initialized. - //isMIDIReady = ((MIDIOut != NULL) && (MIDIIn[0] != NULL)); - isMIDIReady = haveMIDIin && haveMIDIout; -} - -// end of usbhmidi.ino -// --------------------------------- - -void new_enumeration_config_fn(const usb_config_desc_t *config_desc); - - -void _client_event_callback(const usb_host_client_event_msg_t *event_msg, void *arg) -{ - esp_err_t err; - switch (event_msg->event) - { - /**< A new device has been enumerated and added to the USB Host Library */ - case USB_HOST_CLIENT_EVENT_NEW_DEV: - DBGPRINTF1("New device address: %d\n", event_msg->new_dev.address); - err = usb_host_device_open(Client_Handle, event_msg->new_dev.address, &Device_Handle); - if (err != ESP_OK) printf("usb_host_device_open: 0x%x\n", err); - - usb_device_info_t dev_info; - err = usb_host_device_info(Device_Handle, &dev_info); - if (err != ESP_OK) printf("usb_host_device_info: 0x%x\n", err); - //printf("speed: %d dev_addr %d vMaxPacketSize0 %d bConfigurationValue %d\n", - // dev_info.speed, dev_info.dev_addr, dev_info.bMaxPacketSize0, - // dev_info.bConfigurationValue); - - const usb_device_desc_t *dev_desc; - err = usb_host_get_device_descriptor(Device_Handle, &dev_desc); - if (err != ESP_OK) printf("usb_host_get_device_desc: 0x%x\n", err); - - const usb_config_desc_t *config_desc; - err = usb_host_get_active_config_descriptor(Device_Handle, &config_desc); - if (err != ESP_OK) printf("usb_host_get_config_desc: 0x%x\n", err); - // Finally, we get to inspect the new device and maybe connect to it. - new_enumeration_config_fn(config_desc); - break; - /**< A device opened by the client is now gone */ - case USB_HOST_CLIENT_EVENT_DEV_GONE: - printf("Device Gone handle: 0x%lx\n", (uint32_t)event_msg->dev_gone.dev_hdl); - // Mark everything de-initialized so it will re-initialized on another connect. - esp_err_t err; - if (isMIDI || isKeyboard) { - err = usb_host_interface_release(Client_Handle, Device_Handle, Interface_Number); - if (err != ESP_OK) printf("usb_host_interface_release err: 0x%x\n", err); - isMIDI = false; - isKeyboard = false; - } - err = usb_host_device_close(Client_Handle, Device_Handle); - if (err != ESP_OK) printf("usb_host_device_close err: 0x%x\n", err); - isMIDIReady = false; - haveMIDIin = false; - haveMIDIout = false; - isKeyboardReady = false; - break; - default: - printf("Unknown USB event: %d\n", event_msg->event); - break; - } -} - -// Reference: esp-idf/examples/peripherals/usb/host/usb_host_lib/main/usb_host_lib_main.c - -void usbh_setup() -{ - const usb_host_config_t config = { - .intr_flags = ESP_INTR_FLAG_LEVEL1, - }; - esp_err_t err = usb_host_install(&config); - (void)err; - DBGPRINTF1("usb_host_install: 0x%x\n", err); - - const usb_host_client_config_t client_config = { - .is_synchronous = false, - .max_num_event_msg = 5, - .async = { - .client_event_callback = _client_event_callback, - .callback_arg = Client_Handle - } - }; - err = usb_host_client_register(&client_config, &Client_Handle); - DBGPRINTF1("usb_host_client_register: 0x%x\n", err); -} - -void usbh_task(void) -{ - uint32_t event_flags; - - esp_err_t err = usb_host_lib_handle_events(HOST_EVENT_TIMEOUT, &event_flags); - if (err == ESP_OK) { - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { - DBGPRINTF("No more clients\n"); - } - if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { - DBGPRINTF("No more devices\n"); - } - } else { - if (err != ESP_ERR_TIMEOUT) { - printf("usb_host_lib_handle_events: 0x%x flags: %lx\n", err, event_flags); - } - } - - err = usb_host_client_handle_events(Client_Handle, CLIENT_EVENT_TIMEOUT); - if ((err != ESP_OK) && (err != ESP_ERR_TIMEOUT)) { - printf("usb_host_client_handle_events: 0x%x\n", err); - } -} - -// Keep track of which keys / scan codes are being held -uint16_t current_held = 0; -int64_t current_held_ms = 0; -int64_t last_inter_trigger_ms = 0; - - - -uint32_t keycode_to_ctrl_key(uint16_t key) -{ - switch(key) { - case 261: - return LV_KEY_RIGHT; - - case 260: - return LV_KEY_LEFT; - - case 259: - return LV_KEY_UP; - - case 258: - return LV_KEY_DOWN; - - case 27: - return LV_KEY_ESC; - - case 8: - return LV_KEY_BACKSPACE; - - //case SDLK_DELETE: - // return LV_KEY_DEL; - - case 13: - return LV_KEY_ENTER; - - // We could feed in shift here, TODO - case 9: - return LV_KEY_NEXT; - - case 22: - return LV_KEY_NEXT; - - case 25: - return LV_KEY_PREV; - - default: - return '\0'; - } -} - - -static char lvgl_kb_buf[KEYBOARD_BUFFER_SIZE]; - - -void lvgl_keyboard_read(lv_indev_t * indev_drv, lv_indev_data_t * data) { - (void) indev_drv; // unused - - static bool dummy_read = false; - const size_t len = strlen(lvgl_kb_buf); - - // Send a release manually - if (dummy_read) { - dummy_read = false; - data->state = LV_INDEV_STATE_RELEASED; - data->continue_reading = len > 0; - } - // Send the pressed character - else if (len > 0) { - dummy_read = true; - data->state = LV_INDEV_STATE_PRESSED; - data->key = lvgl_kb_buf[0]; - memmove(lvgl_kb_buf, lvgl_kb_buf + 1, len); - data->continue_reading = true; - } -} - - - -void decode_report(uint8_t *p) { - // First byte, modifier mask - uint8_t modifier = p[0]; - uint8_t new_key = 0; - uint8_t key_held_this_session = 0; - // Second byte, reserved - // next 6 bytes, scan codes (for rollover) - //fprintf(stderr,"decode report %d %d %d %d %d %d\n", p[2],p[3],p[4],p[5],p[6],p[7]); - for(uint8_t i=2;i<8;i++) { - if(p[i]!=0) { - key_held_this_session = 1; - uint8_t skip = 0; - for(uint8_t j=2;j<8;j++) { - if(last_scan[j] == p[i]) skip = 1; - } - if(!skip) { // only process new keys - uint16_t c = scan_ascii(p[i], modifier); - if(c) { - - if(keycode_to_ctrl_key(c) != '\0') { - const size_t len = strlen(lvgl_kb_buf); - if (len < KEYBOARD_BUFFER_SIZE - 1) { - lvgl_kb_buf[len] = keycode_to_ctrl_key(c); - lvgl_kb_buf[len + 1] = '\0'; - } - } else { - // put it in lvgl_kb_buf for lvgl - const size_t len = strlen(lvgl_kb_buf); - if (len < KEYBOARD_BUFFER_SIZE - 1) { - lvgl_kb_buf[len] = c; - lvgl_kb_buf[len+1] = '\0'; - } - } - - new_key = 1; - current_held_ms = esp_timer_get_time()/1000; - current_held = c; - //fprintf(stderr, "sending new key %d to MP at time %lld\n", c, current_held_ms); - send_key_to_micropython(c); - } - } - } - } - if(!new_key && !key_held_this_session) { - // we got a message but no new keys. so is a release - //fprintf(stderr, "turning off key\n"); - current_held_ms = 0; - current_held = 0; - last_inter_trigger_ms = 0; - } - for(uint8_t i=0;i<8;i++) last_scan[i] = p[i]; -} - -void keyboard_transfer_cb(usb_transfer_t *transfer) -{ - if (Device_Handle == transfer->device_handle) { - isKeyboardPolling = false; - if (transfer->status == 0) { - //fprintf(stderr, "nb is %d\n", transfer->actual_num_bytes); - //if(transfer->actual_num_bytes > 7 && transfer->actual_num_bytes < 17) { - if (transfer->actual_num_bytes == 8 || transfer->actual_num_bytes == 16) { - uint8_t *const p = transfer->data_buffer; - decode_report(p); - } else if (transfer->actual_num_bytes == 10) { - uint8_t *const p = transfer->data_buffer; - decode_report(p+1); - } else { - printf("Keyboard boot hid transfer (%d bytes) too short or long\n", - transfer->actual_num_bytes); - } - } - else { - printf("kbd transfer->status %d\n", transfer->status); - } - } -} - -bool check_interface_desc_boot_keyboard(const void *p) { - const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p; - - // HID Keyboard - if ((intf->bInterfaceClass == USB_CLASS_HID) && - (intf->bInterfaceSubClass == 1) && - (intf->bInterfaceProtocol == 1)) { - isKeyboard = true; - Interface_Number = intf->bInterfaceNumber; - - - esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle, - Interface_Number, intf->bAlternateSetting); - if (err != ESP_OK) printf("usb_host_interface_claim failed: 0x%x\n", err); - return true; - } - return false; -} - -void prepare_endpoint_hid(const void *p) -{ - const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p; - esp_err_t err; - keyboard_bytes = usb_round_up_to_mps(KEYBOARD_BYTES, endpoint->wMaxPacketSize); - DBGPRINTF2("Setting KB to %d from MPS %d\n", keyboard_bytes, endpoint->wMaxPacketSize); - - // must be interrupt for HID - if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) { - printf("Kbd: Not interrupt endpoint: 0x%02x\n", endpoint->bmAttributes); - return; - } - if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) { - if (KeyboardIn == NULL) { - err = usb_host_transfer_alloc(keyboard_bytes, 0, &KeyboardIn); - if (err != ESP_OK) { - KeyboardIn = NULL; - printf("kbd usb_host_transfer_alloc/In err: 0x%x\n", err); - return; - } - } - if (KeyboardIn != NULL) { - KeyboardIn->device_handle = Device_Handle; - KeyboardIn->bEndpointAddress = endpoint->bEndpointAddress; - KeyboardIn->callback = keyboard_transfer_cb; - KeyboardIn->context = NULL; - isKeyboardReady = true; - KeyboardInterval = endpoint->bInterval; - DBGPRINTF("USB boot keyboard ready\n"); - } - } else { - DBGPRINTF("Ignoring kbd interrupt Out endpoint\n"); - } -} - -void new_enumeration_config_fn(const usb_config_desc_t *config_desc) { - // We just retrieved the config of a newly-connected device. - // Read through it and see if we can claim a recognized device. - // - // &config_desc->val[0] is the same as config_desc - // so the first "descriptor type" found is actually TYPE_CONFIGURATION - // and the call to show_config_desc(p) is equivalent to doing - // show_config_desc(config_desc) here. - - - const uint8_t *p = &config_desc->val[0]; - uint8_t bLength; - int indent = 1; - int last_descriptor = -1; - for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) { - bLength = *p; - if ((i + bLength) <= config_desc->wTotalLength) { - const uint8_t bDescriptorType = *(p + 1); - DBGPRINTF3("config_desc->val[%d]: type=%d length=%d\n", i, bDescriptorType, bLength); - switch (bDescriptorType) { - case USB_B_DESCRIPTOR_TYPE_CONFIGURATION: - // The first record in val[] is by definition the config description. - show_config_desc(p, indent++); - last_descriptor = bDescriptorType; - break; - case USB_B_DESCRIPTOR_TYPE_DEVICE: - DBGPRINTF("USB Device Descriptor should not appear in config\n"); - break; - case USB_B_DESCRIPTOR_TYPE_STRING: - DBGPRINTF("USB string desc TBD\n"); - break; - case USB_B_DESCRIPTOR_TYPE_INTERFACE: - if ((last_descriptor == USB_B_DESCRIPTOR_TYPE_INTERFACE) - || (last_descriptor == USB_B_DESCRIPTOR_TYPE_ENDPOINT)) --indent; - show_interface_desc(p, indent++); - if (!isMIDI && !isKeyboard) { - if (!check_interface_desc_MIDI(p)) { - check_interface_desc_boot_keyboard(p); - } - } - if (!isMIDI && !isKeyboard) { - DBGPRINTF("Interface was neither keyboard nor midi.\n"); - } - last_descriptor = bDescriptorType; - break; - case USB_B_DESCRIPTOR_TYPE_ENDPOINT: - show_endpoint_desc(p, indent); - if (isKeyboard && !isKeyboardReady) { - prepare_endpoint_hid(p); - } else if (isMIDI && !isMIDIReady) { - prepare_endpoint_midi(p); - } - last_descriptor = bDescriptorType; - break; - case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER: - DBGPRINTF("USB device qual desc TBD\n"); - break; - case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION: - DBGPRINTF("USB Other Speed TBD\n"); - break; - case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER: - DBGPRINTF("USB Interface Power TBD\n"); - break; - case 0x21: - // HID - break; - case 0x24: - // UAS PIPE mode - see this for MIDI keyboard. - break; - case 0x25: - // Also see this for MIDI keyboard. - break; - default: - printf("Unknown USB Descriptor Type: 0x%x\n", bDescriptorType); - break; - } - } - else { - printf("USB Descriptor invalid\n"); - return; - } - } -} - -void run_usb() -{ - // Reset key maps - for(uint8_t i=0;i 0) { - if(KeyRepeatTimer - current_held_ms > KEY_REPEAT_TRIGGER_MS) { - if(KeyRepeatTimer - last_inter_trigger_ms > KEY_REPEAT_INTER_MS) { - send_key_to_micropython(current_held); - last_inter_trigger_ms = KeyRepeatTimer; - } - } - } - if (isKeyboardReady && !isKeyboardPolling && (KeyboardTimer > KeyboardInterval)) { - KeyboardIn->num_bytes = keyboard_bytes; - esp_err_t err = usb_host_transfer_submit(KeyboardIn); - if (err != ESP_OK) { - printf("kbd usb_host_transfer_submit/In err: 0x%x\n", err); - } - isKeyboardPolling = true; - KeyboardTimer = 0; - } - } -} diff --git a/tulip/esp32s3/usb_serial_jtag.c b/tulip/esp32s3/usb_serial_jtag.c deleted file mode 100644 index 3289a1b5c..000000000 --- a/tulip/esp32s3/usb_serial_jtag.c +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Patrick Van Oosterwijck - * - * 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. - */ - -#include "py/runtime.h" -#include "py/mphal.h" -#include "usb_serial_jtag.h" - -#if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG - -#include "hal/usb_serial_jtag_ll.h" -#include "esp_intr_alloc.h" -#include "soc/periph_defs.h" - -#define USB_SERIAL_JTAG_BUF_SIZE (64) - -static uint8_t rx_buf[USB_SERIAL_JTAG_BUF_SIZE]; -static volatile bool terminal_connected = false; - -static void usb_serial_jtag_isr_handler(void *arg) { - uint32_t flags = usb_serial_jtag_ll_get_intsts_mask(); - - if (flags & USB_SERIAL_JTAG_INTR_SOF) { - usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SOF); - } - - if (flags & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) { - usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); - size_t req_len = ringbuf_free(&stdin_ringbuf); - if (req_len > USB_SERIAL_JTAG_BUF_SIZE) { - req_len = USB_SERIAL_JTAG_BUF_SIZE; - } - size_t len = usb_serial_jtag_ll_read_rxfifo(rx_buf, req_len); - for (size_t i = 0; i < len; ++i) { - if (rx_buf[i] == mp_interrupt_char) { - mp_sched_keyboard_interrupt(); - } else { - ringbuf_put(&stdin_ringbuf, rx_buf[i]); - } - } - mp_hal_wake_main_task_from_isr(); - } -} - -void usb_serial_jtag_init(void) { - usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | - USB_SERIAL_JTAG_INTR_SOF); - usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | - USB_SERIAL_JTAG_INTR_SOF); - ESP_ERROR_CHECK(esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, - usb_serial_jtag_isr_handler, NULL, NULL)); -} - -void usb_serial_jtag_tx_strn(const char *str, size_t len) { - while (len) { - size_t l = len; - if (l > USB_SERIAL_JTAG_PACKET_SZ_BYTES) { - l = USB_SERIAL_JTAG_PACKET_SZ_BYTES; - } - TickType_t start_tick = xTaskGetTickCount(); - while (!usb_serial_jtag_ll_txfifo_writable()) { - TickType_t now_tick = xTaskGetTickCount(); - if (!terminal_connected || now_tick > (start_tick + pdMS_TO_TICKS(200))) { - terminal_connected = false; - return; - } - } - terminal_connected = true; - l = usb_serial_jtag_ll_write_txfifo((const uint8_t *)str, l); - usb_serial_jtag_ll_txfifo_flush(); - str += l; - len -= l; - } -} - -#endif // CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG diff --git a/tulip/esp32s3/usb_serial_jtag.h b/tulip/esp32s3/usb_serial_jtag.h deleted file mode 100644 index 4eebb19e4..000000000 --- a/tulip/esp32s3/usb_serial_jtag.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2021 Patrick Van Oosterwijck - * - * 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. - */ -#ifndef MICROPY_INCLUDED_ESP32_USB_SERIAL_JTAG_H -#define MICROPY_INCLUDED_ESP32_USB_SERIAL_JTAG_H - -void usb_serial_jtag_init(void); -void usb_serial_jtag_tx_strn(const char *str, size_t len); - -#endif // MICROPY_INCLUDED_ESP32_USB_SERIAL_JTAG_H diff --git a/tulip/linux/Makefile b/tulip/linux/Makefile index 3e912a645..859d9c475 100644 --- a/tulip/linux/Makefile +++ b/tulip/linux/Makefile @@ -1,3 +1,7 @@ +# Tulip linux, based on unix port makefile + +DEBUG=1 + # Select the variant to build for. VARIANT ?= standard MAKEFLAGS += --jobs=4 @@ -9,12 +13,15 @@ TOP = ../../micropython VARIANT_DIR ?= ../../micropython/ports/unix/variants/$(VARIANT) +# If the build directory is not given, make it reflect the variant name. +BUILD ?= build-$(VARIANT) + include $(TOP)/py/mkenv.mk -include ../shared/desktop/mpconfigport.mk -include $(VARIANT_DIR)/mpconfigvariant.mk +include mpconfigport.mk +#include $(VARIANT_DIR)/mpconfigvariant.mk # Use the default frozen manifest, variants may override this. -FROZEN_MANIFEST ?= manifest.py +FROZEN_MANIFEST ?= variants/manifest.py # This should be configured by the mpconfigvariant.mk PROG = tulip @@ -30,6 +37,7 @@ UNAME_S := $(shell uname -s) include $(TOP)/py/py.mk include $(TOP)/extmod/extmod.mk + # LVGL stuff LVGL_BINDING_DIR = $(TOP)/../lv_binding_micropython_tulip LVGL_DIR = $(LVGL_BINDING_DIR)/lvgl @@ -42,7 +50,10 @@ LVGL_MPY_METADATA = $(BUILD)/lvgl/lv_mpy.json QSTR_GLOBAL_DEPENDENCIES += $(LVGL_MPY) CFLAGS_MOD += $(LV_CFLAGS) SRC_C += $(shell find $(LVGL_DIR)/src -type f -name '*.c') -CFLAGS += -DLV_CONF_INCLUDE_SIMPLE +CFLAGS += -DLV_CONF_INCLUDE_SIMPLE + +# This is for the lvgl stuff that uses STATIC +CFLAGS += -DSTATIC=static $(LVGL_MPY): $(ALL_LVGL_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py $(ECHO) "LVGL-GEN $@" @@ -60,30 +71,40 @@ INC += -I$(TOP)/ports/unix INC += -I../shared/ INC += -I../../amy/src/ INC += -I$(TOP)/lib/mbedtls/include -INC += -I/usr/include/SDL2 INC += -I$(TOP)/../lv_binding_micropython_tulip/lvgl/src INC += -I$(TOP)/../lv_binding_micropython_tulip/lvgl/src/libs/lodepng +INC += -I/usr/include/SDL2 + + +GIT_SUBMODULES += lib/berkeley-db-1.xx + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) # compiler settings -CWARN = -Wall -CWARN += -Wextra -Wno-unused-parameter -Wpointer-arith -Wdouble-promotion -CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) -CFLAGS += -DTULIP_DESKTOP +# compiler settings +CWARN = -Wall -Werror +CWARN += -Wextra -Wno-unused-parameter -Wno-unused-variable -Wno-old-style-declaration -Wno-unused-but-set-parameter -Wpointer-arith -Wdouble-promotion -Wno-float-conversion -Wno-missing-declarations -Wno-unused-but-set-variable -Wno-sign-compare -Wno-gnu-variable-sized-type-not-at-end -Wno-undefined-internal +CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) +CFLAGS += -DTULIP_DESKTOP CFLAGS += $(ARCHFLAGS) + # Debugging/Optimization ifdef DEBUG -COPT ?= -Og +#COPT ?= -Og else -COPT ?= -Os +#COPT ?= -Os COPT += -DNDEBUG endif # Remove unused sections. -COPT += -fdata-sections -ffunction-sections +COPT += -fdata-sections -ffunction-sections -O0 + -# Always enable symbols -- They're occasionally useful, and don't make it into the -# final .bin/.hex/.dfu so the extra size doesn't matter. +# Note: Symbols and debug information will still be stripped from the final binary +# unless "DEBUG=1" or "STRIP=" is passed to make, see README.md for details. CFLAGS += -g ifndef DEBUG @@ -111,17 +132,11 @@ ifndef DEBUG # https://gcc.gnu.org/ml/gcc-patches/2004-09/msg02055.html . # # Turning off _FORTIFY_SOURCE is only required when compiling with -O1 or greater -CFLAGS += -U _FORTIFY_SOURCE -fPIC +CFLAGS += -U _FORTIFY_SOURCE endif -# On OSX, 'gcc' is a symlink to clang unless a real gcc is installed. -# The unix port of MicroPython on OSX must be compiled with clang, -# while cross-compile ports require gcc, so we test here for OSX and -# if necessary override the value of 'CC' set in py/mkenv.mk - -LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections +LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) -#LDFLAGS += -lstdc++ $(ARCHFLAGS) # Flags to link with pthread library LIBPTHREAD = -lpthread @@ -131,75 +146,102 @@ ifeq ($(MICROPY_FORCE_32BIT),1) # starting with linux-libc-dev:i386 ifeq ($(MICROPY_PY_FFI),1) ifeq ($(UNAME_S),Linux) -CFLAGS_MOD += -I/usr/include/i686-linux-gnu +CFLAGS += -I/usr/include/i686-linux-gnu endif endif endif ifeq ($(MICROPY_USE_READLINE),1) INC += -I$(TOP)/shared/readline -CFLAGS_MOD += -DMICROPY_USE_READLINE=1 +CFLAGS += -DMICROPY_USE_READLINE=1 SHARED_SRC_C_EXTRA += readline/readline.c endif ifeq ($(MICROPY_PY_TERMIOS),1) -CFLAGS_MOD += -DMICROPY_PY_TERMIOS=1 -SRC_MOD += $(MICROPY_PORT_DIR)/modtermios.c +CFLAGS += -DMICROPY_PY_TERMIOS=1 endif ifeq ($(MICROPY_PY_SOCKET),1) -CFLAGS_MOD += -DMICROPY_PY_SOCKET=1 +CFLAGS += -DMICROPY_PY_SOCKET=1 SRC_MOD += $(MICROPY_PORT_DIR)/modsocket.c endif ifeq ($(MICROPY_PY_THREAD),1) -CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 -LDFLAGS_MOD += $(LIBPTHREAD) +CFLAGS += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 +LDFLAGS += $(LIBPTHREAD) endif + include ../shared/tulip.mk MICROPY_PORT_DIR=../../micropython/ports/unix +ifeq ($(MICROPY_PY_SSL),1) +ifeq ($(MICROPY_SSL_AXTLS),1) + +endif +endif + +# If the variant enables it, enable modbluetooth. +ifeq ($(MICROPY_PY_BLUETOOTH),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1') + +# Figure out which BTstack transport to use. +ifeq ($(HAVE_LIBUSB),1) +# Default to btstack-over-usb. +MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 +else +# Fallback to HCI controller via a H4 UART (e.g. Zephyr on nRF) over a /dev/tty serial port. +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 +endif + +SRC_BTSTACK_C += lib/btstack/platform/embedded/btstack_run_loop_embedded.c +endif +endif + ifeq ($(MICROPY_PY_FFI),1) ifeq ($(MICROPY_STANDALONE),1) -LIBFFI_CFLAGS_MOD := -I$(shell ls -1d $(BUILD)/lib/libffi/out/lib/libffi-*/include) +# Build libffi from source. +GIT_SUBMODULES += lib/libffi +DEPLIBS += libffi +LIBFFI_CFLAGS := -I$(shell ls -1d $(BUILD)/lib/libffi/include) ifeq ($(MICROPY_FORCE_32BIT),1) - LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib32/libffi.a + LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib32/libffi.a else - LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib/libffi.a + LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib/libffi.a endif else -LIBFFI_CFLAGS_MOD := $(shell pkg-config --cflags libffi) -LIBFFI_LDFLAGS_MOD := $(shell pkg-config --libs libffi) +# Use system version of libffi. +LIBFFI_CFLAGS := $(shell pkg-config --cflags libffi) +LIBFFI_LDFLAGS := $(shell pkg-config --libs libffi) endif -ifeq ($(UNAME_S),Linux) -LIBFFI_LDFLAGS_MOD += -ldl -lSDL2 -endif -CFLAGS_MOD += $(LIBFFI_CFLAGS_MOD) -DMICROPY_PY_FFI=1 -LDFLAGS_MOD += $(LIBFFI_LDFLAGS_MOD) -SRC_MOD += $(MICROPY_PORT_DIR)/modffi.c +CFLAGS += $(LIBFFI_CFLAGS) -DMICROPY_PY_FFI=1 +LDFLAGS += $(LIBFFI_LDFLAGS) endif ifeq ($(MICROPY_PY_JNI),1) # Path for 64-bit OpenJDK, should be adjusted for other JDKs -CFLAGS_MOD += -I/usr/lib/jvm/java-7-openjdk-amd64/include -DMICROPY_PY_JNI=1 -SRC_MOD += $(MICROPY_PORT_DIR)/modjni.c +CFLAGS += -I/usr/lib/jvm/java-7-openjdk-amd64/include -DMICROPY_PY_JNI=1 +endif + + +ifeq ($(UNAME_S),Linux) +LDFLAGS += -ldl -lSDL2 endif # source files SRC_C += \ main.c \ - ../shared/desktop/unix_mphal.c \ ../shared/desktop/unix_display.c \ ../shared/desktop/multicast.c \ - ../shared/desktop/mpthreadport.c \ + ../shared/desktop/unix_mphal.c \ ../../amy/src/libminiaudio-audio.c \ + $(MICROPY_PORT_DIR)/mpthreadport.c \ $(MICROPY_PORT_DIR)/gccollect.c \ $(MICROPY_PORT_DIR)/input.c \ - $(MICROPY_PORT_DIR)/modmachine.c \ $(MICROPY_PORT_DIR)/alloc.c \ $(MICROPY_PORT_DIR)/fatfs_port.c \ $(MICROPY_PORT_DIR)/mpbthciport.c \ @@ -210,8 +252,11 @@ SRC_C += \ $(SRC_MOD) \ $(wildcard $(VARIANT_DIR)/*.c) + +#SRC_C += $(LVGL_PP) SRC_C += $(LVGL_MPY) + SHARED_SRC_C += $(addprefix ../../micropython/shared/,\ runtime/gchelper_generic.c \ runtime/interrupt_char.c \ @@ -223,35 +268,29 @@ SHARED_SRC_C += $(addprefix ../../micropython/shared/,\ SRC_CXX += \ $(SRC_MOD_CXX) - OBJ = $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) -OBJ += $(addprefix $(BUILD)/, $(SRC_M:.m=.o)) OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o)) OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) + + # List of sources for qstr extraction SRC_QSTR += $(SRC_C) $(SRC_CXX) $(SHARED_SRC_C) $(EXTMOD_SRC_C) -# Append any auto-generated sources that are needed by sources listed in -# SRC_QSTR -SRC_QSTR_AUTO_DEPS += ifneq ($(FROZEN_MANIFEST),) -# To use frozen code create a manifest.py file with a description of files to -# freeze, then invoke make with FROZEN_MANIFEST=manifest.py (be sure to build from scratch). -CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool -CFLAGS += -DMICROPY_MODULE_FROZEN_MPY CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs -CFLAGS += -DMICROPY_MODULE_FROZEN_STR endif + CPU_BRAND_NAME=linux CFLAGS += -DMICROPY_HW_MCU_NAME="\"$(CPU_BRAND_NAME)\"" CFLAGS += -Wno-double-promotion -Wno-unused-function CXXFLAGS += $(filter-out -Wno-double-promotion -Wmissing-prototypes -Wold-style-definition -std=gnu99,$(CFLAGS) $(CXXFLAGS_MOD)) + ifeq ($(MICROPY_FORCE_32BIT),1) RUN_TESTS_MPY_CROSS_FLAGS = --mpy-cross-flags='-march=x86' endif @@ -269,23 +308,25 @@ endif include $(TOP)/py/mkrules.mk -#undefine compile_c +.PHONY: test test_full + +test: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py + +test_full: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -d thread + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --emit native + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython + cat $(TOP)/tests/basics/0prelim.py | ./$(BUILD)/$(PROG) | grep -q 'abc' -define compile_c -$(ECHO) "CC $<" -$(Q)$(CC) $(CFLAGS) -c -MD -o $@ $< || (echo -e $(HELP_BUILD_ERROR); false) -@# The following fixes the dependency file. -@# See http://make.paulandlesley.org/autodep.html for details. -@# Regex adjusted from the above to play better with Windows paths, etc. -@$(CP) $(@:.o=.d) $(@:.o=.P); \ - $(SED) -e 's/#.*//' -e 's/^.*: *//' -e 's/ *\\$$//' \ - -e '/^$$/ d' -e 's/$$/ :/' < $(@:.o=.d) >> $(@:.o=.P); \ - $(RM) -f $(@:.o=.d) -endef +test_gcov: test_full + gcov -o $(BUILD)/py $(TOP)/py/*.c + gcov -o $(BUILD)/extmod $(TOP)/extmod/*.c -$(BUILD)/%.o: %.m - $(ECHO) "M $<" - clang -I$(INC) $(CFLAGS) -c -o $@ $< # Value of configure's --host= option (required for cross-compilation). # Deduce it from CROSS_COMPILE by default, but can be overridden. @@ -295,7 +336,7 @@ else CROSS_COMPILE_HOST = endif -deplibs: libffi axtls +deplibs: $(DEPLIBS) libffi: $(BUILD)/lib/libffi/include/ffi.h @@ -306,15 +347,15 @@ $(TOP)/lib/libffi/configure: $(TOP)/lib/libffi/autogen.sh # docs and depending on makeinfo $(BUILD)/lib/libffi/include/ffi.h: $(TOP)/lib/libffi/configure mkdir -p $(BUILD)/lib/libffi; cd $(BUILD)/lib/libffi; \ - $(abspath $(TOP))/lib/libffi/configure $(CROSS_COMPILE_HOST) --prefix=$$PWD/out --disable-structs CC="$(CC)" CXX="$(CXX)" LD="$(LD)" CFLAGS="-Os -fomit-frame-pointer -fstrict-aliasing -ffast-math -fno-exceptions"; \ + $(abspath $(TOP))/lib/libffi/configure $(CROSS_COMPILE_HOST) --prefix=$$PWD/out --disable-shared --disable-structs CC="$(CC)" CXX="$(CXX)" LD="$(LD)" CFLAGS="-Os -fomit-frame-pointer -fstrict-aliasing -ffast-math -fno-exceptions"; \ $(MAKE) install-exec-recursive; $(MAKE) -C include install-data-am PREFIX = /usr/local BINDIR = $(DESTDIR)$(PREFIX)/bin -install: $(PROG) +install: $(BUILD)/$(PROG) install -d $(BINDIR) - install $(PROG) $(BINDIR)/$(PROG) + install $(BUILD)/$(PROG) $(BINDIR)/$(PROG) uninstall: -rm $(BINDIR)/$(PROG) diff --git a/tulip/linux/main.c b/tulip/linux/main.c index 97343a6a5..b8abd091b 100644 --- a/tulip/linux/main.c +++ b/tulip/linux/main.c @@ -44,7 +44,7 @@ #include "py/repl.h" #include "py/gc.h" #include "py/objstr.h" -#include "py/stackctrl.h" +#include "py/cstack.h" #include "py/mphal.h" #include "py/mpthread.h" #include "extmod/misc.h" @@ -53,23 +53,22 @@ #include "extmod/vfs_posix.h" #include "genhdr/mpversion.h" #include "input.h" -#include "shared/runtime/pyexec.h" #include "mpthreadport.h" #include "display.h" #include "alles.h" #include "midi.h" #include "sequencer.h" +#include "shared/runtime/pyexec.h" + // Command line options, with their defaults -STATIC bool compile_only = false; -STATIC uint emit_opt = MP_EMIT_OPT_NONE; +static bool compile_only = false; +static uint emit_opt = MP_EMIT_OPT_NONE; #if MICROPY_ENABLE_GC // Heap size of GC heap (if enabled) // Make it larger on a 64 bit machine, because pointers are larger. - -// TODO - make this equivalent always with Tulip CC long heap_size = 4 * 1024 * 1024 * (sizeof(mp_uint_t) / 4); #endif @@ -78,7 +77,6 @@ long heap_size = 4 * 1024 * 1024 * (sizeof(mp_uint_t) / 4); #define MICROPY_GC_SPLIT_HEAP_N_HEAPS (1) #endif - #if !MICROPY_PY_SYS_PATH #error "The unix port requires MICROPY_PY_SYS_PATH=1" #endif @@ -87,12 +85,13 @@ long heap_size = 4 * 1024 * 1024 * (sizeof(mp_uint_t) / 4); #error "The unix port requires MICROPY_PY_SYS_ARGV=1" #endif - - extern int unix_display_draw(); extern void unix_display_init(); -STATIC void stderr_print_strn(void *env, const char *str, size_t len) { + + + +static void stderr_print_strn(void *env, const char *str, size_t len) { (void)env; ssize_t ret; MP_HAL_RETRY_SYSCALL(ret, write(STDERR_FILENO, str, len), {}); @@ -104,12 +103,11 @@ STATIC void stderr_print_strn(void *env, const char *str, size_t len) { const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; - #define FORCED_EXIT (0x100) // If exc is SystemExit, return value where FORCED_EXIT bit set, // and lower 8 bits are SystemExit value. For all other exceptions, // return 1. -STATIC int handle_uncaught_exception(mp_obj_base_t *exc) { +static int handle_uncaught_exception(mp_obj_base_t *exc) { // check for SystemExit if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // None is an exit value of 0; an int is its value; anything else is 1 @@ -134,7 +132,7 @@ STATIC int handle_uncaught_exception(mp_obj_base_t *exc) { // Returns standard error codes: 0 for success, 1 for all other errors, // except if FORCED_EXIT bit is set then script raised SystemExit and the // value of the exit is in the lower 8 bits of the return value -STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_input_kind_t input_kind, bool is_repl) { +static int execute_from_lexer(int source_kind, const void *source, mp_parse_input_kind_t input_kind, bool is_repl) { mp_hal_set_interrupt_char(CHAR_CTRL_C); nlr_buf_t nlr; @@ -148,7 +146,8 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu const vstr_t *vstr = source; lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, false); } else if (source_kind == LEX_SRC_FILENAME) { - lex = mp_lexer_new_from_file((const char *)source); + const char *filename = (const char *)source; + lex = mp_lexer_new_from_file(qstr_from_str(filename)); } else { // LEX_SRC_STDIN lex = mp_lexer_new_from_fd(MP_QSTR__lt_stdin_gt_, 0, false); } @@ -195,7 +194,7 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu #if MICROPY_USE_READLINE == 1 #include "shared/readline/readline.h" #else -STATIC char *strjoin(const char *s1, int sep_char, const char *s2) { +static char *strjoin(const char *s1, int sep_char, const char *s2) { int l1 = strlen(s1); int l2 = strlen(s2); char *s = malloc(l1 + l2 + 2); @@ -210,11 +209,11 @@ STATIC char *strjoin(const char *s1, int sep_char, const char *s2) { } #endif -STATIC int do_repl(void) { +static int do_repl(void) { mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION); mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE); - mp_hal_stdout_tx_str("\nType \"help()\" for more information.\n"); - + mp_hal_stdout_tx_str("\n"); + #if MICROPY_USE_READLINE == 1 // use MicroPython supplied readline @@ -315,24 +314,24 @@ STATIC int do_repl(void) { } int ret = execute_from_lexer(LEX_SRC_STR, line, MP_PARSE_SINGLE_INPUT, true); + free(line); if (ret & FORCED_EXIT) { return ret; } - free(line); } #endif } -STATIC int do_file(const char *file) { +static int do_file(const char *file) { return execute_from_lexer(LEX_SRC_FILENAME, file, MP_PARSE_FILE_INPUT, false); } -STATIC int do_str(const char *str) { +static int do_str(const char *str) { return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false); } -STATIC void print_help(char **argv) { +static void print_help(char **argv) { printf( "usage: %s [] [-X ] [-c | -m | ]\n" "Options:\n" @@ -371,13 +370,13 @@ STATIC void print_help(char **argv) { } } -STATIC int invalid_args(void) { +static int invalid_args(void) { fprintf(stderr, "Invalid command line arguments. Use -h option for help.\n"); return 1; } // Process options which set interpreter init options -STATIC void pre_process_options(int argc, char **argv) { +static void pre_process_options(int argc, char **argv) { for (int a = 1; a < argc; a++) { if (argv[a][0] == '-') { if (strcmp(argv[a], "-c") == 0 || strcmp(argv[a], "-m") == 0) { @@ -457,7 +456,7 @@ STATIC void pre_process_options(int argc, char **argv) { } } -STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { +static void set_sys_argv(char *argv[], int argc, int start_arg) { for (int i = start_arg; i < argc; i++) { mp_obj_list_append(mp_sys_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i]))); } @@ -465,9 +464,9 @@ STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { #if MICROPY_PY_SYS_EXECUTABLE extern mp_obj_str_t mp_sys_executable_obj; -STATIC char executable_path[MICROPY_ALLOC_PATH_MAX]; +static char executable_path[MICROPY_ALLOC_PATH_MAX]; -STATIC void sys_set_excecutable(char *argv0) { +static void sys_set_excecutable(char *argv0) { if (realpath(argv0, executable_path)) { mp_obj_str_set_data(&mp_sys_executable_obj, (byte *)executable_path, strlen(executable_path)); } @@ -481,10 +480,10 @@ STATIC void sys_set_excecutable(char *argv0) { #endif -extern int16_t amy_device_id; -extern void setup_lvgl(); +extern int16_t amy_device_id; + /* MP_NOINLINE int main_(int argc, char **argv); @@ -492,27 +491,45 @@ int main(int argc, char **argv) { #if MICROPY_PY_THREAD mp_thread_init(); #endif + + // Define a reasonable stack limit to detect stack overflow. + mp_uint_t stack_size = 40000 * (sizeof(void *) / 4); + #if defined(__arm__) && !defined(__thumb2__) + // ARM (non-Thumb) architectures require more stack. + stack_size *= 2; + #endif + // We should capture stack top ASAP after start, and it should be // captured guaranteedly before any other stack variables are allocated. // For this, actual main (renamed main_) should not be inlined into // this function. main_() itself may have other functions inlined (with // their own stack variables), that's why we need this main/main_ split. - mp_stack_ctrl_init(); + mp_cstack_init_with_sp_here(stack_size); return main_(argc, argv); } */ +extern void setup_lvgl(); MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { -#if MICROPY_PY_THREAD + + #if MICROPY_PY_THREAD mp_thread_init(); #endif + + // Define a reasonable stack limit to detect stack overflow. + mp_uint_t stack_size = 40000 * (sizeof(void *) / 4); + #if defined(__arm__) && !defined(__thumb2__) + // ARM (non-Thumb) architectures require more stack. + stack_size *= 2; + #endif + // We should capture stack top ASAP after start, and it should be // captured guaranteedly before any other stack variables are allocated. // For this, actual main (renamed main_) should not be inlined into // this function. main_() itself may have other functions inlined (with // their own stack variables), that's why we need this main/main_ split. - mp_stack_ctrl_init(); + mp_cstack_init_with_sp_here(stack_size); #ifdef SIGPIPE @@ -529,17 +546,8 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { signal(SIGPIPE, SIG_IGN); #endif - // Define a reasonable stack limit to detect stack overflow. - mp_uint_t stack_limit = 40000 * (sizeof(void *) / 4); - #if defined(__arm__) && !defined(__thumb2__) - // ARM (non-Thumb) architectures require more stack. - stack_limit *= 2; - #endif - mp_stack_set_limit(stack_limit); - - - // pre_process_options(argc, argv); + //pre_process_options(argc, argv); #if MICROPY_ENABLE_GC #if !MICROPY_GC_SPLIT_HEAP @@ -565,18 +573,15 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); #endif - mp_init(); - - #if MICROPY_EMIT_NATIVE // Set default emitter options MP_STATE_VM(default_emit_opt) = emit_opt; #else (void)emit_opt; #endif - + setup_lvgl(); #if MICROPY_VFS_POSIX @@ -590,6 +595,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table); } #endif + { // sys.path starts as [""] mp_sys_path = mp_obj_new_list(0, NULL); @@ -605,7 +611,12 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { // First entry is empty. We've already added an empty entry to sys.path, so skip it. ++path; } - bool path_remaining = *path; + // GCC targeting RISC-V 64 reports a warning about `path_remaining` being clobbered by + // either setjmp or vfork if that variable it is allocated on the stack. This may + // probably be a compiler error as it occurs on a few recent GCC releases (up to 14.1.0) + // but LLVM doesn't report any warnings. + static bool path_remaining; + path_remaining = *path; while (path_remaining) { char *path_entry_end = strchr(path, PATHLIST_SEP_CHAR); if (path_entry_end == NULL) { @@ -626,7 +637,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { path = path_entry_end + 1; } } - + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); #if defined(MICROPY_UNIX_COVERAGE) @@ -658,9 +669,11 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { printf(" peak %d\n", m_get_peak_bytes_allocated()); */ - //#if MICROPY_PY_SYS_EXECUTABLE - //sys_set_excecutable(argv[0]); - //#endif + /* + #if MICROPY_PY_SYS_EXECUTABLE + sys_set_excecutable(argv[0]); + #endif + */ const int NOTHING_EXECUTED = -2; int ret = NOTHING_EXECUTED; @@ -683,7 +696,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { return invalid_args(); } mp_obj_t import_args[4]; - import_args[0] = mp_obj_new_str(argv[a + 1], strlen(argv[a + 1])); + import_args[0] = mp_obj_new_str_from_cstr(argv[a + 1]); import_args[1] = import_args[2] = mp_const_none; // Ask __import__ to handle imported module specially - set its __name__ // to __main__, and also return this leaf module, not top-level package @@ -713,7 +726,10 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { return handle_uncaught_exception(nlr.ret_val) & 0xff; } - if (mp_obj_is_package(mod) && !subpkg_tried) { + // If this module is a package, see if it has a `__main__.py`. + mp_obj_t dest[2]; + mp_load_method_protected(mod, MP_QSTR___path__, dest, true); + if (dest[0] != MP_OBJ_NULL && !subpkg_tried) { subpkg_tried = true; vstr_t vstr; int len = strlen(argv[a + 1]); @@ -733,7 +749,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { mp_verbose_flag++; #endif } else if (strncmp(argv[a], "-O", 2) == 0) { - if (mp_unichar_mp_isdigit(argv[a][2])) { + if (unichar_isdigit(argv[a][2])) { MP_STATE_VM(mp_optimise_value) = argv[a][2] & 0xf; } else { MP_STATE_VM(mp_optimise_value) = 0; @@ -756,7 +772,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { // Set base dir of the script as first entry in sys.path. char *p = strrchr(basedir, '/'); - path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir); + mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0), mp_obj_new_str_via_qstr(basedir, p - basedir)); free(pathbuf); set_sys_argv(argv, argc, a); @@ -764,11 +780,13 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { break; } } + + const char *inspect_env = getenv("MICROPYINSPECT"); + if (inspect_env && inspect_env[0] != '\0') { + inspect = true; + } */ - //const char *inspect_env = getenv("MICROPYINSPECT"); - //if (inspect_env && inspect_env[0] != '\0') { - // inspect = true; - //} + inspect = true; pyexec_frozen_module("_boot.py", false); pyexec_file_if_exists("boot.py"); @@ -840,6 +858,12 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { return 0; } + + +extern int8_t unix_display_flag; + +#include "lvgl.h" + int main(int argc, char **argv) { // Get the resources folder loc // So thread out alles and then micropython tasks @@ -877,13 +901,12 @@ int main(int argc, char **argv) { unix_display_init(); pthread_t alles_thread_id; pthread_create(&alles_thread_id, NULL, alles_start, NULL); - - //pthread_t midi_thread_id; - ///pthread_create(&midi_thread_id, NULL, run_midi, NULL); - + /* + pthread_t midi_thread_id; + pthread_create(&midi_thread_id, NULL, run_midi, NULL); + */ pthread_t mp_thread_id; pthread_create(&mp_thread_id, NULL, main_, NULL); - int c = 0; sequencer_init(); pthread_t sequencer_thread_id; @@ -891,30 +914,30 @@ int main(int argc, char **argv) { delay_ms(100); // Schedule a "turning on" sound - bleep(); - - -display_jump: - while(c>=0) { - // unix_display_draw returns -1 if the window was quit - c = unix_display_draw(); - if(c>=0) { - // Figure out how long to sleep. ticks has the amount of time already spent for this frame - // so let's fill in the rest with a usleep. - int sleep_ms_for_frame = (int) ((1000.0/TARGET_DESKTOP_FPS) - c); - if(sleep_ms_for_frame > 0) usleep(1000*sleep_ms_for_frame); - } + + +display_jump: + + while(unix_display_flag>=0) { + int ticks = unix_display_draw(); + // Figure out how long to sleep. ticks has the amount of time already spent for this frame + // so let's fill in the rest with a usleep. + int sleep_ms_for_frame = (int) ((1000.0/TARGET_DESKTOP_FPS) - ticks); + if(sleep_ms_for_frame > 0) usleep(1000*sleep_ms_for_frame); } - if(c==-2) { + if(unix_display_flag==-2) { + fprintf(stderr,"restarting display\n"); // signal to restart display after a timing change + unix_display_init(); - c=0; + unix_display_flag = 0; goto display_jump; } + // We're done. join the threads? + return 0; } - void nlr_jump_fail(void *val) { #if MICROPY_USE_READLINE == 1 mp_hal_stdio_mode_orig(); diff --git a/tulip/linux/mpconfigport.h b/tulip/linux/mpconfigport.h index 2de064871..165278606 100644 --- a/tulip/linux/mpconfigport.h +++ b/tulip/linux/mpconfigport.h @@ -34,8 +34,15 @@ // Variant-specific definitions. #include "mpconfigvariant.h" + + #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "tulip" +#undef MICROPY_MALLOC_USES_ALLOCATED_SIZE +#undef MICROPY_MEM_STATS +#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (0) +#define MICROPY_MEM_STATS (0) + // Tulip stuff -- move to mpconfigtulip.h ? #define MICROPY_PY_IO (1) #define MICROPY_PY_SYS_STDFILES (0) @@ -43,7 +50,6 @@ #define MICROPY_HW_BOARD_NAME "Tulip4" - #ifndef MICROPY_CONFIG_ROM_LEVEL #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) #endif @@ -79,25 +85,6 @@ #define MICROPY_EMIT_ARM (1) #endif - -#ifdef MICROPY_MALLOC_USES_ALLOCATED_SIZE -#undef MICROPY_MALLOC_USES_ALLOCATED_SIZE -#define MICROPY_MALLOC_USES_ALLOCATED_SIZE (0) -#undef MICROPY_MEM_STATS -#define MICROPY_MEM_STATS (0) -#endif - -// If enabled, configure how to seed random on init. -#ifdef MICROPY_PY_RANDOM_SEED_INIT_FUNC -#include -void mp_hal_get_random(size_t n, void *buf); -static inline unsigned long mp_random_seed_init(void) { - unsigned long r; - mp_hal_get_random(sizeof(r), &r); - return r; -} -#endif - // Type definitions for the specific machine based on the word size. #ifndef MICROPY_OBJ_REPR #ifdef __LP64__ @@ -133,7 +120,7 @@ typedef long mp_off_t; // Always enable GC. #define MICROPY_ENABLE_GC (1) -#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__) || (defined(__riscv) && (__riscv_xlen == 64))) // Fall back to setjmp() implementation for discovery of GC pointers in registers. #define MICROPY_GCREGS_SETJMP (1) #endif @@ -145,8 +132,8 @@ typedef long mp_off_t; #define MICROPY_HELPER_LEXER_UNIX (1) #define MICROPY_VFS_POSIX (1) #define MICROPY_READER_POSIX (1) -#ifndef MICROPY_TRACKED_ALLOC -#define MICROPY_TRACKED_ALLOC (MICROPY_BLUETOOTH_BTSTACK) +#if MICROPY_PY_FFI || MICROPY_BLUETOOTH_BTSTACK +#define MICROPY_TRACKED_ALLOC (1) #endif // VFS stat functions should return time values relative to 1970/1/1 @@ -162,12 +149,8 @@ typedef long mp_off_t; #define MICROPY_STACKLESS_STRICT (0) #endif -// If settrace is enabled then we need code saving. -#if MICROPY_PY_SYS_SETTRACE -#define MICROPY_PERSISTENT_CODE_SAVE (1) -#define MICROPY_COMP_CONST (0) -#endif - +// Implementation of the machine module. +#define MICROPY_PY_MACHINE_INCLUDEFILE "../../micropython/ports/unix/modmachine.c" // Unix-specific configuration of machine.mem*. #define MICROPY_MACHINE_MEM_GET_READ_ADDR mod_machine_mem_get_addr @@ -183,13 +166,13 @@ typedef long mp_off_t; // Ensure builtinimport.c works with -m. #define MICROPY_MODULE_OVERRIDE_MAIN_IMPORT (1) -// Don't default sys.argv because we do that in main. +// Don't default sys.argv and sys.path because we do that in main. #define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (0) // Enable sys.executable. #define MICROPY_PY_SYS_EXECUTABLE (1) -#define MICROPY_PY_USOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) +#define MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) // Bare-metal ports don't have stderr. Printing debug to stderr may give tests // which check stdout a chance to pass, etc. @@ -200,20 +183,14 @@ extern const struct _mp_print_t mp_stderr_print; // For the native emitter configure how to mark a region as executable. void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size); void mp_unix_free_exec(void *ptr, size_t size); -void mp_unix_mark_exec(void); #define MP_PLAT_ALLOC_EXEC(min_size, ptr, size) mp_unix_alloc_exec(min_size, ptr, size) #define MP_PLAT_FREE_EXEC(ptr, size) mp_unix_free_exec(ptr, size) -#ifndef MICROPY_FORCE_PLAT_ALLOC_EXEC -// Use MP_PLAT_ALLOC_EXEC for any executable memory allocation, including for FFI -// (overriding libffi own implementation) -#define MICROPY_FORCE_PLAT_ALLOC_EXEC (1) -#endif // If enabled, configure how to seed random on init. -#ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC +#ifdef MICROPY_PY_RANDOM_SEED_INIT_FUNC #include void mp_hal_get_random(size_t n, void *buf); -static inline unsigned long mp_urandom_seed_init(void) { +static inline unsigned long mp_random_seed_init(void) { unsigned long r; mp_hal_get_random(sizeof(r), &r); return r; @@ -253,6 +230,7 @@ static inline unsigned long mp_urandom_seed_init(void) { #include #endif + // If threading is enabled, configure the atomic section. #if MICROPY_PY_THREAD #define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) diff --git a/tulip/linux/mpconfigport.mk b/tulip/linux/mpconfigport.mk new file mode 100644 index 000000000..b0695760b --- /dev/null +++ b/tulip/linux/mpconfigport.mk @@ -0,0 +1,46 @@ +# Enable/disable modules and 3rd-party libs to be included in interpreter + +# Build 32-bit binaries on a 64-bit host +MICROPY_FORCE_32BIT = 0 + +# This variable can take the following values: +# 0 - no readline, just simple stdin input +# 1 - use MicroPython version of readline +MICROPY_USE_READLINE = 1 + +# btree module using Berkeley DB 1.xx +MICROPY_PY_BTREE = 1 + +# _thread module using pthreads +MICROPY_PY_THREAD = 1 + +# Subset of CPython termios module +MICROPY_PY_TERMIOS = 1 + +# Subset of CPython socket module +MICROPY_PY_SOCKET = 1 + +# ffi module requires libffi (libffi-dev Debian package) +MICROPY_PY_FFI = 0 + +# ssl module requires one of the TLS libraries below +MICROPY_PY_SSL = 1 +# axTLS has minimal size but implements only a subset of modern TLS +# functionality, so may have problems with some servers. +MICROPY_SSL_AXTLS = 0 +# mbedTLS is more up to date and complete implementation, but also +# more bloated. +MICROPY_SSL_MBEDTLS = 1 + +# jni module requires JVM/JNI +MICROPY_PY_JNI = 0 + +# Avoid using system libraries, use copies bundled with MicroPython +# as submodules (currently affects only libffi). +MICROPY_STANDALONE ?= 0 + +MICROPY_ROM_TEXT_COMPRESSION = 1 + +MICROPY_VFS_FAT = 1 +MICROPY_VFS_LFS1 = 1 +MICROPY_VFS_LFS2 = 1 diff --git a/tulip/macos/Makefile b/tulip/macos/Makefile index 4569a6279..cea92c985 100644 --- a/tulip/macos/Makefile +++ b/tulip/macos/Makefile @@ -1,20 +1,14 @@ +# Tulip macos makefile, based on Micropython unix port makefile + WHICH_ARCH ?= $(shell arch) +DEBUG=1 ifeq ($(WHICH_ARCH), arm64) ARCHFLAGS = -target arm64-macos11 HOMEBREW = /opt/homebrew else ARCHFLAGS = -target x86_64-apple-macos10.15 - # On x86, homebrew FFI seems to have two locations - # /usr/local/homebrew/opt/libffi/include/ffi.h - # /usr/local/opt/libffi/include/ffi.h - # Check which it is - ifneq ("$(wildcard /usr/local/opt/libffi/include/ffi.h)","") - HOMEBREW = /usr/local - else - HOMEBREW = /usr/local/homebrew - endif endif # Select the variant to build for. @@ -31,12 +25,15 @@ TOP = ../../micropython VARIANT_DIR ?= ../../micropython/ports/unix/variants/$(VARIANT) +# If the build directory is not given, make it reflect the variant name. +BUILD ?= build-$(VARIANT) + include $(TOP)/py/mkenv.mk -include ../shared/desktop/mpconfigport.mk -include $(VARIANT_DIR)/mpconfigvariant.mk +include mpconfigport.mk +#include $(VARIANT_DIR)/mpconfigvariant.mk # Use the default frozen manifest, variants may override this. -FROZEN_MANIFEST ?= manifest.py +FROZEN_MANIFEST ?= variants/manifest.py # This should be configured by the mpconfigvariant.mk PROG = tulip.$(WHICH_ARCH) @@ -65,7 +62,10 @@ LVGL_MPY_METADATA = $(BUILD)/lvgl/lv_mpy.json QSTR_GLOBAL_DEPENDENCIES += $(LVGL_MPY) CFLAGS_MOD += $(LV_CFLAGS) SRC_C += $(shell find $(LVGL_DIR)/src -type f -name '*.c') -CFLAGS += -DLV_CONF_INCLUDE_SIMPLE +CFLAGS += -DLV_CONF_INCLUDE_SIMPLE + +# This is for the lvgl stuff that uses STATIC +CFLAGS += -DSTATIC=static $(LVGL_MPY): $(ALL_LVGL_SRC) $(LVGL_BINDING_DIR)/gen/gen_mpy.py $(ECHO) "LVGL-GEN $@" @@ -88,14 +88,22 @@ INC += -I$(TOP)/../lv_binding_micropython_tulip/lvgl/src/libs/lodepng INC += -I./SDL2.framework/Headers + +GIT_SUBMODULES += lib/berkeley-db-1.xx + +INC += -I. +INC += -I$(TOP) +INC += -I$(BUILD) + +# compiler settings + # compiler settings CWARN = -Wall -Werror CWARN += -Wextra -Wno-unused-parameter -Wno-unused-but-set-parameter -Wpointer-arith -Wdouble-promotion -Wfloat-conversion -Wno-missing-declarations -Wno-unused-but-set-variable -Wno-sign-compare -Wno-gnu-variable-sized-type-not-at-end -Wno-undefined-internal CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(CFLAGS_MOD) $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EXTRA) CFLAGS += -DTULIP_DESKTOP -DMACOS -#CFLAGS += -DAMY_DEBUG - CFLAGS += $(ARCHFLAGS) + # Debugging/Optimization ifdef DEBUG #COPT ?= -Og @@ -107,8 +115,9 @@ endif # Remove unused sections. COPT += -fdata-sections -ffunction-sections -O0 -# Always enable symbols -- They're occasionally useful, and don't make it into the -# final .bin/.hex/.dfu so the extra size doesn't matter. + +# Note: Symbols and debug information will still be stripped from the final binary +# unless "DEBUG=1" or "STRIP=" is passed to make, see README.md for details. CFLAGS += -g ifndef DEBUG @@ -153,7 +162,7 @@ endif LDFLAGS_ARCH = -Wl,-map,$@.map -Wl,-dead_strip else # Use gcc syntax for map file -LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections +LDFLAGS_ARCH = -Wl,-Map=$@.map,--cref -Wl,--gc-sections endif LDFLAGS += $(LDFLAGS_MOD) $(LDFLAGS_ARCH) -lm $(LDFLAGS_EXTRA) LDFLAGS += -F. -framework SDL2 -framework CoreFoundation -framework CoreMIDI -framework AudioToolbox -framework CoreAudio -framework Cocoa -lstdc++ $(ARCHFLAGS) @@ -167,80 +176,101 @@ ifeq ($(MICROPY_FORCE_32BIT),1) # starting with linux-libc-dev:i386 ifeq ($(MICROPY_PY_FFI),1) ifeq ($(UNAME_S),Linux) -CFLAGS_MOD += -I/usr/include/i686-linux-gnu +CFLAGS += -I/usr/include/i686-linux-gnu endif endif endif ifeq ($(MICROPY_USE_READLINE),1) INC += -I$(TOP)/shared/readline -CFLAGS_MOD += -DMICROPY_USE_READLINE=1 +CFLAGS += -DMICROPY_USE_READLINE=1 SHARED_SRC_C_EXTRA += readline/readline.c endif ifeq ($(MICROPY_PY_TERMIOS),1) -CFLAGS_MOD += -DMICROPY_PY_TERMIOS=1 -SRC_MOD += $(MICROPY_PORT_DIR)/modtermios.c +CFLAGS += -DMICROPY_PY_TERMIOS=1 endif ifeq ($(MICROPY_PY_SOCKET),1) -CFLAGS_MOD += -DMICROPY_PY_SOCKET=1 +CFLAGS += -DMICROPY_PY_SOCKET=1 SRC_MOD += $(MICROPY_PORT_DIR)/modsocket.c endif ifeq ($(MICROPY_PY_THREAD),1) -CFLAGS_MOD += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 -LDFLAGS_MOD += $(LIBPTHREAD) +CFLAGS += -DMICROPY_PY_THREAD=1 -DMICROPY_PY_THREAD_GIL=0 +LDFLAGS += $(LIBPTHREAD) endif + include ../shared/tulip.mk MICROPY_PORT_DIR=../../micropython/ports/unix +ifeq ($(MICROPY_PY_SSL),1) +ifeq ($(MICROPY_SSL_AXTLS),1) + +endif +endif + +# If the variant enables it, enable modbluetooth. +ifeq ($(MICROPY_PY_BLUETOOTH),1) +ifeq ($(MICROPY_BLUETOOTH_BTSTACK),1) +HAVE_LIBUSB := $(shell (which pkg-config > /dev/null && pkg-config --exists libusb-1.0) 2>/dev/null && echo '1') + +# Figure out which BTstack transport to use. +ifeq ($(HAVE_LIBUSB),1) +# Default to btstack-over-usb. +MICROPY_BLUETOOTH_BTSTACK_USB ?= 1 +else +# Fallback to HCI controller via a H4 UART (e.g. Zephyr on nRF) over a /dev/tty serial port. +MICROPY_BLUETOOTH_BTSTACK_H4 ?= 1 +endif + +SRC_BTSTACK_C += lib/btstack/platform/embedded/btstack_run_loop_embedded.c +endif +endif + ifeq ($(MICROPY_PY_FFI),1) ifeq ($(MICROPY_STANDALONE),1) -LIBFFI_CFLAGS_MOD := -I$(shell ls -1d $(BUILD)/lib/libffi/out/lib/libffi-*/include) +# Build libffi from source. +GIT_SUBMODULES += lib/libffi +DEPLIBS += libffi +LIBFFI_CFLAGS := -I$(shell ls -1d $(BUILD)/lib/libffi/include) ifeq ($(MICROPY_FORCE_32BIT),1) - LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib32/libffi.a + LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib32/libffi.a else - LIBFFI_LDFLAGS_MOD = $(BUILD)/lib/libffi/out/lib/libffi.a + LIBFFI_LDFLAGS = $(BUILD)/lib/libffi/out/lib/libffi.a endif else -LIBFFI_CFLAGS_MOD := $(shell pkg-config --cflags libffi) -LIBFFI_LDFLAGS_MOD := $(shell pkg-config --libs libffi) +# Use system version of libffi. +LIBFFI_CFLAGS := $(shell pkg-config --cflags libffi) +LIBFFI_LDFLAGS := $(shell pkg-config --libs libffi) endif - -# libffi is installed in system on 10.12, so force use of homebrew version -LIBFFI_LDFLAGS_MOD = $(HOMEBREW)/opt/libffi/lib/libffi.a -LIBFFI_CFLAGS_MOD := -I$(HOMEBREW)/opt/libffi/include - - ifeq ($(UNAME_S),Linux) -LIBFFI_LDFLAGS_MOD += -ldl +LIBFFI_LDFLAGS += -ldl endif -CFLAGS_MOD += $(LIBFFI_CFLAGS_MOD) -DMICROPY_PY_FFI=1 -LDFLAGS_MOD += $(LIBFFI_LDFLAGS_MOD) -SRC_MOD += $(MICROPY_PORT_DIR)/modffi.c +CFLAGS += $(LIBFFI_CFLAGS) -DMICROPY_PY_FFI=1 +LDFLAGS += $(LIBFFI_LDFLAGS) endif ifeq ($(MICROPY_PY_JNI),1) # Path for 64-bit OpenJDK, should be adjusted for other JDKs -CFLAGS_MOD += -I/usr/lib/jvm/java-7-openjdk-amd64/include -DMICROPY_PY_JNI=1 -SRC_MOD += $(MICROPY_PORT_DIR)/modjni.c +CFLAGS += -I/usr/lib/jvm/java-7-openjdk-amd64/include -DMICROPY_PY_JNI=1 endif + + # source files SRC_C += \ main.c \ - ../shared/desktop/unix_mphal.c \ ../shared/desktop/unix_display.c \ ../shared/desktop/multicast.c \ - ../shared/desktop/mpthreadport.c \ + ../shared/desktop/unix_mphal.c \ ../../amy/src/libminiaudio-audio.c \ + $(MICROPY_PORT_DIR)/mpthreadport.c \ $(MICROPY_PORT_DIR)/gccollect.c \ $(MICROPY_PORT_DIR)/input.c \ - $(MICROPY_PORT_DIR)/modselect.c \ $(MICROPY_PORT_DIR)/alloc.c \ $(MICROPY_PORT_DIR)/fatfs_port.c \ $(MICROPY_PORT_DIR)/mpbthciport.c \ @@ -277,21 +307,16 @@ OBJ += $(addprefix $(BUILD)/, $(SHARED_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(EXTMOD_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) + + # List of sources for qstr extraction SRC_QSTR += $(SRC_C) $(SRC_CXX) $(SHARED_SRC_C) $(EXTMOD_SRC_C) -# Append any auto-generated sources that are needed by sources listed in -# SRC_QSTR -SRC_QSTR_AUTO_DEPS += ifneq ($(FROZEN_MANIFEST),) -# To use frozen code create a manifest.py file with a description of files to -# freeze, then invoke make with FROZEN_MANIFEST=manifest.py (be sure to build from scratch). -CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool -CFLAGS += -DMICROPY_MODULE_FROZEN_MPY CFLAGS += -DMPZ_DIG_SIZE=16 # force 16 bits to work on both 32 and 64 bit archs -CFLAGS += -DMICROPY_MODULE_FROZEN_STR endif + CPU_BRAND_NAME=`sysctl -n machdep.cpu.brand_string` CFLAGS += -DMICROPY_HW_MCU_NAME="\"$(CPU_BRAND_NAME)\"" CFLAGS += -Wno-double-promotion -Wno-unused-function -Wno-unused-variable @@ -314,19 +339,24 @@ endif include $(TOP)/py/mkrules.mk -#undefine compile_c +.PHONY: test test_full + +test: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py + +test_full: $(BUILD)/$(PROG) $(TOP)/tests/run-tests.py + $(eval DIRNAME=ports/$(notdir $(CURDIR))) + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py -d thread + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --emit native + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) -d basics float micropython + cd $(TOP)/tests && MICROPY_MICROPYTHON=../$(DIRNAME)/$(BUILD)/$(PROG) ./run-tests.py --via-mpy $(RUN_TESTS_MPY_CROSS_FLAGS) --emit native -d basics float micropython + cat $(TOP)/tests/basics/0prelim.py | ./$(BUILD)/$(PROG) | grep -q 'abc' -define compile_c -$(ECHO) "CC $<" -$(Q)$(CC) $(CFLAGS) -c -MD -o $@ $< || (echo -e $(HELP_BUILD_ERROR); false) -@# The following fixes the dependency file. -@# See http://make.paulandlesley.org/autodep.html for details. -@# Regex adjusted from the above to play better with Windows paths, etc. -@$(CP) $(@:.o=.d) $(@:.o=.P); \ - $(SED) -e 's/#.*//' -e 's/^.*: *//' -e 's/ *\\$$//' \ - -e '/^$$/ d' -e 's/$$/ :/' < $(@:.o=.d) >> $(@:.o=.P); \ - $(RM) -f $(@:.o=.d) -endef +test_gcov: test_full + gcov -o $(BUILD)/py $(TOP)/py/*.c + gcov -o $(BUILD)/extmod $(TOP)/extmod/*.c $(BUILD)/%.o: %.m $(ECHO) "M $<" @@ -340,7 +370,7 @@ else CROSS_COMPILE_HOST = endif -deplibs: libffi axtls +deplibs: $(DEPLIBS) libffi: $(BUILD)/lib/libffi/include/ffi.h @@ -351,15 +381,15 @@ $(TOP)/lib/libffi/configure: $(TOP)/lib/libffi/autogen.sh # docs and depending on makeinfo $(BUILD)/lib/libffi/include/ffi.h: $(TOP)/lib/libffi/configure mkdir -p $(BUILD)/lib/libffi; cd $(BUILD)/lib/libffi; \ - $(abspath $(TOP))/lib/libffi/configure $(CROSS_COMPILE_HOST) --prefix=$$PWD/out --disable-structs CC="$(CC)" CXX="$(CXX)" LD="$(LD)" CFLAGS="-Os -fomit-frame-pointer -fstrict-aliasing -ffast-math -fno-exceptions"; \ + $(abspath $(TOP))/lib/libffi/configure $(CROSS_COMPILE_HOST) --prefix=$$PWD/out --disable-shared --disable-structs CC="$(CC)" CXX="$(CXX)" LD="$(LD)" CFLAGS="-Os -fomit-frame-pointer -fstrict-aliasing -ffast-math -fno-exceptions"; \ $(MAKE) install-exec-recursive; $(MAKE) -C include install-data-am PREFIX = /usr/local BINDIR = $(DESTDIR)$(PREFIX)/bin -install: $(PROG) +install: $(BUILD)/$(PROG) install -d $(BINDIR) - install $(PROG) $(BINDIR)/$(PROG) + install $(BUILD)/$(PROG) $(BINDIR)/$(PROG) uninstall: -rm $(BINDIR)/$(PROG) diff --git a/tulip/macos/main.c b/tulip/macos/main.c index f8e01360a..343f05f90 100644 --- a/tulip/macos/main.c +++ b/tulip/macos/main.c @@ -44,7 +44,7 @@ #include "py/repl.h" #include "py/gc.h" #include "py/objstr.h" -#include "py/stackctrl.h" +#include "py/cstack.h" #include "py/mphal.h" #include "py/mpthread.h" #include "extmod/misc.h" @@ -53,22 +53,22 @@ #include "extmod/vfs_posix.h" #include "genhdr/mpversion.h" #include "input.h" -#include "shared/runtime/pyexec.h" #include "mpthreadport.h" #include "display.h" #include "alles.h" #include "midi.h" #include "sequencer.h" +#include "shared/runtime/pyexec.h" + + // Command line options, with their defaults -STATIC bool compile_only = false; -STATIC uint emit_opt = MP_EMIT_OPT_NONE; +static bool compile_only = false; +static uint emit_opt = MP_EMIT_OPT_NONE; #if MICROPY_ENABLE_GC // Heap size of GC heap (if enabled) // Make it larger on a 64 bit machine, because pointers are larger. - -// TODO - make this equivalent always with Tulip CC long heap_size = 4 * 1024 * 1024 * (sizeof(mp_uint_t) / 4); #endif @@ -77,7 +77,6 @@ long heap_size = 4 * 1024 * 1024 * (sizeof(mp_uint_t) / 4); #define MICROPY_GC_SPLIT_HEAP_N_HEAPS (1) #endif - #if !MICROPY_PY_SYS_PATH #error "The unix port requires MICROPY_PY_SYS_PATH=1" #endif @@ -86,32 +85,13 @@ long heap_size = 4 * 1024 * 1024 * (sizeof(mp_uint_t) / 4); #error "The unix port requires MICROPY_PY_SYS_ARGV=1" #endif - - extern int unix_display_draw(); extern void unix_display_init(); -/* -extern void mp_uos_dupterm_tx_strn(const char *str, size_t len); -void display_print_strn(void *env, const char *str, size_t len) { - (void)env; - if(len) { - display_tfb_str((unsigned char*)str, len, 0, tfb_fg_pal_color, tfb_bg_pal_color); - } -} -STATIC void stderr_print_strn(void *env, const char *str, size_t len) { - (void)env; - ssize_t ret; - MP_HAL_RETRY_SYSCALL(ret, write(STDERR_FILENO, str, len), {}); - mp_uos_dupterm_tx_strn(str, len); -} -*/ - - -STATIC void stderr_print_strn(void *env, const char *str, size_t len) { +static void stderr_print_strn(void *env, const char *str, size_t len) { (void)env; ssize_t ret; MP_HAL_RETRY_SYSCALL(ret, write(STDERR_FILENO, str, len), {}); @@ -123,18 +103,11 @@ STATIC void stderr_print_strn(void *env, const char *str, size_t len) { const mp_print_t mp_stderr_print = {NULL, stderr_print_strn}; -/* -const mp_print_t mp_stderr_print = {NULL, display_print_strn}; -const mp_print_t mp_stdout_print = {NULL, display_print_strn}; -const mp_print_t mp_sys_stdout_print = {NULL, display_print_strn}; -const mp_print_t mp_display_print = {NULL, display_print_strn}; -*/ - #define FORCED_EXIT (0x100) // If exc is SystemExit, return value where FORCED_EXIT bit set, // and lower 8 bits are SystemExit value. For all other exceptions, // return 1. -STATIC int handle_uncaught_exception(mp_obj_base_t *exc) { +static int handle_uncaught_exception(mp_obj_base_t *exc) { // check for SystemExit if (mp_obj_is_subclass_fast(MP_OBJ_FROM_PTR(exc->type), MP_OBJ_FROM_PTR(&mp_type_SystemExit))) { // None is an exit value of 0; an int is its value; anything else is 1 @@ -159,7 +132,7 @@ STATIC int handle_uncaught_exception(mp_obj_base_t *exc) { // Returns standard error codes: 0 for success, 1 for all other errors, // except if FORCED_EXIT bit is set then script raised SystemExit and the // value of the exit is in the lower 8 bits of the return value -STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_input_kind_t input_kind, bool is_repl) { +static int execute_from_lexer(int source_kind, const void *source, mp_parse_input_kind_t input_kind, bool is_repl) { mp_hal_set_interrupt_char(CHAR_CTRL_C); nlr_buf_t nlr; @@ -173,7 +146,8 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu const vstr_t *vstr = source; lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, vstr->buf, vstr->len, false); } else if (source_kind == LEX_SRC_FILENAME) { - lex = mp_lexer_new_from_file((const char *)source); + const char *filename = (const char *)source; + lex = mp_lexer_new_from_file(qstr_from_str(filename)); } else { // LEX_SRC_STDIN lex = mp_lexer_new_from_fd(MP_QSTR__lt_stdin_gt_, 0, false); } @@ -220,7 +194,7 @@ STATIC int execute_from_lexer(int source_kind, const void *source, mp_parse_inpu #if MICROPY_USE_READLINE == 1 #include "shared/readline/readline.h" #else -STATIC char *strjoin(const char *s1, int sep_char, const char *s2) { +static char *strjoin(const char *s1, int sep_char, const char *s2) { int l1 = strlen(s1); int l2 = strlen(s2); char *s = malloc(l1 + l2 + 2); @@ -235,11 +209,11 @@ STATIC char *strjoin(const char *s1, int sep_char, const char *s2) { } #endif -STATIC int do_repl(void) { +static int do_repl(void) { mp_hal_stdout_tx_str(MICROPY_BANNER_NAME_AND_VERSION); mp_hal_stdout_tx_str("; " MICROPY_BANNER_MACHINE); - mp_hal_stdout_tx_str("\nType \"help()\" for more information.\n"); - + mp_hal_stdout_tx_str("\n"); + #if MICROPY_USE_READLINE == 1 // use MicroPython supplied readline @@ -340,24 +314,24 @@ STATIC int do_repl(void) { } int ret = execute_from_lexer(LEX_SRC_STR, line, MP_PARSE_SINGLE_INPUT, true); + free(line); if (ret & FORCED_EXIT) { return ret; } - free(line); } #endif } -STATIC int do_file(const char *file) { +static int do_file(const char *file) { return execute_from_lexer(LEX_SRC_FILENAME, file, MP_PARSE_FILE_INPUT, false); } -STATIC int do_str(const char *str) { +static int do_str(const char *str) { return execute_from_lexer(LEX_SRC_STR, str, MP_PARSE_FILE_INPUT, false); } -STATIC void print_help(char **argv) { +static void print_help(char **argv) { printf( "usage: %s [] [-X ] [-c | -m | ]\n" "Options:\n" @@ -396,13 +370,13 @@ STATIC void print_help(char **argv) { } } -STATIC int invalid_args(void) { +static int invalid_args(void) { fprintf(stderr, "Invalid command line arguments. Use -h option for help.\n"); return 1; } // Process options which set interpreter init options -STATIC void pre_process_options(int argc, char **argv) { +static void pre_process_options(int argc, char **argv) { for (int a = 1; a < argc; a++) { if (argv[a][0] == '-') { if (strcmp(argv[a], "-c") == 0 || strcmp(argv[a], "-m") == 0) { @@ -482,7 +456,7 @@ STATIC void pre_process_options(int argc, char **argv) { } } -STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { +static void set_sys_argv(char *argv[], int argc, int start_arg) { for (int i = start_arg; i < argc; i++) { mp_obj_list_append(mp_sys_argv, MP_OBJ_NEW_QSTR(qstr_from_str(argv[i]))); } @@ -490,9 +464,9 @@ STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { #if MICROPY_PY_SYS_EXECUTABLE extern mp_obj_str_t mp_sys_executable_obj; -STATIC char executable_path[MICROPY_ALLOC_PATH_MAX]; +static char executable_path[MICROPY_ALLOC_PATH_MAX]; -STATIC void sys_set_excecutable(char *argv0) { +static void sys_set_excecutable(char *argv0) { if (realpath(argv0, executable_path)) { mp_obj_str_set_data(&mp_sys_executable_obj, (byte *)executable_path, strlen(executable_path)); } @@ -542,7 +516,6 @@ char * get_tulip_home_path() { extern int16_t amy_device_id; - /* MP_NOINLINE int main_(int argc, char **argv); @@ -550,12 +523,20 @@ int main(int argc, char **argv) { #if MICROPY_PY_THREAD mp_thread_init(); #endif + + // Define a reasonable stack limit to detect stack overflow. + mp_uint_t stack_size = 40000 * (sizeof(void *) / 4); + #if defined(__arm__) && !defined(__thumb2__) + // ARM (non-Thumb) architectures require more stack. + stack_size *= 2; + #endif + // We should capture stack top ASAP after start, and it should be // captured guaranteedly before any other stack variables are allocated. // For this, actual main (renamed main_) should not be inlined into // this function. main_() itself may have other functions inlined (with // their own stack variables), that's why we need this main/main_ split. - mp_stack_ctrl_init(); + mp_cstack_init_with_sp_here(stack_size); return main_(argc, argv); } */ @@ -567,12 +548,20 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { #if MICROPY_PY_THREAD mp_thread_init(); #endif + + // Define a reasonable stack limit to detect stack overflow. + mp_uint_t stack_size = 40000 * (sizeof(void *) / 4); + #if defined(__arm__) && !defined(__thumb2__) + // ARM (non-Thumb) architectures require more stack. + stack_size *= 2; + #endif + // We should capture stack top ASAP after start, and it should be // captured guaranteedly before any other stack variables are allocated. // For this, actual main (renamed main_) should not be inlined into // this function. main_() itself may have other functions inlined (with // their own stack variables), that's why we need this main/main_ split. - mp_stack_ctrl_init(); + mp_cstack_init_with_sp_here(stack_size); #ifdef SIGPIPE @@ -589,17 +578,8 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { signal(SIGPIPE, SIG_IGN); #endif - // Define a reasonable stack limit to detect stack overflow. - mp_uint_t stack_limit = 40000 * (sizeof(void *) / 4); - #if defined(__arm__) && !defined(__thumb2__) - // ARM (non-Thumb) architectures require more stack. - stack_limit *= 2; - #endif - mp_stack_set_limit(stack_limit); - - - // pre_process_options(argc, argv); + //pre_process_options(argc, argv); #if MICROPY_ENABLE_GC #if !MICROPY_GC_SPLIT_HEAP @@ -625,11 +605,8 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { mp_pystack_init(pystack, &pystack[MP_ARRAY_SIZE(pystack)]); #endif - mp_init(); - - #if MICROPY_EMIT_NATIVE // Set default emitter options MP_STATE_VM(default_emit_opt) = emit_opt; @@ -638,7 +615,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { #endif setup_lvgl(); - + #if MICROPY_VFS_POSIX { // Mount the host FS at the root of our internal VFS @@ -650,6 +627,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table); } #endif + { // sys.path starts as [""] mp_sys_path = mp_obj_new_list(0, NULL); @@ -665,7 +643,12 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { // First entry is empty. We've already added an empty entry to sys.path, so skip it. ++path; } - bool path_remaining = *path; + // GCC targeting RISC-V 64 reports a warning about `path_remaining` being clobbered by + // either setjmp or vfork if that variable it is allocated on the stack. This may + // probably be a compiler error as it occurs on a few recent GCC releases (up to 14.1.0) + // but LLVM doesn't report any warnings. + static bool path_remaining; + path_remaining = *path; while (path_remaining) { char *path_entry_end = strchr(path, PATHLIST_SEP_CHAR); if (path_entry_end == NULL) { @@ -686,7 +669,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { path = path_entry_end + 1; } } - + mp_obj_list_init(MP_OBJ_TO_PTR(mp_sys_argv), 0); #if defined(MICROPY_UNIX_COVERAGE) @@ -718,9 +701,11 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { printf(" peak %d\n", m_get_peak_bytes_allocated()); */ - //#if MICROPY_PY_SYS_EXECUTABLE - //sys_set_excecutable(argv[0]); - //#endif + /* + #if MICROPY_PY_SYS_EXECUTABLE + sys_set_excecutable(argv[0]); + #endif + */ const int NOTHING_EXECUTED = -2; int ret = NOTHING_EXECUTED; @@ -743,7 +728,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { return invalid_args(); } mp_obj_t import_args[4]; - import_args[0] = mp_obj_new_str(argv[a + 1], strlen(argv[a + 1])); + import_args[0] = mp_obj_new_str_from_cstr(argv[a + 1]); import_args[1] = import_args[2] = mp_const_none; // Ask __import__ to handle imported module specially - set its __name__ // to __main__, and also return this leaf module, not top-level package @@ -773,7 +758,10 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { return handle_uncaught_exception(nlr.ret_val) & 0xff; } - if (mp_obj_is_package(mod) && !subpkg_tried) { + // If this module is a package, see if it has a `__main__.py`. + mp_obj_t dest[2]; + mp_load_method_protected(mod, MP_QSTR___path__, dest, true); + if (dest[0] != MP_OBJ_NULL && !subpkg_tried) { subpkg_tried = true; vstr_t vstr; int len = strlen(argv[a + 1]); @@ -793,7 +781,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { mp_verbose_flag++; #endif } else if (strncmp(argv[a], "-O", 2) == 0) { - if (mp_unichar_mp_isdigit(argv[a][2])) { + if (unichar_isdigit(argv[a][2])) { MP_STATE_VM(mp_optimise_value) = argv[a][2] & 0xf; } else { MP_STATE_VM(mp_optimise_value) = 0; @@ -816,7 +804,7 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { // Set base dir of the script as first entry in sys.path. char *p = strrchr(basedir, '/'); - path_items[0] = mp_obj_new_str_via_qstr(basedir, p - basedir); + mp_obj_list_store(mp_sys_path, MP_OBJ_NEW_SMALL_INT(0), mp_obj_new_str_via_qstr(basedir, p - basedir)); free(pathbuf); set_sys_argv(argv, argc, a); @@ -824,11 +812,13 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { break; } } + + const char *inspect_env = getenv("MICROPYINSPECT"); + if (inspect_env && inspect_env[0] != '\0') { + inspect = true; + } */ - //const char *inspect_env = getenv("MICROPYINSPECT"); - //if (inspect_env && inspect_env[0] != '\0') { - // inspect = true; - //} + inspect = true; pyexec_frozen_module("_boot.py", false); pyexec_file_if_exists("boot.py"); @@ -899,6 +889,9 @@ MP_NOINLINE void * main_(void *vargs) { //int argc, char **argv) { //return ret & 0xff; return 0; } + + + extern int8_t unix_display_flag; #include "lvgl.h" @@ -977,7 +970,6 @@ int main(int argc, char **argv) { return 0; } - void nlr_jump_fail(void *val) { #if MICROPY_USE_READLINE == 1 mp_hal_stdio_mode_orig(); diff --git a/tulip/macos/mpconfigport.h b/tulip/macos/mpconfigport.h index bb866fcab..165278606 100644 --- a/tulip/macos/mpconfigport.h +++ b/tulip/macos/mpconfigport.h @@ -34,6 +34,8 @@ // Variant-specific definitions. #include "mpconfigvariant.h" + + #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "tulip" #undef MICROPY_MALLOC_USES_ALLOCATED_SIZE @@ -47,17 +49,6 @@ #define MICROPY_PY_BUILTINS_HELP_TEXT tulip_desktop_help_text #define MICROPY_HW_BOARD_NAME "Tulip4" -/* -#define MICROPY_ROOT_POINTERS \ - LV_ROOTS \ - void *mp_lv_user_data; \ - const char *readline_hist[50]; \ - void *mmap_region_head; \ -*/ - - -// Unclear how this works -- unicode (in strings) seems fine. maybe this is files -//#define MICROPY_PY_BUILTINS_STR_UNICODE (1) #ifndef MICROPY_CONFIG_ROM_LEVEL #define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) @@ -94,18 +85,6 @@ #define MICROPY_EMIT_ARM (1) #endif - -// If enabled, configure how to seed random on init. -#ifdef MICROPY_PY_RANDOM_SEED_INIT_FUNC -#include -void mp_hal_get_random(size_t n, void *buf); -static inline unsigned long mp_random_seed_init(void) { - unsigned long r; - mp_hal_get_random(sizeof(r), &r); - return r; -} -#endif - // Type definitions for the specific machine based on the word size. #ifndef MICROPY_OBJ_REPR #ifdef __LP64__ @@ -141,7 +120,7 @@ typedef long mp_off_t; // Always enable GC. #define MICROPY_ENABLE_GC (1) -#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__)) +#if !(defined(MICROPY_GCREGS_SETJMP) || defined(__x86_64__) || defined(__i386__) || defined(__thumb2__) || defined(__thumb__) || defined(__arm__) || (defined(__riscv) && (__riscv_xlen == 64))) // Fall back to setjmp() implementation for discovery of GC pointers in registers. #define MICROPY_GCREGS_SETJMP (1) #endif @@ -153,8 +132,8 @@ typedef long mp_off_t; #define MICROPY_HELPER_LEXER_UNIX (1) #define MICROPY_VFS_POSIX (1) #define MICROPY_READER_POSIX (1) -#ifndef MICROPY_TRACKED_ALLOC -#define MICROPY_TRACKED_ALLOC (MICROPY_BLUETOOTH_BTSTACK) +#if MICROPY_PY_FFI || MICROPY_BLUETOOTH_BTSTACK +#define MICROPY_TRACKED_ALLOC (1) #endif // VFS stat functions should return time values relative to 1970/1/1 @@ -170,12 +149,8 @@ typedef long mp_off_t; #define MICROPY_STACKLESS_STRICT (0) #endif -// If settrace is enabled then we need code saving. -#if MICROPY_PY_SYS_SETTRACE -#define MICROPY_PERSISTENT_CODE_SAVE (1) -#define MICROPY_COMP_CONST (0) -#endif - +// Implementation of the machine module. +#define MICROPY_PY_MACHINE_INCLUDEFILE "../../micropython/ports/unix/modmachine.c" // Unix-specific configuration of machine.mem*. #define MICROPY_MACHINE_MEM_GET_READ_ADDR mod_machine_mem_get_addr @@ -191,13 +166,13 @@ typedef long mp_off_t; // Ensure builtinimport.c works with -m. #define MICROPY_MODULE_OVERRIDE_MAIN_IMPORT (1) -// Don't default sys.argv because we do that in main. +// Don't default sys.argv and sys.path because we do that in main. #define MICROPY_PY_SYS_PATH_ARGV_DEFAULTS (0) // Enable sys.executable. #define MICROPY_PY_SYS_EXECUTABLE (1) -#define MICROPY_PY_USOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) +#define MICROPY_PY_SOCKET_LISTEN_BACKLOG_DEFAULT (SOMAXCONN < 128 ? SOMAXCONN : 128) // Bare-metal ports don't have stderr. Printing debug to stderr may give tests // which check stdout a chance to pass, etc. @@ -208,20 +183,14 @@ extern const struct _mp_print_t mp_stderr_print; // For the native emitter configure how to mark a region as executable. void mp_unix_alloc_exec(size_t min_size, void **ptr, size_t *size); void mp_unix_free_exec(void *ptr, size_t size); -void mp_unix_mark_exec(void); #define MP_PLAT_ALLOC_EXEC(min_size, ptr, size) mp_unix_alloc_exec(min_size, ptr, size) #define MP_PLAT_FREE_EXEC(ptr, size) mp_unix_free_exec(ptr, size) -#ifndef MICROPY_FORCE_PLAT_ALLOC_EXEC -// Use MP_PLAT_ALLOC_EXEC for any executable memory allocation, including for FFI -// (overriding libffi own implementation) -#define MICROPY_FORCE_PLAT_ALLOC_EXEC (1) -#endif // If enabled, configure how to seed random on init. -#ifdef MICROPY_PY_URANDOM_SEED_INIT_FUNC +#ifdef MICROPY_PY_RANDOM_SEED_INIT_FUNC #include void mp_hal_get_random(size_t n, void *buf); -static inline unsigned long mp_urandom_seed_init(void) { +static inline unsigned long mp_random_seed_init(void) { unsigned long r; mp_hal_get_random(sizeof(r), &r); return r; @@ -261,6 +230,7 @@ static inline unsigned long mp_urandom_seed_init(void) { #include #endif + // If threading is enabled, configure the atomic section. #if MICROPY_PY_THREAD #define MICROPY_BEGIN_ATOMIC_SECTION() (mp_thread_unix_begin_atomic_section(), 0xffffffff) diff --git a/tulip/macos/mpconfigport.mk b/tulip/macos/mpconfigport.mk new file mode 100644 index 000000000..b0695760b --- /dev/null +++ b/tulip/macos/mpconfigport.mk @@ -0,0 +1,46 @@ +# Enable/disable modules and 3rd-party libs to be included in interpreter + +# Build 32-bit binaries on a 64-bit host +MICROPY_FORCE_32BIT = 0 + +# This variable can take the following values: +# 0 - no readline, just simple stdin input +# 1 - use MicroPython version of readline +MICROPY_USE_READLINE = 1 + +# btree module using Berkeley DB 1.xx +MICROPY_PY_BTREE = 1 + +# _thread module using pthreads +MICROPY_PY_THREAD = 1 + +# Subset of CPython termios module +MICROPY_PY_TERMIOS = 1 + +# Subset of CPython socket module +MICROPY_PY_SOCKET = 1 + +# ffi module requires libffi (libffi-dev Debian package) +MICROPY_PY_FFI = 0 + +# ssl module requires one of the TLS libraries below +MICROPY_PY_SSL = 1 +# axTLS has minimal size but implements only a subset of modern TLS +# functionality, so may have problems with some servers. +MICROPY_SSL_AXTLS = 0 +# mbedTLS is more up to date and complete implementation, but also +# more bloated. +MICROPY_SSL_MBEDTLS = 1 + +# jni module requires JVM/JNI +MICROPY_PY_JNI = 0 + +# Avoid using system libraries, use copies bundled with MicroPython +# as submodules (currently affects only libffi). +MICROPY_STANDALONE ?= 0 + +MICROPY_ROM_TEXT_COMPRESSION = 1 + +MICROPY_VFS_FAT = 1 +MICROPY_VFS_LFS1 = 1 +MICROPY_VFS_LFS2 = 1 diff --git a/tulip/release.sh b/tulip/release.sh index 1ee1dd17d..8b2b26e6f 100755 --- a/tulip/release.sh +++ b/tulip/release.sh @@ -29,7 +29,7 @@ cp shared/py/voices.py fs/ex/my_voices.py cd esp32s3 -source ~/esp/esp-idf-v5.2/export.sh +source ~/esp/esp-idf/export.sh # If sys, just create/upload the last sys and exit if [ "$TYPE" == "sys" ]; then python tulip_fs_create.py diff --git a/tulip/shared/desktop/unix_display.c b/tulip/shared/desktop/unix_display.c index cec317108..442156b15 100644 --- a/tulip/shared/desktop/unix_display.c +++ b/tulip/shared/desktop/unix_display.c @@ -69,7 +69,7 @@ void lvgl_keyboard_read(lv_indev_t * indev_drv, lv_indev_data_t * data) * @param sdl_key the key code * @return LV_KEY_* control character or '\0' */ -uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key) +uint32_t sdl_keycode_to_ctrl_key(SDL_Keycode sdl_key) { /*Remap some key to LV_KEY_... to manage groups*/ @@ -282,7 +282,7 @@ void check_key() { } else if(e.type == SDL_KEYDOWN) { // do LVGL stuff first - const uint32_t ctrl_key = keycode_to_ctrl_key(e.key.keysym.sym); + const uint32_t ctrl_key = sdl_keycode_to_ctrl_key(e.key.keysym.sym); if (ctrl_key == '\0') { // do nothing? } else { diff --git a/tulip/shared/desktop/unix_mphal.c b/tulip/shared/desktop/unix_mphal.c index 20776dce0..9d26c3ce8 100644 --- a/tulip/shared/desktop/unix_mphal.c +++ b/tulip/shared/desktop/unix_mphal.c @@ -48,7 +48,7 @@ #ifndef _WIN32 #include -STATIC void sighandler(int signum) { +static void sighandler(int signum) { if (signum == SIGINT) { #if MICROPY_ASYNC_KBD_INTR #if MICROPY_PY_THREAD_GIL @@ -217,7 +217,7 @@ void mp_hal_stdout_tx_strn(const char *str, size_t len) { //extern void mp_uos_dupterm_tx_strn(const char *str, size_t len); -void mp_hal_stdout_tx_strn(const char *str, size_t len) { +mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) { // TFB log for(uint16_t i=0;i=0) { + //fprintf(stderr, "check and fill %" PRIu32"\n", amy_sysclock()); while(amy_sysclock() >= (next_amy_tick_us/1000)) { sequencer_tick_count++; - uint32_t lag = amy_sysclock() - (next_amy_tick_us/1000); // Check defers for(uint8_t i=0;i defer_sysclock[i]) {