knitr::opts_chunk$set(echo = TRUE, fig.width=9, fig.height=4)
Introducción
La información contenida en este documento acerca del paquete arduinor
se obtuvo de la página de Git Hub de su creador, Hao Zhu y de su página web Hao Zhu R Developer; Data Scientist; Loves everything dynamic. (Según la misma página del creador, por el momento el paquete no funciona para el Sistema Operativo Windows. [2019])
“So, let’s get this package before we start. Again, if you only have a Windows machine, sorry about that. Let’s hope my libserialport
project can go well.” Hao Zhu.
El paquete arduinor
permite obetener lecturas de las placas Arduino a través del puerto serial de la computadora, además de facilitar una visualización mediante una app integrada con shiny. La librería implementa funciones del paquete Rcpp
por lo tanto es una librería que también debe instalarse.
Esta guía es una guía básica en español sobre uso del paquete construido por Hao Zhun, replicando el ejemplo que él mismo propone en su página personal.
Otra opción para interactuar con Arduino desde R, es el paquete Rduino
, el cual contiene funciones para controlar el comportamiento de los pines digitales y análogos de las placas Arduino.
Las pruebas que se hicieron para este documento se hicieron en una computadora con las versiones siguientes de software:
R version 4.0.5 (2021-03-31)
RStudio: Version 1.4.1106
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Linux Mint 20.1
IDE Arduino: 1.8.13
Preparación de la placa Arduino
El código a continuación se implementó en una placa Arduino MEGA 2560 y en una placa Arduino UNO R3.
El código hace lectura de 4 sensores:
- ds18b20 (temperatura. °C)
- dht22 (humedad % - temperatura. °C)
- gy-30 (intensidad lumínica. lux)
- LM35 (temperatura. °C)
// ds18b20
#include <OneWire.h>
#include <DallasTemperature.h>
// dht22
#include "DHT.h"
//gy-30
#include <Wire.h>
#include <BH1750.h>
// ds18b20
OneWire ourWire(2);
DallasTemperature sensors(&ourWire);
// LM35
int LM35 = A0;
// gy-30
BH1750 luxometro;
// dht22
#define DHTPIN 5
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
void setup() {
Serial.begin(9600);
//ds18b20
sensors.begin();
//gy-30
Wire.begin();
luxometro.begin();
//dht22
dht.begin();
}
void loop() {
delay(2000);
//LM35
int lectura = analogRead(LM35);
float mv = (5000 / 1024.0) * lectura;
float celcius = mv / 10;
//ds18b20
sensors.requestTemperatures();
float temp= sensors.getTempCByIndex(0);
Serial.print(temp);
Serial.print(",");
//gy-30
float lux = luxometro.readLightLevel();
//dht22
float h = dht.readHumidity();
float t = dht.readTemperature();
Serial.print(h);
Serial.print(",");
Serial.print(t);
//gy-30
Serial.print(",");
Serial.print(lux);
//LM35
Serial.print(",");
Serial.println(celcius);
}
NOTA: siéntase en la libertad de copiar, modificar, mejorar, criticar cualquier contenido propuesto en el documento; sugerencias y críticas también son bienvenidas.
Después de cargar el código a la placa, hacer unas lecturas con el puerto serial a través de la IDE de Arduino:
Los valores se han separado con una coma “,” en el orden en el cual se mencionaron anteriormente, pero ahora de izquierda a derecha.
Algo muy importante a tener en cuenta a la hora de hacer las lecturas con el puerto serie, es identificar el puerto con el cual se está trabajando, en Linux el nombre del puerto es algo como /dev/ttyACM#
y en Mac el nombre del puerto es algo como /dev/cu.SLAB_USBtoUART
.
En algunos casos se genera un error al identificar el puerto, en este caso en particular, el error se genera debido a los permisos de acceso del sistema operativo sobre el dispositivo.
Este inconveniente se puede resolver escribiendo en la terminal de Linux el siguiente código, que habilita la recepción de datos a través del puerto serie.
sudo chmod a+rw /dev/ttyACM#
Recuerde que #
es el número que arroja la IDE de Arduino en la esquina inferior derecha.
NOTA: Esta solución NO es permanente ni es la mejor solución; cada vez que se desconecta la placa del puerto se pierde la configuración y, se debe habilitar una vez se conecte nuevamente la placa. Se recomienda buscar una solución que resuelva este inconveniente.
Paquete arduinor
Instalación
La instalación se hará desde RStudio.
devtools::install_github("r-arduino/arduinor")
Conexión Puerto Serie
Después de instalar el paquete, vamos a hacer uso de las funciones que trae, una primera función es la que nos permite crear la conexión con el puerto serie ar_init()
. Recuerde hacer uso de la ayuda que brinda RStudio para obtener información acerca de las funciones ?ar_init
.
Se creará un objeto llamado pserie
que configura la conexión con el puerto serie identificando el puerto y la tasa de baudios.
# cargar la librería
library(arduinor)
# iniciar conexión el puerto serie
pserie <- ar_init(serialport = "/dev/ttyACM6", baud = 9600)
Acá es bien importante que la tasa de baudios coincida con la del código de Arduino, de lo contrario no se podrá sincronizar la tasa lectura.
void setup() {
Serial.begin(9600);
}
Otra función útil es ar_flush_hard()
la cual limpia las lecturas aleatoria iniciales.
ar_flush_hard(pserie)
Trasmitir en consola
La función ar_monitor()
trasmite las lecturas del puerto serie capturadas del Arduino hacia la consola de RStudio, se detendrá cuando presione el botón STOP.
ar_monitor(pserie)
La función ar_read()
lee el valor actual y lo transmite en la consola.
Capturar un cierto número de datos
Con la función ar_collect()
es posible ajustar el tamaño de la muestra que desea capturar, por defecto toma 100 valores, pero se puede modificar con la opción size
. Recuerde leer la información completa de la función para revisar las otras opciones que se pueden ajustar. En este caso se recolectaron 10 datos en el objeto llamado ndatos
; la función se detiene cuando captura el dato número 10. También se puede detener el proceso con el botón STOP de la consola.
ndatos <- ar_collect(fd = pserie, size = 10)
> ndatos <- ar_collect(fd = pserie, size = 10)
Flushing Port...
[==============================>-------------------------------] 50%
Flushing Port...
Done
Visualizar los datos
La función ar_plotter()
abre una aplicación con shiny que se ejecuta en la ventana Viewer de RStudio, que muestra los datos en un gráfico interactivo. La opción names =
permite nombrar las variables que se desea acompañen al gráfico.
ar_plotter(fd = pserie, names = c("ds18b20","dHt22", "dhT22", "gy30", "LM35"))
> ar_plotter(fd = con, c("ds18b20","dHt22", "dhT22", "gy30", "LM35"))
Flushing Port...
Listening on http://127.0.0.3:5678
La app tiene botones interactivos para iniciar, reiniciar y detener la trasmisión de los datos, una casilla de selección si se desea que los datos se guarden en un archivo .csv, entre otras opciones propias de la visualización. Para detener la app, se presiona el boton STOP del visor (Viewer).
Hasta este punto se menciona el tema del paquete arduinor
, lo que sigue es una forma de depurar los datos obtenidos con las funciones de la librería, para que posteriormente se pueda operar sobre ellos con el paquete tidyverse
por ejemplo.
Depuración de datos desde .csv
En la parte inferior, se puede seleccionar la casilla “Save to file?” antes de iniciar la captura de datos, para que los datos capturados sean guardados en un archivo .csv
a medida que se proyectan sobre el gráfico interactivo. El nombre del archivo csv también puede ser modificado en la casilla de texto que se encuentra al lado derecho de la opción de guardar archivo. (El archivo se guarda en el directorio de la sesión de trabajo de RStudio.)
Se presenta el conjunto de datos obtenidos con los sensores anteriormente mencionados.
library(tidyverse)
Varios de los paquetes incluidos en el tidyverse
incluyen herramientas para la depuración de datos e incluso, para hacer gráficos.
sensores <- read_csv(file = "arduino_20210505_085105.csv")
── Column specification ──────────────────────────────────────────────────
cols(
ds18b20 = col_double(),
dHt22 = col_double(),
dhT22 = col_double(),
gy30 = col_double(),
LM35 = col_double()
)
sensores
Obtener el archivo .csv
es una buena opción para obtener métricas o un resumen numérico directamente sobre este.
sensores %>%
summary
ds18b20 dHt22 dhT22 gy30
Min. :-127.00 Min. :99.9 Min. :26.30 Min. : 8.33
1st Qu.:-127.00 1st Qu.:99.9 1st Qu.:26.50 1st Qu.: 65.00
Median : 27.00 Median :99.9 Median :26.70 Median : 77.50
Mean : -25.25 Mean :99.9 Mean :26.68 Mean : 74.38
3rd Qu.: 27.31 3rd Qu.:99.9 3rd Qu.:26.90 3rd Qu.: 83.33
Max. : 35.38 Max. :99.9 Max. :27.10 Max. :107.50
LM35
Min. : 0.00
1st Qu.:45.41
Median :45.90
Mean :41.75
3rd Qu.:45.90
Max. :52.25
- Obtener la mediana del sensor gy30
sensores %>%
pull(gy30) %>%
median()
[1] 77.5
- Agregar una columna con el tiempo en segundos.
df_sensores <- sensores %>%
mutate(tiempo = c(seq(1:nrow(sensores))),
tiempo = (2*tiempo)) %>%
relocate(tiempo, everything())
df_sensores
- Generar tabla con las métricas de las variables.
df_sensores %>%
filter(ds18b20 > 0) %>%
select("tiempo","ds18b20","dHt22", "dhT22", "gy30", "LM35") %>%
pivot_longer(cols = -tiempo, names_to = "sensor", values_to = "valor") %>%
group_by(sensor) %>%
summarise(media = round(mean(valor), digits = 2),
mediana = median(valor),
desv = round(sd(valor), digits = 2)
) %>%
datatable(rownames = FALSE)
df_sensores %>%
ggplot(mapping = aes(x = tiempo, y = gy30)) +
geom_line(color = "cadetblue")+
labs(title = "Intensidad Lumínica", subtitle = "Sensor gy-30", y = "lux", x = "Tiempo [s]")+
scale_x_continuous(breaks = seq(2,1702, 60))+
scale_y_continuous(breaks = seq(9,110, 10))+
theme_get()+
theme(axis.text.x = element_text(angle = 45))

- Gráfico para el sensor ds18b20
Se filtran los valores mayores a cero grados.
df_sensores %>%
filter(ds18b20 > 0) %>%
ggplot(mapping = aes(x = tiempo, y = ds18b20)) +
geom_line(color = "firebrick2")+
labs(title = "Temperatura del Suelo", subtitle = "Sensor ds18b20", y = "°C", x = "Tiempo [s]")+
scale_x_continuous(breaks = seq(2,1702, 60))+
scale_y_continuous(breaks = seq(0,40, 1))+
theme_get()+
theme(axis.text.x = element_text(angle = 45))

- Gráfico para el sensor dht22 (Humedad relativa ambiente)
Los valores obtenidos para esta variable se mantienen en un valor de \(99.9\) el cual es un valor erróneo, tal vez debido a una avería en el sensor, o un error en la configuración hecha en el código de Arduino.
df_sensores %>%
ggplot(mapping = aes(x = tiempo, y = dHt22)) +
geom_line(color = "firebrick2")+
labs(title = "Temperatura Ambiente", subtitle = "Sensor dht22", y = "% Humedad", x = "Tiempo [s]")+
theme_get()+
theme(axis.text.x = element_text(angle = 45))

- Gráfico temperatura ambiente sensor dht22
Este sensor mide también la temperatura ambiente, se realiza el gráfico de esta variable.
df_sensores %>%
filter(dhT22 > 0) %>%
ggplot(mapping = aes(x = tiempo, y = dhT22)) +
geom_line(color = "forestgreen")+
labs(title = "Temperatura Ambiente", subtitle = "Sensor dht22", y = "°C", x = "Tiempo [s]")+
scale_x_continuous(breaks = seq(2,1702, 60))+
scale_y_continuous(breaks = seq(0,35, 0.05))+
theme_get()+
theme(axis.text.x = element_text(angle = 45))

- Gráfico temperatura sensor LM35
df_sensores %>%
ggplot(mapping = aes(x = tiempo, y = LM35)) +
geom_line(color = "orange")+
labs(title = "Temperatura Ambiente", subtitle = "Sensor LM35", y = "°C", x = "Tiempo [s]")+
scale_x_continuous(breaks = seq(2,1702, 60))+
scale_y_continuous(breaks = seq(0,60, 5))+
theme_get()+
theme(axis.text.x = element_text(angle = 45))

Depurar captura de datos
La función ar_collect()
del paquete arduinor
permite tomar una cierta cantidad de datos de los sensores, para este ejemplo se tomaron 100 datos..
dt100 <- ar_collect(fd = pserie, size = 100)
EL objeto que se obtiene es un vector de longitud 100 y de clase character
.
class(dt100)
Al darle una dimensión se obtiene un arreglo tipo “matrix” o “array”.
dim(x = dt100) <- c(length(dt100),1)
class(dt100)
[1] "matrix" "array"
head(dt100)
[,1]
[1,] "\n"
[2,] "31.25,99.90,30.90,30.00,31.25\n"
[3,] "\n"
[4,] "31.19,99.90,30.90,30.83,31.25\n"
[5,] "\n"
[6,] "31.25,99.90,30.90,30.83,31.25\n"
- Convertir a data frame.
- Separar los valores de cada variable en diferentes columnas.
- Convertir de caracter a numérico, las variables.
- Crear la variable tiempo.
- Eliminar las filas
NA
que se genera.
df_dt100 <- dt100 %>%
as_data_frame() %>%
separate(col = V1, into = c("ds18b20","dHt22", "dhT22", "gy30", "LM35"), sep = ",") %>%
mutate(ds18b20 = as.numeric(ds18b20),
dHt22 = as.numeric(dHt22),
dhT22 = as.numeric(dhT22),
gy30 = as.numeric(gy30),
LM35 = as.numeric(LM35)) %>%
mutate(tiempo = c(seq(1:nrow(dt100)))) %>%
relocate(tiempo, everything()) %>%
na.omit()
Expected 5 pieces. Missing pieces filled with `NA` in 50 rows [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, ...].
df_dt100
Teniendo la base de datos conformada, es posible obtener las métricas que se desee.
summary(df_dt100)
tiempo ds18b20 dHt22 dhT22
Min. : 2.0 Min. :31.19 Min. :99.9 Min. :30.90
1st Qu.: 26.5 1st Qu.:31.25 1st Qu.:99.9 1st Qu.:30.90
Median : 51.0 Median :31.25 Median :99.9 Median :30.90
Mean : 51.0 Mean :31.26 Mean :99.9 Mean :30.91
3rd Qu.: 75.5 3rd Qu.:31.31 3rd Qu.:99.9 3rd Qu.:30.90
Max. :100.0 Max. :31.37 Max. :99.9 Max. :31.00
gy30 LM35
Min. :23.33 Min. :31.25
1st Qu.:25.83 1st Qu.:31.25
Median :25.83 Median :31.25
Mean :26.37 Mean :31.25
3rd Qu.:26.67 3rd Qu.:31.25
Max. :30.83 Max. :31.25
df_dt100 %>%
pull(LM35) %>%
mean()
[1] 31.25
df_dt100 %>%
select("tiempo","ds18b20","dHt22", "dhT22", "gy30", "LM35") %>%
pivot_longer(cols = -tiempo, names_to = "sensor", values_to = "valor") %>%
group_by(sensor) %>%
summarise(media = mean(valor),
mediana = median(valor),
desv = round(sd(valor), digits = 2)
) %>%
datatable(rownames = FALSE)
Gráfico interactivo
library(plotly)
ggplotly(
df_dt100 %>%
ggplot(mapping = aes(x = tiempo, y = gy30))+
geom_line(color = "green")+
labs(title = "Intensidad Lumínica",
y = "lux",
x = "tiempo [s]")+
scale_x_continuous(breaks = seq(2,100, 2))+
scale_y_continuous(breaks = seq(0,40, 1))+
theme_get()+
theme(axis.text.x = element_text(angle = 45)),
width = 900
)
LS0tCnRpdGxlOiAiR3XDrWE6IFIgeSBBcmR1aW5vIgpzdWJ0aXRsZTogIlBhcXVldGUgYGFyZHVpbm9yYCIKYXV0aG9yOiAiQ1JHIgpkYXRlOiBNYXlvLTIwMjEKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IHNwYWNlCiAgICBjc3M6IGVkaWNpb24uY3NzCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9VFJVRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD00KSAKYGBgCgo8Y2VudGVyPgohW10oYXJkdWlub3IucG5nKXt3aWR0aD0iMzAlIiBoZWlnaHQ9IjMwJSJ9CjwvY2VudGVyPgogIAogIAojIEludHJvZHVjY2nDs24KCkxhIGluZm9ybWFjacOzbiBjb250ZW5pZGEgZW4gZXN0ZSBkb2N1bWVudG8gYWNlcmNhIGRlbCBwYXF1ZXRlIGBhcmR1aW5vcmAgc2Ugb2J0dXZvIGRlIGxhIHDDoWdpbmEgZGUgR2l0IEh1YiBkZSBzdSBjcmVhZG9yLCBbSGFvIFpodV0oaHR0cHM6Ly9naXRodWIuY29tL3ItYXJkdWluby9hcmR1aW5vcikgeSBkZSBzdSBww6FnaW5hIHdlYiBbSGFvIFpodQpSIERldmVsb3BlcjsgRGF0YSBTY2llbnRpc3Q7IExvdmVzIGV2ZXJ5dGhpbmcgZHluYW1pY10oaHR0cHM6Ly96aHVoYW8ub3JnL3Bvc3QvY29ubmVjdC1hcmR1aW5vLWNoaXBzLXdpdGgtci8pLiAoKipTZWfDum4gbGEgbWlzbWEgcMOhZ2luYSBkZWwgY3JlYWRvciwgcG9yIGVsIG1vbWVudG8gZWwgcGFxdWV0ZSBubyBmdW5jaW9uYSBwYXJhIGVsIFNpc3RlbWEgT3BlcmF0aXZvIFdpbmRvd3MuIFsyMDE5XSoqKSAgCgo8YnI+Cgo+ICoiU28sIGxldOKAmXMgZ2V0IHRoaXMgcGFja2FnZSBiZWZvcmUgd2Ugc3RhcnQuIEFnYWluLCBpZiB5b3Ugb25seSBoYXZlIGEgV2luZG93cyBtYWNoaW5lLCBzb3JyeSBhYm91dCB0aGF0LiBMZXTigJlzIGhvcGUgbXkgYGxpYnNlcmlhbHBvcnRgIHByb2plY3QgY2FuIGdvIHdlbGwuIiBIYW8gWmh1LiogIAoKPGJyPgoKRWwgcGFxdWV0ZSBgYXJkdWlub3JgIHBlcm1pdGUgb2JldGVuZXIgbGVjdHVyYXMgZGUgbGFzIHBsYWNhcyBBcmR1aW5vIGEgdHJhdsOpcyBkZWwgcHVlcnRvIHNlcmlhbCBkZSBsYSBjb21wdXRhZG9yYSwgYWRlbcOhcyBkZSBmYWNpbGl0YXIgdW5hIHZpc3VhbGl6YWNpw7NuIG1lZGlhbnRlIHVuYSBhcHAgaW50ZWdyYWRhIGNvbiBzaGlueS4gTGEgbGlicmVyw61hIGltcGxlbWVudGEgZnVuY2lvbmVzIGRlbCBwYXF1ZXRlIGBSY3BwYCBwb3IgbG8gdGFudG8gZXMgdW5hIGxpYnJlcsOtYSBxdWUgdGFtYmnDqW4gZGViZSBpbnN0YWxhcnNlLgoKRXN0YSBndcOtYSBlcyB1bmEgZ3XDrWEgYsOhc2ljYSBlbiBlc3Bhw7FvbCBzb2JyZSB1c28gZGVsIHBhcXVldGUgY29uc3RydWlkbyBwb3IgSGFvIFpodW4sIHJlcGxpY2FuZG8gZWwgZWplbXBsbyBxdWUgw6lsIG1pc21vIHByb3BvbmUgZW4gc3UgcMOhZ2luYSBwZXJzb25hbC4KCk90cmEgb3BjacOzbiBwYXJhIGludGVyYWN0dWFyIGNvbiBBcmR1aW5vIGRlc2RlIFIsIGVzIGVsIHBhcXVldGUgW2BSZHVpbm9gXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvUmR1aW5vL2luZGV4Lmh0bWwpLCBlbCBjdWFsIGNvbnRpZW5lIGZ1bmNpb25lcyBwYXJhIGNvbnRyb2xhciBlbCBjb21wb3J0YW1pZW50byBkZSBsb3MgcGluZXMgZGlnaXRhbGVzIHkgYW7DoWxvZ29zIGRlIGxhcyBwbGFjYXMgQXJkdWluby4KCgpMYXMgcHJ1ZWJhcyBxdWUgc2UgaGljaWVyb24gcGFyYSBlc3RlIGRvY3VtZW50byBzZSBoaWNpZXJvbiBlbiB1bmEgY29tcHV0YWRvcmEgY29uIGxhcyB2ZXJzaW9uZXMgc2lndWllbnRlcyBkZSBzb2Z0d2FyZToKCiogYFIgdmVyc2lvbiA0LjAuNSAoMjAyMS0wMy0zMSlgCiogYFJTdHVkaW86IFZlcnNpb24gMS40LjExMDZgCiogYFBsYXRmb3JtOiB4ODZfNjQtcGMtbGludXgtZ251ICg2NC1iaXQpYAoqIGBSdW5uaW5nIHVuZGVyOiBMaW51eCBNaW50IDIwLjFgCiogYElERSBBcmR1aW5vOiAxLjguMTNgICAKCgoKIyBQcmVwYXJhY2nDs24gZGUgbGEgcGxhY2EgQXJkdWlubwoKPGNlbnRlcj4KIVtdKGlkZWFyZHUucG5nKXt3aWR0aD0iNTAlIiBoZWlnaHQ9IjUwJSJ9CjwvY2VudGVyPiAgCgo8YnI+CgpFbCBjw7NkaWdvIGEgY29udGludWFjacOzbiBzZSBpbXBsZW1lbnTDsyBlbiB1bmEgcGxhY2EgW0FyZHVpbm8gTUVHQSAyNTYwXShodHRwczovL3d3dy5hcmR1aW5vLmNjL2VuL3Btd2lraS5waHA/bj1NYWluL2FyZHVpbm9Cb2FyZE1lZ2EyNTYwKSB5IGVuIHVuYSBwbGFjYSBbQXJkdWlubyBVTk8gUjNdKGh0dHBzOi8vd3d3LmFyZHVpbm8uY2MvZW4vTWFpbi9hcmR1aW5vQm9hcmRVbm8mZ3Q7KS4gIAoKCkVsIGPDs2RpZ28gaGFjZSBsZWN0dXJhIGRlIDQgc2Vuc29yZXM6CgoqIGRzMThiMjAgKHRlbXBlcmF0dXJhLiDCsEMpCiogZGh0MjIgKGh1bWVkYWQgJSAtIHRlbXBlcmF0dXJhLiDCsEMpCiogZ3ktMzAgKGludGVuc2lkYWQgbHVtw61uaWNhLiBsdXgpCiogTE0zNSAodGVtcGVyYXR1cmEuIMKwQykgIAoKPGJyPgoKYGBgCi8vIGRzMThiMjAKI2luY2x1ZGUgPE9uZVdpcmUuaD4gICAgICAgICAgICAgICAgCiNpbmNsdWRlIDxEYWxsYXNUZW1wZXJhdHVyZS5oPgoKLy8gZGh0MjIKI2luY2x1ZGUgIkRIVC5oIgoKLy9neS0zMAojaW5jbHVkZSA8V2lyZS5oPgojaW5jbHVkZSA8QkgxNzUwLmg+CgovLyBkczE4YjIwCk9uZVdpcmUgb3VyV2lyZSgyKTsKRGFsbGFzVGVtcGVyYXR1cmUgc2Vuc29ycygmb3VyV2lyZSk7CgovLyBMTTM1CmludCBMTTM1ID0gQTA7CgovLyBneS0zMApCSDE3NTAgbHV4b21ldHJvOwoKLy8gZGh0MjIKI2RlZmluZSBESFRQSU4gNQojZGVmaW5lIERIVFRZUEUgREhUMjIKREhUIGRodChESFRQSU4sIERIVFRZUEUpOwoKdm9pZCBzZXR1cCgpIHsKICBTZXJpYWwuYmVnaW4oOTYwMCk7CiAgCiAgLy9kczE4YjIwCiAgc2Vuc29ycy5iZWdpbigpOyAKICAKICAvL2d5LTMwCiAgV2lyZS5iZWdpbigpOwogIGx1eG9tZXRyby5iZWdpbigpOwogIC8vZGh0MjIKICBkaHQuYmVnaW4oKTsKfQoKdm9pZCBsb29wKCkgewogIGRlbGF5KDIwMDApOwoKICAvL0xNMzUKICBpbnQgbGVjdHVyYSA9IGFuYWxvZ1JlYWQoTE0zNSk7CiAgZmxvYXQgbXYgID0gKDUwMDAgLyAxMDI0LjApICogbGVjdHVyYTsgCiAgZmxvYXQgY2VsY2l1cyA9IG12IC8gMTA7CiAgCiAgLy9kczE4YjIwCiAgc2Vuc29ycy5yZXF1ZXN0VGVtcGVyYXR1cmVzKCk7CiAgZmxvYXQgdGVtcD0gc2Vuc29ycy5nZXRUZW1wQ0J5SW5kZXgoMCk7CiAgU2VyaWFsLnByaW50KHRlbXApOwogIFNlcmlhbC5wcmludCgiLCIpOwogIC8vZ3ktMzAKICBmbG9hdCBsdXggPSBsdXhvbWV0cm8ucmVhZExpZ2h0TGV2ZWwoKTsKICAvL2RodDIyCiAgZmxvYXQgaCA9IGRodC5yZWFkSHVtaWRpdHkoKTsKICBmbG9hdCB0ID0gZGh0LnJlYWRUZW1wZXJhdHVyZSgpOwogIFNlcmlhbC5wcmludChoKTsKICBTZXJpYWwucHJpbnQoIiwiKTsKICBTZXJpYWwucHJpbnQodCk7CiAgLy9neS0zMAogIFNlcmlhbC5wcmludCgiLCIpOwogIFNlcmlhbC5wcmludChsdXgpOwogIC8vTE0zNQogIFNlcmlhbC5wcmludCgiLCIpOwogIFNlcmlhbC5wcmludGxuKGNlbGNpdXMpOwogCn0KYGBgCjxicj4KCj4gTk9UQTogc2nDqW50YXNlIGVuIGxhIGxpYmVydGFkIGRlIGNvcGlhciwgbW9kaWZpY2FyLCBtZWpvcmFyLCBjcml0aWNhciBjdWFscXVpZXIgY29udGVuaWRvIHByb3B1ZXN0byBlbiBlbCBkb2N1bWVudG87IHN1Z2VyZW5jaWFzIHkgY3LDrXRpY2FzIHRhbWJpw6luIHNvbiBiaWVudmVuaWRhcy4gCgo8YnI+CgpEZXNwdcOpcyBkZSBjYXJnYXIgZWwgY8OzZGlnbyBhIGxhIHBsYWNhLCBoYWNlciB1bmFzIGxlY3R1cmFzIGNvbiBlbCBwdWVydG8gc2VyaWFsIGEgdHJhdsOpcyBkZSBsYSBJREUgZGUgQXJkdWlubzoKCjxjZW50ZXI+CiFbXShwdWVydG9zZXJpZS5wbmcpCjwvY2VudGVyPgoKPGJyPgoKTG9zIHZhbG9yZXMgc2UgaGFuIHNlcGFyYWRvIGNvbiB1bmEgY29tYSAiLCIgZW4gZWwgb3JkZW4gZW4gZWwgY3VhbCBzZSBtZW5jaW9uYXJvbiBhbnRlcmlvcm1lbnRlLCBwZXJvIGFob3JhIGRlIGl6cXVpZXJkYSBhIGRlcmVjaGEuICAKCkFsZ28gbXV5IGltcG9ydGFudGUgYSB0ZW5lciBlbiBjdWVudGEgYSBsYSBob3JhIGRlIGhhY2VyIGxhcyBsZWN0dXJhcyBjb24gZWwgcHVlcnRvIHNlcmllLCBlcyBpZGVudGlmaWNhciBlbCBwdWVydG8gY29uIGVsIGN1YWwgc2UgZXN0w6EgdHJhYmFqYW5kbywgZW4gTGludXggZWwgbm9tYnJlIGRlbCBwdWVydG8gZXMgYWxnbyBjb21vIGAvZGV2L3R0eUFDTSNgIHkgZW4gTWFjIGVsIG5vbWJyZSBkZWwgcHVlcnRvIGVzIGFsZ28gY29tbyBgL2Rldi9jdS5TTEFCX1VTQnRvVUFSVGAuCgpFbiBhbGd1bm9zIGNhc29zIHNlIGdlbmVyYSB1biBlcnJvciBhbCBpZGVudGlmaWNhciBlbCBwdWVydG8sIGVuIGVzdGUgY2FzbyBlbiBwYXJ0aWN1bGFyLCBlbCBlcnJvciBzZSBnZW5lcmEgZGViaWRvIGEgbG9zIHBlcm1pc29zIGRlIGFjY2VzbyBkZWwgc2lzdGVtYSBvcGVyYXRpdm8gc29icmUgZWwgZGlzcG9zaXRpdm8uICAKCgo8Y2VudGVyPgohW10oZXJyb3JwdWVydG8ucG5nKQo8L2NlbnRlcj4KCjxicj4KCkVzdGUgaW5jb252ZW5pZW50ZSBzZSBwdWVkZSByZXNvbHZlciBlc2NyaWJpZW5kbyBlbiBsYSB0ZXJtaW5hbCBkZSBMaW51eCBlbCBzaWd1aWVudGUgY8OzZGlnbywgcXVlIGhhYmlsaXRhIGxhIHJlY2VwY2nDs24gZGUgZGF0b3MgYSB0cmF2w6lzIGRlbCBwdWVydG8gc2VyaWUuIAoKPGNlbnRlcj4KIVtdKHRlcm1pbW9kaS5wbmcpCjwvY2VudGVyPgoKPGJyPgoKYGBgCnN1ZG8gY2htb2QgYStydyAvZGV2L3R0eUFDTSMKYGBgCjxicj4KCgpSZWN1ZXJkZSBxdWUgYCNgIGVzIGVsIG7Dum1lcm8gcXVlIGFycm9qYSBsYSBJREUgZGUgQXJkdWlubyBlbiBsYSBlc3F1aW5hIGluZmVyaW9yIGRlcmVjaGEuICAgCgo8Y2VudGVyPgohW10ocHVlcnRvcy5wbmcpCjwvY2VudGVyPgoKPGJyPgoKPiBOT1RBOiAqKkVzdGEgc29sdWNpw7NuIE5PIGVzIHBlcm1hbmVudGUgbmkgZXMgbGEgbWVqb3Igc29sdWNpw7NuOyBjYWRhIHZleiBxdWUgc2UgZGVzY29uZWN0YSBsYSBwbGFjYSBkZWwgcHVlcnRvIHNlIHBpZXJkZSBsYSBjb25maWd1cmFjacOzbiB5LCBzZSBkZWJlIGhhYmlsaXRhciB1bmEgdmV6IHNlIGNvbmVjdGUgbnVldmFtZW50ZSBsYSBwbGFjYS4gU2UgcmVjb21pZW5kYSBidXNjYXIgdW5hIHNvbHVjacOzbiBxdWUgcmVzdWVsdmEgZXN0ZSBpbmNvbnZlbmllbnRlLioqCgo8YnI+CgojIFBhcXVldGUgYGFyZHVpbm9yYAoKIyMgSW5zdGFsYWNpw7NuCgpMYSBpbnN0YWxhY2nDs24gc2UgaGFyw6EgZGVzZGUgUlN0dWRpby4KCmBgYHIKZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJyLWFyZHVpbm8vYXJkdWlub3IiKQpgYGAKCiMjIENvbmV4acOzbiBQdWVydG8gU2VyaWUKCkRlc3B1w6lzIGRlIGluc3RhbGFyIGVsIHBhcXVldGUsIHZhbW9zIGEgaGFjZXIgdXNvIGRlIGxhcyBmdW5jaW9uZXMgcXVlIHRyYWUsIHVuYSBwcmltZXJhIGZ1bmNpw7NuIGVzIGxhIHF1ZSBub3MgcGVybWl0ZSBjcmVhciBsYSBjb25leGnDs24gY29uIGVsIHB1ZXJ0byBzZXJpZSBgYXJfaW5pdCgpYC4gUmVjdWVyZGUgaGFjZXIgdXNvIGRlIGxhIGF5dWRhIHF1ZSBicmluZGEgUlN0dWRpbyBwYXJhIG9idGVuZXIgaW5mb3JtYWNpw7NuIGFjZXJjYSBkZSBsYXMgZnVuY2lvbmVzIGA/YXJfaW5pdGAuICAKClNlIGNyZWFyw6EgdW4gb2JqZXRvIGxsYW1hZG8gYHBzZXJpZWAgcXVlIGNvbmZpZ3VyYSBsYSBjb25leGnDs24gY29uIGVsIHB1ZXJ0byBzZXJpZSBpZGVudGlmaWNhbmRvIGVsIHB1ZXJ0byB5IGxhIHRhc2EgZGUgYmF1ZGlvcy4KCmBgYHtyIGV2YWw9RkFMU0V9CiMgY2FyZ2FyIGxhIGxpYnJlcsOtYQpsaWJyYXJ5KGFyZHVpbm9yKQojIGluaWNpYXIgY29uZXhpw7NuIGVsIHB1ZXJ0byBzZXJpZQpwc2VyaWUgPC0gYXJfaW5pdChzZXJpYWxwb3J0ID0gIi9kZXYvdHR5QUNNNiIsIGJhdWQgPSA5NjAwKQpgYGAKCjxicj4KCkFjw6EgZXMgYmllbiBpbXBvcnRhbnRlIHF1ZSBsYSB0YXNhIGRlIGJhdWRpb3MgY29pbmNpZGEgY29uIGxhIGRlbCBjw7NkaWdvIGRlIEFyZHVpbm8sIGRlIGxvIGNvbnRyYXJpbyBubyBzZSBwb2Ryw6Egc2luY3Jvbml6YXIgbGEgdGFzYSBsZWN0dXJhLgoKPGJyPgoKYGBgCnZvaWQgc2V0dXAoKSB7CiAgU2VyaWFsLmJlZ2luKDk2MDApOwp9CmBgYAo8YnI+CgpPdHJhIGZ1bmNpw7NuIMO6dGlsIGVzIGBhcl9mbHVzaF9oYXJkKClgIGxhIGN1YWwgbGltcGlhIGxhcyBsZWN0dXJhcyBhbGVhdG9yaWEgaW5pY2lhbGVzLgoKYGBge3IgZXZhbD1GQUxTRX0KYXJfZmx1c2hfaGFyZChwc2VyaWUpCmBgYAoKCiMjIFRyYXNtaXRpciBlbiBjb25zb2xhCgpMYSBmdW5jacOzbiBgYXJfbW9uaXRvcigpYCB0cmFzbWl0ZSBsYXMgbGVjdHVyYXMgZGVsIHB1ZXJ0byBzZXJpZSBjYXB0dXJhZGFzIGRlbCBBcmR1aW5vIGhhY2lhIGxhIGNvbnNvbGEgZGUgUlN0dWRpbywgc2UgZGV0ZW5kcsOhIGN1YW5kbyBwcmVzaW9uZSBlbCBib3TDs24gU1RPUC4KCmBgYHtyIGV2YWw9RkFMU0V9CmFyX21vbml0b3IocHNlcmllKQpgYGAKCjxicj4KCjxjZW50ZXI+CiFbXShjb25zb2xhLnBuZykKPC9jZW50ZXI+Cgo8YnI+CgpMYSBmdW5jacOzbiBgYXJfcmVhZCgpYCBsZWUgZWwgdmFsb3IgYWN0dWFsIHkgbG8gdHJhbnNtaXRlIGVuIGxhIGNvbnNvbGEuCgoKIyMgQ2FwdHVyYXIgdW4gY2llcnRvIG7Dum1lcm8gZGUgZGF0b3MKCkNvbiBsYSBmdW5jacOzbiBgYXJfY29sbGVjdCgpYCBlcyBwb3NpYmxlIGFqdXN0YXIgZWwgdGFtYcOxbyBkZSBsYSBtdWVzdHJhIHF1ZSBkZXNlYSBjYXB0dXJhciwgcG9yIGRlZmVjdG8gdG9tYSAxMDAgdmFsb3JlcywgcGVybyBzZSBwdWVkZSBtb2RpZmljYXIgY29uIGxhIG9wY2nDs24gYHNpemVgLiBSZWN1ZXJkZSBsZWVyIGxhIGluZm9ybWFjacOzbiBjb21wbGV0YSBkZSBsYSBmdW5jacOzbiBwYXJhIHJldmlzYXIgbGFzIG90cmFzIG9wY2lvbmVzIHF1ZSBzZSBwdWVkZW4gYWp1c3Rhci4gRW4gZXN0ZSBjYXNvIHNlIHJlY29sZWN0YXJvbiAxMCBkYXRvcyBlbiBlbCBvYmpldG8gbGxhbWFkbyBgbmRhdG9zYDsgbGEgZnVuY2nDs24gc2UgZGV0aWVuZSBjdWFuZG8gY2FwdHVyYSBlbCBkYXRvICBuw7ptZXJvIDEwLiBUYW1iacOpbiBzZSBwdWVkZSBkZXRlbmVyIGVsIHByb2Nlc28gY29uIGVsIGJvdMOzbiBTVE9QIGRlIGxhIGNvbnNvbGEuCgpgYGB7ciBldmFsPUZBTFNFfQpuZGF0b3MgPC0gYXJfY29sbGVjdChmZCA9IHBzZXJpZSwgc2l6ZSA9IDEwKQpgYGAKCmBgYAo+IG5kYXRvcyA8LSBhcl9jb2xsZWN0KGZkID0gcHNlcmllLCBzaXplID0gMTApCkZsdXNoaW5nIFBvcnQuLi4KWz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT4tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXSAgNTAlCkZsdXNoaW5nIFBvcnQuLi4KRG9uZQpgYGAKCiMjIFZpc3VhbGl6YXIgbG9zIGRhdG9zCgpMYSBmdW5jacOzbiBgYXJfcGxvdHRlcigpYCBhYnJlIHVuYSBhcGxpY2FjacOzbiBjb24gc2hpbnkgcXVlIHNlIGVqZWN1dGEgZW4gbGEgdmVudGFuYSAqKlZpZXdlcioqIGRlIFJTdHVkaW8sIHF1ZSBtdWVzdHJhIGxvcyBkYXRvcyBlbiB1biBncsOhZmljbyBpbnRlcmFjdGl2by4gTGEgb3BjacOzbiBgbmFtZXMgPWAgcGVybWl0ZSBub21icmFyIGxhcyB2YXJpYWJsZXMgcXVlIHNlIGRlc2VhIGFjb21wYcOxZW4gYWwgZ3LDoWZpY28uCgpgYGB7ciBldmFsPUZBTFNFfQphcl9wbG90dGVyKGZkID0gcHNlcmllLCBuYW1lcyA9IGMoImRzMThiMjAiLCJkSHQyMiIsICJkaFQyMiIsICJneTMwIiwgIkxNMzUiKSkKYGBgCgpgYGAKPiBhcl9wbG90dGVyKGZkID0gY29uLCBjKCJkczE4YjIwIiwiZEh0MjIiLCAiZGhUMjIiLCAiZ3kzMCIsICJMTTM1IikpCkZsdXNoaW5nIFBvcnQuLi4KCkxpc3RlbmluZyBvbiBodHRwOi8vMTI3LjAuMC4zOjU2NzgKYGBgCgo8YnI+Cgo8Y2VudGVyPgohW10oYXBwc2hpbnkucG5nKQo8L2NlbnRlcj4KCjxicj4KCkxhIGFwcCB0aWVuZSBib3RvbmVzIGludGVyYWN0aXZvcyBwYXJhIGluaWNpYXIsIHJlaW5pY2lhciB5IGRldGVuZXIgbGEgdHJhc21pc2nDs24gZGUgbG9zIGRhdG9zLCB1bmEgY2FzaWxsYSBkZSBzZWxlY2Npw7NuIHNpIHNlIGRlc2VhIHF1ZSBsb3MgZGF0b3Mgc2UgZ3VhcmRlbiBlbiB1biBhcmNoaXZvICoqLmNzdioqLCBlbnRyZSBvdHJhcyBvcGNpb25lcyBwcm9waWFzIGRlIGxhIHZpc3VhbGl6YWNpw7NuLiBQYXJhIGRldGVuZXIgbGEgYXBwLCBzZSBwcmVzaW9uYSBlbCBib3RvbiBTVE9QIGRlbCB2aXNvciAoVmlld2VyKS4KCkhhc3RhIGVzdGUgcHVudG8gc2UgbWVuY2lvbmEgZWwgdGVtYSBkZWwgcGFxdWV0ZSBgYXJkdWlub3JgLCBsbyBxdWUgc2lndWUgZXMgdW5hIGZvcm1hIGRlIGRlcHVyYXIgbG9zIGRhdG9zIG9idGVuaWRvcyBjb24gbGFzIGZ1bmNpb25lcyBkZSBsYSBsaWJyZXLDrWEsIHBhcmEgcXVlIHBvc3Rlcmlvcm1lbnRlIHNlIHB1ZWRhIG9wZXJhciBzb2JyZSBlbGxvcyBjb24gZWwgcGFxdWV0ZSBgdGlkeXZlcnNlYCBwb3IgZWplbXBsby4KCgojIERlcHVyYWNpw7NuIGRlIGRhdG9zIGRlc2RlIGAuY3N2YAoKPGJyPgoKPGNlbnRlcj4KIVtdKGFwcHNoaW55LnBuZykKPC9jZW50ZXI+Cgo8YnI+CgpFbiBsYSBwYXJ0ZSBpbmZlcmlvciwgc2UgcHVlZGUgc2VsZWNjaW9uYXIgbGEgY2FzaWxsYSAiKipTYXZlIHRvIGZpbGU/KioiIGFudGVzIGRlIGluaWNpYXIgbGEgY2FwdHVyYSBkZSBkYXRvcywgcGFyYSBxdWUgbG9zIGRhdG9zIGNhcHR1cmFkb3Mgc2VhbiBndWFyZGFkb3MgZW4gdW4gYXJjaGl2byBgLmNzdmAgYSBtZWRpZGEgcXVlIHNlIHByb3llY3RhbiBzb2JyZSBlbCBncsOhZmljbyBpbnRlcmFjdGl2by4gRWwgbm9tYnJlIGRlbCBhcmNoaXZvIGNzdiB0YW1iacOpbiBwdWVkZSBzZXIgbW9kaWZpY2FkbyBlbiBsYSBjYXNpbGxhIGRlIHRleHRvIHF1ZSBzZSBlbmN1ZW50cmEgYWwgbGFkbyBkZXJlY2hvIGRlIGxhIG9wY2nDs24gZGUgZ3VhcmRhciBhcmNoaXZvLiAoRWwgYXJjaGl2byBzZSBndWFyZGEgZW4gZWwgZGlyZWN0b3JpbyBkZSBsYSBzZXNpw7NuIGRlIHRyYWJham8gZGUgUlN0dWRpby4pCgo8YnI+CgpTZSBwcmVzZW50YSBlbCBjb25qdW50byBkZSBkYXRvcyBvYnRlbmlkb3MgY29uIGxvcyBzZW5zb3JlcyBhbnRlcmlvcm1lbnRlIG1lbmNpb25hZG9zLgoKPGJyPgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCjxicj4KClZhcmlvcyBkZSBsb3MgcGFxdWV0ZXMgaW5jbHVpZG9zIGVuIGVsIGB0aWR5dmVyc2VgIGluY2x1eWVuIGhlcnJhbWllbnRhcyBwYXJhIGxhIGRlcHVyYWNpw7NuIGRlIGRhdG9zIGUgaW5jbHVzbywgcGFyYSBoYWNlciBncsOhZmljb3MuICAKCjxicj4KCi0gTGVjdHVyYSBkZSBsb3MgZGF0b3M6CgpgYGB7cn0Kc2Vuc29yZXMgPC0gcmVhZF9jc3YoZmlsZSA9ICJhcmR1aW5vXzIwMjEwNTA1XzA4NTEwNS5jc3YiKQpzZW5zb3JlcwpgYGAKPGJyPgoKT2J0ZW5lciBlbCBhcmNoaXZvIGAuY3N2YCBlcyB1bmEgYnVlbmEgb3BjacOzbiBwYXJhIG9idGVuZXIgbcOpdHJpY2FzIG8gdW4gcmVzdW1lbiBudW3DqXJpY28gZGlyZWN0YW1lbnRlIHNvYnJlIGVzdGUuCgo8YnI+CgotIFJlc3VtZW4gbnVtw6lyaWNvOgpgYGB7cn0Kc2Vuc29yZXMgJT4lIAogIHN1bW1hcnkKYGBgCgoKPGJyPgoKKiBPYnRlbmVyIGxhIG1lZGlhbmEgZGVsIHNlbnNvciBneTMwCgpgYGB7cn0Kc2Vuc29yZXMgJT4lIAogIHB1bGwoZ3kzMCkgJT4lCiAgbWVkaWFuKCkKYGBgCgoKPGJyPgoKKiBBZ3JlZ2FyIHVuYSBjb2x1bW5hIGNvbiBlbCB0aWVtcG8gZW4gc2VndW5kb3MuCgpgYGB7cn0KZGZfc2Vuc29yZXMgPC0gc2Vuc29yZXMgJT4lIAogIG11dGF0ZSh0aWVtcG8gPSBjKHNlcSgxOm5yb3coc2Vuc29yZXMpKSksCiAgICAgICAgIHRpZW1wbyA9ICgyKnRpZW1wbykpICU+JSAKICByZWxvY2F0ZSh0aWVtcG8sIGV2ZXJ5dGhpbmcoKSkKCmRmX3NlbnNvcmVzCmBgYAoKCjxicj4KCiogR2VuZXJhciB0YWJsYSBjb24gbGFzIG3DqXRyaWNhcyBkZSBsYXMgdmFyaWFibGVzLgoKYGBge3J9CmRmX3NlbnNvcmVzICU+JSAKICBmaWx0ZXIoZHMxOGIyMCA+IDApICU+JSAKICAgc2VsZWN0KCJ0aWVtcG8iLCJkczE4YjIwIiwiZEh0MjIiLCAiZGhUMjIiLCAiZ3kzMCIsICJMTTM1IikgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gLXRpZW1wbywgbmFtZXNfdG8gPSAic2Vuc29yIiwgdmFsdWVzX3RvID0gInZhbG9yIikgJT4lIAogIGdyb3VwX2J5KHNlbnNvcikgJT4lIAogIHN1bW1hcmlzZShtZWRpYSA9IHJvdW5kKG1lYW4odmFsb3IpLCBkaWdpdHMgPSAyKSwKICAgICAgICAgICAgbWVkaWFuYSA9IG1lZGlhbih2YWxvciksCiAgICAgICAgICAgIGRlc3YgPSByb3VuZChzZCh2YWxvciksIGRpZ2l0cyA9IDIpCiAgICAgICAgICAgICAgICAgICAgICAgICApICU+JSAKICBkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSkKYGBgCgo8YnI+CgoqIEdyw6FmaWNvIGRlbCBzZW5zb3IgZ3kzMAoKYGBge3J9CmRmX3NlbnNvcmVzICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gdGllbXBvLCB5ID0gZ3kzMCkpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiY2FkZXRibHVlIikrCiAgbGFicyh0aXRsZSA9ICJJbnRlbnNpZGFkIEx1bcOtbmljYSIsIHN1YnRpdGxlID0gIlNlbnNvciBneS0zMCIsIHkgPSAibHV4IiwgeCA9ICJUaWVtcG8gW3NdIikrCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgyLDE3MDIsIDYwKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSg5LDExMCwgMTApKSsKICB0aGVtZV9nZXQoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkKYGBgCgo8YnI+CgoqIEdyw6FmaWNvIHBhcmEgZWwgc2Vuc29yIGRzMThiMjAKClNlIGZpbHRyYW4gbG9zIHZhbG9yZXMgbWF5b3JlcyBhIGNlcm8gZ3JhZG9zLgoKYGBge3J9CmRmX3NlbnNvcmVzICU+JQogIGZpbHRlcihkczE4YjIwID4gMCkgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSB0aWVtcG8sIHkgPSBkczE4YjIwKSkgKwogIGdlb21fbGluZShjb2xvciA9ICJmaXJlYnJpY2syIikrCiAgbGFicyh0aXRsZSA9ICJUZW1wZXJhdHVyYSBkZWwgU3VlbG8iLCBzdWJ0aXRsZSA9ICJTZW5zb3IgZHMxOGIyMCIsIHkgPSAiwrBDIiwgeCA9ICJUaWVtcG8gW3NdIikrCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgyLDE3MDIsIDYwKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDQwLCAxKSkrCiAgdGhlbWVfZ2V0KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpCmBgYAoKPGJyPgoKKiBHcsOhZmljbyBwYXJhIGVsIHNlbnNvciBkaHQyMiAoSHVtZWRhZCByZWxhdGl2YSBhbWJpZW50ZSkKCkxvcyB2YWxvcmVzIG9idGVuaWRvcyBwYXJhIGVzdGEgdmFyaWFibGUgc2UgbWFudGllbmVuIGVuIHVuIHZhbG9yIGRlICQ5OS45JCBlbCBjdWFsIGVzIHVuIHZhbG9yIGVycsOzbmVvLCB0YWwgdmV6IGRlYmlkbyBhIHVuYSBhdmVyw61hIGVuIGVsIHNlbnNvciwgbyB1biBlcnJvciBlbiBsYSBjb25maWd1cmFjacOzbiBoZWNoYSBlbiBlbCBjw7NkaWdvIGRlIEFyZHVpbm8uICAKCmBgYHtyfQpkZl9zZW5zb3JlcyAlPiUKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gdGllbXBvLCB5ID0gZEh0MjIpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gImZpcmVicmljazIiKSsKICBsYWJzKHRpdGxlID0gIlRlbXBlcmF0dXJhIEFtYmllbnRlIiwgc3VidGl0bGUgPSAiU2Vuc29yIGRodDIyIiwgeSA9ICIlIEh1bWVkYWQiLCB4ID0gIlRpZW1wbyBbc10iKSsKICB0aGVtZV9nZXQoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkKYGBgCgo8YnI+CgoKKiBHcsOhZmljbyB0ZW1wZXJhdHVyYSBhbWJpZW50ZSBzZW5zb3IgZGh0MjIKCkVzdGUgc2Vuc29yIG1pZGUgdGFtYmnDqW4gbGEgdGVtcGVyYXR1cmEgYW1iaWVudGUsIHNlIHJlYWxpemEgZWwgZ3LDoWZpY28gZGUgZXN0YSB2YXJpYWJsZS4KCmBgYHtyfQpkZl9zZW5zb3JlcyAlPiUKICBmaWx0ZXIoZGhUMjIgPiAwKSAlPiUgCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHRpZW1wbywgeSA9IGRoVDIyKSkgKwogIGdlb21fbGluZShjb2xvciA9ICJmb3Jlc3RncmVlbiIpKwogIGxhYnModGl0bGUgPSAiVGVtcGVyYXR1cmEgQW1iaWVudGUiLCBzdWJ0aXRsZSA9ICJTZW5zb3IgZGh0MjIiLCB5ID0gIsKwQyIsIHggPSAiVGllbXBvIFtzXSIpKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMiwxNzAyLCA2MCkpKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwzNSwgMC4wNSkpKwogIHRoZW1lX2dldCgpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUpKQpgYGAKCgo8YnI+CgoqIEdyw6FmaWNvIHRlbXBlcmF0dXJhIHNlbnNvciBMTTM1CgpgYGB7cn0KZGZfc2Vuc29yZXMgJT4lCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHRpZW1wbywgeSA9IExNMzUpKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gIm9yYW5nZSIpKwogIGxhYnModGl0bGUgPSAiVGVtcGVyYXR1cmEgQW1iaWVudGUiLCBzdWJ0aXRsZSA9ICJTZW5zb3IgTE0zNSIsIHkgPSAiwrBDIiwgeCA9ICJUaWVtcG8gW3NdIikrCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgyLDE3MDIsIDYwKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDYwLCA1KSkrCiAgdGhlbWVfZ2V0KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpCmBgYAoKPGJyPgoKIyBEZXB1cmFyIGNhcHR1cmEgZGUgZGF0b3MKCkxhIGZ1bmNpw7NuIGBhcl9jb2xsZWN0KClgIGRlbCBwYXF1ZXRlIGBhcmR1aW5vcmAgcGVybWl0ZSB0b21hciB1bmEgY2llcnRhIGNhbnRpZGFkIGRlIGRhdG9zIGRlIGxvcyBzZW5zb3JlcywgcGFyYSBlc3RlIGVqZW1wbG8gc2UgdG9tYXJvbiAxMDAgZGF0b3MuLgoKYGBge3IgZXZhbD1GQUxTRX0KZHQxMDAgPC0gYXJfY29sbGVjdChmZCA9IHBzZXJpZSwgc2l6ZSA9IDEwMCkKYGBgCgo8YnI+CgoqIERhdG9zIGRlbCBvYmpldG8gYG5kYXRvc2AKCkVMIG9iamV0byBxdWUgc2Ugb2J0aWVuZSBlcyB1biB2ZWN0b3IgZGUgbG9uZ2l0dWQgMTAwIHkgZGUgY2xhc2UgYGNoYXJhY3RlcmAuCgpgYGB7ciBldmFsPUZBTFNFfQpjbGFzcyhkdDEwMCkKYGBgCgo8YnI+CgoqIERhciBkaW1lbnNpw7NuIGFsIHZlY3RvcgoKQWwgZGFybGUgdW5hIGRpbWVuc2nDs24gc2Ugb2J0aWVuZSB1biBhcnJlZ2xvIHRpcG8gIm1hdHJpeCIgbyAiYXJyYXkiLgoKYGBge3J9CmRpbSh4ID0gZHQxMDApIDwtIGMobGVuZ3RoKGR0MTAwKSwxKQpjbGFzcyhkdDEwMCkKaGVhZChkdDEwMCkKYGBgCjxicj4KCiogQ29udmVydGlyIGEgZGF0YSBmcmFtZS4KKiBTZXBhcmFyIGxvcyB2YWxvcmVzIGRlIGNhZGEgdmFyaWFibGUgZW4gZGlmZXJlbnRlcyBjb2x1bW5hcy4KKiBDb252ZXJ0aXIgZGUgY2FyYWN0ZXIgYSBudW3DqXJpY28sIGxhcyB2YXJpYWJsZXMuCiogQ3JlYXIgbGEgdmFyaWFibGUgdGllbXBvLgoqIEVsaW1pbmFyIGxhcyBmaWxhcyBgTkFgIHF1ZSBzZSBnZW5lcmEuCgoKYGBge3J9CmRmX2R0MTAwIDwtIGR0MTAwICU+JSAKICBhc19kYXRhX2ZyYW1lKCkgJT4lIAogIHNlcGFyYXRlKGNvbCA9IFYxLCBpbnRvID0gYygiZHMxOGIyMCIsImRIdDIyIiwgImRoVDIyIiwgImd5MzAiLCAiTE0zNSIpLCBzZXAgPSAiLCIpICU+JSAKICAgbXV0YXRlKGRzMThiMjAgPSBhcy5udW1lcmljKGRzMThiMjApLAogICAgICAgICBkSHQyMiA9IGFzLm51bWVyaWMoZEh0MjIpLAogICAgICAgICBkaFQyMiA9IGFzLm51bWVyaWMoZGhUMjIpLAogICAgICAgICBneTMwID0gYXMubnVtZXJpYyhneTMwKSwKICAgICAgICAgTE0zNSA9IGFzLm51bWVyaWMoTE0zNSkpICU+JSAKICBtdXRhdGUodGllbXBvID0gYyhzZXEoMTpucm93KGR0MTAwKSkpKSAlPiUgCiAgcmVsb2NhdGUodGllbXBvLCBldmVyeXRoaW5nKCkpICU+JQogIG5hLm9taXQoKQpgYGAKPGJyPgoKYGBge3J9CmRmX2R0MTAwCmBgYAo8YnI+CgpUZW5pZW5kbyBsYSBiYXNlIGRlIGRhdG9zIGNvbmZvcm1hZGEsIGVzIHBvc2libGUgb2J0ZW5lciBsYXMgbcOpdHJpY2FzIHF1ZSBzZSBkZXNlZS4KCjxicj4KCmBgYHtyfQpzdW1tYXJ5KGRmX2R0MTAwKQpgYGAKCjxicj4KCmBgYHtyfQpkZl9kdDEwMCAlPiUgCiAgcHVsbChMTTM1KSAlPiUgCiAgbWVhbigpCmBgYAoKCmBgYHtyfQpkZl9kdDEwMCAlPiUgCiAgIHNlbGVjdCgidGllbXBvIiwiZHMxOGIyMCIsImRIdDIyIiwgImRoVDIyIiwgImd5MzAiLCAiTE0zNSIpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC10aWVtcG8sIG5hbWVzX3RvID0gInNlbnNvciIsIHZhbHVlc190byA9ICJ2YWxvciIpICU+JSAKICBncm91cF9ieShzZW5zb3IpICU+JSAKICBzdW1tYXJpc2UobWVkaWEgPSBtZWFuKHZhbG9yKSwKICAgICAgICAgICAgbWVkaWFuYSA9IG1lZGlhbih2YWxvciksCiAgICAgICAgICAgIGRlc3YgPSByb3VuZChzZCh2YWxvciksIGRpZ2l0cyA9IDIpCiAgICAgICAgICAgICAgICAgICAgICAgICApICU+JSAKICBkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSkKYGBgCgo8YnI+CgojIEdyw6FmaWNvIGludGVyYWN0aXZvCgpgYGB7cn0KbGlicmFyeShwbG90bHkpCgpnZ3Bsb3RseSgKZGZfZHQxMDAgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSB0aWVtcG8sIHkgPSBneTMwKSkrCiAgZ2VvbV9saW5lKGNvbG9yID0gImdyZWVuIikrCiAgbGFicyh0aXRsZSA9ICJJbnRlbnNpZGFkIEx1bcOtbmljYSIsCiAgICAgICB5ID0gImx1eCIsCiAgICAgICB4ID0gInRpZW1wbyBbc10iKSsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDIsMTAwLCAyKSkrCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDQwLCAxKSkrCiAgdGhlbWVfZ2V0KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpLAp3aWR0aCA9IDkwMAopCmBgYAoK