La biblioteca de estator facilita la lectura de su código Arduino

La legibilidad de su código puede marcar la diferencia entre que trabajar con su proyecto sea un placer o un dolor de cabeza absoluto. Esto colabora doblemente con los demás. El análisis de código fácilmente reduce su carga cognitiva y facilita la resolución de problemas. Para intentar ayudar con esto, [PTS93] desarrolló la biblioteca Stator para facilitar la lectura de algunas tareas comunes.

El propósito de la biblioteca es eliminar montones de variables de seguimiento de estado e infinitas declaraciones if / else, de ahí el nombre. Está diseñado principalmente para Arduino IDE pero no depende de la API, por lo que se puede utilizar en otros entornos C ++. Viene con una variedad de herramientas ordenadas para tareas comunes, como leer un sensor analógico con histéresis alrededor de un punto de activación, así como formas sencillas de rastrear cambios de estado en muchas variables. Al usar términos básicos en inglés en lugar de verificaciones condicionales y operadores matemáticos, puede hacer que las cosas sean más legibles y fáciles de seguir.

El poder de la plataforma Arduino siempre ha estado en sus bibliotecas fáciles de usar que facilitan todo, desde pantallas de interfaz LCD hasta trabajar con botones de Amazon Dash.

  • JWhitten dice:

    Los estados son geniales, pero en realidad, solo nombrar sus variables y constantes como algo interesante para comenzar es un primer paso increíble para muchas personas. Deshazte de esos números “mágicos” que parecen surgir de la nada.

    • Natsu dice:

      Inútil para programadores con buenos hábitos,
      y dar hábitos de cama a codificadores perezosos.
      Muy mala idea

      • Harvey dice:

        Arduino no es una computadora de escritorio … Muchas personas nunca aprendieron realmente a programar antes de comenzar con Arduino. Al igual que muchos de ellos nunca aprendieron realmente la electrónica porque usan los módulos prefabricados y solo necesitan conectar cables a las clavijas de la cabeza.

        En mi experiencia, recientemente comencé a usar Arduino, principalmente porque hay muchos módulos en las tiendas redundantes que son realmente baratos. Strip board, perf board, incluso grabar sus propias tablas, es una especie de proyecto propio. Nunca aprendí C, principalmente porque no soy un programador estructurado, tan conectado con BASIC y lenguaje de máquina, desde mis días de programación de computadoras de 8 bits. Juego con microcontroladores ATTiny, pero funcionó mucho, luego mi desarrollador murió. Entonces, Arduino comenzó a verse bastante bien, todo ya estaba a bordo. se conecta directamente al puerto USB. Todavía no me gusta C, pero estoy aprendiendo a sortearlo. Sin embargo, soy más pirateado el código, revisaré el código de otra persona porque las partes que necesito en mis proyectos todavía tienen que hacer algunos cambios, pero funciona. No lo encuentro demasiado horrible a menos que utilicen funciones más avanzadas o utilicen muchas funciones de la biblioteca.

        Arduino se trata realmente de programación, es un mal necesario por el que algunos simplemente pasan, en el mejor de los casos. No me veo usando tales bibliotecas, solo más trabajo y confusión.

      • Shannon dice:

        No estoy de acuerdo, la biblioteca ayudaría tal como explica Lewin, reduce la carga cognitiva. Si tiene dos valores, debe realizar un seguimiento de dos valores y debe verificarlos y actualizarlos constantemente en todas partes. Es un error que espera suceder. La clase Stator sería más confiable porque se ocupa de ambos valores.
        Sería muy bueno si la clase Stator también mostrara varios estados definidos para una máquina de estados:

        enum MachineState {Initialize, WaitForCommand, Work, ReadTemperature};

        Estado de la máquina del estator;
        machineState = Inicializar;

        Sin embargo, llamar “misterio” a un objeto de la clase Stator es una mala idea, eso está claro.

        • dcfusor2015 dice:

          Estar de acuerdo.
          Me río cada vez que veo “mío” en cualquier parte del código. ¡Desperdicio de personajes! No importa quién lo lea, ¡es “mío”! Por lo tanto, no especifica nada en absoluto, excepto en casos muy especiales, donde las palabras servidor y cliente o maestro y esclavo tendrían más sentido de todos modos.

          • BrilaBluJim dice:

            El uso de “mío” en el código de muestra solo ayuda al lector a comprender que el objeto en particular no es parte de la clase.

          • tekkieneet dice:

            cuidado que yo invento algo que ha roto el curso de acción convencional.

        • Shannon dice:

          Jaja, acabo de notar que wordpress se comió mi uso del uso de la plantilla:
          Estado de la máquina del estator;

  • BrilaBluJim dice:

    Si realmente cree que “mystuff.changed ()” es más legible que “state! = LastState”, no sé qué decir. Agregar una función / método llamado cambiado () solo significa que debe mirar esa función para saber qué controla.

    Existe tal adición de código sin una buena razón que conduce a la hinchazón y la locura del código. Cualquiera que sepa un poco sobre C / C ++ sabe lo que hace la construcción “State! = Last State … Last State = State”, porque está justo enfrente de usted. NADIE sabe lo que significa la función cambiado () sin mirarla.

    • Beto dice:

      Esto. Gracias

    • A dice:

      +1

    • Clemens dice:

      Por cierto, los ejemplos en la página de github, con y sin la biblioteca … ni siquiera funcionan de manera equivalente. Las variables “antiguas” se restauran sólo si se cumplen ambas condiciones (ambos valores, excepto sus respectivas variables antiguas), pero el método de la biblioteca change () comprueba si los valores respectivos cambiaron con su última tarea. Comportamiento diferente. Y ese es el problema con estos “ayudantes”. Olvidas qué están haciendo exactamente.

      • tekkieneet dice:

        Es una pérdida de tiempo explorar lo que realmente hacen sobre lo que ofrecen.

    • Ene. dice:

      +1

      Pero me gustaría agregar que CADA código es difícil de entender (algunos más que otros).
      Por tanto, deberían añadirse comentarios útiles. Ver es como una nota para ti en un futuro lejano (después de 3 meses).
      No es una pena escribir comentarios en su código. Algunas personas son perezosas, otras son arrogantes y piensan que no lo necesitarás, pero muchos piensan que lo agregarán más tarde … pero de alguna manera nunca lo hacen. Y, por lo tanto, terminará con un código que es difícil de leer cuando lo ve por primera vez. Especialmente cuando los nombres de las variables están mal elegidos, se utilizan números en lugar de enumeraciones y los paréntesis se omiten o están en todas partes. Una declaración de caso adecuada puede mejorar la estructura simple o cualquier otra en unos segundos … etc.

      MAL HÁBITO:
      escribiendo código sin comentarios

      MAL COMENTARIO: (escribiendo exactamente lo que hace cada línea y no lo que significa en absoluto * /
      / * comprobar si la bandera está activada, bucle a vacío * /
      / * copiar datos del búfer al registro SSP * /
      / * aumentar el puntero * /
      / * variable diminutiva * /
      / * comprobar si la variable es nula * /
      / * hazlo otra vez * /

      BUEN COMENTARIO:
      / * transmitir todos los datos de la cadena al puerto serie * /

      • dcfusor2015 dice:

        Sí, ¡la frustración que ahorras es probablemente tuya! Encuentro los mejores comentarios en la línea de “lo que pensé” y “por qué estructuré esto de la manera que lo hice”. No es pecado poner un breve párrafo aquí y allá.

        • Dan dice:

          Encuentro que los mejores comentarios son los que me dicen por qué no lo hice de otra manera (más corto / más obvio).
          // no se puede simplemente hacer xyz, porque el abc no se configura

          • Neil dice:

            Prefiero los comentarios como especificaciones. Los comentarios explican qué * debería * hacer el código o cuál es el propósito, en lugar de qué hace el código o cómo lo hace. Los comentarios se pueden escribir delante del código de estilo PDL, así como una base de pruebas unitarias. Dichos comentarios solo están en desacuerdo con el código cuando cambia la especificación en lugar de solo la implementación.

          • BrilaBluJim dice:

            +1. Cada función debe comenzar con una especificación. Qué significan los parámetros, qué hace y qué devuelve. Además, cada archivo fuente debe indicar qué grupo de funciones, clases o lo que sea que contenga. He visto muchísimos proyectos de “código abierto”, cuyos archivos comienzan con sus declaraciones legales y luego saltan directamente al código.

    • RoGeorge dice:

      +1

    • Janis Petke dice:

      No estoy tratando de ser sarcástico, pero … ¿Existe una biblioteca para ocultar la “complejidad” de una comparación de enteros? ¿Qué serán beSmaller (), isBigger () e isEqual () a continuación, porque “” y “==” son conceptos igualmente complejos? ¿Qué pasa con las versiones localizadas para nosotros con el inglés como segundo o tercer idioma?

      Se podría intentar utilizar los lenguajes de programación visual si las operaciones matemáticas normales son demasiado difíciles de leer.

    • WA diez Brink dice:

      Bueno, mystuff.changed () es bastante legible en mi opinión. Pero esa no es la mayor preocupación que tengo. La clase Stator también controla la sincronización, lo que agrega más funcionalidad a la clase de la que necesitaría. ¡Esto no es necesario al principio!
      La otra desventaja en mi opinión es que esta biblioteca requiere un compilador de C ++, mientras que es más práctico limitarse a C sin las clases, ya que es más fácil de usar. Las clases pueden hacer las cosas un poco más complejas, pero también tienden a consumir más memoria.
      Además, si desea ejecutar algo como esto, entonces es más fácil ejecutar un evento OnChanged () en lugar de usar una función de revocación que se llama cuando cambia el valor.
      Pero no es bueno porque requiere un poco más de código para realizar una simple verificación.

      • Profe. Fartsparkles dice:

        La idea es que tan pronto como tenga más de una variable para rastrear, obtenga muchos códigos iterativos y el estado simple. = ¡LastState se convierte en state1! = LastState1 && state2! = LastState2 && state3! = LastState3 y todo el código lógico que lo acompaña para manejar la transición de estado.

        Por lo tanto, debe asegurarse de escribir la lógica correctamente para cada uno de los estados. Los pequeños errores se introducen con bastante rapidez, por lo que es más fácil confiar en un edificio más pequeño que se comporta de la misma manera cada vez y no tiende a hacerte olvidar una tarea de un estado3.

        Principalmente hago prototipos rápidos. Más abstracción para evitar que repita errores es útil.

        Si no te ayuda, está bien. No es una biblioteca “esto soluciona todos los problemas”.

        • WA diez Brink dice:

          Hacer prototipos rápidos no es excusa para escribir código incorrecto. Cuando comienzas a usar indicadores de estado que son verdaderos o falsos, ¡lo estás haciendo mal! Es cuando en su lugar, necesitaría campos de bits, donde puede usar un solo int para guardar el estado de 16 a 32 indicadores booleanos diferentes en una variable. Entonces sí, necesitaría definir algunas constantes como STATE1TRUE = 1, STATE2TRUE = 2, STATE3TRUE = 4 e incluso STATE1AND3TRUE = STATE1TRUE || ESTADO3TRUE. Luego puede comparar if (flag && STATE1AND3TRUE == STATE1AND3TRUE) para verificar múltiples banderas a la vez …
          También puede verificar si (flag && STATE1AND3TRUE == STATE1TRUE) para asegurarse de que el estado 1 sea verdadero y el estado 3 no. O marque STATEFALSE (= 0) para ver si ambos son falsos …
          Usar mapas de bits es algo a lo que te acostumbras. La lógica AND / OR / NOT / XOR relacionada tiende a ser difícil para los nuevos desarrolladores, pero una vez que comprenda el concepto, debería ser relativamente fácil. Y para situaciones más complejas, siempre puede escribir las cosas para resolverlas. ¡Con lápiz y papel!

          • Profe. Fartsparkles dice:

            No pretendía ser un comentario para usted, sino para BrightBlueJim.

            De todos modos, ¿de verdad crees que la construcción que describes es más fácil de leer?

    • default_ex dice:

      Eso es exactamente lo que pensé de los ejemplos dados. Es tan seguro usar “! =” Para representar “no es igual a” que no recuerdo la última vez que alguien que no era programador me preguntó qué significaba eso o mostró alguna señal de que no entendía lo que era medio.

      La última vez intenté trabajar con un programador que insistía en escribir funciones estúpidas de “ayuda” como “cambiado ()”. Comencé a completar el código con alternativas como: “state ^ lastState”, “state – lastState”, “~ (state | lastState) == ~ state & ~ lastState” y un montón de otros métodos de prueba de equivalencia obteniendo todo el camino en álgebra lineal. Realmente no pasó mucho tiempo para que ese tipo dejara de perder el tiempo con tales funciones.

  • donvukovic dice:

    ¡¡Mi mascota es ‘{‘ y ‘}’ !!
    Sentado en una revisión de código y el autor de un fragmento de código no puede encontrar el principio y el final de sus funciones porque no pudo encontrar el “{}” en el editor de B / N que a todo el mundo le encanta odiar.
    El espacio en blanco es tan importante como los nombres de variables útiles.

    ¿No es por eso que Python usa tabulaciones / sangrías para leer más el código?
    Incluso el intérprete hace eso.

    ¿Por qué la gente de C / C ++ no puede entender?

    • Gordon J. Endersby dice:

      Las pestañas y los espacios ya no se pueden leer.
      Pasé horas mirando el código de otras personas tratando de averiguar por qué algunas personas no trabajan solo para descubrir que pusieron una pestaña en el lugar equivocado. Buscar algo que no lo es es mucho más difícil que tratar de encontrar el “}” lugar equivocado.

      No hay excusa para ser vago y no definir correctamente su código en una forma legible.
      Los espacios en blanco forzados en el código no son un recurso. Es más fácil hacerlo ilegible.

      • dcfusor2015 dice:

        Incluso los editores más simples tienen una función compatible con corchetes. Y la mayor parte de C o C ++ que leo, y todo lo que escribo también usa una sangría para explicar las cosas. Además, rara vez pongo un paréntesis en la misma línea que el código.
        Aquí están, con espacios en blanco después.
        Puede hacer tal cosa en cualquier idioma que conozca.
        Los lenguajes que me obligan a hacerlo a su manera e insultan mi inteligencia no son habituales aquí.

    • Poppa Yeti dice:

      ¿De qué estás hablando?

      No le faltan corchetes en su código.
      En una parte de su comentario, parece llorar porque no le gustan los paréntesis y prefiere el estilo Python, donde la agrupación se realiza a lo largo de espacios en blanco. En otros lugares parece que se queja de que alguien no usa corchetes y dice que no puede encontrar las llaves.
      ¿Bien, qué es esto?

      Además, por cada editor hay muchas personas a las que les encanta odiarlos. ¿Qué tipo de editor está limitado a B / N? Incluso el editor de línea de comandos más simple funciona en una terminal donde el usuario generalmente puede elegir qué colores. Parece pensar que es obvio de qué editor estás hablando, pero no estoy seguro de que lo sepas.

      Sobre el espacio en blanco … tienes razón. Eso también importa. Pero la mayoría de los programadores con los que trato parecen saberlo. En mi opinión, la lección que la gente debe aprender es que MUCHO espacio en blanco es tan malo como muy poco. Demasiados espacios por sangría significa que el ojo tiene que escanear más a la izquierda / derecha. Escanear a izquierda y derecha es una excelente manera de perder el lugar y dificulta concentrarse en el significado. Abundan 3 espacios, 2 buenos con fuente bloqueada! Además, ¿dedicar una línea para abrir corchetes? ¡POR QUÉ! No hace nada por la legibilidad, solo significa más desplazamiento. Claro que eso es mejor que el escaneo de izquierda a derecha, pero el desplazamiento adicional sin una ventaja todavía duele. Coloque sus paréntesis iniciales al final del nombre del método, condicional o declaración de bucle. No lo pongas en la siguiente línea.

      Ah, y la forma en que se maneja la sangría para las declaraciones de cambio en casi todas las guías de estilo y al igual que el comportamiento predeterminado de casi todos los editores … ¡¿¡¿WTF está mal con la gente?!?!?!

      Está bien, es suficiente. / despotricar

      • Donvukovic dice:

        Ejemplo perfecto:

        >> Está bien, es suficiente. / rant (fin de perorata)

        ¿Dónde está el comienzo?

        ¡Oh, te olvidaste de agregarlo con prisa para ponerte furioso!

  • Donvukovic dice:

    Hola Lewin,
    “Trabajar con Amazon Dash Buttons”.
    tiene que leer:
    “Trabajar con botones de Arduino Dash”.

    Ya sabes, la verdad en la publicidad.

  • Tweepy dice:

    Quizás un buen (y fácil) comienzo sería agregar un “formador automático” en el editor Arduino.

    • Señalado dice:

      que tiene

    • Niklas dice:

      que es ctrl-t?

  • Enero 42 dice:

    Puedo ver lo que podría ser útil para el autor, pero ¿ayuda ese nivel de abstracción? ¿Está realmente claro lo que están haciendo o solo estamos agregando una capa de hinchazón?

    El código puro probablemente sería un mejor comienzo: podría comenzar colocando el ‘{‘ en el lugar correcto …

  • WA diez Brink dice:

    Sería mucho mejor cuando los codificadores Arduino aprendan a codificar mejor. En serio, si estos codificadores hicieran un mejor trabajo, ¡esta biblioteca no sería necesaria!
    El lenguaje C tiene una gran herramienta para lo que son operaciones aleatorias. Esto le permite mantener varios estados dentro de una variable. Generalmente se prefiere el uso de mapas de bits porque ahorra memoria. Curiosamente, en realidad puede usar campos multiproceso en sus estructuras de esta manera:
    estructura {
    traje sennoma int: 2;
    valor int sin nombre: 4;
    cara int sin nombre: 1;
    estado int sin nombre: 1;
    } tarjeta;

    Y sí, eso crearía una carta de 8 bits para los cuatro palos (tréboles, espadas, corazones y diamantes) más el valor de la carta de 0 a 15, 0 sería un bromista, 1 el as y 11, 12 y 13 Jack, Queen y King, lo que le permite agregar dos valores de cartas adicionales. La cara sería una indicación si el valor nominal está por encima, por lo que es visible y el estado indicaría si la carta todavía está en el juego o no. O lo que quieras, de verdad. Por lo tanto, puede verificar card.face para ver si está boca arriba o hacia abajo o card.value para verificar su valor. Y si es necesario, se puede transmitir con bastante facilidad, porque es solo un byte en total …
    También puede crear campos más grandes, entre otras cosas. Por ejemplo, un mazo completo podría definirse como:
    estructura {
    fósiles int sin nombre: 13;
    clubes int sin nombre: 13;
    corazones sin nombre: 13;
    diamantes sin nombre: 13;
    } plataforma;
    Son 52 bits en total, pero el compilador lo convertirá en 64 bits. Y cada campo de la estructura indicaría si esa carta específica está dentro del mazo o no. También puede utilizar esta estructura para representar la mano de un jugador o incluso controlar patrones ganadores específicos. Incluso puede usar los 12 bits restantes para estados adicionales en la plataforma si es necesario.
    Pero las operaciones binarias como estas son demasiado difíciles para la mayoría, aunque facilitan mucho la codificación cuando se usan correctamente …

    • Shannon dice:

      No estoy realmente seguro de lo que está tratando de decir con esto, lo útil de la biblioteca no es el hecho de que pone dos valores en un objeto, es que usa C ++: sobrecarga operativa para hacer que el carga más fácil de rastrear el estado. Si tuviera que implementarlo, no optaría por las dos versiones de plantilla, pero eso no está ni aquí ni allá.

      • WA diez Brink dice:

        Solo digo que crear una biblioteca general para manejar estados es un poco excesivo y conduce a código innecesario. Puede tener varios indicadores dentro de una variable mediante el uso de operadores bit a bit Y / A / / XOR / NOT lógica junto con una lista de constantes o valores predefinidos. Y si necesita algo más que indicadores booleanos, también puede usar campos de bits dentro de un tipo y especificar la longitud en bits para cada valor. Mi ejemplo es un juego de cartas en el que puedo almacenar una baraja completa de cartas dentro de un int o una carta con dos estados en un valor de 8 bits.
        Todo esto sin usar C ++ …
        Y como dije, la biblioteca funciona demasiado, pero no hace las cosas importantes … Realiza un seguimiento de la edad de la variable, lo cual es un poco inútil. Pero tampoco hace nada útil cuando se cambia el valor. Sería útil si llama a una función de revocación cuando se cambia el valor, para que responda inmediatamente a cualquier cambio.

  • Nik dice:

    El arte de usar código sucio para leer el código de otros 😉

    bool cambiado () {
    if (_state! = _lastState) {
    devuelve verdadero;
    } otro {
    falso retorno;
    }
    }

    debería estar escrito

    bool cambiado () {
    return (_state! = _lastState);
    }

    (y estamos hablando de microcontroladores donde los ciclos de la CPU y la memoria son una preocupación)

    • Enero 42 dice:

      no, debe ser
      bool cambiado ()
      {
      return (_state! = _lastState);
      }

      • Rog Fanther dice:

        Yo pondría tres velocidades delante de esa palabra clave de retorno, pero este estilo es mejor. A veces, tuve que imprimir el código y crear líneas con un marcador para encontrar algunos “}” engañosos …

        • Jim dice:

          O podría ser …

          bool cambiado ()
          {
          return (_state! = _lastState);
          }

          Esto Ese es un estilo de sangría poco común, pero lo he usado durante décadas y lo encuentro muy legible.

          • Andy Pugh dice:

            Es difícil comentar sobre un estilo con sangría en un foro que expone espacios de plomo.

          • Jim dice:

            OOPS no sabía esto sobre los espacios de liderazgo. Simplemente imagina 3 espacios delante de cada línea después de la primera.

            Gracias Andy

  • Andy Pugh dice:

    No soy un buen programador, pero parece que codifico muchas máquinas de estado. Y yo uso “cambiar”

    estado int estático
    cambiar (estado) {
    caso 0; // Inactivo
    si read_input () estado = 1;
    temporizador = 10000;
    // fali-tra
    caso 1; // espera la restauración
    if (temporizador – = período) 0) romper; // seguir esperando
    estado = 3;
    // fali-tra
    caso 2;
    caso 3;


    y así

    }

    • Roberto dice:

      en C ++ use el interruptor con estructura enum, y ahorrará mucho tiempo / espacio de memoria … y es fácil de codificar

      • tekkieneet dice:

        Los estados nombrados también mejoran la legibilidad sobre un conjunto de constantes numéricas.

  • stefan dice:

    Gente … Simplemente cambie a PlatformIO con Visual Studio Code (o cualquier IDE) y disfrute de actualizaciones, formato de código, suavizado y un IDE estable …
    Solo asegúrese de hacerlo en un día lluvioso, tomará un tiempo acostumbrarse al nuevo flujo.

    • dcfusor2015 dice:

      Texto sublime para mí, pero incluso uso gedit … algún editor moderno es decente hoy en día. Todos los soportes compatibles son fáciles, casi todos son mejores que el IDE de Arduino …

      Todos tienen actualizaciones, y la lista de idiomas que resaltan la sintaxis podría ser un mejor criterio. O la capacidad de refactorización para que realmente funcione a pesar de las reglas extrañas en algún idioma, donde el mismo nombre se puede usar como una función o variable según el contexto …

      • Edgee dice:

        +1

  • Rog Fanther dice:

    Muchas opiniones, muchas de ellas bien razonadas. Comencé a leer el artículo y pensé que no me gustaba la idea, luego consideré que también había creado funciones simples en el pasado solo para simplificar la lectura del código cuando estaba cansado o tenía prisa.

    La solución de Lewin no es adecuada para todas las personas y también es algo que no es factible. Es solo una sugerencia más sobre formas de hacer que su código sea más legible para usted, principalmente, y una forma de inducir estas cosas a “cómo podría explicar mi código”.

    En el caso de sus ejemplos, si uno usa la comparación de estados en un solo lugar, no hay necesidad de tener otra abstracción y la hinchazón que la acompaña. Pero como nos enseñó nuestro primer profesor de código, “si tienes que repetir ese fragmento de código en muchos lugares, hay que convertirlo en una función”.

  • Marcos dice:

    Me sorprende la cantidad de trucos que se reinventan porque los niños pequeños no lo saben y luego todos se alegran.

    • RandyKC dice:

      No me sorprende, es nuevo si no lo has enseñado.
      El problema es la mala manera en que documentamos y comunicamos nuestras “innovaciones” y “transmitimos” ese conocimiento a otros.

  • aad dice:

    qué pasa con una simple c-macro:
    #define cambiado (ab) (did _ ## ab! = ab)? (si _ ## xy = ab) era, verdadero: falso

    • Jim dice:

      Lo que está mal es que es tan ilegible que dudo que sea útil. Parece que falta algo …

      • tekkieneet dice:

        No es tan difícil, pero requiere un conocimiento básico de la vinculación C-macro.
        Al menos le pondría un soporte de asta alrededor por si acaso.

Ricardo Vicente
Ricardo Vicente

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *