Hernán Grecco: Simulated devices in PyVISA: early preview

PyVISA started as wrapper for the NI-VISA library and therefore you need to install National Instruments VISA library in your system. This works most of the time, for most people. But sometimes you need to test PyVISA without the devices or even without VISA.

Starting form version 1.6, PyVISA allows to use different backends. These backends can be dynamically loaded. PyVISA-sim is one of such backends. It implements most of the methods for Message Based communication (Serial/USB/GPIB/Ethernet) in a simulated environment. The behavior of simulated devices can be controlled by a simple configuration in plain text. In the near future, you will be able to load this from file to change it depending on your needs.


To test it you need to install PyVISA 1.6 which is currently only available from GitHub:

$ pip install -U https://github.com/hgrecco/pyvisa/zipball/master

And then install:

$ pip install -U https://github.com/hgrecco/pyvisa-sim/zipball/master
   

For those of you interest in the internals, the plugin mechanism for PyVISA hooks at the VisaLibrary level. Mocking the Library allows for a comprehensive and powerful testing.

By the end of the week I will be blogging about another cool VISA backend which will be opened soon: PyVISA-py. It is a backend that does not require the NI-VISA library. Stay tuned!

Remember that this is an early preview. Submit your bug reports, comments and suggestions in the Issue Tracker. We will address them promptly.

Or fork the code: https://github.com/hgrecco/pyvisa-sim/

Marcelo Fernández: Comparando costos de Amazon EC2 y Google Computing Engine

Estuve mirando y por suerte son prácticamente similares las tablas disponibles en cada site (EC2 / GCE) y es relativamente sencillo compararlas [1][2].

Para sus Data Centers en USA, establecí las siguientes relaciones:

  • En las versiones de VMs “standard“, los precios son exactamente iguales, con configuraciones llamativamente similares.
  • En las versiones “high memory“, es más barato Google (la mitad), aunque Amazon te da el doble de CPUs por una VM con la misma cantidad de memoria.
    Ej: Google te da 4 CPUs / 26 GB de RAM y Amazon en cambio te da 8 CPUs para su configuración de 26 GB de RAM. Dado que estamos en “high memory“, igualé a cantidad de memoria disponible para luego decir “Google es la mitad de barato”.
  • En las versiones “large” (“high cpu” de Google), Amazon es un 15% más caro para igual cantidad de CPUs, pero te da el doble de memoria en sus VMs.

Observaciones:

  • Se desprende de lo anterior Amazon EC2 ofrece perfiles más simétricos que Google comparando la relación de cantidades de CPU/Memoria. Puede ser útil para algunas aplicaciones o no, ya que va obviamente asociado al costo.
  • Amazon tiene configuraciones con más CPUs ya que llega a 32 CPUs y más memoria también: 244 GB; Google llega a 16 CPUs y 104 GB de memoria.
  • No comparé tamaños de almacenamiento, asumo que no tiene tanta relevancia para nuestra aplicación (se disponen de varias decenas de GB en disco en ambos).
  • Amazon dice que te incluye discos de estado sólido (SSD). Google te cobra ambos (?) 0,04 USD por GB/mes el disco estándar, 0,325 USD GB/mes el SSD.
  • Fundamental para la región en la que vivo: Amazon tiene Data Center en San Pablo (Brasil), Google no; sólo sirve VMs desde USA, Europa y Asia/Pacífico.
  • Amazon tiene diferentes tipo de instancias, en este caso comparé las “Bajo demanda” aka “Dedicadas” (según el lugar que se mire en la documentación), que son las más caras. Las otras son tipo prepagas (“Instancias Reservadas”), donde según entiendo, uno abona un importe fijo y después usa X cantidad de tiempo y se descuenta del fijo.
    Hay otros tipos de instancias pero no se ajustan a nuestro uso (“Instancias Puntuales” y “Instancias optimizadas para EBS”). En este apartado, pareciera que Amazon tiene largamente muchas más ofertas y más desarrollo del negocio que Google.

Bonus Track: versus Linode.com

  • Linode tiene hasta 20 CPUs / 96 GB RAM máximo [3].
  • Linode es mucho más barato que ambos; es más simple su tabla de precios y más “1 a 1” la distribución de CPUs / RAM (4GB / 4CPUs, 8 GB / 6 CPUs, y así hacia arriba), además de que da bastante más storage en SSD.
    Por ejemplo, 4 GB / 4 CPUs en Linode: USD 0,06/hora (USD 40/mes), mientras que en Amazon estamos en USD 0,28/hora por 15GB / 4 CPUs o USD 0,105/hora por 3,75GB / 2 CPUs. Eso sí, Linode está en USA (Newark/Atlanta/Dallas/Fremont), Europa (Londres) y Asia (Tokio), no en Sudamérica.
  • Comparando Linode contra GCE:
    • 1 Linode (2 GB de RAM/ 2 CPUs / 3 TB transfer / 48 GB SSD): U$S 20/mes.
    • 1 GCE (1,70 GB RAM / CPUs “shared” (?) / 48 GB SSD): U$S 33.29/mes (no dicen si hay límite de transferencia).
  • A Linode lo conocemos los que estamos “en la pomada”, y hay casos en donde suena mucho mejor “lo tengo repartido en la nube de Amazon/Google”, nobleza obliga.

[1] http://aws.amazon.com/es/ec2/purchasing-options/dedicated-instances/
[2] https://cloud.google.com/products/compute-engine/
[3] https://www.linode.com/pricing

¿Sugerencias, comentarios?

Saludos

Gonzalo Martinez: Aprendiendo Erlang parte 6 Modulos III

Más acerca de módulos

Antes de movernos a profundizar nuestro conocimiento en relación a como escribir funciones y algunos fragmentos de código, pero antes tenemos un poco más de información que te será útil en el futuro.

Una de las primeras son los metadatos de los módulos. Los atributos de los módulos son metadatos que describen el módulo en si mismo. Donde podemos encontrar estos metadatos cuando no tenemos acceso al código fuente? Bueno, el compilador nos ayuda con esto: cuando compilamos un módulo, este toma la mayoría de los atributos y los almacena en una función llamada module_info/0 . Así pueden ver los metadatos de un módulo.

9> useless:module_info().
[{exports,[{add,2},
{hello,0},
{greet_and_add_two,1},
{module_info,0},
{module_info,1}]},
{imports,[]},
{attributes,[{vsn,[174839656007867314473085021121413256129]}]},
{compile,[{options,[]},
{version,"4.6.2"},
{time,{2009,9,9,22,15,50}},
{source,"/home/ferd/learn-you-some-erlang/useless.erl"}]}]
10> useless:module_info(attributes).
[{vsn,[174839656007867314473085021121413256129]}]
El snippet anterior además muestra la función module_info/1 que permite solicitar una pieza especifica de información.

Sintaxis en funciones

Coincidencia de patrones

Ahora tenemos la habilidad de almacenar y compilar código, podemos empezar a escribir funciones más avanzadas. La primera función que vamos a escribir necesita saludar de manera diferente según el genero.  En la mayoría de los lenguajes podrías escribir algo así.

function saludar(Genero, Nombre)
    if Genero == masculino then
        printf("Hola, Sr. %s!", Nombre)
    else if Genero == femenino then
        printf("Hola Sra. %s!", Nombre)
    else
        printf("Hola, %s!", Nombre)

Con coincidencia de patrones (pattern matching), erlang te ayuda a no escribir tanto código similar. La misma función en erlang se vería como esto.

saludar(masculino, Nombre) ->
    io:format("Hola, Sr. ~s!", [Nombre]);
saludar(femenino, Nombre) ->
    io:format("Hola, Sra. ~s!", [Nombre]);
saludar(_, Nombre) ->
    io:format("Hola, ~s!", [Nombre]).

Hay que adminit que la función de impresión por pantalla es un poco más fea en erlang que entro lenguaje pero ese no es el punto. La diferencia principal aquí es que nosotros usamos la coincidencia de patrones para definir las dos partes de una función se debe utilizar y ligas los valores al mismo tiempo. Aquí no se necesita primero ligar los valores y entonces compararlos.
En lugar de:

function(Args)
    if X then
        Expression
    else if Y then
        Expression
    else
        Expression

Nosotros escribimos:

function(X) ->
    Expression;
function(Y) ->
    Expression;
function(_) ->
    Expression.

De esta manera se obtiene los mismo resultados pero con un estilo más declarativo. Cada una de estas declaraciones de funciones es llamada clausula de función. Las clausulas de función deben ser separadas con punto y coma ";"

La coincidencia de patrones en las funciones puede ser más complejo y poderoso que eso. Tal vez recuerdas de capitulos anteriores donde usabamos coincidencia de patrones patrones para encontrar la cabeza y cola de una lista. Vamos a hacer esto.  Creamos un modulo llamado funciones.

-module(functions).
-compile(export_all).

La primera función que vamos a escribir es head/1, que actua exactamente como erlang:hd/1 que toma una lista como argumento y retorna su primer elemento. Los haremos con la ayuda del signo "|".

head([H|_])  -> H.

Si vos escribis functions:head([1,2,3,4]) en la terminal (una vez que el modulo sea compilado), puedes esperar que te retorne el valor 1. Consecuentemente para obtener el segundo elemento de la lista, puedes crear la siguiente función.

second([_,X|_]) -> X.

La lista será deconstruida por erlang en orden a hacer coincidir los patrones. Intentalo en la terminal.

1> c(functions).
{ok, functions}
2> functions.head([1,2,3,4]).
1
3> functions.second([1,2,3,4]).
2

esto podría ser repetido en la lista tanto como quitas, pero es impráctico para cientos de valores. Esto se puede resolver escribiendo funciones recursivas, aunque veremos como más adelante. Por ahora concentremonos más en la coincidencia de patrones. El concepto de variables libres y llenas nosotros los discutimos anteriormente, esto es así también para las funciones, podemos entonces comparar y conocer si dos parametros pasados a una función son lo mismo o no. Para esto, crearemos una función llamada same/2 que toma dos argumentos y dice si son identicos.

same(X,X) ->
    true;
save(_,_) ->
    false.

Y es así de simple.

Guardas, guardas.

Las guardas son clausulas adicionales que pueden ir en la cabecera de una función para hacer la coincidencia de patrones más expresiva. Como mencionamos antes la coincidencia de patrones está de alguna manera limitada ya que no puede expresar cosas como rangos de valores o cierto tipo de datos. Unos conceptos que no podemos representar serían los siguientes. Es este jugador de 12 años demasiado petizo para jugar con los profesionales? Es esta distincia demasiado larga para caminar sobre tus manos? Eres demasiado viejo o demasiado joven para manejar un auto?. No puedes responder esto simplemente con coincidencia de patrones. Se puede representar la pregunta sobre el manejo de un auto de la siguiente manera.

old_enough(0) -> false;
old_enough(1) -> false;
old_enough(2) -> false;
...
old_enough(14) -> false;
old_enough(15) -> false;
old_enough(_) -> true.

Pero esto es increiblemente impráctico. Puedes hacerlo si lo quieres, pero trabajarás solo en tu código por siempre. Si quieres eventualmente hacer amigos, entonces debes usar el modulo de guardas  así podremos escribir la pregunta sobre el manejo de la siguiente manera.

old_enough(X) when X >= 16 -> true;
old_enough(_) -> false.

y listo. Como puedes ver es mucho más limpio y corto. Notarás que la regla básica de una guarda es que debe retornar true cuando es correcta, la guarda puede fallar si retorna false o si lanza una excepción. Supongamos ahora que no queremos tener en cuenta a las personas que son mayores de 104 años. Entonces deberiamos cuidarnos de eso, pero como?, simplemente agregando una segunda guarda.

right_age(X) when X >= 16, X <= 104 ->
    true;
right_age(_) ->
    false.

La "," funciona como un "y tambien", y el punto y coma ";"  funciona como un "o sino"


http://learnyousomeerlang.com/modules#mode-about-modules

Hernán Grecco: PyVISA 1.6 brings comprehensive resources classes, thread-safety and more

PyVISA is a Python frontend for the VISA library that enables controlling all kinds of measurement equipment through GPIB, RS232, USB, Ethernet among others interfaces.

PyVISA 1.6 release is close and it brings several new nice things. One of the most visible improvements is the addition of a comprehensive set of resources classes, each mapping to a one of the 13 types of resources. Each class implements the methods and attributes that the specific session can handle, providing a Pythonic way to interact with the device (See here). This has allowed us to implement higher level functions such as the group execute trigger in the GPIB Interface.

PyVISA 1.6 brings a much better way to query a device for values, providing a comprehensive API to convert back and from ASCII and binary blocks. An API to write values to a device has been added (See here).

PyVISA 1.6 is thread-safe. While the VISA library has always been thread-safe, PyVISA was not. We have refactored the code removing several global variables that were used to handle state and tweaked the API accordingly.

PyVISA 1.6 continues on the road started with 1.5 to improve debugging, usability and reporting. The quality and density of logging messages have been improved as well as the information provided with the error messages. Python 3.4 Enums are now used to handle constants (the backported packaged enum34 is installed in older Python version).

There is one more change under the hood. PyVISA 1.6 can discover and use other backends to talk to devices (See here). This feature is allowing us to prototype some new packages about which I am going to blog next week.

All these things are only possible as we have dropped some backwards compatibility requirements. In particular, we have removed the legacy package, the string manipulation functions and all the singletons. Most programs will run without major changes but in any case, all API modifications are documented here to help with the transition.

All this would not have been possible without the support of a great community that has been helping with the development and testing different development versions. But we really need your help before uploading this to PyPI as an stable release. PyVISA is all about interacting with your operating system (finding and locating the C library) and you instruments. With the variety of platforms around, it is very difficult to test. We are asking all users to install PyVISA from the github repository and test it on your programs. You can install PyVISA or upgrade to the development version using pip:
 

$ pip install -U https://github.com/hgrecco/pyvisa/zipball/master

Again, this release is only possible thanks to the contribution of a lot of people that contributed bug reports, testing and code. Thanks to everybody!

Submit your bug reports, comments and suggestions in the Issue Tracker. We will address them promptly.

Read the development docs: https://pyvisa.readthedocs.org/en/master/
or fork the code: https:/https://github.com/hgrecco/pyvisa/

Diego Sarmentero: Películas de Aventuras!

Si bien hay muchas películas de aventuras muy buenas, y en esta lista ni siquiera incluyo otras que podría decir que son mis favoritas, tenia ganas de hacer una lista de películas de aventuras, pero de aquellas que tienen cierta ambientación especial, todas estas películas diría que tienen una estética muy similar entre fantasia y algo de absurdo (en mas o menos detalle), y eso es lo que las hace tan interesantes para mi:

The Grand Budapest Hotel


City of Ember


Hugo


Big Fish


The Adventures of Tintin








Cual película agregarían a esta lista??


Gonzalo Martinez: WeUseShort - Un acortador de Urls con tu propio dominio

Con la intención de probar AngularJS en algunas cuestiones y hacer algo sencillo en Flask y Redis para probar esas tecnologías fue que me dispuse a hacer el pequeño ejemplo de un Acortador de Urls que cualquier puede usar para acortar las urls que envia en sus redes sociales. Si bien twitter lo hace por default a veces es bonito tener tu propio subdominio acortado con tu propia redirección. Por eso está es una aplicación muy sencilla que cumple su objetivo y que está abierta a que la mejores y propongas cambios.


Caracteristicas:
Acorta urls usando tu propio dominio.
Redis como db.
Cuenta los hits por cada url.
Login sencillo.
Ultrarápido

Mejoras pensadas para un futuro:

Mapa de Geolocalización por IP origen. (GeoIP)
Redirección por tipo de dispositivo (user-agent?)

Si alguien quiere colaborar

https://github.com/gonzafirewall/WeUseShort

Están más que invitados.

Mariano Guerra: Making a Chat App with Erlang, Rebar, Cowboy and Bullet

this is a continuation of the post about erlang, cowboy and rebar.

let' start by adding the bullet dep:

{sub_dirs, ["rel"]}.

{deps, [
    {cowboy, "1.0.0", {git, "https://github.com/ninenines/cowboy", {tag, "1.0.0"}}},
    {bullet, "0.4.1", {git, "https://github.com/extend/bullet", {tag, "0.4.1"}}}
]}.

now get the deps:

./rebar get-deps

add bullet as a dependency on disrupt.app.src:

{application, disrupt,
 [
  {description, ""},
  {vsn, "1"},
  {registered, []},
  {applications, [
                  kernel,
                  stdlib,
                  cowboy,
                  bullet
                 ]},
  {mod, { disrupt_app, []}},
  {env, []}
 ]}.

register our bullet handler with cowboy:

start(_StartType, _StartArgs) ->
    {ok, ChannelPid} = disrupt_channel:new(),
    Dispatch = cowboy_router:compile([
        {'_', [
               {"/chat", bullet_handler, [{handler, disrupt_chat_handler}, {channel, ChannelPid}]},
               {"/ui/[...]", cowboy_static, {priv_dir, disrupt, "assets",
                                             [{mimetypes, cow_mimetypes, all}]}}
        ]}
    ]),
    {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
        {env, [{dispatch, Dispatch}]}
    ]),
    disrupt_sup:start_link().

the important lines are:

{ok, ChannelPid} = disrupt_channel:new(),

were we create a new chat channel that will be passed to all disrupt_chat_handler instances here:

{"/chat", bullet_handler, [{handler, disrupt_chat_handler}, {channel, ChannelPid}]},

also note that I changed the path of the ui to /ui/... instead of it being at the root.

add the js libs we need to use bullet (yes, I could use bower, but let's keep it simple):

mkdir priv/assets/vendor
mkdir priv/assets/js
cp deps/bullet/priv/bullet.js priv/assets/vendor
wget http://code.jquery.com/jquery-2.1.1.min.js -O priv/assets/vendor/jquery.js

we create a really simple chat page at index.html:

<!doctype html>
<html>
 <head>
  <title>Chat!</title>
  <meta charset="utf-8">
  <script src="vendor/jquery.js"></script>
  <script src="vendor/bullet.js"></script>
  <script src="js/app.js"></script>
  <style>
   body{ font-family: helvetica; color: #333; background-color: #fefefe;
    margin-left: 25%; width: 50%; }
   p, textarea{ padding: 0; }
   p, button, textarea{ width: 100%; margin: 1em 0; }
   label{ float: left; width: 45%; }
   input{ width: 50%; float: right; }
   input, textarea{ border: 1px solid #ddd; }
  </style>
 </head>
 <body>
  <p>
  <label for="input">Nickname</label>
  <input type="text" id="nick" value="anonymous"/>
  </p>
  <textarea id="output" cols="80" rows="25"></textarea>
  <p>
  <label for="input">Input</label>
  <input type="text" id="input"/>
  <button id="send">Send</button>
  </p>
 </body>
</html>

add code to handle the chat app on priv/assets/js/app.js:

/*globals $, document, window*/
function disruptApp(document, window, $) {
    'use strict';
    var input = document.getElementById('input'),
        output = document.getElementById('output'),
        nickInput = document.getElementById('nick'),
        send = document.getElementById('send'),

        connection;

    function sendMessage(text) {
        var nick = getNick();
        connection.send(nick + ': ' + text);
    }

    function onSendClicked() {
        var text = input.value.trim();

        if (text !== '') {
            sendMessage(text);
        }

        input.value = '';
    }

    function getNick() {
        var nick = nickInput.value.trim();

        if (nick === '') {
            return 'anonymous';
        } else {
            return nick;
        }
    }

    function notify(text) {
        var date = (new Date()).toLocaleString();
        output.innerHTML = output.innerHTML + '[' + date + '] ' + text + '\n';
    }

    function onData(data) {
        notify(data);
    }

    send.addEventListener('click', onSendClicked);

    function start(url, options, notify, onData) {
        var connection = $.bullet(url, options);

        connection.onopen = function(){
            notify('online');
        };

        connection.onclose = connection.ondisconnect = function(){
            notify('offline');
        };

        connection.onmessage = function(e){
            if (e.data === 'pong'){
                notify('pong');
            } else {
                onData(e.data);
            }
        };

        connection.onheartbeat = function(){
            connection.send('ping');
            notify('ping');
        };

        return connection;
    }

    connection = start('ws://localhost:8080/chat', {}, notify, onData);
}

document.addEventListener("DOMContentLoaded", function() {
    'use strict';
    disruptApp(document, window, $);
});

we need a simple pubsub mechanism for channels, I won't explain it here in detail but you can read about erlang's gen_event behaviour which is the one that does all the work:

-module(disrupt_channel).
-behaviour(gen_event).

-export([new/0, subscribe/2, unsubscribe/2, send/2]).

-export([init/1, handle_event/2, handle_call/2, handle_info/2, code_change/3,
         terminate/2]).
%% API

new() -> gen_event:start_link().

subscribe(Channel, Pid) ->
    gen_event:add_handler(Channel, {disrupt_channel, Pid}, [Pid]).

unsubscribe(Channel, Pid) ->
    gen_event:delete_handler(Channel, {disrupt_channel, Pid}, [Pid]).

send(Channel, Event) ->
    gen_event:notify(Channel, Event).

-record(state, {pid}).
%% callbacks
init([Pid]) -> {ok, #state{pid=Pid}}.

handle_event(Msg, State=#state{pid=Pid}) ->
    Pid ! Msg,
    {ok, State}.

handle_call(_, State) -> {ok, ok, State}.

handle_info(_, State) -> {ok, State}.

code_change(_OldVsn, State, _Extra) -> {ok, State}.

terminate(_Reason, _State) -> ok.

the important part from the code above is the fact that we store the pid of the bullet handler on the gen_event instance so we can send the message back when we get notified:

handle_event(Msg, State=#state{pid=Pid}) ->
    Pid ! Msg,
    {ok, State}.

the bullet handler for the chat channel is:

-module(disrupt_chat_handler).

-export([init/4, stream/3, info/3, terminate/2]).

-record(state, {channel}).

init(_Transport, Req, Opts, _Active) ->
    io:format("channel init ~p~n", [Opts]),
    {channel, ChannelPid} = lists:keyfind(channel, 1, Opts),
    disrupt_channel:subscribe(ChannelPid, self()),
    {ok, Req, #state{channel=ChannelPid}}.

stream(<<"ping">>, Req, State) ->
    io:format("ping received~n"),
    {reply, <<"pong">>, Req, State};

stream(Data, Req, State=#state{channel=ChannelPid}) ->
    io:format("message received ~s~n", [Data]),
    disrupt_channel:send(ChannelPid, {msg, self(), Data}),
    {ok, Req, State}.

info({msg, _Sender, Data}, Req, State) ->
    io:format("msg received ~p~n", [Data]),
    {reply, Data, Req, State}.

terminate(_Req, #state{channel=ChannelPid}) ->
    io:format("unsubscribing from channel~n"),
    disrupt_channel:unsubscribe(ChannelPid, self()),
    ok.

we subscribe on init, unsubscribe on terminate, when we receive a ping message we reply it only to the sender with pong, if we receive something else we send the message to the channel so it gets sent to all subscribers, that get the message on the info function where they send it to the browsers.

build:

rm -rf rel/disrupt && ./rebar compile generate

run:

./rel/disrupt/bin/disrupt console

open http://localhost:8080/ui/index.html in two or more browsers and chat!

../galleries/misc/bullet-chat.png

Mariano Guerra: Serving Static Files with Erlang, Cowboy and Rebar, (raw material)

the "serving static files from a server" market needs some disruption, let's tackle that problem with some erlang.

create the folder:

mkdir disrupt
cd disrupt

get rebar:

wget http://cloud.github.com/downloads/basho/rebar/rebar
chmod u+x rebar

generate app:

./rebar create-app appid=disrupt

add dependencies:

vim rebar.config

the file should contain the following code:

{deps, [
    {cowboy, "1.0.0", {git, "https://github.com/ninenines/cowboy", {tag, "1.0.0"}}}
]}.

this tells to get cowboy 1.0.0 from git as dependency, let's fetch the dependencies:

./rebar get-deps

now let's serve some static files, open src/disrupt_app.erl, change the start function so it looks like this:

start(_StartType, _StartArgs) ->
    Dispatch = cowboy_router:compile([
        {'_', [
            {"/[...]", cowboy_static, {priv_dir, disrupt, "assets",
                [{mimetypes, cow_mimetypes, all}]}}
        ]}
    ]),
    {ok, _} = cowboy:start_http(http, 100, [{port, 8080}], [
        {env, [{dispatch, Dispatch}]}
    ]),
    disrupt_sup:start_link().

the code above is taken from the static example from cowboy adapted to our needs: https://github.com/ninenines/cowboy/tree/master/examples/static_world

docs here: http://ninenines.eu/docs/en/cowboy/1.0/guide/static_handlers/

now make the priv/assets folder:

mkdir -p priv/assets

and put some content in the index file:

echo "hello static world" > priv/assets/index.html

let's make a release for the project, first create the release files:

mkdir rel
cd rel
../rebar create-node nodeid=disrupt
cd ..

edit rel/reltool.config, the line:

{lib_dirs, []},

should change to:

{lib_dirs, ["../deps"]},

add the line:

sub_dirs, ["rel"]}.

to the top of the rebar.config file

if not you get this error:

Command 'generate' not understood or not applicable

yay helpfulness!

now we are one confusing error closer to our goal, but now we get:

ERROR: generate failed while processing /home/mariano/tmp/disrupt/rel: {'EXIT',{{badmatch,{error,"disrupt: Missing application directory."}},
         [{rebar_reltool,generate,2,[]},
          {rebar_core,run_modules,4,[]},
          {rebar_core,execute,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]},
          {rebar_core,process_each,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]}]}}

only because I've seen some other erlang projects I decided that this may be the solution:

mkdir -p apps/disrupt
cd apps/disrupt
ln -s ../../src
ln -s ../../ebin
ln -s ../../priv
cd ../..

we try again to make the release and we get the error again, nice!

for no apparent reason that the fact that I saw it on some other project, change the line on rel/reltool.config:

{lib_dirs, ["../deps"]},

to:

{lib_dirs, ["../deps", "../apps"]},

trying again:

./rebar compile generate

success!

let's try running it:

./rel/disrupt/bin/disrupt console

and what do we get? well, a crash! \o/:

Exec: /home/mariano/tmp/disrupt/rel/disrupt/erts-5.10.4/bin/erlexec -boot /home/mariano/tmp/disrupt/rel/disrupt/releases/1/disrupt -mode embedded -config /home/mariano/tmp/disrupt/rel/disrupt/releases/1/sys.config -args_file /home/mariano/tmp/disrupt/rel/disrupt/releases/1/vm.args -- console
Root: /home/mariano/tmp/disrupt/rel/disrupt
Erlang R16B03 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(disrupt@127.0.0.1)1>
=INFO REPORT==== 29-Aug-2014::11:49:32 ===
    application: disrupt
    exited: {bad_return,
             {{disrupt_app,start,[normal,[]]},
              {'EXIT',
               {undef,
                [{cowboy_router,compile,
                  [[{'_',
                     [{"/[...]",cowboy_static,
                       {priv_dir,disrupt,"assets",
                        [{mimetypes,cow_mimetypes,all}]}}]}]],
                  []},
                 {disrupt_app,start,2,
                  [{file,"src/disrupt_app.erl"},{line,13}]},
                 {application_master,start_it_old,4,
                  [{file,"application_master.erl"},{line,269}]}]}}}}
    type: permanent
{"Kernel pid terminated",application_controller,"{application_start_failure,disrupt,{bad_return,{{disrupt_app,start,[normal,[]]},{'EXIT',{undef,[{cowboy_router,compile,[[{'_',[{\"/[...]\",cowboy_static,{priv_dir,disrupt,\"assets\",[{mimetypes,cow_mimetypes,all}]}}]}]],[]},{disrupt_app,start,2,[{file,\"src/disrupt_app.erl\"},{line,13}]},{application_master,start_it_old,4,[{file,\"application_master.erl\"},{line,269}]}]}}}}}"}

Crash dump was written to: erl_crash.dump
Kernel pid terminated (application_controller) ({application_start_failure,disrupt,{bad_return,{{disrupt_app,start,[normal,[]]},{'EXIT',{undef,[{cowboy_router,compile,[[{'_',[{"/[...]",cowboy_static

with all my time reading erlang crashes I see an undef there, it seems the app cant find cowboy_router:compile

but I have it on my deps..

well, let's put it on src/disrupt.app.src applications for no other reason that I've seen people do that, the file should look like this:

{application, disrupt,
 [
  {description, ""},
  {vsn, "1"},
  {registered, []},
  {applications, [
                  kernel,
                  stdlib,
                  cowboy
                 ]},
  {mod, { disrupt_app, []}},
  {env, []}
 ]}.

see the cowboy as last element on the applications list? that's what you should add.

let's try again:

./rebar compile generate

and I get:

<some output removed here>

==> rel (generate)
ERROR: generate failed while processing /home/mariano/tmp/disrupt/rel: {'EXIT',{{badmatch,{error,"Application cowboy is used in release \"disrupt\" and cannot be excluded"}},
         [{rebar_reltool,generate,2,[]},
          {rebar_core,run_modules,4,[]},
          {rebar_core,execute,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]},
          {rebar_core,process_each,5,[]},
          {rebar_core,process_dir0,6,[]},
          {rebar_core,process_dir,4,[]}]}}

"Application cowboy is used in release "disrupt" and cannot be excluded"

who told you to exclude it?

let's apply a technic we already used a lot before, let's make up reasons for what it may be failing, it says exclude there and I've seen a lot of exclude and include atoms in rel/reltool.config, maybe it's that?

let's layer some other technic I use a lot, let's try to make that file look as similar as another one I've seen that works, in this case it's the reltool.config file generated by riak_core rebar template:

https://github.com/basho/rebar_riak_core/blob/master/riak_core.reltool.config

ok, that one seems to have less stuff than ours, let's start commenting everything that looks different until we reach this point:

{sys, [
    {lib_dirs, ["../deps", "../apps"]},
        %{erts, [{mod_cond, derived}, {app_file, strip}]},
        %{app_file, strip},
        {rel, "disrupt", "1",
            [
                kernel,
            stdlib,
            sasl,
            disrupt
                ]},
        {rel, "start_clean", "",
            [
                kernel,
            stdlib
                ]},
        {boot_rel, "disrupt"},
        {profile, embedded},
        %{incl_cond, exclude},
        {excl_archive_filters, [".*"]}, %% Do not archive built libs
    {excl_sys_filters, ["^bin/.*", "^erts.*/bin/(dialyzer|typer)",
        "^erts.*/(doc|info|include|lib|man|src)"]},
        {excl_app_filters, ["\.gitignore"]},
        {app, sasl,   [{incl_cond, include}]},
        %{app, stdlib, [{incl_cond, include}]},
        %{app, kernel, [{incl_cond, include}]},
        {app, disrupt, [{incl_cond, include}]}
    ]}.

{target_dir, "disrupt"}.

{overlay, [
    {mkdir, "log/sasl"},
        {copy, "files/erl", "\{\{erts_vsn\}\}/bin/erl"},
        {copy, "files/nodetool", "\{\{erts_vsn\}\}/bin/nodetool"},
        {copy, "files/disrupt", "bin/disrupt"},
        {copy, "files/disrupt.cmd", "bin/disrupt.cmd"},
        {copy, "files/start_erl.cmd", "bin/start_erl.cmd"},
        {copy, "files/install_upgrade.escript", "bin/install_upgrade.escript"},
        {copy, "files/sys.config", "releases/\{\{rel_vsn\}\}/sys.config"},
        {copy, "files/vm.args", "releases/\{\{rel_vsn\}\}/vm.args"}
    ]}.

trying again:

rm -rf rel/disrupt
./rebar compile generate

success!

now let's try running it:

./rel/disrupt/bin/disrupt console

the output we get:

Exec: /home/mariano/tmp/disrupt/rel/disrupt/erts-5.10.4/bin/erlexec -boot /home/mariano/tmp/disrupt/rel/disrupt/releases/1/disrupt -mode embedded -config /home/mariano/tmp/disrupt/rel/disrupt/releases/1/sys.config -args_file /home/mariano/tmp/disrupt/rel/disrupt/releases/1/vm.args -- console
Root: /home/mariano/tmp/disrupt/rel/disrupt
Erlang R16B03 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(disrupt@127.0.0.1)1>

that's what I would call a non crashing system, let's see if it works:

curl http://localhost:8080/index.html

we get:

hello static world

success!

and that's how you serve a static file with erlang, cowboy and rebar :)

Gabriel Patiño: OpenStreetMap en tu GPS Garmin

A principios del 2008 me crucé con un proyecto que me había parecido muy interesante: OpenStreetMap. Es un sitio donde podés ayudar a definir mapas del mundo real, no solo calles, sino también puntos de interés, escuelas, recorridos de colectivos, paradas... prácticamente un relevamiento total del planeta.

Funciona al estilo Wikipedia: el que sabe de alguna región, colabora. También hay mucha gente que releva caminos o puntos con un GPS, y luego se basa en la información obtenida. Otros prefieren una opción un poco más simple pero no tan precisa, y es mapear sobre los mapas satelitales de acceso público.

Desde que lo descubrí, fui colaborando tímidamente por las zonas que conocía, arreglando alguna calle con el sentido mal definido, agregando algo nuevo que veía en las vacaciones, etc. Cuando me mudé a San Martín de los Andes a principios del 2013 vi que por acá faltaban relevar muchas calles y barrios, principalmente los que no son de fácil acceso, así que aprovechaba cada salida en auto o caminando y me llevaba el GPS para registrar los recorridos.

Actualmente el mapa está lo suficientemente detallado como para preguntarme: ¿se podrá poner este mapa en el GPS del auto?

Tengo un Garmin, y hasta ahora solo tenía disponible el mapa del proyecto Mapear. No es que no me guste ni tengo nada en contra de este proyecto, es más, creo que el 99% de los usuarios de GPS lo deben tener y no saben que existe otra opción. El tema es como se maneja cada proyecto. El proyecto Mapear no es un proyecto abierto, podés colaborar pero siempre a través de la barrera que te pueden poner los propietarios del mismo, o sea, si por algún motivo no quieren aceptar tu colaboración, alpiste. Otro tema es que los datos del mapa no son de dominio público, o sea que no podrías hacer cualquier cosa con el mapa. En pocas palabras, te permiten usarlo pero no modificarlo.

En cambio, el proyecto OpenStreetMap (OSM) te permite agregar directamente lo que quieras o necesites. Podes usar los mapas para cualquier uso, ponerlos en tu sitio, y en algunos lugares (como SMAndes) están mucho más detallados que los mapas de Google Maps. Todo gracias a la gente que colabora, sean vecinos o visitantes que colaboraron con lo que vieron en las vacaciones.

Volviendo al tema y como era de esperar, no fui el primero con la idea de usar OSM en el Garmin. Con poco buscar encontré varias opciones, algunas tan interesantes como un sitio que te genera los mapas para la parte del mundo que quieras, espectacular para viajeros.

La opción que más me gustó es la que armó un colaborar del equipo de OSM de Argentina, Martin Andres Gomez Gimenez, pero sólo es para Argentina y países limítrofes.

Se vé particularmente bien en el GPS, y genera una nueva versión aproximadamente una vez por mes. Está buenísimo ver errores o cosas que faltan cuando vas circulando, llegás a casa y los arreglas en OSM, esperás una nueva versión y listo, problema resuelto.

La instalación es sumamente simple:
  • Se bajan el archivo gmapsupp.img de alguno de estos enlaces:
  • Copian el archivo al GPS, para esto hay dos opciones:
  • Via USB:
    • Conectas el GPS a la compu por USB.
    • Copias el archivo en la carpeta del GPS donde ya exista un archivo con el mismo nombre.
  • Via tarjeta de memoria (para los GPS con tarjeta de memoria):
    • Pones la tarjeta en la compu.
    • Copias el archivo en la carpeta donde ya exista un archivo con el mismo nombre.
  • En ambos casos es recomendable guardar el archivo gmapsupp.img original por las dudas.
  • Tengo entendido que en algunos GPS podés tener varios mapas, en ese caso se puede renombrar el archivo lo por lo que quieras manteniendo la extensión img. No lo pude probar porque mi GPS no lo soporta.

Bueno, eso es todo, que lo disfruten, y a colaborar con el proyecto OSM que esta muy bueno y es muy fácil.

Gabriel Patiño: Mazo de leña

Hace rato que andaba necesitando un mazo para darle a los formones o encajar los encastres de madera cuando hago algún proyecto, así que me hice uno, y de la forma más simple y barata:

Fui hasta la pila de leña, busque un pedazo para la cabeza, y otro para el mango, cortar, encastrar, y lijar un poquito.


Listo, un mazo que no marca la madera por $0. La madera es álamo de unos troncos que cortaron unos chicos en el fondo de casa, y estaba esperando a ser consumida en el próximo asado.


Este mazo fué indispensable para hacer las colas de milano del porta rollos que hice hace poco, y creo que va a seguir funcionando por un buen rato.

Lo bueno es que si se rompe o no me sirve, igualmente puedo usarlo en alguna parrillada :)

Algunos detalles: