Weblogs Código

Variable not found

¡MVP*10!

julio 02, 2020 06:18

MVP*10Es una alegría, un honor y un auténtico lujo poder compartir con todos vosotros que, por décimo año consecutivo, he sido reconocido por Microsoft como Most Valuable Professional (MVP) en la categoría Developer technologies.

Cuando fui nombrado MVP por primera vez, jamás pensé que este privilegio fuera a durar tanto. Un par de añitos quizás, lo suficiente como para poder visitar Redmond alguna vez y poder contar a los nietos que "yo estuve allí" ;) Pero diez años más tarde, aquí estamos todavía, con las mismas ganas y entusiasmo de poder seguir formando parte de este club de amigos a los que sigo y admiro tanto. ¡Impresionante!

Esta vez la notificación me ha pillado en una videollamada con el gran Jorge Turrado, amigo y compañero MVP, que me ha sacado del fragor de la batalla diaria para inyectarme la dosis de adrenalina que supone darse cuenta de pronto de que hoy era el día de nombramientos y todavía no me había llegado el famoso email :D Por cierto, Jorge ¡felicidades, por tu merecida renovación!

Muchas gracias a todos los que hacéis posible que me lleve estos momentazos. A los amigos y amigas de blog, porque sin su apoyo no habría sido posible llegar hasta tan lejos; al equipo del programa MVP, por su incansable labor y exquisito trato con todos, y, por supuesto, a mis tres niñas, por consentírmelo todo :)

Finalmente, no me gustaría cerrar este post sin enviar mi más sincera enhorabuena a los nuevos MVP; disfrutad de ese momento tan bonito. Si renováis en el futuro os llevaréis grandes alegrías, pero la primera vez nunca se olvida. Y también enviar un fuerte abrazo (con distancia social, eso sí ;)) a los que repetís galardón: como ya he dicho alguna vez, lo difícil no es sólo llegar, sino también mantenerse.

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

Actualización a Montevideo Bicis con datos de accidentes 2019

junio 30, 2020 12:00

Montevideo Bicis es un sitio web que presenta información “objetiva” para gente que quiera circular en bicicleta en Montevideo. Aprovecha Datos Abiertos de la Intendencia de Montevideo y y UNASEV. Recientemente UNASEV liberó los datos de accidentes de tránsito de 2019, así que el sitio está actualizado con los datos de accidentes de tránsito hasta 2019.

También corregí un error en el JavaScript del comportamiento de los botones de “Datos completos”. Ahora se muestran bien las tablas con los datos completos de todos los años. Hay información desde 2012 hasta 2019, y mientras sigan abriendo los datos, seguiré actualizando.

Aproveché la actualización también para hacer algunas mejoras en el código, más que nada en cuanto a legibilidad para que me sea más fácil de mantener. En algún momento hasta le dedicaré un tiempo más para eliminar todo lo que va quedando del “Hackathon Driven Development” que le dio vida.

Estaciones Movete

El sitio incluye un mapa con información también de datos abiertos de la Intendencia de Montevideo. Uno de los datos son las estaciones Movete, estaciones de préstamo de bicicletas públicas en Montevideo. Lamentablemente me enteré que este servicio dejó de funcionar. En parte lo atribuyen a la reducción importante de usuarios, que yo atribuiría a la falta de infraestructura y educación vial contra la cual pretende luchar el sitio. Pero no entremos en una verborragia violenta que bastantes problemas tiene el mundo hoy para andar enojándose porque una ciudad no es amigable para los ciclistas…

Los datos del mapa están basados en datos de la Intendencia de Montevideo, pero según la fuente no se actualizan desde marzo de 2018. Tengo entendido que se han creado varias ciclovías nuevas desde entonces, pero necesito que se actualicen los datos fuente. Ya agregué a mano una vez el estacionamiento del MNAV (¡gracias Eduardo de Informática en MNAV!), pero no tengo información (y paciencia) para agregar a mano las rutas.

En fin, si les interesa el tema, pueden visitar MontevideoBicis.com, y ver el código fuente en GitHub. Y si tienen ideas o críticas sobre el sitio, son más que bienvenidos a compartirlos en los comentarios de este post.

 

Montevideo Bicis 2019

» Leer más, comentarios, etc...

Fixed Buffer

Contenedores Docker en Azure (aci) desde Docker Desktop

junio 30, 2020 08:00

Tiempo de lectura: 8 minutos
Imagen ornamental con el logo de Azure Container Instances (aci) para la entrada sobre como desplegar contenedores en Azure con Docker Desktop

Se van acercando las vacaciones de verano y ya están tan cerca que se pueden sentir. Con las vacaciones de verano llega el final de la temporada y un merecido descanso. Con esa idea en mente, pensaba cerrar la temporada con una entrada sobre Dapper que se iba a publicar en lugar de esta, pero el reciente anuncio de Microsoft sobre el soporte experimental para desplegar contenedores Docker directamente en Azure Container Instances (aci de aquí en adelante) desde Docker Desktop ha trastocado un poco los planes.

Si quieres saber como desplegar un agente de DevOps efímero sobre aci, no te pierdas la entrada ‘Azure DevOps Ephimeral Agents: Agentes de usar y tirar

¿Por qué es tan interesante poder desplegar aci directamente desde Docker Desktop?

Normalmente, trabajar con Docker en local es más que suficiente para poder hacer pruebas y desarrollos que antes o después acaben desplegados por ejemplo en Kubernetes. Aunque este suele ser el proceso habitual, también existen modelos de orquestación basados en docker-compose.

Imagina que necesitas que un equipo de testing haga pruebas sobre una web que estas desarrollando. Si bien es cierto que se podría desplegar la imagen sobre un App Service directamente, habría que gestionarlo a parte, actualizar sus imágenes,…

Gracias a este nuevo modelo, vamos a poder tener un funcionamiento mucho más sencillo, ya que va a ser directamente desde Docker Desktop donde vamos a gestionar los despliegues a contenedores en Azure Container Instances. Nuestro flujo de trabajo sería algo como esto:

La imagen muestra un diagrama de flujo hacia DockerHub/ACR y desde ahí hacia Azure Container Instances

Aunque ahora mismo está en una fase muy preliminar (se hizo público el 25 de junio de 2020), la idea detras de esto es conectar el aci con por ejemplo un Storage de Azure de modo que tengamos volúmenes persistentes o que podamos por ejemplo crear un despliegue complejo utilizando directamente docker-compose.

Esto se suma a que los recursos dejan de ser parte de nuestra máquina para empezar a ser parte de la infraestructura de Azure. De este modo, si nuestro entorno tiene un consumo que nuestra máquina no puede soportar bien, al estar alojado en Azure ese problema desaparece de raíz.

Configurando Docker Desktop para desplegar sobre ACI

Llegados a este punto, vamos a empezar a configurar Docker Desktop para poder desplegar sobre ACI. El primero de los requisitos es tener la versión 2.3.2.0 Edge (o superior). Esta versión se puede descargar desde le propia web de descargas de Docker Desktop:

La imagen señala el canal Edge de MacOS y de Windows en la sección de descargas de Docker Desktop

En caso de que todavía no este disponible la versión 2.3.2.0 en las descargas (a mí me ha pasado), puedes descargarlo utilizando uno de estos enlaces para Windows o para MacOS.

Una vez que hemos instalado la nueva versión, lo primero que vamos a necesitar es autenticarnos con nuestra cuenta de Azure. Para esto basta con ejecutar el comando:

docker login azure

Esto nos enviará a la web de Azure para que introduzcamos las credenciales. Una vez que nos autentiquemos, en la consola recibiremos un mensaje de confirmación de que todo ha ido bien:

PS C:\Users\jorge> docker login azure
login succeeded

Con esto, ya casi tenemos todo listo para desplegar contenedores en Azure (aci) desde Docker Desktop. Solo nos falta un paso más, crear un contexto de tipo aci en Docker Desktop. Para esto basta con ejecutar:

docker context create aci myacicontext

Este comando nos permite a día de hoy, definir ciertos parámetros adicionales para poder ajustar más la configuración. Estos parámetros son:

  • location: Permite definir la ubicación de los contenedores
  • resource-group: Permite indicar que los contenedores deben crearse en un grupo de recursos concreto (que debe existir previamente)
  • subscription-id: Permite indicar el Id de una suscripción en concreto de Azure, especialmente útil si tienes más de una suscripción en tu cuenta.

Por ejemplo, en caso de definir los 3 parámetros el comando sería algo así:

docker context create aci myacicontext --location westeurope --subscription-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group docker-aci

Con esto, ya tenemos disponible el contexto myacicontext listo para desplegar contenedores aci desde Docker Desktop. Esto podemos comprobarlo con el comando:

docker context ls

Como mínimo, este comando debería respondernos 2 contextos, el default de Docker Desktop y el que acabamos de crear.

PS C:\Users\jorge> docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
myacicontext *      aci                 docker-aci@westeurope

De ahora en adelante, podemos especificar sobre que contexto queremos aplicar los comandos Docker con el argumento –context Contexto. También podemos especificar el contexto por defecto que debe utilizarse si no se informa específicamente con el comando:

docker context use contexto

Vamos a probarlo

Una vez que tenemos el contexto listo, basta con ejecutar un run normal para que se despliegue automáticamente. Por ejemplo, podemos utilizar el comando:

docker run -p 80:80 -d nginx

Esto mostrará por consola una salida un tanto diferente a la habitual cuando se usa el contexto local:

PS C:\Users\jorge> docker run -p 80:80 -d nginx
[+] Running 2/2
 - sleepy-cray             Created                                         4.5s
 - single--container--aci  Done                                           20.6s
sleepy-cray

Si ahora vamos a la suscripción de Azure en el grupo de recursos que le hemos indicado, podemos encontrarnos algo como esto:

La imagen muestra el grupo de recursos docker-aci de con un aci llamado sleepy-cray

En caso de que cuando creásemos el contexto no le indicásemos un grupo de recursos, se crearía uno automáticamente con un GUID como nombre.

Podemos gestionar el propio contenedor desde el mismo Docker. En este caso hemos desplegado una imagen de nginx y hemos mapeado el puerto 80, por tanto, si obtenemos la IP del contendor, deberíamos poder acceder desde cualquier navegador. Para obtener esa IP basta con que ejecutemos:

docker ps

Esto nos mostrará por consola la salida habitual del comando, indicándonos la IP del contenedor:

CONTAINER ID        IMAGE               COMMAND             STATUS              PORTS
sleepy-cray         nginx                                   Running             52.143.11.203:80->80/tcp

Basta con navegar a la dirección IP para ver que todo funciona correctamente:

La imagen muestra la página de bienvenida de nginx y señala la IP desde la que se ha accedido que es 52.143.11.203

Al igual que hacemos con cualquier otro contenedor, también podríamos obtener los logs desde el aci utilizando en Docker Desktop el comando:

docker logs id-del-contenedor

Por ejemplo, para nuestro nginx:

PS C:\Users\jorge> docker logs sleepy-cray
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
10.240.255.56 - - [28/Jun/2020:19:37:17 +0000] "GET / HTTP/1.1" 400 157 "-" "-" "-"
10.240.255.55 - - [28/Jun/2020:19:43:13 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.56" "-"
10.240.255.55 - - [28/Jun/2020:19:43:13 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://52.143.11.203/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.56" "-"
2020/06/28 19:43:13 [error] 27#27: *154 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.240.255.55, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "52.143.11.203", referrer: "http://52.143.11.203/"
PS C:\Users\jorge>

Por último, para borrar el contenedor basta con que ejecutemos el comando:

docker rm id-del-contenedor

Utilizando Docker Compose sobre aci

Hasta ahora hemos planteado el uso de docker para levantar contenedores individuales, pero no es la única posibilidad. Gracias a Docker Compose podemos orquestar diferentes configuraciones de contenedores de manera que podamos por ejemplo levantar una web y una base de datos. Como no podía ser de otra manera, esta es una característica soportada por esta versión preview (aunque con muchas limitaciones).

Para poder usar esta funcionalidad, basta con que creemos un fichero docker-compose.yml con un contenido como este:

version: '3.4'

services:
   db:
     image: mysql:5.7
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "80:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
       WORDPRESS_DB_NAME: wordpress

Una vez hecho el fichero, basta con ejecutar el comando:

docker compose -f .\docker-compose.yml up

OJO. El comando es docker compose y no docker-compose. Esto es porque se utiliza la docker cli en vez de docker-compose cli.

Tras la ejecución del comando, podremos obtener una salida como esta:

PS C:\Users\jorge\OneDrive\Escritorio\aci> docker compose -f .\docker-compose.yml up  
[+] Running 4/4
 - aci                Created                                  4.4s
 - db                 Done                                    66.7s
 - wordpress          Done                                    66.7s
 - aci--dns--sidecar  Done                                    66.7s

Esto va a levantar un aci con varios contenedores dentro para la base de datos, el propio wordpress y un contenedor adicional que sirve de dns entre ambos. Ahora mismo, podemos ir a la interfaz de Azure y ver que efectivamente se han creado:

La imagen muestra la interfaz de Azure donde se ve centro del aci que se han desplegado los contenedores db, wordpress y aci--dns--sidecar

Con esto deberíamos poder entrar en la IP que se ha asignado y usar nuestro wordpress, pero actualmente parece que o no está soportado el uso de variables o tiene algún bug por lo que nuestro escenario no es capaz de arrancar al no asignar las variables de entorno en el contenedor. Puedes seguir la información al respecto en esta incidencia.

Conclusión

Gracias a esta nueva vuelta de tuerca de la mano de Docker y Microsoft, se amplían mucho los posibles escenarios de desarrollo que podemos conseguir con Docker antes de llegar a desplegar en un Kubernetes. Es posible hacer despliegues de entornos de pruebas de manera muy sencilla y gestionarlo tal cual haríamos como si el entorno fuese local de nuestra máquina.

Hay que reconocer que la línea actual de desarrollo es muy interesante en cuanto a sus ambiciones, despliegue de aci desde Docker Desktop directamente, pudiendo utilizar cuentas de almacenamiento de Azure como volúmenes para los contenedores o pudiendo desplegar con docker-compose como sistema de orquestación.

Llegados a este punto, es también muy importante señalar que es un producto extremadamente reciente, que no está recomendado para su uso en producción y que su documentación actualmente brilla por su ausencia. Si bien es cierto que se detalla cómo crear un contenedor, no hay nada de información sobre cómo utilizar los volúmenes, o solución de errores.

Aun así y con todo, pienso que apunta muchas maneras para tener su hueco en el día a día del desarrollo con Docker una vez que madure un poco.

**La entrada Contenedores Docker en Azure (aci) desde Docker Desktop se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Variable not found

Cómo solucionar el error "Unable to connect to web server 'IIS Express'" en Visual Studio

junio 30, 2020 07:51

Unable to connect to web server 'IIS Express'

Va un post rapidito, pero que seguro que puede ahorrar quebraderos de cabeza a más de uno que se encuentre con este problema al iniciar desde Visual Studio una aplicación ASP.NET, ASP.NET Core MVC/Web API, Razor Pages, o incluso Blazor que utilicen por debajo IIS Express.

El problema ocurre justo al ejecutar la aplicación con F5 o Ctrl+F5 desde el entorno de desarrollo; en ese momento, la ejecución se detiene y aparece un cuadro de diálogo con el mensaje:

"Unable to connect to web server 'IIS Express'"

Creo que llevo años encontrándome de vez en cuando con este problema al arrancar las aplicaciones, y nunca entendí muy bien por qué pasaba. Buscaba por la red y solo encontraba soluciones relativas a eliminar el archivo de configuración applicationhost.config que Visual Studio guarda en la carpeta ".vs" de la solución, a reiniciar el IDE o incluso la máquina, abrirlo como Administrador, o cambiar el puerto en la configuración del proyecto, normalmente algunas unidades por arriba o por abajo que el puerto asignado inicialmente.

Esta última opción es la que más veces me ha funcionado, pero no siempre iba bien, por lo que al final tampoco la identifiqué como una clara receta para paliar el problema que nos ocupa.

Hace unos días me ha vuelto a ocurrir con un proyecto que uso muy a menudo, y, de un día para otro, ha dejado de funcionar y ha comenzado a lanzarme a la cara el maldito cuadro de diálogo. La diferencia es que por fin he podido encontrar una respuesta satisfactoria, al menos para alguno de los escenarios que pueden causar el error :)

El motivo de este error es que Windows ha reservado para su uso interno el puerto utilizado por la aplicación, por lo que no podemos utilizarlo con IIS Express. La solución es sencilla: debemos cambiarlo por otro que esté libre.

Y claro, la cuestión es saber qué puertos están libres en el equipo.

Para ello, podemos ejecutar la siguiente instrucción de la consola, que nos mostrará justamente lo contrario, es decir, los puertos reservados:

C:\>netsh interface ipv4 show excludedportrange protocol=tcp

Protocolo tcp Intervalos de exclusión de puertos

Puerto de inicio Puerto final
---------- --------
2869 2869
4800 4800
5357 5357
8145 8145
9009 9009
50000 50059 *
54950 55049
55050 55149
55454 55553
55554 55653
61566 61665
61666 61765
61766 61865
61866 61965
63053 63152
63153 63252
63353 63452
63453 63552
63858 63957
63958 64057
64058 64157
64158 64257

* - Exclusiones de puertos administrados.

C:\>_

Lo que vemos en el listado son rangos de puertos. Por ejemplo, podemos ver que están ocupados desde el 50000 hasta el 50059, o desde el 64158 hasta el 64257. Si nuestro proyecto usa uno de ellos, se generará el maldito error (que, dicho sea de paso, ya podría dar algo más de información sobre lo que ocurre...)

Sabiendo esto, ya es fácil localizar un puerto libre. En mi caso podemos ver que el 51001 está libre, así que solo hay que modificarlo en las propiedades del proyecto:

Cambiar el puerto del proyecto

¡Espero que os sea de ayuda!

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 410

junio 29, 2020 06:05

Enlaces interesantes

Como ya adelantamos en la entrega 404 de enlaces interesantes, los códigos de estado 404 y 410 son muy similares, pues ambos permiten al servidor expresar que el recurso solicitado no existe.

La diferencia entre ambos es que HTTP 410 (Gone) que el recurso existió pero ya no está disponible, mientras que HTTP 404 no permite distinguir entre un recurso que jamás ha existido y uno que simplemente ha desaparecido.

Y ahora vamos con una nueva recopilación de enlaces que, como siempre, espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin / MAUI

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Blog Bitix

    Hemeroteca #17

    junio 28, 2020 09:00

    Hugo

    Que leáis este blog para mi es mucho aparte de para compartir conocimiento que voy aprendiendo me permite canalizar la frustración de no poder usar en el trabajo mucho de lo que escribo, demasiados años ya. Los pocos comentarios, retweets y favoritos en twitter que recibo me hacen no perder la esperanza y en cierta medida me anima a seguir escribiendo. Aunque se que mi motivación no debería depender de esto si encima no recibiese vistas sería algo que me desmotivaría.

    En meses anteriores he mejorado el diseño del blog, aún tengo mejoras pendientes que realizar pero no el suficiente tiempo para realizarlas. En los siguientes meses quiero aprender al menos a un nivel básico más sobre temas de SEO para que el tiempo que dedico al blog tenga el máximo rendimiento. Empezaré al publicar los artículos haciendo un pequeño análisis de palabras clave y primeras posiciones de resultados para el título que ponga en el artículo en el buscador Google y en la herramienta Google Search Console. Para los nuevos artículos que escribo pero también para los más visitados que he escrito, introduciendo en ellos algunos pequeños cambios y actualizaciones. Otro aspecto de SEO que tendré en cuenta es la intención de búsqueda, es decir, lo que un usuario espera encontrar cuando hace una búsqueda, normalmente la respuesta que se espera es a la pregunta ¿qué es? o ¿cómo? con lo que incluir un párrafo la respuesta a estas preguntas posiblemente los artículos posicionan mejor en Google.

    En los cambios de diseño que he realizado están unas pequeñas modificaciones en la página de Archivo y hemeroteca donde recojo todos los artículos publicados, he añadido una pequeña descripción de cada sección y un icono junto al título del artículo que indique la temática del mismo. Esto permite identificar los artículos por tema de una forma visual y sin necesidad de leer texto si alguien busca artículos de un cierto tema.

    Supongo que habrá sido por la pandemia del COVID-19 y las medidas de confinamiento durante estos meses y ahora con el verano y las vacaciones continuará algún tiempo más, en este tiempo he notado una pequeña bajada en las visitas y una notable bajada en los ingresos de AdSense pasando de unos 30€ mensuales a 15€. Aunque no estoy completamente seguro de haya sido por los nuevos requerimientos para publicidad digital con los nuevos archivos ads.txt, en principio cumplo y en Google AdSense no tengo ninguna advertencia.

    En este semestre he seleccionado mejor la temática de los artículos a escribir y publicar siguiendo dos principios: que sean interesantes para mi o que tengan potencial de atraer visitas. Y de algunos estoy muy contento de haberlos escrito como los relativos a Testcontainers para pruebas de integración, SDKMAN para instalar múltiples versiones de Java, Storybook para sistemas de diseño con TypeScript y React, el artículo sobre el futuro de la concurrencia con Project Loom, el recolector de basura de Java, el patrón open session in view con sus ventajas y sus alternativa, programación con AspectJ e incluiría varios artículos más en este párrafo. En otras épocas los artículos que seleccionaba para escribir en algunas ocasiones lo hacía a modo de relleno sin prácticamente planificación planificaba más lo que podía escribir sin necesidad de investigar mucho. También contento con algunos de los artículos sobre GNU/Linux como Fedora Silverblue.

    También he seguido haciendo pequeños cambios en el script para instalar Arch Linux con múltiples correcciones de errores y soporte de nuevas características. El repositorio de GitHub de Arch Linux Install Script ya tiene unas 230 estrellas y más de 100 forks de gente de numerosos países Australia, EEUU, Alemania, Colombia, Suecia y de muchos otros de todo el mundo. Hay un usuario que me envía que falla en el script cuando hago cambios lo cual se lo agradezco ya que algunos a mi me pasan desapercibidos y no tengo tiempo para probar cada cambio. También he recibido algunos pull requests que me han permitido corregir algunos otros errores y añadir algunas nuevas características.

    En este semestre han sido 43 nuevos artículos de las temáticas habituales sobre Java, GNU/Linux e incluyendo como novedad el análisis de algún juego después de haber completado la historia principal. Estoy publicando casi dos artículos a la semana con la intención a ver si consigo hacer subir la curva de visitas.

    Artículos sobre Java.

    Artículos bore GNU/Linux.

    Artículos sobre desarrollo web.

    Artículos sobre juegos con la PlayStation.

    Algunos nuevos desempaquetados.

    Y una contribución al proyecto Apache Tapestry con la que actualicé el diseño de la página del proyecto a una versión más moderna y soportando múltiples dispositivos con un diseño adaptable.

    Y otros que no entran en las categorías anteriores.

    » Leer más, comentarios, etc...

    Blog Bitix

    Desempaquetado y análisis de altavoz inteligente Amazon Echo con asistente Alexa

    junio 26, 2020 06:30

    El altavoz es un dispositivo que también ha añadido funciones de dispositivo inteligente, al igual que ha ocurrido con los teléfonos móviles y televisores. Son controlados por voz y con la aplicación para smartphone capaces de leer noticias, el tiempo, el tráfico, música, radios o de servicios en streaming, el centro para controlar dispositivos del hogar y muchas otras funcionalidades agregables mediante skills. Amazon, Goolge y Apple tiene su versión de altavoz inteligente, pequeños con buena calidad de sonido y diferentes modelos entre los que elegir.

    Amazon

    Los altavoces que tengo los compré con el primer ordenador, el venerable AMD Athlon XP 1800+, pagado con el dinero de mi primer trabajo en el 2004, unos Creative Fourpointsurround FPS 1600 4.1. Unos altavoces con uno dedicado a los graves o subwoofer con a mi entender una muy buena calidad de sonido y potencia. Durante esta casi una década y media he estado muy contento con ellos, hasta ahora que he decidido buscar una alternativa por un problema que llevo arrastrando no desde hace días, semanas o meses sino ya algunos años.

    El problema de mis altavoces

    El problema es que aleatoriamente en ciertos periodos de tiempo cada pocos minutos el volumen sube y al cabo de un tipo vuelve a bajar, en algunas ocasiones aguanta un cuarto de hora o media hora sin variación de volumen. Para ver vídeos donde alguien está hablando y algunos sonidos no se nota pero escuchando música sí, la diferencia entre el tono alto y bajo tiene suficiente diferencia como para que sea molesto, con este problema es difícil ajustar el volumen y en algunas ocasiones requiere ajustarlo desde el mando de control y desde el volumen del entorno de escritorio del ordenador.

    El problema tiene que estar en la placa alojada en el altavoz de graves o en el potenciómetro del control de volumen que tiene. He abierto el subwoofer y la placa electrónica tiene numerosos condensadores de electrolito líquido pero ninguno presenta signos de estar claramente dañado, normalmente los condensadores de electrolito líquido en mal estado se abomban en la parte superior y en los más dañados el líquido sale que al secarse genera alrededor del condensador como una arenilla.

    Seguramente los altavoces tengan arreglo pero no sé lo suficiente ni tengo las herramientas como para arreglarlos, para cambiar el potenciómetro necesitaría comprar un soldador, estaño, desoldador y un potenciómetro sustituto para el actual. Comprar el material para intentar arreglarlos sin ninguna garantía de repararlos viene siendo algo similar a comprar unos altavoces nuevos, y al final es lo que he hecho buscar unos sustitutos.

    Me dará pena tener que sustituirlos pero para escuchar música de forma cómoda no encuentro otra opción.

    Los altavoces sustitutos candidatos

    Cuando compro algo electŕonico u algún otro producto que más o menos entiendo para saber las diferencias entre un producto y otro normalmente me informo lo mejor que puedo hasta decidirme por la opción que mejor considero evaluando el precio y características de cada opción. Los artículos de las series de desempaquetado de tecnología y desempaquetado de productos no tecnológicos tienen algunos ejemplos que incluyen un Intel NUC, una PlayStation 4 o un termo de agua sanitaria Fleck o el contrato con la compañía eléctrica Holaluz.

    En esta ocasión no he evaluado muchas opciones ni he analizado en detalle todas las opciones. Sé que quería unos altavoces con una calidad como los que tenía ya que unos de los principales uso que les doy es escuchar música, esto incluye casi por necesidad que tengan altavoz de graves. Otro requerimiento es que no fuesen muy grandes y no tuviesen muchos cables, los que tenía eran 4.1 pero siempre los he usado como 2.1 por imposibilidad en mi habitación de colocar los traseros como se debiera. Un número grande de altavoces requiere muchos cables que hay que ordenar.

    Con estas opciones he buscado las primeras opciones que ofrece Amazon al buscar por altavoces y similares búsquedas (altavoces subwoofer, altavoces 2.1). Unos de la propia marca Amazon Basics, algunas otras opciones similares a los Creative en diferentes rangos de precio.

    Los básicos.

    Los similares a los que tenía.

    En el momento de buscar estas opciones y un poco lo que me ha decidido a comprarlos es que los altavoces inteligentes de Amazon estaban de oferta, entre los varios que tiene está los altavoces inteligentes Echo con asistente Alexa, bastante pequeño y compacto, con pocos cables y con buena calidad de sonido al tener altavoz de graves, mucho mejor que la de los propios altavoces del monitor o de un portátil, al menos la suficiente para mi.

    Finalmente, me he decidido por el Amazon Echo de tercera generación, por precio está en la categoría de altavoces de gama media-alta de las que he encontrado pero para lo que lo quería y este tiene es la que elegido. Mi uso será principalmente como altavoz para el ordenador y la PlayStation, posiblemente no use muchas de sus opciones como altavoz inteligente pero otras es posible que sí como la posibilidad de escuchar música de Amazon Music o la radio sin necesidad de tener el ordenador encendido o como hilo musical al dormir. Lo que pierdo es el sonido estéreo pero recupero una buena cantidad de espacio en la mesa y quito un par de cables.

    Desempaquetado de Amazon Echo (3ª generación)

    El color que he elegido ha sido el negro o antracita, otros colores son azul o añil, gris claro y gris oscuro. Es bastante compacto, las dimensiones son de 14,5 cm de alto y 9,5 cm de diámetro, requiere alimentación eléctrica e incluye un pequeño cargador rectangular a juego con el color del altavoz también de color negro.

    La caja incluye un pequeño manual de puesta en funcionamiento y configuración.

    Desempaquetado Amazon Echo Desempaquetado Amazon Echo Desempaquetado Amazon Echo

    Desempaquetado Amazon Echo Desempaquetado Amazon Echo Desempaquetado Amazon Echo

    Desempaquetado Amazon Echo Desempaquetado Amazon Echo

    Desempaquetado Amazon Echo Desempaquetado Amazon Echo Desempaquetado Amazon Echo

    Desempaquetado Amazon Echo

    Primeros pasos, funcionamiento y privacidad

    La configuración consiste básicamente en hacer que el altavoz Echo se conecte a la red WiFi de nuestra casa. Para hacer la configuración es necesario utilizar un smartphone y descargar e instalar la aplicación de Alexa para el móvil ya sea Android o iOS de sus tiendas de aplicaciones, en el proceso también se requiere activar la conexión Bluetooth en el móvil y conectarse al dispositivo Echo.

    Cuando se compra en Amazon se ofrece la posibilidad de preconfigurarlo para que se conecte a la WiFi automáticamente si ya se tiene otro dispositivo. Para hacer la configuración manualmente en la aplicación de Alexa del móvil es añadir el nuevo dispositivo y seguir los pasos, poner el altavoz en modo configuración si es necesario pulsado el botón de acción durante 6 segundos, se encenderá el anillo de luz con color naranja. Hay que buscar la red WiFi por el nombre e introducir su contraseña, si la WiFi está oculta hay que introducir además su nombre.

    Configuración Amazon Echo

    Configuración Amazon Echo Configuración Amazon Echo Configuración Amazon Echo

    Configuración Amazon Echo Configuración Amazon Echo Configuración Amazon Echo

    Configuración Amazon Echo Configuración Amazon Echo Configuración Amazon Echo

    Configuración Amazon Echo Configuración Amazon Echo Configuración Amazon Echo

    Configuración Amazon Echo

    La aplicación permite configurar el volumen, el ecualizador según las preferencias y el modo de entrada o salida de la clavija jack 3.5mm. En mi caso tengo el ordenador conectado por HDMI al monitor, por el mismo HDMI va la señal de imagen para el monitor y de sonido para los altavoces, desde el monitor con la salida de jack 3.5mm con un cable macho-macho conecto el monitor a los altavoces.

    Aplicación Alexa Aplicación Alexa Aplicación Alexa

    Aplicación Alexa

    Aparte de la aplicación móvil Alexa tiene una aplicación accesible con el navegador desde el ordenador, en ella se pueden realizar las acciones de configuración, añadir nuevos dispositivos y ver las listas, recordatorios, alarmas y activar o desactivar skills.

    El altavoz queda vinculado a la cuenta de Amazon y en esta se guarda todo el historial de comandos de voz, como esto es un inconveniente para los usuarios que protegen su privacidad se puede configurar para eliminar el historial de interacciones y hacerlo automáticamente para eliminar las interacciones más antiguas de 3 meses. También se puede configurar para que Amazon no utilice nuestra voz para entrenar sus algoritmos de inteligencia artificial de reconocimiento de voz. Otra opción recomendable a desactivar es la opción de realizar compras por control de voz, esto es un problema si viendo un vídeo que incluye el comando necesario para realizar la compra y se activa Alexa sin ser nosotros conscientes.

    El asistente Alexa se basa en la nube por eso requiere conexión permanente a internet, se activa por una palabra de activación que puede ser Alexa, Echo o Amazon. El altavoz está permanentemente escuchando esta palabra, si la detecta a continuación escucha el comando de voz, cuando el comando termina lo transmite a la nube para su reconocimiento y recibe la respuesta. Mientras no se detecte la palabra de activación no hay comunicación con internet ni la nube. El altavoz posee un botón para desactivar completamente la escucha, incluida la palabra de activación, este estado se identifica por el anillo luminoso rojo. Esto dota al dispositivo de privacidad salvo que contenga funciones maliciosas ocultas.

    El volumen de sonido al máximo en GNOME es muy bajo, tengo que poner el Echo al nivel 6 o 7 para tener un volumen satisfactorio, con este volumen si Alexa habla supongo es posible que se entere el vecino por eso me gustaría que Alexa tuviese el modo susurro activado siempre. En GNOME la aplicación Retoques tiene un opción para activar la sobreamplificación lo que permite subir el volumen por encima del máximo, la misma opción indica de que esto puede suponer una pérdida de calidad, no la he notado. El sobrevolumen se usa desde el mismo panel de control de volumen.

    Aplicación Retoques de GNOME para activar la sobreamplicación

    Aplicación Retoques y configuración de sonido en GNOME

    Escuchando música aún perdiendo el estéreo no noto tanta diferencia como pensaba, jugando en la PlayStation si noto diferencia en el estéreo.

    Las rutinas son una serie de comandos que son ejecutados por Alexa, se pueden activar a una cierta hora o con un comando de voz personalizado. También pueden activarse desde la aplicación del smartphone. Son muy útiles, por ejemplo para poner una radio y a un determinado volumen o bajar el volumen a una cierta hora los días entre semana.

    Programación de rutinas Programación de rutinas Programación de rutinas

    Programación de rutinas

    El anillo luminoso puede tomar varios colores con diferentes significados de notificaciones, error o actividad: Amarillo, Rojo, Azul, Naranja, Morado, Verde, Blanco.

    En las páginas de ayuda hay más información de los dispositivos Alexa:

    Primeras impresiones

    Se configura fácil, es bastante pequeño y requiere menos cables que otros altavoces. Para su tamaño pesa bastante aunque esto no es muy relevante, su peso y tamaño tampoco es muy grande pudiendo colocarse en cualquier lugar. En su tamaño es posiblemente el altavoz con mejor calidad de sonido y diseño. La calidad del sonido en graves es buena, yo no percibo una diferencia significativa con los altavoces 2.1 que tenía con subwoofer y estéreo, y estos eran ya bastante buenos.

    Aún sin crear un perfil de voz Alexa reconoce perfectamente la voz no me ha dado ningún problema salvo en alguna rara ocasión no reconocer la palabra de activación. Tanto en voz normal como hablando en modo susurro incluso cuando el altavoz está reproduciendo música e invocando a Alexa a unos metros de distancia en el lado más alejado de la habitación la escucha. Si está reproduciendo música y se invoca baja el volumen del altavoz para reconocer mejor el comando.

    La potencia de volumen del Echo no llega a la que podrían alcanzar los FPS1600 que tenía pero en su escala de niveles de volumen de 0 al 10 el 4-5 es el normal y el 8 ya lo considero elevado. El Echo tiene clavija jack 3.5mm que puede funionar como entrada o salida, de salida para enviar sonido a otros altavoces externos mejores o como entrada que es lo que utilizaré para escuchar el sonido del ordenador. También permite recibir sonido por conexión Bluetooth desde el móvil o desde el ordenador o tablet.

    Si se pretenden utilizar los botones físicos es recomendable ubicar el altavoz al alcance de la mano aunque el único realmente imprescindible es el de desactivar los micrófonos para que Alexa deje de escuchar la palabra de activación y el comando de voz. El volumen se puede cambiar con comandos de voz.

    Se le puede pedir música de diferentes géneros clásica, electrónica, techno y reproduce canciones de Amazon Music gratuitamente aunque sin la posibilidad de elegir un artista o cancion determinada para lo cual se necesita un servicio de suscripción. Es muy cómodo estar en la cama despertarte a la 7 y sin levantarte decirle que te ponga una alarma dentro de dos horas o pedirle que te ponga música o la radio al volumen deseado. Para personas con movilidad o visión reducida es un gran dispositivo de ayuda ya que al estar basado principalmente en la voz es accesible para estas personas aunque para algunas tareas se requiere utilizar la aplicación.

    Amazon Echo escuchando un comando de voz Cable jack 3.5mm macho-macho

    Amazon Echo escuchando un comando de voz y cable jack 3.5mm

    Funcionalidades y skills

    Para darle el mayor uso al Amazon Echo la dificultad está en conocer qué comandos de voz se pueden utilizar. Aún desconozco en gran medida las posibilidades de los altavoces inteligentes y del asistente virtual Alexa controlador por voz. Requiere conexión a internet mediante WiFi soportando redes redes 802.11 a/b/g/n/ac (WiFi 5) pero no ax (WiFi 6).

    El altavoz inteligente soporta una serie de funciones invocadas por control de voz, algunos comandos son para controlar el volumen del altavoz, para saber la hora, de información general a modo de enciclopedia, traducciones, el tiempo del día, de mañana o del fin de semana, noticias, crear listas de tareas, listas de compra, crear recordatorios, alarmas o temporizadores o escuchar música. Estos son algunos de los comandos por defecto más útiles. También es capaz de reproducir música en streaming de servicios como Amazon Music, Apple o Spotify así como leer noticias de actualidad y reproducir emisoras de radio.

    Algunos ejemplos de comandos de voz son los siguientes sin ser esta una lista completa. Hay más comandos de voz.

    Volumen, hora y meteorología.

    • “Alexa, sube el volumen”
    • “Alexa, pon el volumen al 5”
    • “Alexa, ¿qué hora es?”
    • “Alexa, ¿va a llover mañana?”
    • “Alexa, ¿qué tiempo va a hacer en Pontevedra este fin de semana?”
    • “Alexa, ¿qué tiempo hace en París?”

    Información general.

    • “Alexa, ¿cuánto mide El Teide?”
    • “Alexa, ¿cuánto es 100 entre 16?”
    • “Alexa, ¿quién es el Primer Ministro de Italia?”
    • “Alexa, ¿cómo se dice «Te quiero» en francés?”
    • “Alexa, ¿cómo se dice «Buenos días» en japonés?”
    • “Alexa, ¿cCómo se dice «Muchas gracias» en italiano?”

    Noticias.

    • “Alexa, ¿cuáles son las noticias?”

    Listas de tareas y compra

    • “Alexa, añade «sacar a pasear al perro» a mi lista de tareas”
    • “Alexa, añade «leche» a mi lista de la compra”

    Recordatorios, temporizadores y alarmas.

    • “Alexa, pon un recordatorio”
    • “Alexa, te ayuda a que no se te olvide nada”
    • “Alexa, pon un temporizador de 10 minutos para la pizza”
    • “Alexa, pon una alarma para las 8 de la mañana”
    • “Alexa, despiértame por la mañana”
    • “Alexa, pon una alarma recurrente todos los días de entre semana a las siete de la mañana”
    • “Alexa, ¿qué alarmas tengo?”
    • “Alexa, pospón…” (y disfruta de unos minutos más de sueño)

    Escuchar música por género o música utilizando el servicio gratuito Amazon Music o con suscripciones de servicios como Amazon Music Unlimited o Spotify.

    • “Alexa, pon música”
    • “Alexa, siguiente”
    • “Alexa, ¿qué canción es esta?”
    • “Alexa, para la música”
    • “Alexa, pon música de los 80”
    • “Alexa, pon música de los 60”
    • “Alexa, pon música rock”
    • “Alexa, pon música country”

    Para usar skills.

    • “Alexa, abre el diario punto es”
    • “Alexa, abre el diario punto es” y dame las noticias de política

    Las skills son nuevas funcionalidades, habilidades y comandos que Alexa es capaz de reconocer. Activar skills es muy sencillo, están organizadas en categorías: novedades, alimentos y bebidas, meteorología, coche conectado, compras, curiosidades y humor, deportes, educación y referencia, estilo de vida, hogar digital, infantil, juegos y curiosidades, local, música y audio, negocios y finanzas, noticias, películas y TV, productividad, salud y bienestar, servicios, local, viaje y transporte. Desactivar una skill es igual de sencillo, en un listado se muestran las activadas. Las skills no se instalan en el Echo simplemente se activan y desactivan, hay gran cantidad de ellas aunque las realmente útiles serán muchas menos.

    Skills Skills Skills

    Skills

    Además de los dispositivos inteligentes como enchufes y bombillas el Echo es capaz de controlar cualquier aparato que tenga un mando por infrarrojos o radiofrecuencia incluyendo televisores, reproductores de vídeo o aires acondicionados. Con un emisor compatible con Alexa es capaz de emitir las señal infrarrojo y radiofrecuencia que el mando del aparato electrónico original y controlarlo. Otros dispositivos controlables son enchufes para encender y apagar aparatos o bombillas regulables en color e intensidad para crear ambientes cómodas para bajar la luminosidad al ver una película.

    Problemas, algunos que he resuelto y cosas que me gustaría que tuviese

    Y ahora unos problemas y cosas que me gustaría que tuviese después de los primeros momentos de uso. Algunos son por problemas con mi ordenador otros son mejoras que podrían tener los altavoces Echo.

    La configuración que quería como altavoz para el ordenador funciona correctamente, el conector jack 3.5mm se puede configurar como entrada para el ordenador o como salida para unos altavoces externos. Pero si se le indica algún contenido que reproduzca sonido desde la nube la entrada de línea se desactiva, una vez se para el contenido de la nube hay que configurar el altavoz de nuevo, se puede hacer desde la aplicación de smartphone cambiando la salida de audio y de nuevo a la entrada de audio o con el siguiente comando de voz pero no desde la aplicación web. El sonido de la entrada de línea no se puede parar, Alexa lo que hace es silenciar el volumen por eso para recuperarlo hay que cambiarlo, una forma con su comando de voz. Esto lo pongo en la sección de problemas porque hasta después de unos días de uso pensaba que la única posibilidad era utilizar la app de móvil y no se podía hacer con un comando de voz, esto es posiblemente el mayor inconveniente que tenía el resto son mejoras que me gustaría que tuviera.

    • “Alexa, reproduce contenido desde la entrada de línea”
    • “Alexa, sube el volumen de la entrada de línea”

    En mi caso con Arch Linux en el ordenador es capaz de conectarse al Echo por Bluetooth, lo reconoce como dispositivo de audio pero luego en la configuración del sonido a veces no se puede seleccionar como dispositivo de audio de salida el altavoz Bluetooth, en vez de esto aparece como dispositivo de entrada. Esto quizá sea un problema del entorno de escritorio GNOME, a la implementación de Bluetooth en GNU/Linux o a Amazon Echo Plus as bluetooth speaker on Ubuntu 20.04. El mismo problema le pasa a algunos usuarios de Ubuntu. Después de varias pruebas he conseguido que me funcione, los pasos que he seguido están basados en el enlace anterior que han sido poner el Echo en modo emparejamiento, emparejarlos e iniciar la conexión por Bluetooth desde Linux mientras se reproduce sonido. La opción para realizar la conexión desde el panel de control de Bluetooth parece que lo intenta e inmediatamente la opción se desactiva de nuevo pero después de intertarlo varias veces seguidas al cabo unos segundos se conecta y comienza la reproducción.

    El sonido del ordenador por Bluetooth soluciona un problema que tenía con el sonido por conexión HDMI. Por HDMI y sacando el sonido por jack 3.5mm desde el monitor ocurre que cuando se activa el protector de pantalla o la pantalla de bloqueo la señal del HDMI se apaga y afecta al sonido que también deja de emitirse, por Bluetooth la reproducción continua perfectamente, aunque el Bluetooth de la PlayStation 4 no es compatible con Amazon Echo. Usando mi móvil lo he conectado por Bluetooth para reproducir música perfectamente, si pongo el reproductor las canciones se escuchan sin problema y parando vuelve a reproducir la entrada de línea si se usa esta forma de conexión.

    Los dispositivos Bluetooth al gual que la WiFi tiene la opción de hacerse visibles emitiendo su nombre esto está visible para cualquier persona que se encuentre en el rango de alcance con la posibilidad de intentar el emparejamiento y cuanto menos conocer la presencia del dispositivo que suele incluir su modelo, es un problema de privacidad. El Echo solo se hace visible en el momento de emparejamiento que se inicia desde la app pero Linux por defecto se queda siempre en modo visible y posibilitando el emparejamiento, para desactivar esta visibilidad al cabo de un tiempo hay que modificar el archivo de configuración /etc/bluetooth/main.conf con los siguientes valores.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    ...
    
    # How long to stay in discoverable mode before going back to non-discoverable
    # The value is in seconds. Default is 180, i.e. 3 minutes.
    # 0 = disable timer, i.e. stay discoverable forever
    DiscoverableTimeout = 180
    
    # How long to stay in pairable mode before going back to non-discoverable
    # The value is in seconds. Default is 0.
    # 0 = disable timer, i.e. stay pairable forever
    PairableTimeout = 180
    
    ...
    main.conf

    No se puede configurar el volumen de sonido de forma independiente para cada categoría de sonido, por ejemplo me gustaría tener una configuración de volumen para la voz de Alexa, por skill, notificaciones y alarmas y para la entrada de línea. El volumen de Alexa y sus funciones me parece demasiada alta comparada con el volumen de la entrada de línea. También me gustaría poder configurar Alexa para que hablase y respondiese siempre en modo susurro y no solo cuando uno le da un comando de voz susurrando.

    Utilizar un comando de voz para subir y bajar el volumen en algunas ocasiones es cómodo y en esas ocasiones si se está a cierta distancia, tiene botones físicos pero no son tan cómodos como un potenciómetro. También me gustaría que tuviese una forma de apagarlo completamente con un botón físico, con un comando de voz que no esté permanentemente encendido. También un botón para silenciar el volumen para quitar el audio temporalmente y recuperar el volumen original más tarde sin parar la reproducción. O un botón para parar el audio o la reproducción.

    Que todo lo que se pueda hacer con la voz o con los botones físicos se pueda hacer desde una aplicación aunque sea escribiendo el texto. Ahora hay algunas funciones que hay que realizarlas con comandos de voz y otras con la aplicación de Alexa. Las rutinas al activar los skills solo permiten abrirlas, no indicarle el comando exacto de apertura, algunas skills ofrecen varias opciones.

    Estaría bien que el audio del altavoz fuera la suma de todas las fuentes de sonido ya que si le pongo a reproducir una radio se deja de el escuchar audio de la entrada de línea en este caso el ordenador.

    Configurar que el sonido no pueda ser más de un nivel a ciertas horas, estar de noche y que se active el volumen al máximo porque ha entendido mal un comando al cambiar el volumen, estar a las doce de la noche y que el altavoz se ponga al máximo volumen por una mala interpretación del comando de voz hace que el vecino se entere de ruido e inmediatamente haya que buscar el botón físico para bajar el volumen. Las rutinas en cierta medida permiten programar que a una hora o con un comando hagan una serie de acciones, entre ellas está el cambiar el volumen.

    Rutinas Rutinas Rutinas

    Rutinas

    El Echo necesita de conexión a internet, en mi caso lo tengo enchufado a la misma regleta de conexión a la corriente que el router. Cuando enciendo la regleta con el botón el router tarda más en conectarse en alguna ocasión y proporcionar conexión a internet al resto de dispositivos el Echo incluido. Cuando el Echo se enciende y no tienen internet no sirve de nada y aún pasado varios minutos después de que el router ya proporcione internet el Echo no ha sido capaz de reconectarse a internet por si mismo, le he tenido que desenchufar y enchufar después de que el router haya terminado de inicializarse, en este momento sí el Echo ha tardado menos de 30 segundos en inicializarse.

    Dado que son unos altavoces dependiente de servicios de Amazon me surgen las siguientes preguntas, ¿Amazon dará servicio a los Echo durante al menos dos décadas? ¿ningún Echo se quedará sin soporte ni perderá sus funcionalidades pasados varios años?

    Por otro lado, si los altavoces inteligentes tienen éxito como producto con el tiempo saldrá nuevas versiones con más funcionalidades que dejarán desfasados a las versiones anteriores, espero que algunos de los problemas comentados se resuelvan también para las versiones actuales.

    Familia de altavoces inteligentes Amazon Echo y alternativas

    La familia de altavoces Echo con el asistente Alexa tiene varios miembros.

    • Echo Flex: de conexión directa al enchufe para ser colocado en cualquier estancia como baño o cocina.
    • Echo Dot: pequeño, con display formado varios 7 segmentos que permite mostrar la hora adecuado como reloj de mesilla.
    • Echo: ofrece mejor sonido que el Dot, incluye un subwofer para sonidos graves.
    • Echo Plus: como el Echo pero incorpora el controlador de dispositivos Zigbee que necesitan algunos dispositivos del hogar digital como las bombillas Philips.
    • Echo Studio: el dispositivo con el mejor sonido que ofrecen los Echo pero también ocupa más y es más caro, puede sustituir a las barras de sonido para una televisión.

    Los Echo Show incluyen una pantalla con la posibilidad de realizar videoconferencias.

    Tanto Apple como Google ofrecen sus versiones de altavoces inteligentes, Apple el HomePod y Google los Home y Nest Mini.

    Conclusión

    El Amazon Echo es pequeño, tiene buena calidad de sonido, solo necesita dos cables y tiene funciones adicionales a unos altavoces tradiciones, en algunas cosas son mejores y en otras podrían mejorar. En oferta tiene un precio similar al de unos altavoces de calidad 2.1. Se gana en comodidad, en espacio pero no se tiene sonido estéreo, salvo que se compre una pareja de ellos, y la conexión auxiliar de entrada de línea no es la predeterminada.

    Para mi caso que lo pretendo utilizar principalmente como sustituto de unos altavoces para el ordenador y PlayStation sirven pero no están diseñados específicamente para esta función pero la cumplen con suficiencia. El hecho de requerir permanentemente conexión a internet por el reconocimiento de voz en la nube hace que no sirva de nada sin ella. El mayor problema que me he encontrado ha sido conocer el comando de voz para reproducir el audio de la entrada de línea sin utilizar la app pero lo tiene, además si se está escuchando música o la radio de internet y se empieza a reproducir contenido por la entrada de línea se empieza a reproducir el contenido de la entrada de línea de forma automática.

    En algunos aspectos me gusta, se puede usar sin necesidad de ordenador, hay multitud de skills de noticias, radios o el tiempo. El servicios de Amazon Music proporciona música de forma gratuita de diferentes géneros.

    Y en otros no me convencen completamente, sobre todo los botones físicos adicionales que comentaba que echo en falta. La aplicación tiene margen para añadir más funciones que a los usuarios les serían muy útiles y entonces sí serían una opción recomendable para más usuarios.

    El Echo y Echo Studio son una buena consideración como altavoz para ver películas con una mejor calidad de sonido que la que ofrecen los altavoces integrados de las televisiones, la calidad de sonido y precio es similar que una barra de sonido pero tienen funciones adicionales.

    Cuando compré el Echo tenía dudas de si sería buena opción como sustituto de mis antiguos altavoces pero pasados unos días de uso por ahora estoy bastante contento con él. Si tienes cualquier duda de los Echo añade un comentario y trataré de darle respuesta.

    Vídeos

    En YouTube hay numerosos vídeos con desempaquetados, analizando los altavoces y la familia de altavoces Echo y explicando como funcionan las skills que se pueden activar.

    » Leer más, comentarios, etc...

    Bitácora de Javier Gutiérrez Chamorro (Guti)

    Feed en tiempo real del Instagram de @Guardatiempo

    junio 26, 2020 09:19



    Cuando en publiqué Fotos de relojes en el Instagram de Guardatiempo en el que hacía referencia a la cuenta de Instagram @guardatiempo, dejé pendiente un feed en tiempo real de las últimas publicaciones. Una forma en que no hiciera falta acudir a la red social para visualizar las últimas fotografías… ¡Y finalmente, aquí lo tenéis!

    Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

    » Leer más, comentarios, etc...

    Picando Código

    Ex-Zodiac – Shooter 3D con gráficos poligonales inspirado en los clásicos de los 90’s ahora en Kickstarter

    junio 24, 2020 11:00

    Hace un tiempo escribía en el blog sobre Ex-Zodiac, un juego inspirado en Star Fox original escrito en Godot. Desde entonces he ido siguiendo su desarrollo y estoy muy contento de ver que hay un plan para completar su desarrollo y financiarlo a través de Kickstarter.

    Ex-Zodiac

    Ex-Zodiac es un juego shooter con gráficos estilizados que evocan juegos 3D de principios de los 90’s. La protagonista Kyuu lucha para liberar los mundos del Sistema Estelar Sanzaru, invadido por la organización terrorista intergaláctica conocida como Zodiac. Ya hay un demo disponible que podemos descargar en Steam, y en lo que va de desarrollo se puede ver el potencial y lo divertido que viene siendo el juego.

    Características generales:

    -- Estilo visual retro, colorido y con pocos polígonos emulando los juegos de la época.
    -- 12 niveles principales (más áreas secretas y caminos alternativos).
    -- Varias rutas para completar el juego.
    -- Jefes gigantes al final de cada nivel, cada uno pilotado por un miembro de Zodiac.
    -- Banda sonora estilo 16-bit por +TEK combinando FM y síntesis wavetable.

    Su desarrollador es Ben Hickling quien viene trabajando en él hace más de 2 años en su tiempo libre. El objetivo del Kickstarter es que pueda dedicarle más tiempo al desarrollo y terminarlo. Va a estar disponible inicialmente en Linux, Mac y Windows por medio de Steam, Humble e Itch.io. Dentro de las recompensas del Kickstarter además de obtener el juego, podemos obtener el soundtrack por medio de Steam o Bandcamp, y un montón de extras más.

    Pixeljam va a estar ayudando con el marketing, community management y asegurarse que la campaña en Kickstarter cumpla con sus promesas. La empresa ha creado, consultado en y publicado casi 30 juegos desde 2005, siendo Dino Run y Nova Drift sus títulos más conocidos.

    La campaña recién empezó y ya alcanzó la meta inicial de USD 25.190. Inicialmente no hay “stretch goals” planeados, con la idea de que todo el dinero recaudado por encima de la meta inicial va a ser volcado en “más juego”. Tampoco hay muchas recompensas extra, todo el contenido es digital, y el desarrollador se puede concentrar simplemente en desarrollar y terminar el juego, y no preocuparse por producir y entregar recompensas físicas. Tras haber sido quemado más de una vez por Kickstarters que prometen demasiado y terminan sin entregar nada o un producto mediocre, me parece excelente mantener expectativas y metas medidas para poder cumplirlas.

    Una buena noticia es que está en la mira la posibilidad de portar el juego a Nintendo Switch. Como se encuentra en una etapa muy temprana de desarrollo, no prometen nada. Pero una vez publicado el juego, van a tener una mejor idea de lo que se necesita para publicarlo en Switch. Cuentan con la experiencia y recursos de Pixeljam para esto.

    Visita el Kickstarter

    YouTube Video

    » Leer más, comentarios, etc...

    Variable not found

    Cómo invocar métodos estáticos C# desde Javascript con Blazor (interop 2/3)

    junio 23, 2020 06:18

    Blazor
    Hace poco veíamos mecanismos de interop que permitían invocar código Javascript desde Blazor, con objeto de poder seguir utilizando código existente en bibliotecas o componentes, ya sean nuestros o de terceros.

    Pero Blazor también aporta mecanismos para la interoperación en sentido contrario, es decir, permitir que desde código Javascript podamos invocar métodos escritos en C#, ya sea con Blazor Server o Blazor WebAssembly.

    De hecho, Blazor permite invocar métodos tanto estáticos como de instancia, aunque en este post nos centraremos sólo en los primeros porque son más sencillos de entender. En un artículo posterior profundizaremos en el segundo escenario.

    Invocando métodos estáticos escritos en C# desde Javascript

    Para que un método estático escrito en C#, ya sea utilizando Blazor Server o Blazor WebAssembly, pueda ser accesible desde Javascript, éste debe estar decorado con el atributo [JSInvokable]. El método puede encontrarse en el interior de una página, en una clase personalizada o donde queramos; siempre que esté decorado de esa forma, Blazor podrá localizarlo.

    El siguiente método podría valer como ejemplo:
    public class Calculator
    {
    [JSInvokable]
    public static int Sum(int a, int b)
    {
    return a + b;
    }
    }
    Para ejecutar este método desde Javascript, podríamos hacerlo de forma muy sencilla utilizando el método invokeMethodAsync() del objeto DotNet, un proxy proporcionado en el lado cliente por el framework:
    var sum = await DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2);
    alert(sum);
    invokeMethodAsync() retorna un objeto Promise, por lo que puede esperarse su retorno utilizando await o bien mediante el clásico then():
    DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2).then(sum => alert(sum));
    En cualquier caso, el primer parámetro de invokeMethodAsync() indica el nombre del ensamblado donde se encuentra el método a invocar, que normalmente coincidirá con el nombre del proyecto.
    Seguidamente hay que especificar el nombre del método, y luego los parámetros a enviarle.

    Observad que esto podría crear problemas de ambigüedad si tenemos varios métodos estáticos invocables con el mismo nombre dentro del mismo ensamblado. En este caso, la llamada anterior fallará en tiempo de ejecución si existe un método Sum() invocable en cualquier otra clase del ensamblado BlazorJsDemo, mostrando por consola un error como el siguiente:
    blazor.server.js:8 Uncaught (in promise) Error: System.InvalidOperationException: 
    The assembly 'BlazorJsDemo' contains more than one [JSInvokable] method with identifier 'Sum'.
    All [JSInvokable] methods within the same assembly must have different identifiers.
    You can pass a custom identifier as a parameter to the [JSInvokable] attribute.
    Para solucionarlo, podemos emplear el parámetro identifier del atributo [JSInvokable] y así indicar un identificador distinto en uno de los métodos:
    [JSInvokable("Add")]
    public static int Sum(int a, int b)
    {
    return a + b;
    }
    Los métodos pueden recibir y retornar cualquier tipo de datos, pues Blazor gestionará automáticamente la serialización y deserialización. Y también pueden ser asíncronos, como en el siguiente ejemplo:
    public static async Task<ProcessResult> DoSomethingComplex()
    {
    await Task.Delay(5000); // Simulate a hard job
    return new ProcessResult() { ... };
    }

    ¡Ojo al momento de ejecución!

    Un aspecto sumamente importante a la hora de hacer invocaciones de este tipo es que tenemos que asegurar que el framework Blazor haya sido inicializado previamente en la página, pues de lo contrario encontraremos errores como el siguiente en la consola del navegador:
    Blazor: No .NET call dispatcher has been set
    Esto normalmente no ocurrirá porque Blazor es inicializado en cuanto carga la página, pero si quisiéramos hacer una invocación muy temprana podríamos utilizar un código como el siguiente en Blazor WebAssembly:
    <!-- File: wwwroot/index.html -->
    ...
    <script src="_framework/blazor.webassembly.js" autostart="false"></script>
    <script>
    Blazor.start({})
    .then(async () => {
    // Blazor was initialized
    var sum = await DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2);
    alert(sum);
    });
    </script>
    Y el código equivalente en Blazor Server:
    ...
    @* File: Pages/_Host.cshtml *@
    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
    Blazor.start({})
    .then(async () => {
    // Blazor was initialized
    var sum = await DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2);
    alert(sum);
    });
    </script>
    En ambos casos, fijaos que lo que hacemos es cargar los scripts propios de Blazor pero estableciendo el atributo autostart a false para que el framework no arranque automáticamente.

    Justo después, llamamos a Blazor.start() para arrancarlo manualmente, pero utilizamos then() para tomar el control en el momento en que finaliza su inicialización.

    A partir de ese punto ya podremos considerar Blazor cargado, por lo que podremos invocar métodos estáticos sin problema.

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Variable not found

    Enlaces interesantes 409

    junio 22, 2020 06:05

    Enlaces interesantes

    Usado frecuentemente en las respuestas a peticiones de tipo PUT, el código de estado HTTP 409 es retornado por el servidor para indicar que existe un conflicto con el estado actual del recurso destino de la petición, esperando que el cliente solucione el problema y reintente la operación. Como ayuda, en el cuerpo de la respuesta suele incluirse información que facilitaría al cliente la resolución del conflicto.

    Un ejemplo podría ser cuando intentamos actualizar con PUT una entidad con un número de versión posterior al que estamos enviando, lo que indicaría que otra petición distinta modificó el recurso antes que nosotros.

    Y ya sin conflictos, van los enlaces recopilados durante la semana pasada que, como no podía ser de otra forma, espero que os resulten interesantes :-)

    Por si te lo perdiste...

    .NET Core / .NET

    ASP.NET Core / ASP.NET

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

      Web / HTML / CSS / Javascript

      Visual Studio / Complementos / Herramientas

      Xamarin

      Otros

      Publicado en Variable not found.

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      Casio necesita un buen software y el desastre de G-Shock Connected

      junio 21, 2020 04:14



      En el artículo ¿El futuro de los relojes digitales? ya referenciaba las deficiencias del software G-Shock Connected escrito por Casio Computer Co. Ltd. Un aspecto que ya había mencionado en las pruebas de los Casio G-Shock GMW-B5000D, GW-B5600 y sobre todo del GPR-B1000 «Rangeman». No importa si hablamos de plataforma iOS como Android, en ambas …

      Casio necesita un buen software y el desastre de G-Shock Connected Leer más »



      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Blog Bitix

      Las sentencias de control de flujo en Java (if, switch, for, while, do-while, try-catch, break, continue e invocación)

      junio 21, 2020 05:30

      El lenguaje de programación Java utiliza un paradigma orientado a objetos pero también emplea otros paradigmas como el funcional con la incorporación de las lambas en Java 8 y el imperativo en los bloques de sentencias de los métodos. En este artículo están los tipos de sentencias de control de flujo disponibles en el lenguaje de programación Java: condicionales, de repetición, de asignación, de gestión de excepciones e invocación de métodos en Java.

      Java

      Java es un lenguaje orientado a objetos donde aplicando los principios de encapsulación, herencia y polimorfismo, el código está dentro de los métodos de las clases de los objetos. El código dentro de los métodos sigue los principios de los lenguajes imperativos con una secuencia de sentencias de asignación, control de flujo, llamada a otros métodos y de repetición, más recientemente con la incorporación de las lambdas y referencias a métodos en Java 8 es posible emplear también un enfoque de programación funcional.

      La orientación a objetos es una forma de organizar el código y los datos que maneja ese código de modo que se respeten los principios de encapsulación, una forma de reutilizar el código con herencia y una forma de abstraer el comportamiento dependiendo de la clase concreta que implementa el método. Pero el código de Java no es únicamente código orientado a objetos también es un lenguaje imperativo para el código de los métodos.

      El código imperativo se basa en la ejecución de forma secuencial de un conjunto de sentencias. Las sentencias de un método o programa son de diferente tipo: de asignación, condicionales, de repetición, de gestión de excepciones para controlar errores y de llamadas a funciones, en el caso de los lenguajes orientados a objetos llamadas a métodos. Estas sentencias individuales se pueden anidar unas dentro de otras por ejemplo tener una sentencia de repetición dentro del bloque de sentencias de una condicional.

      Cada uno de estos tipos de sentencias forman las piezas básicas de construcción de los programas, combinadas en múltiples lineas de código forman programas complejos que sirven para el propósito para que el programa fue escrito.

      Sentencias de asignación

      Las sentencias de asignación sirven para asignar nuevos valores y referencias a objetos a variables y propiedades de objetos. La sintaxis de la asignación consta del nombre de variable que toma el valor a la izquierda, el operador de asignación en el medio y de la expresión a la derecha. El valor de una variable o propiedad cambia con una sentencia de asignación, el valor anterior se reemplaza por el nuevo valor. El nuevo valor de la variable es el resultado de evaluar la expresión que proporciona el valor, con una asignación de inicialización es posible asignar un valor al mismo tiempo que se declara una variable o propiedad de un objeto.

      Una variable tiene un valor si se trata de un tipo primitivo de datos, en caso de tener como tipo una clase contiene una referencia a una instancia de un objeto de ese tipo o la referencia nula. Una expresión devuelve como resultado un valor y este es el asignado a la variable por la sentencia de asignación.

      Una expresión puede contener múltiples operadores: para datos booleanos (de lógica &&, ||, ! y de comparación ==, !=, <, >, <=, >=), aritméticos para datos numéricos (+, -, *, /, %, ++, –) o operadores para datos binarios (&, |, ^, ~, «, », »>). Otros operadores de asignación (+=, -=, *=, /=, %=, «=, »=, &=, ^=, |=) toman como primer operando el valor de la variable, esto evita repetir el nombre de la variable en la expresión y facilita la legibilidad del código.

      1
      2
      3
      4
      5
      6
      7
      
      variable = expresión;
      
      int x = 1;
      List list = List.of("1", "2");
      
      x = 2;
      list = List.of("3", "4");
      Asignacion.java

      El operador ternario ?: es una expresión condicional que devuelve el valor de la expresión según el resultado de evaluar una expresión booleana.

      1
      
      variable = (condicionExpresionBoleana) ? expresionTrue : expresionFalse;
      CondicionalTernario.java

      Setencias condicionales (if, switch)

      Las sentencias condicionales son un tipo de sentencia que evalua una expresión booleana y dependiendo de su valor verdadero o falso ejecuta o no su su bloque de sentencias asociado para cada caso. Las sentencias a continuación de la condición se ejecutan si la sentencia if se evalúa como verdadero. La sentencia if además puede tener otro bloque de sentencias a ejecutar si la expresión booleana se evalúa como falso, el bloque de sentencias else. Las sentencias if y else se pueden encadenar.

      Diagrama sentencia if Diagrama sentencia if-else

      Diagramas sentencias if e if-else
      Fuente: beginnersbook.com
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      
      if (condicionExpresionBoleana)
         sentenciaTrue
      
      if (condicionExpresionBoleana) {
         bloqueSentenciasTrue
      }
      
      if (condicionExpresionBoleana) {
         bloqueSentenciasTrue
      } else {
         bloqueSentenciasFalse
      }
      
      if (condicionExpresionBoleana) {
         bloqueSentenciasTrue
      } else if (condicionExpresionBoleana) {
         bloqueSentenciasTrue
      } else {
          bloqueSentenciasFalse
      }
      
      int a = 3;
      int b = 2;
      if (a > b) {
          System.out.println("a es mayor que b");
      } else {
          System.out.println("a no es mayor que b");
      }
      CondicionalIf.java

      Cuando una sentencia if tiene muchas ramas y la expresión condicional comprueba en todos los casos diferentes valores de una misma variable se utiliza la sentencia switch. Si el valor de la variable coincide con el valor del bloque del caso se ejecutan las sentencias de ese bloque. Cada bloque ha de estar finalizado con sentencia break para no evaluar las sentencias del siguiente bloque. El caso default permite ejecutar un bloque de sentencias si el valor de la expresión no coincide con ninguno de los valores de los casos, siendo como la parte else de las sentencias if.

      Diagrama sentencia switch

      Diagrama sentencia switch
      Fuente: beginnersbook.com
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      switch (variable) {
         case 1: {
             bloqueSentencias
         } break;
         case 2: {
             bloqueSentencias
         case 3: {
             bloqueSentencias
         } break;
         default: {
             bloqueSentencias
         }
      }
      CondicionalSwitch.java

      La sentencia if equivalente del switch anterior sería el siguiente. si es posible se prefiere usar la sentencia switch sobre la if equivalente, más si hay un varias ramas, ya que es mas sencilla, legible y no hace falta indicar en cada expresión de condición la expresión que devuelve el valor.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      if (variable == 1) {
         bloqueSentencias
      } else if (variable == 2) {
         bloqueSentencias
      } else if (variable == 3) {
         bloqueSentencias
      } else {
         bloqueSentencias
      }
      
      CondicionalSwitchIf.java

      Las sentencias if se pueden anidar unas dentro de otras esto dificulta la legibilidad del código, para evitar múltiples anidaciones y crear varias ramas se utilizan guard clauses que simplifican el flujo del programa.

      Sentencias de repetición (for, foreach, while, do-while, break, continue)

      Las sentencias de repetición permiten ejecutar un bloque de sentencias durante un número determinado de veces o mientras se cumpla una condición. En cada iteración después de ejecutar el bloque de sentencias la condición se vuelve a evaluar si se sigue cumpliendo, si se cumple se realiza una nueva iteración si no se cumple se sale del bucle y se continua con la siguiente sentencia del programa. Esta evaluación de la condición y ejecución del bloque de sentencias se realiza hasta que la condición del blucle while no se cumpla. Hay varios tipos de bucles.

      La sentencia while ejecuta un bloque de sentencias mientras se cumpla una condición, puede ocurrir el caso de que la condición de la sentencia while no se cumpla y por tanto el bloque de sentencias de repetición no se ejecute ninguna vez. La comprobación de la condición se realiza antes de entrar al bucle.

      Diagrama sentencia while

      Diagrama sentencia while
      Fuente: beginnersbook.com
      1
      2
      3
      
      while (condicionExpresionBoleana) {
         sentences
      }
      RepeticionWhile.java

      En el bucle do-while la comprobación de la condición está después del bloque de sentencias de repetición, a diferencia del bucle while en el do-while el bloque de sentencias se ejecutan al menos una vez.

      Diagrama sentencia do-while

      Diagrama sentencia do-while
      Fuente: beginnersbook.com
      1
      2
      3
      
      do {
         bloqueSentencias
      } while (condicionExpresionBoleana);
      RepeticionDoWhile.java

      La sentencia for utilizan otra sintaxis para realizar bucles, una de las 4 formas de hacer un bucle for contiene una inicialización, condición de repetición e incremento además del bloque de sentencias a ejecutar. Otras formas de bucle for son el forearch para colecciones de objetos.

      Diagrama sentencia for

      Diagrama sentencia for
      Fuente: beginnersbook.com
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      for (inicializacion; condicionExpresionBoleana; incremento) {
         bloqueSentencias
      }
      
      for (int i = 0; i < 10; ++i) {
         System.out.println(i);
      }
      
      // Foreach
      Collection<Object> collection = ...;
      for (Object object : collection) {
         System.out.println(object);
      }
      RepeticionFor.java

      Dentro de las sentencias de bucle se pueden emplear las palabras reservadas break y continue. La sentencia break permite salir del bucle inmediatamente sin necesidad de evaluar la condición. La palabra continue dejar de ejecutar sentencias del bucle y evaluar de nuevo la condición de bucle, si se sigue cumpliendo la condición se ejecuta de nuevo el bloque de sentencias. Las sentencias break y continue normalmente se utilizan dentro de una sentencia condicional if.

      Diagrama sentencia continue

      Diagrama sentencia continue
      Fuente: beginnersbook.com
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      
      // Break
      for (int i = 0; i < 10; ++i) {
         if (i == 5) {
             break;
         }
         System.out.println(i);
      }
      
      // Resultado
      0
      1
      2
      3
      4
      
      // Continue
      for (int i = 0; i < 10; ++i) {
         if (i == 5) {
             continue;
         }
         System.out.println(i);
      }
      
      // Resultado
      0
      1
      2
      3
      4
      6
      7
      8
      9
      BreakContinue.java

      Un bucle infinito es en un bucle que se itera contnuamente porque la condición de iteración se cumple siempre. Ejecutar continuamente un bloque de sentencias hace que el procesador consuma todos los recursos que se disponen de cómputo de procesador o una alta actividad de entrada y salida que degrada el rendimiento del sistema sin producir ningún resultado útil cuanto menos si no genera errores en el resto de programas del sistema. Suele ser por un error de programación y para resolverlo habitualmente hay que matar el proceso del programa y reiniciarlo, si no se corrige el error en el bucle en las mismas condiciones se producirá de nuevo el bucle infinito.

      Setencias de control de expceciones (try-catch, throw)

      Las expresiones try-catch son el mecanismo de control de errores en Java. Estas expresiones permiten tratar las excepciones lanzadas por la palabra reservada throw en los métodos invocados de su bloque de sentencias.

      1
      2
      3
      4
      5
      
      try {
         bloqueSentencias
      } catch (Exception e) {
         bloqueSentenciasTratamientoExcepcion
      }
      TryCatch.java

      Las excepciones se lanzan con la palabra reservada throw, toda excepción ha de heredar de Exception y si no hereda de RuntimeExecption ha de declararse en la firma del método para indicar que el método puede lanzar esa excepción en caso de no ser tratada dentro del mismo método con un try-catch.

      1
      2
      3
      4
      5
      6
      7
      
      void exception() throws Exception {
          throw new Exception();
      }
      
      void runtime() {
          throw new RuntimeException();
      }
      Throw.java

      Invocación de métodos

      Las funciones en los lenguajes orientados a objetos dentro de las clases, las clases encapsulan las variables y las funciones o métodos. Los métodos tiene acceso además de a los parámetros que recibe a las variables del objeto en las están contenido y otros métodos de la misma clase u otros objetos respetando los ámbitos de visibilidad de las palabras reservadas public, protected, private y default.

      Las expresiones de invocación a métodos se componen del objeto que recibe la llamada a uno de sus métodos separado por un punto y nombre del método. Si el método llamado devuelve un objeto se puede encadenar otra nueva llamada a un método del objeto devuelto. El valor o referencia a objeto devuelto se puede asignar a una variable también.

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      String string = "Hola Mundo!";
      System.out.println(string);
      
      // Resultado
      Hola Mundo!
      
      string = string.toUpperCase();
      System.out.println(string);
      
      // Resultado
      HOLA MUNDO!
      Invocacion.java

      » Leer más, comentarios, etc...

      Blog Bitix

      Generar en el dominio los identificativos de las entidades aplicando DDD antes de persistirlas en la base de datos

      junio 19, 2020 03:00

      Las bases de datos tiene la capacidad de generar identificativos para los datos que se insertan. En el caso de las bases de datos relacionales con secuencias que generan en el momento de inserción la clave primaria de la fila en una tabla, normalmente es un número y utilizando Java con JPA con las anotaciones Id, GeneratedValue y SequenceGenerator en la clase Java que representa a la entidad. Para Domain Driven Design delegar en el momento de inserción la generación del identificativo de la entidad es un problema ya que hace que la entidad sea inválida al no tener identidad hasta persistirla y la base de datos es un elemento externo que debe ser independizado del dominio de la aplicación. En este artículo comento una implementación siguiendo los principios de DDD para dar solución a estos dos problemas.

      Java

      Tradicionalmente la tarea de generar los identificativos de las entidades de dominio se delega en la base de datos en el momento de persistir la entidad. La base de datos en la columna de la tabla de la entidad para el identificativo generalmente es de tipo numérico y la base de datos le asigna un valor incremental para cada fila o entidad guardada.

      Este modelo de delegar en la base de datos el generar la identificativos de las entidades tiene dos problemas en la teoría de Domain Driven Design o DDD:

      • La aplicación requiere y es dependiente de un sistema externo para asignar la identidad de una entidad del dominio creada en la aplicación.
      • La entidad no tiene identidad inicialmente, lo que significa que la entidad es creada con un estado inválido por ser incompleto.

      Que la entidad no tenga identidad asignado y esté incompleta en el momento de creación tiene inconvenientes ya que al implementar los métodos equals y hashCode en Java para una entidad estos se basa en el identificativo de la entidad para determinar si dos instancias de un objeto es la misma, si la entidad no tiene identidad el método equals es ineficaz. Al mismo tiempo el método hashCode, y también el método equals, es utilizado por la API de colecciones de Java en su mayoría con lo que la entidad no es posible guardarla en colecciones que dependan de estos métodos para su correcto funcionamiento. Para usar los métodos equals y hashCode de las entidades es necesario esperar a guardar la entidad en la base de datos para que se le asigne el identificativo.

      También en DDD se suelen utilizar eventos como mecanismo de comunicar que en el sistema se ha sucedido algo, si la entidad no tiene identificativo no es posible comunicar que ha ocurrido algo, al menos no incluyendo el identificativo.

      Identificativos universales como identificadores

      Una posibilidad es generar identificativos universales para los identificativos de las entidades, sin embargo, la clase UUID depende de elementos externos al dominio como el tiempo del sistema. Al mismo tiempo la entidad no es consciente de la existencia de otras entidades y no le es posible determinar la unicidad del identificativo.

      En DDD todo elemento que dependa de algo externo ha de se independizado del dominio. De modo que el UUID aplicando DDD no se genera en la entidad sino en la capa de servicio mediante un elemento externo que en la terminología de DDD es un adaptador, el identificativo se le proporciona a la entidad en el momento de creación en el constructor como parámetro.

      1
      2
      3
      
      System.out.println(UUID.randomUUID().toString());
      ...
      95ba87c1-f0ac-4c55-9efa-257dbe291a7d
      UuidGenerator.java

      Delegar la generación de identificativos en el repositorio

      Dado que en DDD se utiliza un repositorio para persistir las entidades en una base de datos externa a la lógica de dominio, la tarea de generar los identificadores que depende de un elemento externo es posible ubicarla en la misma clase repositorio, de esta manera la lógica queda con cohesión ya que todo lo relativo a la entidad está ubicada en su repositorio.

      Al mismo tiempo delegar la tarea de crear el identificativo en el repositorio permite variar la implementación, una opción es delegar en la base de datos la obtención del identificativo o utilizar el método de identificativo universal anterior. En el caso de delegar en la base de datos la generación del identificativo, es la base de datos la que lo genera igual que en el caso de la autogeneración pero ahora no de manera implícita sino de forma explícita.

      Ejemplo utilizando JPA y Spring Data

      Utilizando Spring Data con JPA para añadir métodos personalizados en la clase del repositorio hay que crear una interfaz que los incluya y construir una implementación de esa interfaz. La misma interfaz es implementada por la interfaz de Spring Data, y Spring haciendo su magia y por composición crea un repositorio que tiene tanto los métodos implementados por Spring como la implementación de los métodos personalizados, en este caso el de generar el identificativo.

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      
      package io.github.picodotdev.blogbitix.entitiesid.domain.product;
      
      import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
      import org.springframework.data.jpa.repository.Modifying;
      import org.springframework.data.jpa.repository.Query;
      import org.springframework.data.repository.PagingAndSortingRepository;
      
      public interface ProductRepository extends PagingAndSortingRepository<Product, ProductId>, JpaSpecificationExecutor<Product>, CustomProductRepository {
      
          @Override
          @Modifying
          @Query("delete from Product")
          void deleteAll();
      }
      ProductRepository.java
      1
      2
      3
      4
      5
      6
      
      package io.github.picodotdev.blogbitix.entitiesid.domain.product;
      
      public interface CustomProductRepository {
      
          ProductId generateId();
      }
      CustomProductRepository.java
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      package io.github.picodotdev.blogbitix.entitiesid.domain.product;
      
      import org.springframework.beans.factory.annotation.Autowired;
      
      import java.math.BigInteger;
      import javax.persistence.EntityManager;
      
      public class CustomProductRepositoryImpl implements CustomProductRepository {
      
          @Autowired
          private EntityManager entityManager;
      
          @Override
          public ProductId generateId() {
              BigInteger id = (BigInteger) entityManager.createNativeQuery("select nextval('product_id_seq')").getSingleResult();
              return ProductId.valueOf(id);
          }
      }
      CustomProductRepositoryImpl.java

      En la clase de la entidad no se usa la anotación GeneratedValue. En vez de esa anotación en este ejemplo se utiliza la anotación EmbeddedId y la anotación Embeddable, aplicando otro de los principios de DDD que es utilizar un tipo especico que representa el identificativo de la entidad en vez de un tipo proporcionado por el lenguaje como un Long o BigInteger. Un tipo específico para la identidad tiene varias ventajas como aprovechar los beneficios del compilador para detectar errores y de los IDE con asistencia de código.

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      
      package io.github.picodotdev.blogbitix.entitiesid.domain.product;
      
      import java.math.BigDecimal;
      import java.time.LocalDate;
      import java.util.Objects;
      import javax.persistence.EmbeddedId;
      import javax.persistence.Entity;
      import javax.persistence.Id;
      import javax.persistence.Table;
      
      @Entity
      @Table(name = "Product")
      public class Product {
      
          @Id
          @EmbeddedId
          private ProductId id;
      
          private String name;
          private LocalDate date;
          private BigDecimal price;
          private Integer units;
      
          public Product() {
          }
      
          public Product(ProductId id, String name, LocalDate date, BigDecimal price, Integer units) {
              this.id = id;
              this.name = name;
              this.date = date;
              this.price = price;
              this.units = units;
          }
      
          ...
      
          @Override
          public int hashCode() {
              return Objects.hash(id);
          }
      
          @Override
          public boolean equals(Object o) {
              if (this == o)
                  return true;
              if (o == null)
                  return false;
              if (!(o instanceof Product))
                  return false;
      
              Product that = (Product) o;
              return Objects.equals(this.id, that.id);
          }
      }
      Product.java
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      
      package io.github.picodotdev.blogbitix.entitiesid.domain.product;
      
      import java.io.Serializable;
      import java.math.BigInteger;
      import java.util.Objects;
      import javax.persistence.Embeddable;
      
      @Embeddable
      public class ProductId implements Serializable  {
      
          private BigInteger id;
      
          protected ProductId() {
          }
      
          protected ProductId(BigInteger id) {
              this.id = id;
          }
      
          ...
      
          public static ProductId valueOf(BigInteger id) {
              return new ProductId(id);
          }
      
          @Override
          public int hashCode() {
              return Objects.hash(id);
          }
      
          @Override
          public boolean equals(Object o) {
              if (this == o)
                  return true;
              if (o == null)
                  return false;
              if (!(o instanceof ProductId))
                  return false;
      
              ProductId that = (ProductId) o;
              return Objects.equals(this.id, that.id);
          }
      }
      
      ProductId.java

      De esta forma ahora las entidades creadas son completamente válidas desde el momento de generación en el dominio ya que tienen su identificador. Dado que la entidad tiene su propio identificativo desde el inicio de su existencia es posible guardar la entidad en colecciones y lanzar eventos de dominio que incluyan su identificador sin tener que esperar que la base de datos le autogenere uno.

      En este caso de prueba se observa que la entidad Product creada se crea en el constructor con su identificativo asignado sin esperar a que la base de datos lo genere, la base de datos y JPA simplemente persisten el valor que tiene asignado.

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      
      package io.github.picodotdev.blogbitix.entitiesid.domain.product;
      
      ...
      
      @SpringBootTest
      @ContextConfiguration(initializers = { DefaultPostgresContainer.Initializer.class })
      public class ProductRepositoryTest {
      
          @Autowired
          private ProductRepository productRepository;
      
          @Test
          void testRepositoryGenerateId() {
              // given
              ProductId id = productRepository.generateId();
              Product product = new Product(id, "Raspberry Pi", LocalDate.now(), new BigDecimal("80.0"), 10);
      
              // and
              productRepository.save(product);
      
              // then
              assertEquals(product, productRepository.findById(id).get());
          }
      }
      ProductRepositoryTest.java

      En las trazas se observa la SQL para obtener el valor de la secuencia y la SQL de insert para guardar la entidad.

      1
      2
      3
      
      Hibernate: select nextval('product_id_seq')
      Hibernate: select product0_.id as id1_0_0_, product0_.date as date2_0_0_, product0_.name as name3_0_0_, product0_.price as price4_0_0_, product0_.units as units5_0_0_ from product product0_ where product0_.id=?
      Hibernate: insert into product (date, name, price, units, id) values (?, ?, ?, ?, ?)
      System.out
      Terminal

      El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
      ./gradlew test

      » Leer más, comentarios, etc...

      Una sinfonía en C#

      Azure Devops: ¿Cómo saber el nombre de todas las variables y sus valores?

      junio 17, 2020 04:00

      Si son como yo seguramente se confundirán todo el tiempo qué variable predefinda tiene qué valor, además de tener claro el valor en sí.

      Por ejemplo al crear un pipeline de build no recordar si Build.ArtificatsStagingDirectory es lo mismo que Agent.BuildDirectory, etc. más aún, si además las variables que nosotros definimos tienen el valor esperado.

      En este post vamos a ver un pequeño truco para averiguarlo en 2 minutos.

      PowerShell al rescate

      Además con este método vamos a saber cómo acceder a estas variables desde PowerShell, es decir, si hacemos un script (o inline) para que se ejecute como un step de un build o release podemos acceder a todas las variables.

      Las variables de Azure Devops son variables de entorno en la máquina que se ejecuta el agente.

      Al fin de cuentas cada variable de sistema de Azure Devops como las que definimos nosotros terminan llegando al agente como variables de entorno, y podemos acceder a ellas desde PowerShell o desde cualquier aplicación, solo tenemos que tener en cuenta algunas reglas.

      Conversiones de nombre

      • Primero, los nombres de todas las variables se convierten a mayúsculas.
      • Segundo, los . que separan (por ejemplo) Agent.BuildDirectory, se reemplazan por _

      Y listo, entonces System.ArtifiactsDirectory podemos encontrarlo como variable de entorno con el nombre SYSTEM_ARTIFACTSDIRECTORY

      Y cómo hacemos para averiguar todos los nombres y valores desde Azure Devops?

      Bien, el truco es simple, solo tenemos que crear un pipeline de build (o release, depende de qué variables queremos conocer) y agregamos una tarea de Powershell, elegimos que el Type del script sea “inline” y ponemos lo siguiente

      ls env:

      image

      Solo eso, y la ejecutamos (si es un build guardar y encolar, si es un release, guardar, generar release, encolar) y cuando finaliza miramos el log, y veremos todas las variables de entorno de la máquina del agente en cuestión y entre ellas las propias de Azure Devops.

      image

      Ah, y para leer variables de entorno (como por ejemplo las de Azure Devops sean predefinidas o las creadas por nosotros) es tan simple como:

      $variableValue = [System.Environment]::GetEnvironmentVariable("SYSTEM_DEFAULTWORKINGDIRECTORY")

      Y obtenemos el valor de System.DefaultWorkingDirectory en este caso

      Enjoy.

      » Leer más, comentarios, etc...

      Fixed Buffer

      Azure DevOps Ephimeral Agents: Agentes de usar y tirar

      junio 16, 2020 08:00

      Tiempo de lectura: 7 minutos
      Imagen ornamental para el artículo "Azure DevOps Ephimeral Agents: Agentes de usar y tirar"

      En la última entrada tocamos un tema un poco diferente ya que nos salimos de temas como entity framework core, código de alto rendimiento y la línea general del blog para hablar de un tema un poco más general como es el cómo controlar un ordenador utilizando simplemente la mirada.

      Hoy toca volver a la carga con la temática habitual y les toca el turno a los agentes efímeros de Azure DevOps.

      ¿Qué es un agente efímero de Azure DevOps?

      La primera pregunta que hay que resolver es obviamente que es un agente efímero de Azure DevOps, y la respuesta ya se puede intuir del título, un agente de usar y tirar. La idea es crear un agente que esté disponible para poder realizar un trabajo y al acabarlo se elimine.

      Los agentes de Azure DevOps es el nombre con el que se conoce a las diferentes máquinas que están conectadas a la organización de DevOps y que son las encargadas de realizar los trabajos de integración y despliegue. A su vez, los agentes se organizan en grupos llamados pools

      ¿Por qué se puede querer un agente de usar y tirar?

      La segunda pregunta que hay que responder es sin duda para que puede hacer falta algo así. Para ello hace falta conocer un poco el funcionamiento de los agentes de Azure DevOps.

      Cuando tu creas una organización, automáticamente y sin que tengas que preocuparte de nada, se crean diferentes pools de agentes hospedados por Microsoft.

      Esto se puede comprobar de manera sencilla yendo a las propiedades de la organización o a las de cualquier proyecto. Dentro de ellas hay un apartado específico para los pools de agentes donde se muestran los diferentes grupos de agentes disponibles.

      La imagen señala el botón Agent pools dentro de las propiedades de la organización y muestra los dos pools que hospeda Microsoft que son Azure Pipelines y Default

      Lo bueno de estos agentes hospedados por Microsoft es que no requieren de ningún preparativo ni mantenimiento, están listos para usar desde el primer momento y corren directamente sobre Azure. Si el software que traen instalado de serie es suficiente (que es lo habitual), puedes usarlos sin preocuparte de nada.

      ¿Entonces, dónde está el problema? Principalmente tienen 2 problemas:

      • Eres dependiente del software preinstalado
      • Todos los recursos que necesites deben ser accesibles desde internet

      Para el primero de los problemas se puede buscar alguna solución como por ejemplo instalar tú mismo lo necesario en el agente dentro del propio pipeline. En cambio, el segundo caso es el que generalmente se vuelve un problema serio…

      Imagina que durante el proceso de integración continua o para desplegar necesitas acceder a un recurso de una red privada. Al no tener ningún tipo de control sobre los agentes, no hay manera posible de conectarlo a la red privada y por tanto el agente no va a poder hacer su trabajo.

      Hasta la llegada a Azure DevOps de los agentes efímeros, la única solución que nos quedaba era la de tener un agente dentro de la red privada. Esto va desde una máquina virtual hasta un pod de Kubernetes, sea el modelo que sea y se hospede donde se hospede, necesitabas crear el agente previamente y asociarlo a un pool para poder usarlo.

      En honor a la verdad, existía alguna manera como cuenta Gisela Torres en su blog, que consistía en que tú mismo creases y destruyeses un contenedor en Azure Container Instances durante el pipeline.

      La idea de estos agentes efímeros de Azure DevOps es que puedas crear un agente de un solo uso como un contenedor de Azure Container Instances dentro de la red privada, y a diferencia de la aproximación que nos ofrecía Gisela, se destruya solo al terminar independientemente todo. Esto es especialmente útil ya que garantiza la optimización de costes al evitar posibles problemas que impidan la destrucción del contenedor una vez acabado el trabajo.

      Poniendo en marcha un agente efímero

      Vale, ya tenemos claro el concepto sobre cuándo y por qué utilizar un agente efímero para Azure DevOps. ¿Cómo lo ponemos en marcha?

      En primer lugar, hay que tener en cuenta que vamos a crear un agente en un contenedor Docker, por lo tanto, vamos a necesitar crear una imagen Docker y subirla a un repositorio. Para crear la imagen vamos a partir del Dockerfile base para agentes efímeros de Azure DevOps que podemos encontrar en su repositorio de Github. Estas imágenes base a diferencia de las habituales, añaden toda la infraestructura necesaria para parar el agente, desregistrarlo del pool, y borrar el contenedor automáticamente.

      Una vez que tenemos la imagen subida a un contenedor, vamos a crear un pool especifico que será donde se registren las instancias de los agentes a medida que se vayan creando. Para eso basta con ir de nuevo a la sección Agent pool de las propiedades de la organización o del proyecto, y pulsar sobre el botón de ‘añadir pool’ en la parte superior derecha. Esto nos mostrará una pequeña ventana donde elegiremos el nombre del pool. Una vez creado, hay que recordar el nombre ya que lo necesitaremos más adelante.

      Como último paso de configuración previa, vamos a tener que crear las dos conexiones de servicio que vamos a utilizar en caso de que no las tengamos ya, una será hacia el registro de imágenes de docker y la otra hacia la propia suscripción de Azure. Esto se puede conseguir desde el botón ‘nueva conexión de servicio’ en la sección de conexiones de servicio.

      La imagen señala la sección de conexiones de servicio y el botón de crear nueva conexión de servicio

      Aquí vamos a crear en caso de que no exista una conexión de tipo Azure Resource Manager hacia la suscripción y otra de Docker Registry hacia el registro donde está la imagen.

      También es necesario que tengamos un token que tenga permisos para operar el grupo de agentes. Este es el mismo requisito que si estuviésemos creando una agente de Azure DevOps normal y lo podemos conseguir siguiendo la documentación oficial.

      Por último pero no menos importante, vamos a necesitar instalar la tarea Ephemeral Pipelines Agents desde el marketplace. Una vez que lo tengamos, ya estamos listos para empezar a crear pipelines que utilicen agentes efímeros de Azure DevOps.

      Crear un pipeline que utilice el agente efímero

      Hemos llegado a la parte más fácil de todas, vamos a crear un pipeline multi etapa donde la primera etapa va a crear el agente efímero y la segunda va a realizar el trabajo dentro de nuestra red privada. Para eso basta con que creemos un pipeline con un yaml como este:

      trigger: none
      
      variables:
        # Nombre del grupo de recursos donde se creará el agente
        ResourceGroup: 'ephemeral-agents'
        # Nombre del grupo de recursos donde está la red privada
        vNetResourceGroup: 'ephemeral-agents'
        # Nombre de la red privada
        vnetName: private-network
        # Nombre de la subnet donde se conectará el agente
        subnetName: 'agents'
        # Localización
        location: 'West Europe'
        # Nombre del ACR
        acrName: fixedbuffer
        # Nombre de la imagen del agente
        imageName: $(acrName).azurecr.io/agent
        # Nombre de la conexión de servicio de ARM
        azureSubscription: 'Visual Studio Enterprise'
        # Nombre de la conexión de servicio de Docker Registry
        containerRegistryConnection: 'fixedbuffer'
        # Nombre del pool de agente
        agentpool: ephemeral
        # Token para el agente, puede ser $(System.AccessToken) si tiene suficientes permisos
        accessToken: 'accessToken'
      
      
      stages:
      - stage: PrepareAgent
        jobs:
        - job: PrepareAgent
          displayName: Prepare Agent
          pool: Hosted Ubuntu 1604
          steps:
          - task: AzureContainerAgentCreate@0
            displayName: 'Azure Container Create for pool ephemeral'
            inputs:
              azureSubscription: $(azureSubscription)
              resourceGroupName: $(ResourceGroup)
              location: $(location)
              azureDevOpsToken: $(accessToken)
              containerRegistry: $(containerRegistryConnection)
              imageName: $(imageName)
              agentPool: $(agentpool)
              agentPrefix: 'ephemeral-'
              vnetResourceGroupName: $(vNetResourceGroup)
              vnetName: $(vnetName)
              subnetName: $(subnetName)
      
      - stage: Deploy
        jobs:
        - deployment: Deploy
          environment: 'Production'
          displayName: Deploy Files
          pool: ${{variables.agentpool}}
          strategy:
            runOnce:
              deploy:
                steps:
                - script: echo 'Hello World'
      

      Sí todo va bien, cuando ejecutemos este pipeline debería crearse un agente en la suscripción el cual va a ejecutar el stage de Deploy (en este caso dirá Hello World) y una vez termine se borrará el agente.

      Conclusión

      La posibilidad de utilizar agentes efímeros en Azure DevOps simplifica enormemente la infraestructura necesaria para ciertos despliegues donde las redes privadas requerirían de levantar una máquina exclusivamente para esa labor. Pero ojo, no es oro todo lo que reluce, hay que evaluar si realmente compensa utilizar este modelo ya que, si por ejemplo hacemos muchos despliegues a día, o son despliegues que duran varias horas, quizás no sea una opción interesante a nivel de costes. En los proyectos en los que trabajo por ejemplo suele haber un cluster de kubernetes conectado a esa red privada y resulta mucho más interesante tener un pod desplegado dedicado a esa labor.

      Pese a todo, si se hacen despliegues una o dos veces al día y no son especialmente lentos, la opción de utilizar agentes efímeros puede ser una muy buena solución para evitar tener que preocuparse de mantener máquinas y librarse de algunos costes. Cuanto menos, son una herramienta muy interesante a tener en cuenta 🙂

      **La entrada Azure DevOps Ephimeral Agents: Agentes de usar y tirar se publicó primero en Fixed Buffer.**

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      Casio fx-9860GIII

      junio 16, 2020 06:44



      Hace un par de meses Casio anunciaba la disponibilidad de sus nuevas calculadoras gráfica GIII, con el objetivo de sustituir a las anteriores GII lanzadas en 2009. Mientras que el modelo de entrada fx-7400GIII heredero de la fx-7400GII, no va a llegar a España, sí que ha llegado, y de hecho ya se vende la …

      Casio fx-9860GIII Leer más »



      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Variable not found

      Cómo invocar funciones Javascript desde Blazor (interop 1/3)

      junio 16, 2020 06:05

      BlazorAunque uno de los objetivos de partida de Blazor es permitir que toda la lógica de presentación de una aplicación pueda ser implementada sin tocar una línea de Javascript, sería absurdo ignorar que este lenguaje dispone de uno de los ecosistemas más amplios y ricos del mundo del software. Sin duda, sería una pena (y un error) intentar ignorar la ingente cantidad de bibliotecas y componentes que existen para él.

      Tampoco podemos olvidar que puede que nosotros mismos tengamos bibliotecas Javascript que nos podría interesar reutilizar en nuevas interfaces de usuario. De nuevo, sería un error que el hecho de saltar a Blazor nos obligara a reescribir todo desde cero.

      Por estas razones Blazor dispone de mecanismos para interoperar con Javascript bidireccionalmente, pues:
      • desde nuestras aplicaciones Blazor podemos invocar funciones Javascript,
      • y desde Javascript podemos invocar métodos escritos en C# con Blazor.
      En este post vamos a centrarnos en la primera posibilidad, dejando la segunda para otras publicaciones de la serie que llegarán más adelante.

      ¿Cómo invocar métodos Javascript desde Blazor (Server o WebAssembly)?

      Durante el arranque de una aplicación Blazor se registra en el inyector de dependencias un objeto IJSRuntime, una abstracción que nos dará acceso a los objetos y funciones Javascript que estén disponibles en la página actual.

      Como podemos ver, esta interfaz es bastante sencilla pues sólo nos facilita un método InvokeAsync() con un par de variantes:
      public interface IJSRuntime
      {
      ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
      ValueTask<TValue> InvokeAsync<TValue>(
      string identifier, CancellationToken cancellationToken, object[] args);
      }
      Un detalle interesante es que, por detrás, cada "sabor" de Blazor deberá implementar esta interfaz expresamente, pues la forma de acceder al Javascript del navegador cambia radicalmente según si estamos ejecutando nuestra aplicación con Blazor Server o Blazor WebAssembly.
      Por tanto, ya de forma intuitiva, podemos ver que para invocar código script desde Blazor, lo único que tenemos que hacer es solicitar al inyector una instancia de IJSRuntime y utilizar el método InvokeAsync(), como en el siguiente ejemplo, que en este caso retornará el valor obtenido tras hacer un prompt al usuario:
      @inject IJSRuntime JsRuntime

      <h1>Blazor To JS Demo</h1>
      <button class="btn btn-primary" @onclick="PromptNameAsync">Click!</button>
      @if (!string.IsNullOrEmpty(name))
      {
      <p>Hello, @name</p>
      }

      @code {
      string name;

      async Task PromptNameAsync()
      {
      name = await JsRuntime.InvokeAsync<string>("prompt", "What is your name?");
      }
      }
      El parámetro genérico de InvokeAsync() indica el tipo de dato retornado por la llamada a la función Javascript; el primer parámetro es el identificador de la función y a continuación van los argumentos que queramos enviarle.

      Además de los métodos impuestos por la interfaz IJSRuntime, podemos utilizar métodos extensores que nos simplificarán algunos escenarios. Por ejemplo, si estamos llamando a una función que no retorna ningún valor, podremos utilizar InvokeVoidAsync() de una forma muy similar a como hemos visto arriba:
      await JsRuntime.InvokeVoidAsync("console.log", "Hello world!");
      await JsRuntime.InvokeVoidAsync("alert", "Warning!");
      Por supuesto, la función a invocar puede ser de las existentes "de serie" en Javascript, o bien una función personalizada que hayamos implementado sobre la página o en un .js vinculado a ella, por ejemplo:
      await JsRuntime.InvokeVoidAsync("location.reload");
      Observad que en todos los casos la llamada es asíncrona; esto, además de por razones estructurales (en Blazor Server el método a invocar está físicamente separado del punto desde el cual realizamos la llamada), es para permitir la llamada a funciones asíncronas que retornan un objeto Promise de Javascript:
      function executeSomething() {
      return new Promise((resolve, reject) => {
      console.log("Waiting 3 seconds...")
      setTimeout(() => resolve(), 3000);
      });
      }
      La función anterior podría ser ejecutada de la siguente manera:
      async Task PromptNameAsync()
      {
      await JsRuntime.InvokeVoidAsync("executeSomething");
      }
      Otras variantes del método nos permiten indicar un token que podemos utilizar para cancelar la llamada cuando nos interese. Por ejemplo, el siguiente código muestra cómo utilizar un timeout, lo cual puede venir bien si no queremos que una llamada se quede "enganchada" más tiempo de la cuenta:
      try
      {
      var source = new CancellationTokenSource(TimeSpan.FromSeconds(1));
      await JsRuntime.InvokeVoidAsync("executeSomething", source.Token);
      await JsRuntime.InvokeVoidAsync("alert", "Done!");
      }
      catch (TaskCanceledException ex)
      {
      await JsRuntime.InvokeVoidAsync("alert", "The task was cancelled!");
      }
      Como parámetros en las llamadas podemos utilizar cualquier tipo de objeto, que serán serializados automáticamente y enviados a la función. Por ejemplo, imaginad que tenemos una clase de datos como la siguiente:
      // Friend.cs
      public class Friend
      {
      public string Name { get; set; }
      public int Age { get; set; }
      }
      Esta clase podríamos utilizarla directamente en una invocación a través del interop con Javascript:
      @page "/"
      @inject IJSRuntime JsRuntime

      <h1>Complex object demo</h1>
      <button class="btn btn-primary" @onclick="ShowFriendData">Click!</button>

      @code {
      async Task ShowFriendData()
      {
      var friend = new Friend {Name = "John", Age = 32};
      await JsRuntime.InvokeVoidAsync("showFriendData", friend);
      }
      }
      Y la función Javascript podría ser la siguiente; observad que al serializar se han convertido las propiedades a camel casing, que es lo que se esperaría desde el lado Javascript:
      function showFriendData(friend) {
      alert(`Hello, ${friend.name}, you are ${friend.age} years old`);
      }
      De la misma forma, podríamos utilizar tipos complejos como retorno de estas llamadas, es decir, hacer un InvokeAsync<Friend>() sobre una función Javascript que retornara un objeto de dicho tipo.

      ¿Y si no quiero ejecutar una función, sino leer una variable o propiedad?

      Es importante tener en cuenta que, como su nombre sugiere, InvokeAsync() sólo puede ser utilizado para invocar funciones. Si quisiéramos, por ejemplo, obtener el valor de una variable o propiedad, deberíamos utilizar una función JS personalizada.

      Por ejemplo, supongamos que queremos obtener el título de la página actual, disponible en la propiedad de la página document.title desde Blazor. Lo primero sería crear la función que actuará como puente:
      function getTitle() {
      return document.title;
      }
      Y luego, ya desde Blazor, podríamos invocarla con total normalidad:
      @page "/title"
      @inject IJSRuntime JsRuntime

      <h1>Show title Demo</h1>
      <button class="btn btn-primary" @onclick="ShowTitle">Click!</button>

      @code {
      async Task ShowTitle()
      {
      var title = await JsRuntime.InvokeAsync<string>("getTitle");
      await JsRuntime.InvokeVoidAsync("alert", "Title: " + title);
      }
      }
      ¡Y esto es todo! Creo que a lo largo de este post hemos visto lo más destacado de este mecanismo que nos permite invocar funciones Javascript desde nuestro código C#/Blazor.

      En entradas posteriores veremos cómo hacerlo en sentido inverso, es decir, permitir que desde Javascript invoquemos métodos escritos en C#.

      Publicado en Variable not found.

      » Leer más, comentarios, etc...

      Blog Bitix

      Comando para convertir una imagen SVG a PNG y JPG con diferentes tamaños y color de fondo con Inkskape

      junio 14, 2020 08:45

      El formato de imagen SVG tiene la ventaja de permitir el escalado de la imagen sin pérdida de calidad y suele tener menor tamaño que la imagen equivalente en formato PNG y JPG. Si es necesario la imagen SVG es exportable a formato de imagen PNG y JPG con el tamaño deseado o color de fondo. El editor de imágenes Inkscape permite con su utilidad de línea de comandos automatizar y exportar archivos SVG a PNG y JPG y ocultar y mostrar las capas deseadas del archivo original para obtener el resultado deseado en la exportación.

      Inkscape

      GNU

      El formato de imagen SVG es un formato de imagen vectorial donde las líneas, formas, posición y colores se describen en formato texto, tiene la ventaja de ser un formato escalable que no pierde resolución independiente del tamaño de imagen en la que se represente, es decir, la imagen tiene la misma calidad al tamaño full hd 1920x1080 que a 4K 3840x2560 que en 800x600 píxeles.

      Con las imágenes de fotos en formato JPG y sin pérdida de calidad PNG la resolución adecuada para mostrar estas imágenes es la original del archivo a otra resolución hay que hacer un escalado con un algoritmo para añadir o quitar píxeles, el escalado es una operación imprecisa que resta algo de calidad a la imagen. Escalar el tamaño de una imagen JPG o PNG es necesario para obtener la imagen en otros tamaños, dependiendo del número de píxeles a añadir si se hace más grande que la original o píxeles a quitar si se reduce el tamaño la pérdida de calidad se nota más o menos.

      Imagen en formato JPG original Imagen en formato JPG escalada a 300x200 píxeles

      Imagen en formato JPG original y escalada a 300x200 píxeles

      Los navegadores y dispositivos móviles ya soportan como formato de imagen el SVG, en la web y los dispositivos móviles es especialmente adecuado este formato ya que además de adaptarse a la variedad de tamaños de los dispositivos de escritorio o móviles suelen tener un menor tamaño de archivo lo que hace que se descarguen más rápido al requerir menos ancho de banda.

      Aún con los beneficios que posee el formato SVG algunas aplicaciones no soportan el formato SVG y en este caso es necesario hacer una conversión de SVG a los formatos binarios rasterizados PNG o JPG. El formato SVG permite obtener estas imágenes PNG y JPG en diferentes tamaños sin pérdida de calidad.

      Inkscape es un editor de imágenes vectoriales con una utilidad de línea de comandos que permite convertir y exportar imágenes en formato SVG a PNG y JPG en el tamaño y con el color de fondo deseado. El siguiente comando convierte todos los archivos SVG a PNG de una carpeta. En el comando se indican varios parámetros como la anchura deseada de la imagen, el color de fondo, los identificativos de las capas a exportar, el formato de salida y el nombre del archivo creado. Posteriormente con un segundo comando hay que convertir las imágenes de formato PNG a JPG, dependiendo del tipo de imagen, los colores y degradados de la imagen el tamaño en formato PNG será mayor o menor que en formato JPG.

      1
      2
      
      $ for f in *.svg; do inkscape -w 750 "$f" --export-background white --export-background-opacity 1 --export-type png --export-filename "$(basename ${f%.*})-750.png"; done;
      $ for f in *.png; do convert "$f" "${f%.*}.jpg"; done;
      inkscape-convert-svg-png.sh

      Imagen original en formato SVG

      Imagen original en formato SVG

      El editor Inkscape permite definir capas con diferentes elementos de la imagen, la linea de comandos permite exportar únicamente capas deseadas de la imagen para obtener el resultado deseado en la exportación. El SVG anterior contiene en el mismo archivo diferentes capas con diferentes versiones de la imagen adecuadas para un fondo claro y oscuro.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      BASENAME="apache-tapestry"
      ARTWORK_FILE="$BASENAME-artwork.svg"
      
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icontext-800-light.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icontext-800-dark.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightIconObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icon-800-light.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkIconObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icon-800-dark.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightTextObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-text-800-light.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkTextObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-text-800-dark.png"
      inkscape-png-versions.sh

      Imagen en formato PNG Imagen en formato PNG

      Imagen en formato PNG Imagen en formato PNG

      Imagen en formato PNG Imagen en formato PNG

      Diferentes versiones de la imagen SVG en formato PNG

      También es posible modificar el SVG original para mostrar y ocultar las capas visibles del archivo. Los siguientes comandos permiten exportar a PNG la imagen en diferentes versiones (icono y texto, solo icono o solo texto), con diferente color de fondo (transparente, blanco y negro) y en diferente tamaño. Esto permite automatizar y hacerlo mucho más rápido que el repetitivo proceso que sería realizar manualmente la exportación usando la interfaz gráfica de Inkscape.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      BASENAME="apache-tapestry"
      ARTWORK_FILE="$BASENAME-artwork.svg"
      
      $ (cp "$ARTWORK_FILE" "$BASENAME-icon-light.svg" && inkscape "$BASENAME-icon-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-icon-dark.svg" && inkscape "$BASENAME-icon-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-text-light.svg" && inkscape "$BASENAME-text-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb EditDeselect --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-text-dark.svg" && inkscape "$BASENAME-text-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-icontext-light.svg" && inkscape "$BASENAME-icontext-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-icontext-dark.svg" && inkscape "$BASENAME-icontext-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      inkscape-svg-versions.sh

      Imagen en formato SVG Imagen en formato SVG

      Imagen en formato SVG Imagen en formato SVG

      Imagen en formato SVG Imagen en formato SVG

      Diferentes versiones de la misma imagen SVG

      » Leer más, comentarios, etc...

      Blog Bitix

      Comando para convertir una imagen SVG a PNG y JPG con diferentes tamaños, formatos y color de fondo con Inkskape

      junio 14, 2020 08:45

      El formato de imagen SVG tiene la ventaja de permitir el escalado de la imagen sin pérdida de calidad y suele tener menor tamaño que la imagen equivalente en formato PNG y JPG. Si es necesario la imagen SVG es exportable a formato de imagen PNG y JPG con el tamaño deseado o color de fondo. El editor de imágenes Inkscape permite con su utilidad de línea de comandos automatizar y exportar archivos SVG a PNG y JPG y ocultar y mostrar las capas deseadas del archivo original para obtener el resultado deseado en la exportación.

      Inkscape

      GNU

      El formato de imagen SVG es un formato de imagen vectorial donde las líneas, formas, posición y colores se describen en formato texto, tiene la ventaja de ser un formato escalable que no pierde resolución independiente del tamaño de imagen en la que se represente, es decir, la imagen tiene la misma calidad al tamaño full hd 1920x1080 que a 4K 3840x2560 que en 800x600 píxeles.

      Con las imágenes de fotos en formato JPG y sin pérdida de calidad PNG la resolución adecuada para mostrar estas imágenes es la original del archivo a otra resolución hay que hacer un escalado con un algoritmo para añadir o quitar píxeles, el escalado es una operación imprecisa que resta algo de calidad a la imagen. Escalar el tamaño de una imagen JPG o PNG es necesario para obtener la imagen en otros tamaños, dependiendo del número de píxeles a añadir si se hace más grande que la original o píxeles a quitar si se reduce el tamaño la pérdida de calidad se nota más o menos.

      Imagen en formato JPG original Imagen en formato JPG escalada a 300x200 píxeles

      Imagen en formato JPG original y escalada a 300x200 píxeles

      Los navegadores y dispositivos móviles ya soportan como formato de imagen el SVG, en la web y los dispositivos móviles es especialmente adecuado este formato ya que además de adaptarse a la variedad de tamaños de los dispositivos de escritorio o móviles suelen tener un menor tamaño de archivo lo que hace que se descarguen más rápido al requerir menos ancho de banda.

      Aún con los beneficios que posee el formato SVG algunas aplicaciones no soportan el formato SVG y en este caso es necesario hacer una conversión de SVG a los formatos binarios rasterizados PNG o JPG. El formato SVG permite obtener estas imágenes PNG y JPG en diferentes tamaños sin pérdida de calidad.

      Inkscape es un editor de imágenes vectoriales con una utilidad de línea de comandos que permite convertir y exportar imágenes en formato SVG a PNG y JPG en el tamaño y con el color de fondo deseado. El siguiente comando convierte todos los archivos SVG a PNG de una carpeta. En el comando se indican varios parámetros como la anchura deseada de la imagen, el color de fondo, los identificativos de las capas a exportar, el formato de salida y el nombre del archivo creado. Posteriormente con un segundo comando hay que convertir las imágenes de formato PNG a JPG, dependiendo del tipo de imagen, los colores y degradados de la imagen el tamaño en formato PNG será mayor o menor que en formato JPG.

      1
      2
      
      $ for f in *.svg; do inkscape -w 750 "$f" --export-background white --export-background-opacity 1 --export-type png --export-filename "$(basename ${f%.*})-750.png"; done;
      $ for f in *.png; do convert "$f" "${f%.*}.jpg"; done;
      inkscape-convert-svg-png.sh

      Imagen original en formato SVG

      Imagen original en formato SVG

      El editor Inkscape permite definir capas con diferentes elementos de la imagen, la linea de comandos permite exportar únicamente capas deseadas de la imagen para obtener el resultado deseado en la exportación. El SVG anterior contiene en el mismo archivo diferentes capas con diferentes versiones de la imagen adecuadas para un fondo claro y oscuro.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      BASENAME="apache-tapestry"
      ARTWORK_FILE="$BASENAME-artwork.svg"
      
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icontext-800-light.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icontext-800-dark.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightIconObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icon-800-light.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkIconObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icon-800-dark.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightTextObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-text-800-light.png"
      $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkTextObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-text-800-dark.png"
      inkscape-png-versions.sh

      Imagen en formato PNG Imagen en formato PNG

      Imagen en formato PNG Imagen en formato PNG

      Imagen en formato PNG Imagen en formato PNG

      Diferentes versiones de la imagen SVG en formato PNG

      También es posible modificar el SVG original para mostrar y ocultar las capas visibles del archivo. Los siguientes comandos permiten exportar a PNG la imagen en diferentes versiones (icono y texto, solo icono o solo texto), con diferente color de fondo (transparente, blanco y negro) y en diferente tamaño. Esto permite automatizar y hacerlo mucho más rápido que el repetitivo proceso que sería realizar manualmente la exportación usando la interfaz gráfica de Inkscape.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      BASENAME="apache-tapestry"
      ARTWORK_FILE="$BASENAME-artwork.svg"
      
      $ (cp "$ARTWORK_FILE" "$BASENAME-icon-light.svg" && inkscape "$BASENAME-icon-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-icon-dark.svg" && inkscape "$BASENAME-icon-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-text-light.svg" && inkscape "$BASENAME-text-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb EditDeselect --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-text-dark.svg" && inkscape "$BASENAME-text-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-icontext-light.svg" && inkscape "$BASENAME-icontext-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      $ (cp "$ARTWORK_FILE" "$BASENAME-icontext-dark.svg" && inkscape "$BASENAME-icontext-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
      inkscape-svg-versions.sh

      Imagen en formato SVG Imagen en formato SVG

      Imagen en formato SVG Imagen en formato SVG

      Imagen en formato SVG Imagen en formato SVG

      Diferentes versiones de la misma imagen SVG

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      PSeInt

      junio 12, 2020 08:37



      Después del lenguaje Scratch, seguimos hablando un poco sobre programación, hoy con un software hispano escrito por el argentino Pablo Novara. Su nombre es PSeInt, la abreviatura de PSeudo Intérprete, y su cometido es precisamente ese, un intérprete de lenguaje pseudocódigo, que además cuenta con algunas ayudas para los estudiantes. Personalmente nunca me ha gustado …

      PSeInt Leer más »



      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Coding Potions

      Vue directives - Guía completa de uso

      junio 11, 2020 12:00

      Introducción ¿qué son las directivas?

      Las directivas son unos atributos que puedes añadir en el HTML de los componentes que permiten controlar el elemento DOM sobre el que se colocan.

      Si ya has usando Vue estoy seguro ya hayas usado directivas, aunque no lo sabías. Cuando usas el v-model y el v-show realmente lo que estás usando son directivas, auque estas vienen ya creadas con Vue.

      Como dentro de una directiva tienes acceso al elemento DOM sobre el que se aplica, las posibilidades son casi infinitas. Puedes ejecutar código javascript directamente al elemento del DOM y además puedes personalizar cada directiva porque admiten el paso de parámetros.

      En el artículo anterior (Cómo se usan los filters de Vue) hablamos de los filters y de cómo se usan insertándolos en los datos de la vista, pero las directivas son totalmente distintas, auque también se usan dentro de la vista de los componentes, las directivas se ponen como atributo del HTML y no dentro de los datos que se pintan. Ahora veremos esto con más detalle.

      ¿Cómo se crean las directivas?

      Lo primero que tienes que saber, es que las directivas siempre se usan poniendo dentro de la etiqueta HTML v- seguido del nombre de la directiva.

      Primero vamos a crear la directiva más básica, una que no recibe nada ni hace nada. Yo lo que suelo hacer es crear un fichero llamado direcives.js para tener todas las directivas localizadas, ya que no aconstumbro a tener muchas.

      Por ejemplo, vamos a crear la directiva esta que no hace nada, la voy a llamar void:

      // src/directives.js
      
      import Vue from 'vue'
      
      Vue.directive('void', function(el) {
        console.log("Directiva");
      })
      

      Para usar este fichero de directivas lo tienes que importar en el main.js:

      // /src/main.js
      
      import Vue from 'vue'
      import App from './App'
      import "./filters"
      
      new Vue({
        el: '#app',
        template: '<App/>',
        components: { App },
      })
      

      Para usar esta directiva que hemos creado tan solo tienes que poner dentro de la etiqueta HTML del componente:

      <template>
        <div class="component">
          <div v-void></div>
        </div>
      </template>
      

      Recuerda que siempre tienes que poner delante del nombre de la directiva el v-.

      Como ves, la directiva es una función que recibe un parámetro el. Este parámetro es el elemento DOM sobre el que se apica la directiva.

      Hooks de las directivas

      Las directivas tienen sus propios hooks, es decir, tienen una serie de funciones para controlar su ciclo de vida, veamos cuáles son:

      • bind: Se ejecuta en el momento en el que la directiva se añade al elemento del DOM. Sería como el created() de los componentes
      • inserted: Cuando se ha insertado en el DOM el elemento sobre el que se aplica esta directiva. Como el mounted() de los componentes.
      • updated: Cuando cambia el elemento del DOM sobre el que está aplicada la directiva.
      • componentUpdated: Cuando todo el componente y el hijo han terminado de ser actualizados.
      • unbind: Cuando la directiva es eliminada de ese elemento del DOM.

      En el anterior ejemplo la directiva era una función. Para usar los hooks es necesario que la directiva sea un objeto con los hooks que quieres usar.

      Veamos un ejemplo de directiva llamada focus que lo que hace es poner el foco en el navegador sobre el elemento en el que se inserta la directiva. Para ello uso el hook bind para porder hacer el focus al crearse el elmento en el DOM:

      Vue.directive('focus', {
        // Cuando el elemento enlazado se inserta en el DOM...
        inserted: function (el) {
          // Enfoca el elemento
          el.focus()
        }
      })
      

      Parámetros de los hooks

      Cada uno de los hooks antes mencionados pueden recibir como parámetro de la función lo siguiente:

      • el: El elemento del DOM sobre el que se aplica la directiva
      • binding: Objeto solo de lectura que contiene información que puede recibir la directiva
      • vnode: Objeto solo de lectura que contiene el nodo del virtual dom asociado a la directiva
      Vue.directive('hooks', {
        bind: function(el, binding, vnode) {
          console.log(el);
          console.log(binding);
          console.log(vnode);
        }
      })
      

      Pasar información a las directivas

      Como hemos dicho antes, v-show y v-if son directivas por lo que el paso de infotmación a las directivas es igual a estas dos. Todo lo que pongas dentro al llamar a la directiva es código javascript, métodos o variables y propiedades computadas. Por ejemplo

      <div v-color="'red'"></div>
      <div v-color="getColor()"></div>
      

      Ahora bien, para usar este valor que pasas dentro de la directiva, tienes que usar el parámetro binding visto anteriormente. Dentro de este parámetro puedes acceder a su propiedad value para recoger el valor que pasas desde la directiva. Por ejemplo:

      Vue.directive("color", {
        inserted: function(el, binding, vnode) {    
          el.style.color = binding.value;
        }
      })
      

      Argumentos

      Las directivas de Vue también aceptan argumentos. Los argumentos en vue se usan poniendo dos puntos después del nombre de la directiva. Por ejemplo:

      <app-navigation v-sticky:bottom></app-navigation>
      

      Para usar este valor dentro de la directiva tenemos que usar la propiedad arg que viene dentro del objeto binding:

      Vue.directive("sticky", 
        function(el, binding, vnode) {    
          console.log(binding.arg);
      });
      

      Modificadores

      También puedes usar modificadores en las directivas. Para usarlos es simplemente poniendo un punto después del nombre de la directiva y a continuación el nombre del modificador:

      <div v-test.foo></div>
      

      Dentro de la directiva, puedes recoger los modificadores así:

      Vue.directive("sticky", 
        function(el, binding, vnode) {  
          const modifiers = binding.modifiers;  
          console.log(modifiers);
      });
      

      Los modificadores normalmente se usan para eventos o directivas nativas, por ejemplo: click.stop.

      Directivas interesantes

      v-click-outside

      Sirve para detectar cuando se hace click fuera de un elemento del HTML. Esta directiva suele ser especialmente útiles en popups. Cuando el usuario hace click fuera del popup puedes ejecutar una función para ocultarlo:

      <div v-click-outside="onClickOutside"></div>
      

      https://github.com/ndelvalle/v-click-outside

      v-hotkey

      Sirve para poder hacer bindeos de teclas. Por ejemplo puedes hacer que al pulsar Esc se cierre un popup o que con cierta combinación de teclas ocurra algo:

      <template>
        <span v-hotkey="keymap" v-show="show"> Press `ctrl + esc` to toggle me! Hold `enter` to hide me! </span>
      </template>
      
      <script>
      export default {
        data () {
          return {
            show: true
          }
        },
        methods: {
          toggle () {
            this.show = !this.show
          },
          show () {
            this.show = true
          },
          hide () {
            this.show = false
          }
        },
        computed: {
          keymap () {
            return {
              // 'esc+ctrl' is OK.
              'ctrl+esc': this.toggle,
              'enter': {
                keydown: this.hide,
                keyup: this.show
              }
            }
          }
        }
      }
      </script>
      

      https://github.com/Dafrok/v-hotkey

      v-clipboard

      Simplemente añade la funcionalidad de copiar al portapapeles cuando se pulsa el botón:

      <button v-clipboard="value">
        Copy to clipboard
      </button>
      

      https://github.com/Inndy/vue-clipboard2

      v-lazyload

      Esto del lazyload es una cosa muy recomendable en cualquier web, sobre todo si tienes muchas imágenes. De lo que se trata es que las imágenes solo se carguen si están dentro del campo de visión del usuario, es decir, si hay imágenes debajo del scroll, solo se cargarían cuando entren en pantalla, cosniguiendo que la web cargue más rápido.

      Para implementar lazyload tienes que poner la ruta a tu imagen dentro del atributo v-lazy, pero cuaidado, no pongas el atributo src de la imagen para que no se cargue sola.

      <img v-lazy="https://www.domain.com/image.jpg">
      

      https://github.com/hilongjw/vue-lazyload

      Conclusiones

      Las directivas pueden llegar a ser muy potentes y te pueden salvar más de una vez de tener que añadir más código. Lo bueno como siempre, es que son reutilizables y vas a hacer que el código sea más mantenible.

      Por cierto, que se me olvidaba, puedes declarar directivas de forma local también dentro de un solo componente, de tal forma que solo sea accesible para ese componente en particular. Para ello tienes que crear la directiva o importarla dentro de la propiedad directives del componente.

      » Leer más, comentarios, etc...

      Arragonán

      Mejorando la velocidad de la suite de tests

      junio 09, 2020 12:00

      Cuando tienes la buena costumbre de escribir tests, ya sea antes o después, vas generando una red de seguridad que nos da feedback para saber que los escenarios cubiertos siguen funcionando correctamente. Eso nos facilita el cambiar el código con mayor confianza ya sea para refactorizar o añadir alguna nueva funcionalidad.

      Ese ciclo de feedback debería ser razonablemente corto, debería ayudarnos a concentrarnos y fluir en lo que estemos haciendo en ese momento. Si resulta lento seguramente acabaremos poniéndonos a hacer otras cosas mientras esperamos y seremos menos efectivos.

      Tira cómica de XKCD de Compiling

      En las suites de tests normalmente los principales cuellos de botella, además de los end-to-end, suelen ser los que hacen uso de sistemas externos como pueden ser la base de datos o el sistema de ficheros. Por eso, en la medida de lo posible, es saludable que las suites de tests traten de parecerse a la pirámide de test.

      Pero aún así los proyectos van creciendo y de la mano lo hacen las suites de tests, por lo que ese feedback irremediablemente se va ralentizando. Hay soluciones que minimizan el problema, como usar herramientas que ejecutan algunos tests al guardar el código o usar tags en los tests para ejecutar sólo un subconjunto de ellos.

      Pero eso no nos resolverá la raíz de los problemas, puede que incluso los enmascare. Lo mejor es dedicar un tiempo en analizar la suite de tests para detectar qué origina que sea lenta y ver si vale la pena intentar mejorarlo.

      Medir y mejorar

      Por ejemplo hace poco trabajando en el backend de Devengo, donde tenemos costumbre de escribir bastantes tests, tenía la percepción de que en unas pocas semanas los tiempos de ejecución de la suite de tests que ejecuta conjuntamente los unitarios y de integración habían degradado mucho. Tenía la hipótesis de que revisando los tests de integración podríamos encontrar puntos donde optimizar y obtener feedback antes.

      Podría haberme puesto a lo loco a cambiar tests e ir probando si bajaban los tiempos, pero suele ser más productivo utilizar herramientas que te ayuden a detectar las fuentes de los problemas y tomar decisiones en base a los datos que te dan. Así que empecé dedicando algunos ratos de holgura en analizar la suit de tests con TestProf.

      Con TestProf pude comprobar qué tipo de artefactos eran en los que se iba más tiempo y cuáles eran concretamente los tests más lentos, que era información bastante interesante. Pero lo más clarificador fue comprobar cómo nuestros tests hacían uso de las factorías de factory_bot que usamos para preparar los tests.

      Evidenciaba que el uso de las associations estaban provocando que se estuvieran creando en cascada muchos elementos en la base de datos y que había unas pocas factorías en las que se iba notablemente más tiempo. Ya con esos KPIs es mucho más fácil tomar decisiones de por dónde empezar.

      En la primera sentada decidí no tocar el código de producción y reducir el número de elementos que se creaban en cascada, ya que en muchos casos no eran necesarios. Así que los cambios fueron básicamente dos:

      • Minimizar el número de elementos en algunos usos de create_list para que esos tests cubrieran sólo casos límite.
      • Cambiar un buen número de tests para que en la propia preparación se crearan elementos de las associations usando el método create de forma explícita, de ese modo al pasar sus referencias a las factorías se evitaban generar muchas de las creaciones en cascada.

      Eso supuso un reducción de entre 15-20% del tiempo. Aún siendo una mejora sustancial había mucho margen de mejora porque el mayor cuello de botella seguía siendo los tiempos de una factoría en concreto.

      Tal y como me indicó Iván, estaba claro el principal sospechoso, ya que teníamos anotado un concern de deuda técnica desde hacía tiempo: una dependencia en un callback de ActiveRecord que había que quitar y mover a un sitio más apropiado. Así que en la segunda sentada trabajé en ese refactor y una vez hecho redujo significativamente los tiempos de ejecución, pasando de lanzar la suite en casi 2 minutos a algo menos de 30 segundos en mi máquina.

      Conclusiones

      Trata de buscar herramientas que te ayuden a medir antes de hacer cambios, es difícil ser consciente de si un cambio provoca mejoras sin ninguna referencia. De hecho, en algunos contextos incluso podría valer la pena instrumentalizar el efecto que tienen este tipo de mejoras en la productividad de los equipos.

      A más rápido siempre es mejor pero, igual que con el porcentaje de cobertura, tampoco creo que haya que andar obsesionándose con ello. Así que preocúpate (de nuevo igual que con la baja cobertura) cuando la lentitud de tu suite te esté suponiendo un obstáculo para el flujo en tu trabajo.

      Y recuerda que la suite de tests que vamos dejando tiene que aportar valor como red de seguridad para introducir cambios en el código. Así que ante todo debería ayudarnos a documentar lo que hace (y cómo está diseñado) el software y ser expresiva cuando falla.


      Más referencias

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      Lenguaje de programación Scratch

      junio 03, 2020 08:15



      Continuamos con la programación informática, una temática que había dejado algo abandonada, y que después del ataque retro de Depurar aplicaciones DOS con Watcom C y OpenWatcom C++, hoy atacaré con una nueva, y original tendencia: el lenguaje Scracth. Desde hace mucho que me fascinan los lenguajes de programación orientados a niños. Creo que la …

      Lenguaje de programación Scratch Leer más »



      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Fixed Buffer

      Cómo controlar un ordenador Windows utilizando solo los ojos

      junio 02, 2020 08:00

      Tiempo de lectura: 6 minutos
      Imagen ornamental para la entrada "Cómo controlar un ordenador Windows utilizando solo los ojos"

      Esta semana vengo a hablar de un tema un poco diferente y que se sale un poco de la temática del blog. No va a ser una entrada relacionada con ejecutar pruebas de código en Docker o escribir código de alto rendimiento en .Net Core ni ningún tema relacionado con el desarrollo.

      Hoy vamos a hablar de accesibilidad y de cómo poder controlar un ordenador con el movimiento de los ojos.

      ¿Por qué esta entrada?

      Sinceramente me hubiese gustado descubrir que esto era posible por simple investigación o por ocio, pero el motivo que hay detrás de investigar sobre esto es que mi padre tiene ELA en un estado muy avanzado y pronto será la única manera de comunicarse que va a disponer. Es por eso que he investigado sobre el tema para encontrar una manera sencilla de controlar un ordenador simplemente con la mirada.

      ELA es una enfermedad degenerativa incurable actualmente que hace que poco a poco pierdas la capacidad de controlar tu cuerpo hasta el punto de no ser capaz de mover ni un músculo.

      En lo personal, conocía de la existencia de este tipo de tecnologías, ya que por ejemplo Stephen Hawking (que también padecía ELA) controlaba su ordenador con la mirada. Lo que desconocía y ha sido una grata sorpresa, es que la tecnología de seguimiento ocular ha avanzado mucho en los últimos años y es fácil encontrar el hardware necesario para ello. Es por eso que escenarios como controlar un ordenador solo con la mirada es ahora posible con un coste realmente bajo. El único hardware extra cuesta unos 150€.

      Antes de seguir, decir que voy a hablar de productos comerciales y no es ningún tipo de publicidad encubierta ni nada por el estilo. Simplemente pretendo poner las cosas más fáciles a gente que igual no tiene la facilidad que tengo yo para manejarme en la era digital.

      Una vez hecha la introducción, ¡vamos a ponernos manos a la obra!

      ¿Qué necesito para controlar un ordenador con los ojos?

      Esta pregunta es muy sencilla, lo primero de todo es un ordenador, unos ojos, y un sistema que permita seguir el movimiento de los ojos y traducirlo a movimientos del ratón.

      No se requiere ninguna maravilla de ordenador, de hecho, todo esto puede funcionar en una tablet sin mayor esfuerzo. El único requisito mínimo es que el equipo sea Windows 8 o superior y que tenga instalado .Net Framework 4.6.

      Respecto al hardware para hacer el seguimiento de los ojos y poder controlar así el ordenador, no está definido un modelo exacto pero en mi caso concreto yo me he decantado por Tobii Eye Tracker 4C.

      La imagen muestra el Tobii Eye Tracker 4C utilizado para controlar el ordenador con los ojos

      Tiene un coste de 169€ al momento de escribir esta publicación y funciona muy bien con Windows 10.

      Instalar y calibrar el Tobii Eye Tracker 4C

      Una vez recibido el aparato, el propio paquete contiene todo lo necesario para poder colocarlo. El sistema de sujeción está compuesto por una pegatina para pegar en la parte inferior de la pantalla y la barra se fija a la pegatina mediante imanes, por lo que se puede poner y quitar el tracker de manera sencilla.

      La imagen muestra el Tobii Eye Tracker 4C montado en la parte inferior de la pantalla para controlar el ordenador con los ojos

      Una vez que lo hemos colocado en la pantalla, hay que instalar el software del fabricante que podemos encontrar en la sección de descargas de su web.

      Una vez que hemos descargado e instalado el software (Tobii Eye Tracking), basta con arrancarlo para que la primera vez se nos lance un asistente de calibración de manera automática. Durante el proceso de calibración del dispositivo, se nos pedirán cosas como indicarle la posición del dispositivo en la pantalla gracias a unas marcas que trae el propio dispositivo y después tendremos que mirar fijamente una serie de puntos para que se haga la calibración. Ese es todo el trabajo que requiere calibrar el dispositivo.

      Una vez que lo hayamos hecho, se quedará un icono en la barra de notificaciones de Windows para poder volver a acceder a la configuración del aparato.

      La imagen muestro el icono del programa Tobii Eye Tracking en la barra de iconos

      ¿Con solo esto ya podemos controlar el ordenador con los ojos? No, pero estamos cerca ya.

      Instalar OptiKey para controlar el ordenador con los ojos

      Ahora mismo gracias al hardware de seguimiento ocular, ya tenemos información sobre donde estamos mirando en la pantalla. Para poder controlar el ordenador con la mirada solo nos falta algo que convierta esa información en movimientos del ratón.

      Esto es lo que vamos a conseguir gracias al software Open Source OptiKey. OptiKey es un programa hecho por Julius Sweetland y cuyo código fuente está disponible en Github que va a ser el encargado de convertir ese movimiento de los ojos en movimientos del ratón. Este software es gratuito y el proyecto sale adelante a través de Patreon.

      Basta con descargarlo desde su página web donde se encuentra disponible en primera página.

      La imagen muestra el botón de download de la página web de OptiKey

      Donde nos lleva a otro apartado para seleccionar la versión que mejor se adapta a las necesidades. Puede ser control total, solo chat, solo ratón… En este caso cómo lo que queremos es controlar el ordenador entero con la mirada, vamos a seleccionar la versión pro (que es solo nomenclatura, no es que sea de pago).

      La imagen señala el botón download de la version pro de OptiKey

      Una vez que lo hemos descargado, lo vamos a instalar con el propio asistente de instalación que ofrece. Cuando lo tenemos instalado basta con ejecutar el programa OptiKeyPro desde la propia barra de herramientas.

      La imagen muestra el programa OptiKeyPro en la barra de busqueda de Windows

      Esto va a dividir la pantalla en 2, dejando arriba un teclado con botones gracias a los cuales vamos a poder controlar el ordenador con la mirada.

      La imagen muestra OptiKeyPro controlando un ordenador con el movimiento de los ojos

      El funcionamiento, aunque pueda parecer complejo es muy simple, basta con mirar a una tecla en concreto y mantener la mirada para que aparezca un círculo que al completarse pulsa la tecla.

      Conclusiones

      Se que es una entrada diferente al contenido habitual de este blog y aunque está relacionada con la informática, está más cerca de la accesibilidad que del desarrollo. Como bien decía al principio, la finalidad es dar a conocer este tipo de tecnología a la gente que tiene mayores dificultades para acceder a la información y dar una guía fácil de seguir sobre como poder controlar un ordenador simplemente con la mirada.

      Que nadie se preocupe porque la próxima entrada volveremos a los temas habituales de FixedBuffer, ya que últimamente he trabajado con varias cosas interesantes de las que hablaremos muy pronto.

      Por último, y esto es la primera vez que lo hago, pido a todos los lectores que lleguen hasta aquí que comportan la entrada para intentar llegar ese grupo de personas como puede ser mi madre, que no son frikis tecnológicos pero que que pueden necesitar en algún momento saber que esto existe.

      Y ya para terminar, ¿tu sabias que era tan sencillo controlar un ordenador solo con la mirada?

      **La entrada Cómo controlar un ordenador Windows utilizando solo los ojos se publicó primero en Fixed Buffer.**

      » Leer más, comentarios, etc...

      Meta-Info

      ¿Que es?

      Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

      rss subscripción

      Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
      planetacodigo

      planetacodigo

      Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

      Idea: Juanjo Navarro

      Diseño: Albin