segunda-feira, 31 de outubro de 2016

Porta-Retratos Digital Caseiro II (O Bug)

O porta-retratos digital funcionou bem por cerca de 8 horas e então parou com um erro de malloc(). Após algumas simulações e pesquisa dos bugs do Tk, descobri que o defeito está na classe Tk::Photo. Quando a imagem é carregada pela memória (com -data), os bits ficam guardados para sempre. Quando são lidos do disco, os dados não persistem além da próxima leitura.

Então, reescrevi alguns trechos e o código ficou um pouco menor. A função scale() não retorna mais os dados; agora ela escreve um arquivo.

sub scale {
  my ($width, $height, $image)=@_;
  my $scaled=$image->scale(xpixels=>$width,ypixels=>$height,type=>'min');
  $scaled->write(file=>'tmp.jpg', type=>'jpeg');  
}

Por sua vez, a função next_photo() lê este arquivo:

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  $image->open(file=>$filename);
  scale($photo_width, $photo_height, $image);
  $photo->configure(-file=>'tmp.jpg');
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  
  $image->open(file=>$filename);
  scale($thumb_width, $thumb_height, $image);
  $thumb->configure(-file=>'tmp.jpg');
  
  $main->update();
}

Evidentemente, não quero escrever este arquivo no cartão de memória para não apressar o seu fim. Então, a solução é criar um disco em RAM e escrever lá. Ele não precisa ser muito grande, os JPEGs temporários não passam de 100KB (por isso, o script original demorou a ocupar toda a memória).

quinta-feira, 27 de outubro de 2016

Porta-Retratos Digital Caseiro

Os preços dos porta-retratos digitais estão começando a ficar interessantes, mas construir o próprio é mais divertido.

Usei um Raspberry Pi A+ (256MB de RAM e um ARM11 de 700MHz) com a última versão do Raspbian Jessie. Eu queria mesmo era usar um Raspberry Pi Zero (que custa apenas US$5), mas são muito procurados e ainda não apareceram na minha loja preferida.

Comecei muito otimista (como sempre) e tentei montar uma solução com HTML, Javascript, jQuery, transições, etc. Ela resultou ser muito pesada. Então, tentei em Tcl/Tk, mas manipular os tamanhos das imagens resultou ser muito trabalhoso (possível, mas trabalhoso).

Então, só me restou usar Perl. Foi preciso instalar dois pacotes: Tk e Imager.


sudo apt-get install perl-tk
sudo apt-get install libjpeg-devel
sudo cpanm Imager
sudo cpanm Imager::File::JPEG
sudo cpanm Tk::HideCursor 


A interface é muito simples. Há um quadro grande com uma foto qualquer e uma foto pequena com a foto seguinte. A foto grande é escolhida aleatoriamente, mas a pequena é sempre a que segue (para haver uma relação entre elas). Além disso, exibo as horas e a data.

O script recebe um único parâmetro que indica o diretório onde as fotos são armazenadas. Ctrl+C suavemente encerra o programa.

Futuramente, espero exibir algo mais interessante no quadro das horas (a previsão do tempo ou as notícias).

#!/usr/bin/perl
use Tk;
use Tk::JPEG;
use Tk::Photo;
use Tk::HideCursor;
use Imager;
use File::Find;
use MIME::Base64;
use POSIX 'strftime';
use strict;

my @file_list;

find(\&wanted, $ARGV[0]);

sub wanted {
    return unless -f;
    return unless /\.jpe?g$/i;
    push @file_list, $File::Find::name;
}

my $main = MainWindow->new (
  -title => 'PiFrame',
  -background => 'black'
);

my $w=$main->screenwidth();
my $h=$main->screenheight();
$main->overrideredirect(1);
$main->MoveToplevelWindow(0,0);
$main->geometry(join('x',$w,$h));
$main->hideCursor();

my $photo_width=int($w*3/4);
my $photo_height=$h;
my $thumb_width=$w-$photo_width;
my $thumb_height=int($h/3);

my $canvas=$main->Canvas(
  -width=>$photo_width,
  -height=>$h,
  -background=>'black',
  -highlightthickness => 0);
$canvas->pack(-side=>'left');

my $small_canvas=$main->Canvas(
  -width=>$thumb_width,
  -height=>$thumb_height,
  -background=>'black',
  -highlightthickness => 0);
$small_canvas->pack(-side=>'bottom');

my $time;

my $clock=$main->Label(
  -textvariable=>\$time,
  -width=>100,
  -height=>100,
  -background=>'black',
  -foreground=>'white',
  -highlightthickness => 0,
  -font=>'courier 40 bold');
$clock->pack(-side=>'top');

my $photo=$canvas->Photo();
$canvas->createImage(0,0,-image=>$photo,-anchor=>'nw');
my $thumb=$small_canvas->Photo();
$small_canvas->createImage(0,0,-image=>$thumb,-anchor=>'nw');

sub scale {
  my ($width, $height, $image)=@_;
  my $scaled=$image->scale(xpixels=>$width,ypixels=>$height,type=>'min');
  my $data;
  $scaled->write(data=>\$data, type=>'jpeg');
  return encode_base64($data)
}

my $image=Imager->new();

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  $image->open(file=>$filename);
  $photo->configure(-data=>scale($photo_width, $photo_height, $image));
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  $image->open(file=>$filename);
  $thumb->configure(-data=>scale($thumb_width, $thumb_height, $image));
  
  $main->update();
}

$canvas->repeat(60000, sub {eval {next_photo()}});
$canvas->repeat(1000, sub {$time=strftime("%H:%M\n%A\n%d/%m", localtime)});

next_photo();

MainLoop;

O sistema operacional que usei para este projeto é o Raspbian com Pixel. As fotos estão armazenadas na pasta /data01/fotos. Então, adicionei a seguinte linha ao arquivo  /home/pi/.config/lxsession/LXDE-pi/autostart para que o sistema inicie automaticamente:

@perl /home/pi/piframe.pl /data01/fotos/

Para evitar que o gerenciamento de energia apague a tela por falta de movimentação, é preciso editar o arquivo /etc/lightdm/lightdm.conf. A linha original e a nova são, respectivamente:

#xserver-command=X
xserver-command=X -s 0 dpms


O gasto de memória oscila entre 25MB e 50MB, o que cabe confortavelmente no espaço que o A+ oferece. Um Pi 3 talvez possa executar isso ao mesmo tempo que ofereça o serviço do Amazon Echo.

quinta-feira, 15 de setembro de 2016

Melhorias ao SQL

Lendo um relato sobre um pobre programador que apagou uma tabela inteira, porque esqueceu o where num delete, fiquei a pensar que o SQL deveria ter sintaxe diferente para alguns casos.

Os updates e os deletes de tabelas inteiras deveriam indicar a intenção explicitamente:

update my_table set my_column=0 all rows;
delete other_table all rows;

E se alguém esquecesse do all rows ou do where, seria um erro de sintaxe. Aposto que essa mudança evitaria muito sofrimento.

quarta-feira, 14 de setembro de 2016

Recuperando um roteador WR1043ND com um Raspberry Pi

Um amigo apresentou-me um desafio: recuperar um roteador cuja atualização do firmware dera errado. O roteador, um TP-Link WR1043ND, não funcionava mais. Esse equipamento tem uma interface serial na placa. É uma interface de 3,3V que é compatível com a interface serial do Raspberry Pi.

Infelizmente, não basta abrir a caixa: a interface serial não tem os pinos instalados. Comprei os pinos, um pouco de fio e conectores para construir um cabinho. Depois comecei a perceber que cabinhos com os conectores adequados estão por toda parte: os mouse Genius antigos os tem, assim como os cabos de som dos drives antigos de CD. Até mesmo o cabo de um drive de disquetes ou de HD IDE serviria com algumas modificações. Também aprendi que montar um cabo sem um crimpador é difícil.

No Raspberry Pi, os pinos 6, 8, e 10 servem a porta serial, sendo, respectivamente, o terra (G), a transmissão (Tx) e a recepção (Rx). Eles são vizinhos de dois pinos com 5V (o 2 e o 4), então é preciso ter cuidado. No roteador, para minha sorte, Rx e Tx estão trocados. Percebi isso apenas depois de ter montado o cabo com pinagem idêntica nas duas pontas. Sorte de principiante.

Como os pinos são muito pequenos e não tenho nenhuma prática com soldagem, pedi ajuda à esposa amada, que, embora também não soubesse soldar, tem mais habilidade. O resultado ficou além do esperado.



Do outro lado, os pinos:


São quatro pinos, porque não havia um conector de três disponível. O quarto pino é de energia (3,3V) e é desnecessário para este projeto.

As instruções para atualizar o firmware via a interface serial estão disponíveis na wiki do OpenWRT. Para esse modelo, há duas opções para carregar o arquivo: via tftp e via a própria interface serial. Como eu estava longe do roteador e usando um Raspberry Pi que não tem uma porta ethernet, resolvi executar tudo pela interface serial.

O primeiro passo é instalar o kermit (para enviar o arquivo) e o minicom (para interagir com o roteador):

sudo apt-get install kermit minicom

A porta serial aparece como o device /dev/serial0 (que é apenas um link para /dev/ttyAMA0). Então, para iniciar a comunicação, o comando é:

minicom -b 115200 -o -D /dev/serial0

E só depois liguei o roteador. Ele não dá muito tempo para interromper a sequência de boot. Assim que aparecer o texto "Autobooting in 3 seconds" é preciso digitar tpl para interromer o processo normal. Pode ser preciso desligar e religar o roteador várias vezes.

Pelo minicom, digitei um comando para limpar um bloco de memória e outro para carregar o arquivo novo:

erase 0xbf020000 +7c0000 
loadb 0x81000000

Depois, noutro terminal, iniciei o kermit sem parâmetros e digitei os seguintes comandos:

set line /dev/serial0
set speed 115200
set carrier-watch off
set handshake none
set flow-control none
robust
set file type bin
set file name lit
set rec pack 1000
set send pack 1000
set window 5
send wr1043nd.bin 

Isso deveria durar uns 20 minutos, mas houve muitos erros na transmissão e o envio levou duas horas. Os erros atribuo à qualidade do meu cabo.


Terminado o envio, dois comandos terminam o processo, copiando o arquivo para o flash e reiniciando o roteador:

cp.b 0x81000000 0xbf020000 0x7c0000
bootm 0xbf020000

Eu entendo que a interface serial esteja escondida, porque seria fácil usar uma interface de 5V (ou 12V) e queimar o equipamento, mas acho que foi economia demais não colocar os pinos (que custam centavos). De qualquer forma, o experimento foi um sucesso e várias habilidades novas foram adquiridas.

terça-feira, 6 de setembro de 2016

A linha mais comprida de um arquivo II (O Desafio)

Em janeiro, achei a linha mais comprida de um arquivo usando comandos do shell e usando perl (que resultou ser muito mais rápido).

O Linux pediu revanche, porque eu havia ignorado o awk. Perl provou mesmo ser eficiente.


$time perl -MData::Dumper -ne '$n{length($_)}++; END {print Dumper(%n)}' arquivao.txt
$VAR1 = '1088';
$VAR2 = 349647;

real    0m1.326s
user    0m0.814s
sys     0m0.371s

$time awk 'length($0) > max { max=length($0) } END { print max }' arquivao.txt
1087

real    0m21.400s
user    0m18.596s
sys     0m0.455s

Perl deixou o awk comendo poeira. A diferença nos números deve-se ao fato do awk já ignorar o caractere de fim de linha.

segunda-feira, 5 de setembro de 2016

Múltiplos dos primos

Há anos eu havia escrito um programa para encontrar inteiros candidatos a primos, pulando todos os que eu já sabia serem múltiplos de alguns poucos primos.

Resolvi brincar com o contrário: achar sequências de pulos que levariam a múltiplos dos primeiros primos.


#!/usr/bin/perl
my @primes=qw(
      2      3      5      7     11     13     17     19     23     29 
     31     37     41     43     47     53     59     61     67     71 
     73     79     83     89     97    101    103    107    109    113 
    127    131    137    139    149    151    157    163    167    173 
    179    181    191    193    197    199    211    223    227    229 
    233    239    241    251    257    263    269    271    277    281 
    283    293    307    311    313    317    331    337    347    349 
    353    359    367    373    379    383    389    397    401    409 
    419    421    431    433    439    443    449    457    461    463 
    467    479    487    491    499    503    509    521    523    541 
    547    557    563    569    571    577    587    593    599    601 
    607    613    617    619    631    641    643    647    653    659 
    661    673    677    683    691    701    709    719    727    733 
    739    743    751    757    761    769    773    787    797    809 
    811    821    823    827    829    839    853    857    859    863);

my @steps=(2);
my $product=1;
my @sub=@primes[0..5];
map { $product*=$_ } @sub;
for my $i (2..$product) {
  $found=0;
  for (@sub) {
    if ($i%$_==0) {
      $found=1;
      last;
    }
  }
  if($found) {
    push @steps, $s;
    $s=1;
  } else {
    $s++;
  }
}
print "@steps\n";

A série precisa ir até o produtório dos n primos. Para n=2 (2 e 3), ela vai até 6. Para n=3 (2, 3, e 5), vai até 30, e assim por diante. Os passos são sempre de 1 ou 2, porque um passo maior que 2, pularia um par.

Os primeiros resultados são:

n=1 
2
n=2 
2 1 1 2
n=3 
2 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 2
n=4 
2 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 2 2 1 1 2 1 1 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 
2 2 1 1 1 1 2 1 1 2 1 1 1 1 2 1 1 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 2 1 1 1 1 1 1 2 1 1 1 1 2 1 1 2 1 1 1 1 2 2 
1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 1 1 2 1 1 2 2 1 1 2 1 1 1 1 2 2 1 1 1 1 2 1 1 2 2 1 1 2 2 1 1 1 1 1 1 1 1 2

A soma é igual ao produto, obviamente. Ademais, os inversos das médias são as sequência de frações da última investigação sobre primos. Isto é, para n=2, a média é 6/4 e os dois primos são fatores de 2/3 dos números inteiros. Além disso, as duas metades das sequências são espelhadas, o que podemos usar para economizar espaço e processamento.


quinta-feira, 1 de setembro de 2016

Renovação da habilitação

Parecia mais um dia normal de 2050. Zveg entrou em seu veículo elétrico multipassageiro e iniciou o sistema. Após a breve rotina de inicialização e verificação, o veículo indagou:

-Bom dia, Zveg. Sua habilitação expirará em 15 dias. Deseja executar o teste de renovação?

Zveg irritou-se, mas conformou-se a finalmente a executar o teste, ao menos para deixar de ter que responder a essa pergunta.

-Certo, vamos logo com isso.
-Iniciando teste de habilitação. Fase 1, teste de visão. Quais são as letras exibidas no parabrisas?
-T, P, E...
-Por favor, mantenha sua cabeça contra o encosto.
-Certo, então, T, P, E, V, e X.
-Resposta satisfatória. Tendo em vista o número de pontos acumulados, Zveg, é preciso executar o teste de simulação e sinalização e o escore mínimo é 73.

O veículo escureceu o ambiente e iniciou a simulação. Zveg estava um pouco preocupado; andava dirigindo com desatenção e agora precisava de um desempenho melhor para compensar as faltas.

Durante 20 minutos, o teste de simulação e sinalização correu. O veículo apresentava distintas situações de tráfego e expedia ordens. Em alguns casos, as ordens levariam a infrações e Zveg devia indicar a ação correta a ser tomada:

-Este local não é adequado para estacionar e tampouco posso fazer o retorno, então devo procurar outra rua.

Ao fim do teste, o veredito foi informado prontamente e sem rodeios:

-O resultado é 76. A próxima renovação será em 180 dias. Deseja repetir o teste de simulação e sinalização? Com 80 pontos, a renovação só será necessária em 365 dias.

Zveg já estava farto.

-Não!
-Enviarei os dados do teste para o registro central. Aguarde um insta..zuuuup.

O veículo escureceu e então as luzes piscaram violentamente por alguns segundos.

-Bom dia, Zveg. Sua habilitação expirará em 15 dias. Deseja executar o teste de renovação?