Mostrando entradas con la etiqueta games. Mostrar todas las entradas
Mostrando entradas con la etiqueta games. Mostrar todas las entradas

2023/08/03

Emulador chip-8

 

En el marco cyberciruja, gzalo dictó un excelente taller de emulación de CHIP-8 el sábado 2023-07-22. Comparto mi experiencia y resultado.


Me apoyé en el ejemplo provisto, tanto el base como los fragmentos de código de la presentación, le refactoricé las globales a un struct, quité el boolean y renombré .cpp a .c, es que había una trampita de estar usando g++ para compilar código C.

 

Screen dump al finalizar la ejecución
Screen dump al finalizar la ejecución

 

Mi objetivo, además de la diversión, es repasar un poco C para no olvidar demasiado, probar algunos recursos que no he usado mucho y otros que sí pero no en C. Entonces ejercité:

  • Makefile 
  • procesar argumentos con getopt
  • FSM
  • testing unitario sin framework
  • valgrind
  • gdb

Mi enfoque fue incrementar de modo testeable la menor funcionalidad posible en cada paso. En un comienzo, testeable era que ejecutara el programa hasta la próxima instrucción no implementada, luego agregué test unitario.

Antes de seguir, para no spoilearte, te dejo mi recomendación de cómo encararlo:

  • Elegí e inicializá las estructuras u objetos de tu arquitectura.
  • Hacé el loop de ejecución.
  • Implementá los dumps de los registros y estructuras
  • Cargá la ROM
  • Armá el switch para los opcodes y los switches internos para los subcodes, poné en cada rama una llamada a una función unimplemented(), unknown() en el default del switch principal, invalid() en cada default de los switches internos:


     void unimplemented(){
       printf("UNIMPLEMENTED INSTRUCTION\n");
       exit(1);
     }

  • Ejecutá el programa, tiene que tirar un unimplemented, implementá esa instrucción. Y así....

Ya sea a mano o con un framework de testing, respetá TDD.

  • Ejecutá hasta el unimplemented: eso te genera el requerimiento
  • Escribí el test ateniendote a la especificación, ojo que hay sutiles diferencias
    • https://chip8.gulrak.net/ (marcá solo CHIP-8) 
    • https://github.com/Timendus/chip8-test-suite
    • https://en.wikipedia.org/wiki/CHIP-8
    • https://tobiasvl.github.io/blog/write-a-chip-8-emulator/
  • Dejá que falle para comprobar que es efectivo en la falla
  • Hardcodeá para comprobar que es efectivo en el éxito
  • Implementá hasta que pase
  • Refactorizá
  • Again...

 

Spoiler Alert 


Ahora te cuento lo que hice yo, que más o menos se parece, está en cpantel/CHIP-8

Implementé la carga del programa en memoria y comprobé visualmente que soc_dump_memory() y hexdump -C ROM/1-chip8-logo.ch8 correspondieran. Recordá que en el dump debe estar la fuente, luego todos ceros y la imagen del ROM recién en 0x200, 512 decimal)

Comparación de memory dump y hexdump de 3-corax+.ch8
Comparación de memory dump y hexdump de 3-corax+.ch8

Implementé  avanzar un paso, que haga el fetch y la descomposición en opcode, nibbles, X, Y, keys, NN, NNN, el nombre que sea útil en cada contexto

Tomé el ROM 1-chip8-logo.ch8 que es el que menos instrucciones precisa. De cada instrucción que invoca, implementé el decode y execute. Obviamente para SPRITE me copié de lo que Gustavo proveyó.

Para la primera instrucción que es CLS, mi clear_screen toma un valor, así en init() pongo la pantalla en blanco y ante la primera llamada de CLS se pone en negro.

Implemente que varias medidas intrusivas se activen con left shift, tales como:

  • avance paso a paso
  • volcado de memoria, video, registros, teclas
  • des/activación de debug

 Esto es por que el teclado esta mapeado en:

  1 2 3 4

   q w r t

    a s d f

     z x c v

Y algunas de esas letras son buenas como atajos:

while (!quit) {
  while (SDL_PollEvent(&event)) {
    if (event.type == SDL_QUIT) {
      quit = 1;
    } else if(event.type == SDL_KEYDOWN){
      if (event.key.keysym.mod & KMOD_LSHIFT ) {
 
      // para que se vea bien acá
        // sym = event.key.keysym.sym;
        if(sym == SDLK_r) run = ! run;
        if(sym == SDLK_n) soc_step(&soc,1);
        if(sym == SDLK_d) {debug = ! debug;         run=0;}
        if(sym == SDLK_t) {show_time = ! show_time; run=0;}
        if(sym == SDLK_v) {soc_dump_registers(&soc);run=0;}
        if(sym == SDLK_m) {soc_dump_memory(&soc);   run=0;}
        if(sym == SDLK_s) {soc_dump_screen(&soc);   run=0;}
        if(sym == SDLK_k) {soc_dump_key(&soc);      run=0;}
        if(sym == SDLK_h) {show_help();             run=0;}
        if(sym == SDLK_q) quit = 1;
      } else {
        if(sym == SDLK_1) soc_press_key(&soc, 1);
        ....

      }
    } else if(event.type == SDL_KEYUP){
      if (event.key.keysym.mod == KMOD_NONE ) {
        if(sym == SDLK_1) soc_release_key(&soc, 1);
        ...

    if (run) soc_step(&soc, debug);
  }
  ...


Ahí entrás en un loop de ir implementando la siguiente instrucción que falle....


pc: 200 fetch: 00E0 opcode: 0 X: 0 Y: E opcode2: 0 NN: E0 NNN: 0E0
pc: 202 fetch: A22A 
opcode: A X: 2 Y: 2 opcode2: A NN: 2A NNN: 22A
pc: 204 fetch: 600C 
opcode: 6 X: 0 Y: 0 opcode2: C NN: 0C NNN: 00C
pc: 206 fetch: 6108 
opcode: 6 X: 1 Y: 0 opcode2: 8 NN: 08 NNN: 108
pc: 208 fetch: D01F 
opcode: D X: 0 Y: 1 opcode2: F NN: 1F NNN: 01F
UNIMPLEMENTED INSTRUCTION


Implementé una opciones de línea de comando para determinar:

  • Estado inicial variable
    • Corriendo o pausado
    • Imprimiendo debug o no
    • Hacer o no dump de pantalla al finalizar
  • Cantidad de instrucciones a ejecutar hasta detenerse
  • Cantidad de instrucciones por frame, 8 parece ok
  • Delay para completar el frame, medio en vano, todo ocurre en mucho menos de un milisegundo, el default de 16 parece ok
  • ROM a cargar

Fui tomando cada ROM de https://github.com/Timendus/chip8-test-suite y no hubo problemas hasta la quinta, quirks. De paso ahí noté que muchas de las operaciones tenían una equis en lugar del check ok en los anteriores, entonces tuve que...

 

Test

 

Llegó el momento de agregar test unitario. Hasta este momento tenía algo parecido a:

void step(struct typeSOC* soc) {
  struct typeInstruction ins;
  fetch(soc,&ins);
  soc->pc+=2;
  switch (ins.opcode) {
    case 0x3:
      if (soc->v[ins->X] == ins->NN) soc->pc+=2;
    break;
    ...
  ...

Lo cual es imposible de probar, la implementación de la instrucción debe estar en una función aislada para no estar probando a la vez el fetch y fundamentalmente ese soc->pc+=2;

Me llevé la implementación de cada operación a otro archivo

  switch (ins.opcode) {
    case 0x0: // CLS
      switch(ins.NNN) {
        case 0x0E0:
          api(soc, &ins, OPCODE_CLS);
          if (debug) printf("  CLS\n");
    break;

    case 0x0EE: // RET
          api(soc,&ins, OPCODE_RET);
    break;
 

 Este es el test de XOR

void test_XOR(struct typeSOC* soc, struct typeInstruction* ins) {
  soc->v[8] = 0xff;
  soc->v[2] = 0x2a;
  ins->fetch = 0x8823;
  predecode(ins);
  api(soc, ins, OPCODE_XOR);
  assert_equal(0xd5, soc->v[8], "XOR: bad result");
}

 

siendo assert_equal:

void assert_equal(uint32_t expected, uint32_t got, char * msg) {
  if (expected != got) {
    printf("\n%s, expected: %x got: %x\n", msg, expected, got);
  } else {
    printf(".");
  }
}

En lo versionado actual quedaron unos if's en lugar de assert_equal, algún dia...
 

Pude haber implementado una función para cada instrucción, tipo api_CLS(), api_RET() o como lo hice, que es pasarle a una sola función el código y ahí dentro otro switch(), lo cual puede ser mal visto por que parece haber repetición de código, pero, no estoy tan seguro. El primer switch tiene switches anidados y el segundo no, es plano. 

Usar funciones distintas es mejor manera de autodocumentar y un mejor lugar para poner los unimplemented(). Los invalid() y unknown() quedarían en el switch de soc.c.

Tal como hice los test, sin framework, tiene una muy evidente desventaja, que es no poder imprimir un puntito tras cada test realizado de modo elegante. Ni poder llevar la cuenta de los tests y asserts ejecutados. Digo de manera elegante, siempre se puede llenar todo de printfs y globales...

Otra limitación, no es que no se pueda hacer con C, es que de haber usado un lenguaje orientado a objetos, quedaría muy prolijo encapsular todo con setters y getters en lugar de acceder directamente a los registros y memorias y así poder recopilar algunas estadísticas como:

  • uso de registros
  • uso de instrucciones
  • uso del stack
  • uso de botones
  • área de memoria leída y escrita

Esto serviría para hacer una versión reducida, como para portar a FPGA chica o lo que me interesaría, el core RISC-V icicle en la EDU-FPGA.

 

Traza

 

Un recurso es analizar una traza prexistente, se puede obtener de los emuladores octo. Por ejemplo en (https://timendus.github.io/chip8-test-suite/3-corax+.html), poniendo algo como

console.log("op: " + op.toString(16) + " o: "
+ o.toString(16) + " pc: " + this.pc.toString(16));


cerca de la línea 470 cuando descompone el fetch, genera una traza. Si en tu emulador hacés algo compatible, con ./tools/compare comparás.

Esto sirve para las partes donde no hay uso de timer ni lectura de teclado.

 

Headless


Implementé una versión headless, más que todo porque en mi máquina no quería instalar SDL2, entonces hice todo en otra con ssh y una ventana de VNC, solo en esa ventana se puede ejecutar código con SDL. Medio en vano pues con ssh -X funciona casi ok.

 

Dump ascii

 

Motivado por la posibilidad de testear la imagen rederizada, implementé que se haga un dump por terminal. Para ser franco no es ascii, es unicode, pero bueno. Es la primera imagen de este post, se puede ejecutar en cualquier momento o al finalizar.

 

Autostop

 

Aunque ningún programa baremetal como son estos termina, quizás queremos que terminen al terminar de ejecutar. Esto tiene sentido en los tests y la manera de implementarlo es hacer un salto incondicional a esta misma instrucción, o sea al PC actual. Esto se detecta y finaliza la emulación.

  if (ins.opcode == 1 && soc->pc == ins.NNN) {
     return 1;
  }


Mi implementación en C

 

Está en cpantel/CHIP-8 y te cuento que no me maté mucho en que los componentes respetaran la jerarquía, fijate que debió haber sido así:
 

struct typeSOC
  pc            -> soc.cpu.pc
  i             -> soc.cpu.i
  v[]           -> soc.cpu.v[]
  stack_pointer -> soc.cpu.stack_pointer
  key[]         -> soc.keyboard.keys[]
  kb_state      -> soc.keyboard.state
  last_key      -> soc.keyborad.last 
  stack[]       -> soc.stack[]
  memory[]      -> soc.memory[]
  screen[]      -> soc.screen.memory[]
  redraw        -> soc.screen.redraw 
  delay_timer   -> soc.timer.delay
  sound_timer   -> soc.timer.sound

 
y la parte de la instruction, quizás así:
 
  count         -> soc.cpu.instruction.count
  fetch         -> soc.cpu.instruction.fetch
  opcode        -> soc.cpu.instruction.opcode
  X/key         -> soc.cpu.instruction.X/key
  Y/N/opcode2   -> soc.cpu.instruction.Y/N/opcode2
  NN            -> soc.cpu.instruction.NN
  address/NNN   -> soc.cpu.instruction.address/NNN
 
 
pero implica agregar archivos y un montón de refactorización, no vale la pena.

Valgrind lo usé para ./tools/compare, no para chip8, así que no sé si

 

Posibilidades de mejora

 

RND: proveerle una seed por línea de comando.

Comparar la memoria de pantalla con una imagen de referencia.

Poder cambiar mediante parámetros de línea los colores de background y foreground.

Que en debug imprima el código disassembly. 

Que pase el test quirks. 

Implementar las validaciones de uso del stack y algunas otras.

Que imprima algún help para las command line options.

Que valide que exista el archivo de ROM y su longitud.

Usar funciones para cada instrucción en lugar del segundo switch.

 

Conclusión

 

Muy buena la explicación de gzalo y su asistencia posterior, me destrabó al menos en un ínfimo pero mortífero error muy difícil de notar en la lectura del teclado.

La experiencia fue muy buena, sin presión, hacer algo que no sirve para nada por fuera de haberlo hecho. Es que los juegos son horribles, inusables, no probé ninguno más allá de cargarlo para ver que funcionara algo.

La mayor parte de las posibilidades de mejora serían aplicables en caso de querer desarrollar programas para esta plataforma, nada más lejos de mis intenciones, se lo dejo a la gente más retro.







2021/05/26

El gusanito revive en un trabajo práctico: Parte 2: el modelo choca con la realidad.

Habiendo fracasado rotúndamente en usar inputattach en la "Parte 1: inputattach" que ni siquiera he podido publicar, he aceptado rebajarme a usar USBMouse.

 

Esta placa es de 3.3 v "tolerante a 5v" en los pines marcados como FT en la hoja de datos, o sea, prácticamente todos. Siendo que el encoder usa 5v, me facilita la vida, la parte analógica siempre me asusta.


Como en otras tantas ocasiones, lo que voy a contar no es la crónica exacta de lo que transité ni necesariamente respetaré el orden de lo que hice, pues te aseguro que te resultaría completamente incomprensible. En su lugar voy a hacer una especie de collage significativo.

El la introducción hay más contexto.

 

Conectar un botón y que se comporte como botón izquierdo


Quizás un poco contrario al Diseño Emergente, como ya tenía una idea intuitiva de hacia donde pensaba ir, no hice la implementación más sencilla que funcione, si no que medio que me anticipé.

Lo que hice fue usar una InterruptIn y consideré el rebote mediante un Timeout, tal que si llega una transición, espere un ratito y compruebe un momento despues en qué estado quedo y en caso de corresponder recién ahí actúe.

Como actuar es pedirle al Mouse press() o release(), eso no lo puedo invocar en el Callback de InterruptIn o Timeout pues esos métodos probablemente usan un mutex, cosa que no se puede hacer en un Interrupt Handler y además no es buena práctica ejecutar mucho código durante una interrupción, se lo doy a una EventQueue y en algún momento lo hace y aparentemente funcionó muy bien.



setup
setup


 


click
click

 

Estos diagramas están en el repo en la carpeta doc, necesitás astah para verlos.

Dicho de otra manera, tenemos una clase genérica Debouncer<> que recibe un objeto y dos de sus métodos para rise() y fall(). Cuando Debouncer recibe por ejempo un rise(),  le pide a Timeout que llame a un método propio checkRise() que verifica que esté risen y encola una llamada a otro método propio notifyRise() que va eventualmente a llamar al método asociado al objeto provisto.

 

Conectar el encoder rotativo

 

Un encoder rotativo tiene dos pines, que van diciendo 00->01->11->10 si lo girás para un lado y lo inverso si es para el otro, usando un Gray Coding. Si estuviste prestando atención, no podés dejar de notar que es como si estuvieses haciendo clicks en dos botones, asi que si asocio dos InterruptIn a un mismo objeto pero con cuatro métodos, en teoría debería funcionar.

Fijate, esta es la instanciación de un click izquierdo asociado al botón azul de la placa:


Debouncer<MouseClick> leftButtonDebouncer(
   BUTTON1,
   &queue,
   &leftButton,
   &MouseClick::press,
   &MouseClick::release
);


y esto para el Encoder. objservá que es el mismo objeto encoder:

Debouncer<Encoder> clkEncoderDebouncer(
   PC_9,
   &queue,
   &encoder,
   &Encoder::clkUp,
   &Encoder::clkDown
);
Debouncer<Encoder> dtEncoderDebouncer(
   PC_8,
   &queue,
   &encoder,
   &Encoder::dtUp,
   &Encoder::dtDown
);

De hecho, funciona bastante bien usando una leve adaptación de la lógica del ejemplo de uso de USBMouse (en ./mbed-os/drivers/USBMouse.h), pero...

 

Muy excéntrico e irreversible
Muy excéntrico e irreversible

En negro es el giro para un lado, ponele que es aceptable, pero cuando invertís, se destroza. Esto tiene que ver con pequeños errores que debería tratar con un conocimiento intermedio de trigonometría y análisis matemático que me falta, así que lo voy a resolver mediante ingenio. Además, no me quiero anticipar pues prometí que mi camino iba a ser instructivo a costa de la verácidad, hay un error con el tratamiento de los tiempos y los estados de las interrupciones, luego veremos.

Va mi primera POC, qué pasa si en lugar de calcular posiciones según los ángulos, dado que el encoder tiene 30 posiciones precalculo los desplazamientos. Comenzaré con sólo cuatro y girando siempre para el mismo lado.

 

cuatro puntos
cuatro puntos

Ahora para una lado y para el otro, para pescarle la lógica.

 

ambos sentidos de giro
ambos sentidos de giro

En amarillo en el código la modificación para invertir el sentido, en el dibujo un leve movimiento del cursor del mouse para evidenciar que es correcto.

Ahora resta precalcular para 30 puntos.


30 pasos ambos sentidos
30 pasos ambos sentidos

Ese leve desplazamiento a la derecha es para diferenciar un sentido del otro.


30 pasos ambos sentidos
30 pasos ambos sentidos

Ya sé, ya se que le falta un montón de optimizaciones como calcular un solo cuadrante y usarlo de modo espejado, pero la verdad es que a esta placa le sobran bytes y ciclos de reloj, va a quedar así.

Aparte, el STEPS en realidad es 60, pues se generan dos interrupciones por paso.

 

Links útiles


https://os.mbed.com/blog/entry/Simplify-your-code-with-mbed-events/

El código está versionado y corresponde al commit "math fixed, still excentric"

2021/04/18

El gusanito revive en un trabajo práctico: intro

El gusanito


Hace unos años me sentía frustrado por que personas menores que yo con mejor motricidad obtenían mejores resultados en el juego Slither.io pese a yo tener una mejor visión estratégica, fenómeno que tambien ocurre con UrbanTerror y con Fornite y me imagino que con cualquier otro video juego de tiempo real.


Con respecto a Urban Terror tengo una idea que en algún momento concretaré, pero relacionado a Slither.io pasé de la idea a la práctica y llegué bastante lejos, te resumo lo que hice antes:


El gusanito va hacia donde esté el cursor del mouse, lo cual a mi me complica pues tengo que ver el escenario y a la vez dónde está el cursor.


Slither.io
Slither.io


Por la naturaleza de la interfaz, que viene a ser rotativa como el Gyruss, me parece que conviene usar un controlador de tipo Paddle y más o menos lo implementé con un encoder rotativo y una placa EDU-CIAA-NXP tal que se hace pasar por un mouse.

A medida que el encoder gira, el programa transforma en movimientos de mouse tal que hacen una circunferencia pero no terminé de hacer la matemática y algún otro problema tuve y le ganó otra prioridad y cayó en el olvido.

Todo eso está mucho más largo en:


Slither - 1 - Intro
Slither - 2 - encoder con edu-ciaa-nxp
Slither - 3 - usb mouse con edu-ciaa-nxp
Slither - 4 - usb mouse y encoder con edu-ciaa-nxp

 

El trabajo práctico


En la materia Introducción a los Sistemas Embebidos del CEIoT se nos encomendó desarrollar una aplicación tal que:
  1. Debe emplearse la placa NUCLEO
  2. Puede ser una aplicación diferente (no necesariamente domótica)
  3. Puede utilizar los sensores vistos o agregar los nuevos
  4. Debe usar al menos una entrada analógica
  5. Debe usar comunicación serie
  6. Realizar la temporización del sistema basada en interrupciones y uso de timers

La placa NUCLEO es la que estamos usando para todos los ejercicios

Los sensores vistos, al momento de escribir esto fueron botones, sensor de gas vía i2c, potenciómetro y sensor de temperatura vía entrada analógica y supongo que luego veremos teclado matricial y sensor de movimiento al menos.

La verdad es que aunque no trabajo de esto, ya tengo bastante experiencia y sólo el punto 6 me resulta un desafío, así que podía inventarme una aplicación boba que cumpliera para poder aprobar o buscarme algo más difícil para aprender y ahí recordé al gusanito, me aferré al segundo requerimiento y pensé en cómo rescatarlo para el trabajo práctico.

 

Una reflexión muy personal

 

Como alumno, uno puede estar cursando algo por el conocimiento o la certificación, esto último se traduce en la aprobación y si querés entrar en detalles, la nota. Yo soy partidario de cursar, hacer trabajos prácticos, laboratorios y rendir exámenes por el conocimiento y que la certificación sea un efecto colateral.

En esta materia en particular, la verdad verdad es que voy a profundizar el conocimiento, pues ya he tomado varios cursos que más o menos cubren lo mismo y he practicado mucho por mi cuenta. Aunque no me interesa mayormente, en términos objetivos el resultado más importante de esta cursada resulta ser la certificación.

Como docente, entiendo que distintas personas sepan más o menos del tema de antemano y tendría la tentación de pedirle a cada una más o menos según su condición. A mi y a los ingenieros electrónicos que asisten les pediría más que a los estudiantes de sistemas, que pueden estar en el horno por que todo es nuevo.

Y podría hacerlo en términos de acompañar su aprendizaje, pero no en términos de certificación, no le puedo pedir más a quien más sabe, pues estaría discriminando y o forzando mi visión sobre como debe ver la vida esa persona. De todos modos para otorgar la certificación lo que importa en última instancia es lo que la persona sabe, no si lo acquirió ahora o antes.

Entonces, si alguien ya sabe todo, para aprobar le pediría lo mismo que a quien entra sin saber nada. Digo "pediría" pues por más que soy docente aún no he logrado por falta de experiencia y agravado por lo remoto poder discernir claramente cuáles alumnos saben más que otros.

Ok, pero cuando se trata de mí, hace mucho que no soy mero alumno, acompaño al docente y soy docente de mi mismo, entonces, si tengo tiempo, debo elevar la vara, tal como hice con el trabajo práctico de la reciente materia Basic Digital Design


Volviendo al trabajo práctico


Lo que me he propuesto es:

  • Usar la placa NUCLEO (requerimiento 1).
  • Un encoder rotativo para determinar el giro (requerimientos 3 y 6).
  • Un potenciómetro para el radio de giro (requerimiento 4).
  • Un potenciómetro opcional para la resolución (requerimiento 4).
  • La conexión serie para implementar algún protocolo de mouse (requerimiento 5).

 

El requerimiento 2 más que un requerimiento es una libertad, lo cumple la aplicación misma.

Los problemas

Encoder rotativo

Lo del encoder rotativo ya lo había resuelto antes, tengo que ver si me sirve y quizás corregir. Tambien agregar los potenciómetros.

¿Qué es lo de la resolución? Si jugás un rato observarás que si ponés el cursor bien cerca del bicho a ojo diría que tenés como unos cinco grados de resolución, si lo ponés bien lejos, un grado. El problema es que el encoder rotativo que tengo es de unos 10 grados. Lo que puedo hacer es que no tenga, no sé como se dice, ¿la misma relación de giro? O sea, que esos 10 grados se traduzcan en un solo grado.

Lo puedo lograr por sofware, claro, o con mecánica, introduciendo un componente extra con un engranaje que haga la reducción. Comprenderás que esto está muy alejado de mi torpeza, no será así.

Para el trabajo práctico me quedaré con los 10 grados pero quizás agregue un potenciometro para cambiar la escala, veremos.

Radio de giro

Esto tiene que ver con la resolución pero no en grados, en el perímetro recorrido.

Sobre este tema y la resolución me falta reflexionar e investigar bastante y probablemente quede fuera del trabajo práctico tal profundidad y me contente con algo básico

Comunicación serie

Podría negociar con la cátedra usar USB, que es más difícil que lo que voy a hacer, pero la realidad es que ya lo hice con la EDU-CIAA-NXP. Mientras exploraba encontre que existe inputattach que permite recibir por comunicación serie un protocolo de mouse o teclado y conectarlo como si fuera tal dispositivo, soporta un montón de protocolos.

Me dije entonces, ha llegado la hora de aprender a hacer ingeniería inversa de protocolos seriales de mouse y como probablemente sea el punto crítico, será el primer tema a resolver, a publicar en unos días o si fracaso, enterrar en el olvido algunos años más hasta que reviva con otra excusa.



2020/04/11

Twister del azar

El problema es cómo obtener un buen random para claves temporarias para usuarios o tokens para flujos. Lo típico en una recuperación de contraseñas o cuando un workflow pasa por un mail.

Si uno llama random() no sirve, ya que el (pseudo) azar, da siempre la misma secuencia. Eso en sí no está mal, si uno está haciendo un juego o una simulación y quiere testear componentes que usen azar, lo bueno sería que ese azar sea el mismo en cada ejecución de un mismo unit test.

Luego en producción, hay que darle una semilla para que arranque en algún lugar distinto de la secuencia.

En basic creo recordar (no pienso mirar en google) que era randomize(). En opencobol, que es justo con lo que estaba haciendo las pruebas es random(valor) para cambiar la semilla y random() para obtener el valor al azar.

Esa semilla suele ser tomada de la fecha/hora, lo cual no es muy bueno:

A lo largo de todo el año hay 365x24x60x1000 = 31.536.000.000, ok.

Pero como más o menos se puede saber el día y quizas la hora se reduce a 60x60x1000=3.600.000, no tan ok.

Y si más o menos sabés el minuto 2x60x1000=120.000, mucho menos ok.

Dependiendo del escenario, algunos ataques serían factibles. Por lo general los online no, ya que hay otras medidas de control, por ejemplo no aceptar más que un cierto número de conexiones en un lapso determinado desde una misma IP.

Si a eso se le suma que el token tiene un tiempo de vida y está correctamente calculado según el número de conexiones y el tiempo por IP, está mitigado.

Pero si el ataque es offline o no disponemos de mecanismos adicionales, estamos en el horno.

Podemos agregar un "secreto" a este número y recuperamos la fortaleza inicial, ¿no? Salvo que ese secreto forma parte del código y aunque no compartimos ni descuidamos el código, damos por sentado que está comprometido.



A falta de un mainframe, he aquí una implementación en linux con opencobol que usa los "milisegundos" como semilla.


       IDENTIFICATION DIVISION.
       PROGRAM-ID. principal.
       ENVIRONMENT DIVISION.
       DATA DIVISION.

       WORKING-STORAGE SECTION.
       01  SEED                 PIC 9(08).
       01  WS-TIME.
        03  DUMMY               PIC 9(06).
        03  MILLISECONDS        PIC 99.
       01 IDX                   PIC 9.
       01 RVALUE                PIC 9(09) COMP-3.

       PROCEDURE DIVISION.
       PROGRAM-BEGIN.
         DISPLAY "Generando random sin seed...".
         PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX = 5
           COMPUTE RVALUE = FUNCTION RANDOM * 1000000000
           DISPLAY RVALUE
         END-PERFORM.

         DISPLAY "Generando random con seed...".
         ACCEPT WS-TIME FROM TIME.
         MOVE MILLISECONDS TO SEED(1:2).
         ACCEPT WS-TIME FROM TIME.
         MOVE MILLISECONDS TO SEED(3:2).
         ACCEPT WS-TIME FROM TIME.
         MOVE MILLISECONDS TO SEED(5:2).
         ACCEPT WS-TIME FROM TIME.
         MOVE MILLISECONDS TO SEED(7:2).
         COMPUTE RVALUE = FUNCTION RANDOM(SEED).

         PERFORM VARYING IDX FROM 1 BY 1 UNTIL IDX = 5
           COMPUTE RVALUE = FUNCTION RANDOM * 1000000000
           DISPLAY RVALUE
         END-PERFORM.

       PROGRAM-DONE.
          STOP RUN.



Ejemplo de ejecuciones sucesivas, obsérvese el patrón de repeticiones en amarillo:


?> make && ./principal
Generando random sin seed...
180428938
846930886
168169277
171463691
Generando random con seed...
146627632
119773096
977866279
695807865

?>./principal
Generando random sin seed...
180428938
846930886
168169277
171463691
Generando random con seed...
169097225
730835526
213552898
116350005

?> ./principal

Generando random sin seed...
180428938
846930886
168169277
171463691
Generando random con seed...
395357036
623986740
139161360
104338109


La mejor solución es un auténtico random, que en cobol en mainframe se obtiene accediendo a la placa criptográfica, si la hay.

Como poder usar un mainframe no suele ser sencillo, ¿qué tal un microcontrolador? Es más barato y entra en una mano.

Hay un juego muy simpático, que consiste en ir poniendo manos y pies en unos círculos de colores, terminando los participantes muy enredados. El problema que tiene es que tiene una ruleta que tiene que manipular una persona externa al juego, que suele ser algún progenitor.

Para evitar mi responsabilidad como progenitor, lo que hice fué este simpático muñeco, que mediante leds de colores indica a los jugadores donde poner sus extremidades, al azar, claro, si no no lo mencionaría en este post.


Frente




Espalda





El código está en github, una versión baremetal sin azar y una arduino con azar, ambas se queman con USBasp.


El código es bastante sencillo:
  • En baremetal main() / arduino setup() se configuran los puertos como salidas. 
  • fullBlink() prende y apaga todo.
  • heartbeat() invierte todo.
  • set() dependiendo de la posición a mostrar, lo mapea a uno de los pines.
  • main() hace un poco de circo de comienzo y luego alterna entre elegir y la extremidad/color.



Lo que importa es obtener el seed, para lo cual tuve horribles sufrimientos.

Primero pensé en utilizar un micrófono, pero mi ignorancia analógica me lo impidió. Luego quise leer un fotorreceptor infrarrojo, pero no sé que falló. Por último decidí medir el tiempo hasta que alguien oprimiera un botón y eventuales rebotes, con el ruido del ADC ni hace falta apretar el botón, que está conectado a A0.

Velo en ejecución:





Ah, me olvidaba, para los tokens, estas son algunas medidas útiles:


ComposiciónRango de valoresValores posibles en cada posiciónPosiciones necesarias segun bits
100128256
Hexadecimal0-910303977
Números0-9A-F16253264
LetrasA-Z26212754
Letras y números0-9A-Z36192550
Todas la letras y números0-9A-Za-z52172143

Los números salen de acá, pegar en una hoja de cálculo como fórmula:

CDEF
3
100128256
410=LOG(POTENCIA(2;D$3);$C4)=LOG(POTENCIA(2;E$3);$C4)=LOG(POTENCIA(2;F$3);$C4)
516=LOG(POTENCIA(2;D$3);$C5)=LOG(POTENCIA(2;E$3);$C5)=LOG(POTENCIA(2;F$3);$C5)
626=LOG(POTENCIA(2;D$3);$C6)=LOG(POTENCIA(2;E$3);$C6)=LOG(POTENCIA(2;F$3);$C6)
736=LOG(POTENCIA(2;D$3);$C7)=LOG(POTENCIA(2;E$3);$C7)=LOG(POTENCIA(2;F$3);$C7)
852=LOG(POTENCIA(2;D$3);$C8)=LOG(POTENCIA(2;E$3);$C8)=LOG(POTENCIA(2;F$3);$C8)

Y acá un caso concreto de wordpress del 2015, lo primero que encontré.

Si notás una cierta falta de ritmo en esta entrada, puede ser debido a que la hice en 2017, no pude hacer funcionar el azar bien rápido y algo me distrajo. Luego, con #quedateencasa me puse a hacer cosas y tuve que programar con USBasp y esté era el único ejemplo que tenía, así que lo reviví y acá está.

2019/09/19

Juegos de cálculo seguro distribuido

En ECI 2018 hubo un curso de cripto donde mostraron varias maneras de que un grupo de adversarios puedan ejecutar cálculos con información que tienen sin compartirla, por ejemplo, empresas pueden calcular promedio de fraudes  sin que ninguna revele sus cifras.

Para mostrarlo de modo intuitivo tanto para charlas internas en el trabajo como para H4CKED 2019, he desarrollado dos juegos que paso a compartir.


En ambos casos se supone que la transmisión es completamente secreta.

Con azar

La idea es que hacemos una ronda de personas, donde la primera toma su número y le suma otro al azar, guardando ambos en secreto. El resultado se lo pasa a la siguiente participante, que le suma su número y así hasta dar la ronda completa. La primera persona le resta el número al azar a lo obtenido y tiene la sumatoria de todas las participantes, que ya puede compartir. Cualquiera divide por la cantidad de participantes y ya tenemos el promedio que queríamos.






La debilidad de este algoritmo es que cualquier par de participantes separadas por una sola persona puede atacar a esta, pues saben el valor de entrada y el de salida, cuya resta es el valor de la víctima.


Con descomposición

Una mejor versión invulnerable al ataque anterior consiste en que cada participante descompone su valor en tantos sumandos al azar como participantes hayan y envía uno a cada participantes. Cada participante toma los sumandos recibidos, los suma y comparte el resultado. Estos resultados se suman, se divide por la cantidad de participantes y nuevamente tenemos el promedio que buscábamos.








El escenario de ataque factible para este caso es que todas las participantes menos una se pongan de acuerdo para atacar a esa. Sabiendo el total, se le resta el acumulado por las atacantes y tenemos el valor de la víctima.

Multiplicación


Tambien se puede multiplicar de esta manera, con factores, salvo que si alguien tiene un cero, al menos una participante va a saberlo. Quizás habría que agregar algunas rondas de intercambio, pero para un juego ya se complica innecesariamente.

En el caso del primer juego, se puede multiplicar un cero cerca del comienzo es más identificable que al final.


Otras maneras


Hay otras maneras pero no son tan intuitivas, al menos para quienes no sabemos más que matemáticas básicas, así que no son candidatas para juegos didácticos como estos. Tampoco sé si resuelven el problema del cero en la multiplicación.

Acá tenés las tarjetas por si querés jugar







2014/11/06

Juego de Piolines




La receta es muy sencilla


Se distribuyen piolines de menos de un metro entre los concurrentes, que deberían ser más de diez. Las personas deben tomar uno o más piolines y conectarse con las cercanas. Esto va a formar una red.

Las personas representan routers y los piolines conexiones.

Se necesitan dos personas, situados en lugares opuestos para que hagan de Servidor y Navegante, teniendo un solo piolín el Navegante.

Hay que tener preparados varios diálogos en tarjetas. De un lado deben tener un Origen y un Destino, por ejemplo:



De uno u otro lado una fracción que representa la posición de este Paquete con respecto al total.

Del otro lado, la Petición


 o la Respuesta, con su render como gentileza.



De este modo estamos representando:

Origen y Destino            Información del encabezado IP
Paquete/total                  Información TCP
Nombre host                  Información del encabezado HTTP o SNI
Petición                          Cuerpo del mensaje HTTP

El primer diálogo se hace con autenticación con envío de credenciales con http y se muestra como los Nodos intervinientes pueden ver el contenido de las tarjetas.

Luego, se repite colocando las tarjetas en sobres, escribiendo en el sobre únicamente las direcciones. Se puede poner el nombre de host en las Peticiones para representar SNI. En este caso tenemos una Respuesta:


En el Destino yo he puesto el nombre en lugar de la IP y en el Origen la IP (y viceversa en las Respuestas), para recalcar que el Navegador tiene IP aunque no nombre.

Se explica que a diferencia del juego, en la realidad no se pueden abrir los sobres, no por ser una regla sino por que es prácticamente imposible.

La tercera actividad es mostrar resolución de nombres y quizás enrutamiento, pidiendo a alguien que oficie de servidor de DNS. El Navegador recibe de la Persona un pedido a un dominio y le pregunta al DNS cual es la dirección IP correspondiente. Una vez que la tiene el Navegador envía la Petición al Nodo al que se halla conectado, el cual decide hacia donde reenviarlo utilizando un mapa aproximado. Se explica tambien que los paquetes son copias y que pueden haber duplicaciones y extravíos.

La cuarta es mostrar como el DNS puede haber sido comprometido y el papel de los certificados en detectar la situación.

La quinta es mostrar las cookies, mediante su agregado al mensaje, sin escribirla sino adjuntando otra tarjeta.


Salvo lo de los cookies, de un modo un otro he realizado  con algunos grupos todas las actividades, con otros menos. La he realizado con quinto, sexto y séptimo grado de primaria, un grupo de quinto y sexto año de secundaria y algunos colados en un break de la Ekoparty 2014, un grupo de profesionales diversos, en el Agile Open MDP 2014, en Agiles Argentina 2014 y en Agile Open Educación 2014, en este último caso con dos o tres personas, sin piolines.


En caso de niños, la red se rompe, arregla y reconfigura continuamente. Lejos de retarlos, se les explica que así es Internet en realidad y su origen militar. Tambien es conveniente tras la primera actividad retirar los hilos y que las tarjetas circulen por todos siguiendo un único camino, para disminuir la distracción.

En mi experiencia, se puede utilizar este juego como actividad disparadora para explicar otros temas, como el almacenamiento de mensajes y credenciales en aplicaciones web ligándolo a privacidad y la conveniencia de no reutilizar contraseñas en distintos sitios.

En particular he explicado la diferencia entre la Persona, el Navegador y las librerías o el sistema operativo, por ejemplo: "La Persona pide ir a un sitio al Navegador. Este arma una parte de la Petición y se la pasa al sistema operativo, el cual avergua la IP y arma la Petición efectiva."

No lo he hecho, pero se podrían mechar diversos temas criptográficos, protocolos de enrutamiento, ingeniería social, virtual hosting. Depende del tiempo, del nivel de los participantes y de cuanto sepas. Como es de esperar, recomiendo que sólo se expliquen los temas que se dominan. Esta es una guía para el juego, no para los temas.

El otro juego que he desarrollado es http://seguridad-agile.blogspot.com/2011/05/juego-agile-ul-ubiquitous-language.html, hace más de dos años, como que inventar juegos no es lo mio.










2014/07/18

Origami sui generis en entorno no agile

Sin poder exponer mucho las circunstancias exactas, menos por consideración a la seguridad que a mis compañeritos, les comparto esta experiencia de la que participé, unos juegos parecidos a los que usamos en nuestros encuentros ágiles.

La persona que trajo los juegos no tenía experiencia en dirigirlos así que cuando se empantanó le ofrecí el juego del origami colaborativo[1]. Como no lo tenía preparado ni sé ningún origami, la consigna que inventé fue que en cada pareja una persona le explicara a la otra como hacer un avión de papel según su propio modelo.

Formé tres parejas, una enfrentada, una de espaldas y otra en trencito, siendo la persona de atrás quien dirigía.

Repartí hojas sin excluir a cada director de pareja tambien, como para facilitarle que hiciera su avión mientras explicaba. Esto, según pude descubrir luego, indujo en la consigna que había que hacer dos aviones iguales, que no es necesariamente lo mismo que hacer el avión del que daba las instrucciones.

Las variaciones al juego fueron menos resultado de mi afán de innovar que de no recordar la forma original.

Los resultados fueron absolutamente hilarantes:

Los que estaban de frente y se podían comunicar y ver libremente, aunque no meter las manos, hicieron el avión más antiestético que quepa imaginar, pero volaba.

Los que estaban de espaldas, hicieron aviones distintos, ambos correctos. Al parecer obraron de buena fé por el desconcierto mostrado al darse vuelta y ver los resultados.

Los que estaban en trencito, que supuse debieron haber sido los segundos y no los terceros en terminar, se rindieron. El que dirigía acusó al dirigido y éste se defendió alegando que nunca había hecho un avión de papel antes. Entonces tomé el teléfono y llamé a la producción de The Big Bang Theory y pregunté si tenían lugar para otro personaje.

Luego me enteré de que habían intentado hacer trampa: el que dirigía le había dicho "hacé cualquier avión y yo te copio, ya que te puedo ver".

Me pregunto si alguien habrá estado en una situación así en la vida real, más allá de documentar después de diseñar.

Se puede estar descontento en el trabajo por muchos motivos, pero no por estas situaciones. Pocas veces me he divertido tanto, son unos genios.




[1] http://tastycupcakes.org/2009/06/collaborative-origami/

2013/11/25

Juego: Capture the Datacenter


En esta entrada explicaré los detalles técnicos de como armar una juego tipo "Capture the Datacenter" que es lo que hubo en la Ekoparty 2013 con el nombre de "La Caja" en el marco del Lockpicking Village de Infobyte. Esta entrada refleja lo que aprendí en compañia de todo el grupo y reflexiones posteriores mías. Hay una mezcla entre lo que se hizo, lo que no se hizo y lo que se podría hacer.

Como atacar cada sensor y subsistema es tarea para el hogar.


El concepto del juego es proteger físicamente un premio mediante cerraduras mecánicas y electrónicas y utilizar sensores de intrusión, haciendo una suerte de modelo de un datacenter.

El objetivo de la experiencia es un tanto turbio, tal como lo es el lockpicking y el pentesting, al respecto opino http://seguridad-agile.blogspot.com/2013/09/por-que-lockpicking.html


Si se quitan las cerraduras, los sistemas complejos de autenticación y la metáfora con la intrusión al datacenter, puede ser un interesante proyecto escolar, ya que para construirlo permite ejercitar el ingenio, obliga a aprender variadas técnicas y puede verse como un juego de ingenio, tanto para quien lo construye como para quien lo juega.

El contenedor


Se puede hacer de fibrofácil, que es una madera reconstituida relativamente resistente, fácil de trabajar pues carece de veta, con un peso aceptable. Es un tanto flexible, lo que puede afectar la alineación de algunos sensores.

Tip para el atacante jugador: no es conveniente apoyarse ni sacudir el contenedor.

La evolución de la forma fue: hagamos un desafío donde haya que obtener un premio abriendo cerraduras sin activar sensores tipo laser, movimiento, lo que sea, inspirado en un jueguito que hubo en segurinfo 2013, con unos espejos y un laser verde. Pero eso es difícil de calibrar, necesitamos mucho espacio bien protegido para que no se rompa. ¿Y si lo metemos en una caja y le ponemos algunas puertas? A ciegas primero, sin tapa despues, ponemos una malla de laser o infrarrojos, con acrílico cerrado, con un agujero, que se pueda sacar pero tenga sensores.

Hace falta proteger a los sensores, pongamos una tapa parcial de madera y particionemos con acrílico para que los sensores ópticos puedan actuar, luego vemos si hace falta perforar.


Fue muy interesante la evolución, como cada uno iba aportando y construiamos un modelo mental.

El problema de este tipo de desarrollo es que no es como software, donde refactorizás y listo. Cada tornillo que pongas en la caja queda marcado. Si cortás un vidrio o un espejo, queda cortado. No podés hacer backup, no hay versionamiento. No podés tener un entorno en la casa de cada uno.

Los sensores


Comencemos con el que descartamos, el laser infrarrojo. Como todos sabemos, el laser puede ser dañino para los ojos, uno infrarrojo que no se puede ver, queda entonces descartado, así como las trampas con ácido, electrocutantes o que digan malas palabras.

El laser rojo de 5mw ha sido probado reflejado con hasta siete espejos comunes y una distancia total de una veintena de metros, así que con unos pocos se puede hacer una malla bastante cerrada. El problema es que cada espejo aumenta la dispersión y que al atravesar el acrílico va teniendo reflejos fantasma que no aportan valor pues no ayudan a confundir al atacante. La próxima vez me parece que será mejor usar sólo dos espejos grandes y perforar el acrílico.



No se consiguen detectores de luz y un laser sobre una celda fotovoltaica no produce la suficiente corriente como para ser detectada. Sin embargo, los transistores infrarrojos detectan muy bien el laser rojo y los tubos fluorescentes y en realidad casi cualquier cosa. Ha resultado conveniente conectarlo a una entrada analógica para poder compensar en software los desajustes en la detección.

Con los transistores infrarrojos y leds infrarrojos se pueden hacer tambien detectores de interrupción de menor distancia, se podrían conectar a entradas analógicas o digitales según cada caso.

Tantos con unos comos con otros, se puede medir que el haz haya sido interrumpido. El inconveniente de este método es que se puede enfocar un laser externo sobre el sensor para anularlo. Para evitarlo, se puede modular una señal, pero sería mejor modularizarlo, poner la lógica en un microcontrolador y que provea una salida binaria al sistema detector.

El PIR (passive infrarred) es un sistema modular que detecta movimientos mediante cambios en la iluminación infrarroja existente. Es la típica cajita en la esquina del ambiente que se prende cuando te movés.

El detector por ultrasonido es otro sistema modular, arduino tiene kits. La salida es binaria tambien. Funciona emitiendo ultrasonido y evaluando el eco.

El detector de humo tambien es modular y se puede implementar con un par led/transistor infrarrojo. En una cámara oscura, se colocan sin que se "vean" pero cruzándose sus trayectorias. Cuando haya humo, este brillará iluminado por el led y afectará al transistor.

Motion es una aplicación que analiza un stream de video y según unas zonas configurables detecta movimiento. Esto se puede conectar a la aplicación controladora embebiendo las librerías o mediante pipes, sockets, según sea local o remota. Se puede considerar binaria.

Detector de contacto, es lo mismo que los botones de los ascensores, esos que no se presionan, sólo se tocan. Si no me equivoco, cuando los tocás hacés de antena para los 50/60 hertz de la red eléctrica que te rodea. Esto se puede implementar con un poco de electrónica o un microcontrolador.

Detector de tilt, es de los flippers, si sacudís mucho o cambiás el plano del contenedor, se activa. Se pueden usar  acelerómetros y modularizarlo.


¿Por qué un microcontrolador para cada sensor? Por el principio unix de que cada componente hace una sola tarea y la hace bien. Se puede conectar todo a un solo microcontrolador, pero ahí empezas con que tenés que implementar cosas complicadas, que no alcanza la velocidad para samplear, analizar y transmitir todo a tiempo. Es para usar unas pocos horas y no hay restricciones de espacio.

Y por último los simples interruptores. Son como botones de un timbre, si los apretás, suenan. Esos son "normalmente abiertos". Si son como los que prenden la luz al abrir la puerta de la heladera son "normalmente cerrados" y son los que más nos interesan. Se pueden conectar en serie muchos si son "cerrados" o en paralelo si son "abiertos" y considerarlos un solo sensor, así nos ahorramos patitas de entrada.

Un detalle a considerar con los interruptores es el "debouncing", el rebote. Cuando se abren o cierran la transición no es suave, se genera un ruido que puede ser necesario filtrar, ya sea por medios electrónicos o por software.

Si el detector de tilt se hace artesanalmente, se puede considerar un caso particular de interruptor.


La electrónica

Las fuentes de pc proveen varios voltajes útiles como 12, 5 y 3.3v. En esta experiencia en particular los laser eran de 3v y fueron alimentados meditante fuentecita regulada de 3v y paralelamente un puente de división de tensión de los 12v a 3v. Ambos funcionan.



Es conveniente usar cables y no alambres (varios hilos, un hilo) de distintos colores, conectores y borneras o pines, ya que el tener todo soldado en el aire con termocontraible y cinta aislante, aunque se ve lindo es muy difícil de mantener y diagnosticar.






Para conectar los módulos al microcontrolador principal una técnica muy práctica es usar optoacopladores, esto es un led del lado del módulo sensor y un transistor infrarrojo del lado detector. Esto sirve para que no tener que adaptar 12v a 5v ni que haya corriente circulando entre los distintos módulos.


             -----+              +--------------
                  |              |
            12v  led >-luz-> transistor        5v
                  |              |
             -----+              +--------------

Se arman con un sorbete, enfrentando los diodos y reforzando con cinta aislante o funda termocontraible.


Arquitectura


Una arquitectura posible es la siguiente:

 Cuando releía esto antes de publicar, encontré "hmi" en la arquitectura y me costó mucho recordar que quise decir: "human machine interface".




Para conectar el microcontrolador, un arduino, hubo que hacer un circuito impreso parecido al de la foto.

Abajo se puede apreciar al centro las resistencias para cada entrada analógica, que se conectan en los pines de la derecha. Usar cables de colores y anotar la polaridad en el impreso ayuda mucho.

Para las entradas digitales, pueden hacer falta resistencias pull-up o pull-down[1] para que no floten libres, que en esta encarnación no estan incluidas en el circuito y se deben poner cerca del diodo. Yo tenía entendido que los microcontroladores las incluyen y que se pueden configurar por software como up o down, pero quizás me estoy confundiendo con otro microcontrolador más poderoso.



Control físico de acceso

Cerraduras y candados en las puertas, cierre magnético controlado por autenticación biométrica. Las técnicas para burlar estos sistemas se explicaron en el Lockpicking Village y varios de los participantes pudieron de un modo u otro aplicarlas exitosamente.

Con respecto a la autenticación biométrica, sólo puedo decir http://seguridad-agile.blogspot.com/2013/11/por-que-no-biometria.html y su actualización http://seguridad-agile.blogspot.com/2015/09/por-que-no-biometria-redux.html

El software


El sistema controla los sensores, asignándole una penalidad a cada uno, lo cual incide en el defcon. Se puede calibrar la sensibilidad de cada sensor analógico e invertir la lógica, para lidiar con normal-abierto y normal-cerrado.

Esta es una captura de pantalla actual y luego un video de una versión primitiva



Se puede apreciar como el módulo de movimiento supera el umbral pero no se activa, pues está negado. La interfaz no es muy intuitiva, pero es lo que hay, busquen a un diseñador de interacción.



En el video se puede apreciar malamente como el cliente web actualiza con f5 la vista del defcon.

La evolución fue scratch[2] para arduino[3][4] como POC. Luego processing[5] [6] y terminó ejecutándose como applet dentro de eclipse. Desde processing para aca, todo fue versionado con git, salvo el software que va en el microcontrolador y se programa con arduino ide. Hubo que modificar el código que viene como ejemplo por la cantidad de entradas analógicas que vienen hardcodeadas, es que deben ser para otro modelo. Retrospectivamente, hubiese sido mejor no usar processing, pero tampoco fué tan grave.

El software está en https://github.com/cpantel/boxControl

Las reglas del juego

Es importante que hayan reglas claras para evitar malos entendidos y prolongar la vida útil del juego. Es importante definir el alcance, como que no vale cortar cables, desatornillar un panel, lo que sea.

La mejor regla es: "No rompas nada, dejá el juego en condiciones para que el próximo jugador pueda jugar".



[1] http://en.wikipedia.org/wiki/Pull-up_resistor
[2] http://scratch.mit.edu
[3] http://www.arduino.cc
[4] http://s4a.cat
[5] http://processing.org
[6] http://playground.arduino.cc/Interfacing/Processing‎