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.


  • Lectura de los datos:
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.


  • Resumen numérico:
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)


  • Gráfico del sensor gy30
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)


  • Datos del objeto ndatos

EL objeto que se obtiene es un vector de longitud 100 y de clase character.

class(dt100)


  • Dar dimensión al vector

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