1-wire LEGO LED stripe

This post is part 2 of 3 of  Rede de controlo 1-wire

E agora um primeira experiência com múltiplos DS2413 numa mesma microLAN…

Cada dispositivo 1-wire tem um ID distinto gravado em fábrica:

$sudo ls /mnt/1wire/
3A.4C2B13000000  81.086D33000000  settings    structure  uncached
3A.B0E216000000  bus.1          statistics  system

Vemos que temos 3 dispositivos de 2 classes diferentes.  A classe 81 é a do Master Controller (o adaptador USB DS9490R) e a classe  3A é a do DS2413. Temos portanto dois dispositivos DS2413 na rede, cada um com o seu ID:

  • 4C2B13000000
  • B0E216000000

Na forma como o owfs lida com 1-wire o par “Classe.ID” funciona como um endereço. É possível definir alias mas funcionam apenas localmente (isto é: se movermos a microLAN do meu Ubuntu para o LEGO ev3dev tẽm de ser redefinidos) e na minha primeira experiência com alias perdi o acesso aos dispositivos por isso deixo para outra altura.

Sendo da mesma classe, estes dois dispositivos partilham os mesmos atributos (PIO.A, PIO.B e PIO.ALL por exemplo). Se tiver um LED em cada PIO destes devices podemos por exemplo acender todos os LEDs percorrendo todos os dispositivos da classe 3A e escrevendo “1,1” na subpasta PIO.ALL.

O programa abaixo acende cada LED em sequência, primeiro num sentido depois no outro:

import sys, traceback
from time import sleep
from subprocess import call

# B0E216000000/PIO.A = primeiro LED
# B0E216000000/PIO.B = segundo LED
# 4C2B13000000/PIO.A = terceiro LED
# 4C2B13000000/PIO.B = quarto LED

def main():
    try:

# apagar tudo ao comecar

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        while True:
        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

                call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.A", shell=True); # LED1
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

                call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.B", shell=True); # LED2
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.A", shell=True); # LED3
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.B", shell=True); # LED4
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.A", shell=True); # LED3
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

                call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.B", shell=True); # LED2
                sleep(0.25);


    except (KeyboardInterrupt, SystemExit):
        print "Exiting...";
    except Exception:
        traceback.print_exc(file=sys.stdout);

# apagar tudo antes de sair

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);
    sys.exit(0);

if __name__ == "__main__":
    main()

 

E o programa abaixo acende as luzes aleatoriamente, mais próprio para um concerto ou uma discoteca (com tempo hei-de estudar a forma de usar as rotinas PyAudio para medir a intensidade de uma música e controlar as luzes ao ritmo desta embora duvide que o EV3 tenha CPU para tal).

 

 

import sys, traceback
from time import sleep
from subprocess import call
from random import randrange

# B0E216000000/PIO.A = primeiro LED
# B0E216000000/PIO.B = segundo LED
# 4C2B13000000/PIO.A = terceiro LED
# 4C2B13000000/PIO.B = quarto LED


def main():
    try:

# apagar tudo ao comecar

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        while True:
        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        if (randrange(2) == 1):
                    call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.A", shell=True); # LED1

        if (randrange(2) == 1):
                    call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.B", shell=True); # LED2

        if (randrange(2) == 1):
            call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.A", shell=True); # LED3

        if (randrange(2) == 1):
            call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.B", shell=True); # LED4
                
                sleep(0.25);


    except (KeyboardInterrupt, SystemExit):
        print "Exiting...";
    except Exception:
        traceback.print_exc(file=sys.stdout);

# apagar tudo antes de sair

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);
    sys.exit(0);

if __name__ == "__main__":
    main()

 

LEGO Power Functions MOD: SuperLamp

No meu rover AD4M4ST0R estava a utilizar 3 conjuntos 8870 (LEGO Power Functions Light) para iluminar o ambiente antes de tirar uma foto com a webcam. Como os LEDs utilizados pela LEGO consomem muito pouco (entre 3 a 4 mA quando usados no EV3) foram necessários 6 para poder tirar fotos satisfatórias em ambientes escuros.

Eu acho o conjunto 8870 um pouco desajeitado com aquela peça 2×2 intermédia. E também acho caro os €6 ou mais custam avulso (os que tenho vieram com os conjuntos 8293 (LEGO Power Functions Accessory Box).

Numa conversa no fórum PLUG ocorreu aproveitar os LEDs de uma lanterna ou candeeiro baratos, ontem recuperei essa ideia ao encontrar no Leroy Merlin estas lanternas por €2.99:

A lanterna funciona com 3 pilhas AAA. A acreditar no rótulo, consome 360 mW o que a 4.5V significa 80 mA, não é exagerado para o EV3. Ligando duas em série nem precisaria preocupar-me com a tensão, pode ser ligado directamente a uma bateria Power Functions ou a uma porta NXT/EV3.

O corpo da lanterna é em alumínio. Desenroscando a parte de trás da lanterna podemos retirar o suporte das pilhas mas como a parte frontal (com os LEDs e o «vidro») não é desenroscável usei um lápis sem bico para fazer pressão por dentro e retirar:

Os seis LED estão soldados a uma placa de circuito impresso circular com duas pistas também circulares, concêntricas. Os LEDs estão montados em paralelos, todos os ânodos numa primeira pista e todos os cátodos numa segunda pista. A pista interior estava ligada ao suporte das pilhas e é o positivo, a pista exterior fazia contacto com o corpo da lanterna e é o negativo.

Não parece existir nenhuma forma de limitação de currente (numa série de lanternas «gambiarra» de origem chinesa, com 24 a 28 LEDs e também 3 pilhas AAA, encontrei sempre uma resistência de 1.5Ω de 1W).

Para testar liguei o positivo de uma conjunto de pilhas Power Functions à pista interior de uma das placas e o negativo à pista exterior da outra placa. Depois liguei as restantes pistas (pista exterior da primeira placa e pista interior da segunda placa):

E funciona!

Com um multímetro em modo Amperímetro verifica-se o consumo:

Uhmmmm… bastante mais que os 80 mA esperados. Mas aceitável à mesma para o propósito em vista (períodos curtos, controlo PWM).

Confirmadas as ligações soldei os fios e isolei com fita de electricista:

Pode-se ver na parte superior da imagem acima que usei fichas «mini-banana» junto ao terminal Power Functions de modo a poder trocar a polaridade se necessário).

A «LEGOlização» foi mais uma vez conseguida à custa do famigerado Kragle (cola de contacto transparente)

E finalmente a montagem no AD4M4ST0R:

Com o ev3dev utilizei o segunte comando para comparar consumos:

cat /sys/class/power_supply/legoev3-battery/current_now

Para cada cenário medi 10 vezes e calculei a média (sempre com o EV3 com bateria ligada ao carregador):

  • Em repouso: 317 mA
  • Com 6 LEDs Power Functions @100%: 340 mA
  • Com SuperLamps @100%:  579 mA

Portanto os 6 LEDs Power Functions consomem 23 mA e as minhas SuperLamps consomem 262 mA (onze vezes mais!). É um valor bastante superior aos 181 mA medidos com o multímetro e o suporte de pilhas Power Functions e que já faz considerar a hipótese de adicionar um díodo do tipo 1N400x para reduzir um pouco a tensão aplicada aos LEDs e com isso reduzir a corrente.

 

 

 

LEGO EV3 + TI SensorTag

Chegou hoje o SensorTag da Texas Instruments.

É um dispositivo BLE (Bluetooth 4.0 Low Energy) com vários sensores (temperatura, humidade, aceleração/orientação e pressão atmosférica, bem como 2 botões de pressão).

Com o ev3dev foi facílimo ler a temperatura ambiente, bastou consultar dois links:

Partindo de um dongle BT4.0 funcional em hci0 (ver o meu artigo anterior, LEGO B3AC0N)

Carregar no botão de discovery do SensorTag e fazer um scan BLE:

root@ev3dev:~# hcitool -i hci0 lescan
BC:6A:29:AB:83:4D SensorTag
BC:6A:29:AB:83:4D (unknown)
BC:6A:29:AB:83:4D SensorTag
BC:6A:29:AB:83:4D (unknown)
BC:6A:29:AB:83:4D SensorTag
...
^C

 

root@ev3dev:~# gatttool -b BC:6A:29:AB:83:4D -I
[BC:6A:29:AB:83:4D][LE]> connect
Attempting to connect to BC:6A:29:AB:83:4D
Connection successful
[BC:6A:29:AB:83:4D][LE]> char-read-hnd 0x25
Characteristic value/descriptor: 00 00 00 00
[BC:6A:29:AB:83:4D][LE]> char-write-cmd 0x29 01
[BC:6A:29:AB:83:4D][LE]> char-read-hnd 0x25
Characteristic value/descriptor: ae fe 74 0c
[BC:6A:29:AB:83:4D][LE]>

 

Os quatro bytes do valor medido (ae fe 74 0c) correspondem a duas temperaturas: a do objecto em frente e a ambiente, no formato

ObjLSB ObjMSB AmbLSB AmbMSB

Assim:

Temp Amb = 0c74 = 3188

O valor real é obtido dividindo por 120:

3188/128 = 24.90625 = 25ºC

 (Valor confirmado pela aplicação Android BLE SensorTag da TI)

Nota: ocasionalmente a conexão BLE falha. Nessas situações pode-se reiniciar o dongle com os comandos:

hciconfig hci0 down
hciconfig hci0 up

(em casos extremos será necessário remover fisicamente o dongle e voltar a inseri-lo ou na sua impossibilidade o velho reboot resolve tudo)

 

 

 

LEGO B3AC0N

Com os updates recentes ao kernel do ev3dev e a inclusão do módulo btusb [a meu pedido, modéstia à parte] já é possível utilizar dongles Bluetooth 4.0 com o LEGO EV3.

Enquanto não me chegam às mãos beacons e outros dispositivos BT 4.0 para testar, o único aparelho que tenho é um telemóvel Android que infelizmente apenas “consome” serviços. Bem, demos-lhe qualquer coisa para consumir: um beacon – não posso dizer iBeacon porque é trademark da Apple… mas é a mesma coisa: um «farol» que pode servir para marcar presença, orientação, medição de distâncias, fornecimento personalizado de informação…

A implementação é muito fácil, está quase tudo no post “piBeacon – DIY Beacon with a Raspberry Pi” da Adafruit.

Claro que primeiro é necessário um dongle Bluetooth 4.0 como este (qualquer um que funcione num Raspberry Pi a correr Raspbian deverá funcionar também com o EV3 a correr ev3dev).

É necessário também uma versão recente do ev3dev (baseado em Debian jessie, com kernel  3.16.1-1 ou superior, neste momento estou a usar a 3.16.1-2). Quem já tiver instalado a versão pre-release (ev3dev-jessie-2014-07-12) que à data é a mais recente disponível para download, basta

apt-get update
apt-get dist-upgrade
apt-get install linux-image-3.16.1-2

Para confirmar que temos o dongle a funcionar usamos o comando hciconfig:

root@ev3dev:~# hciconfig -a
hci1:    Type: BR/EDR  Bus: UART
    BD Address: 00:17:EC:02:91:B7  ACL MTU: 1021:4  SCO MTU: 180:4
    UP RUNNING PSCAN
    RX bytes:563 acl:0 sco:0 events:27 errors:0
    TX bytes:879 acl:0 sco:0 commands:27 errors:0
    Features: 0xff 0xff 0x2d 0xfe 0x9b 0xff 0x79 0x83
    Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
    Link policy: RSWITCH HOLD SNIFF PARK
    Link mode: SLAVE ACCEPT
    Name: 'ev3dev-0'
    Class: 0x000000
    Service Classes: Unspecified
    Device Class: Miscellaneous,
    HCI Version: 2.1 (0x4)  Revision: 0x0
    LMP Version: 2.1 (0x4)  Subversion: 0x191f
    Manufacturer: Texas Instruments Inc. (13)

hci0:    Type: BR/EDR  Bus: USB
    BD Address: 00:1A:7D:DA:71:13  ACL MTU: 310:10  SCO MTU: 64:8
    UP RUNNING PSCAN
    RX bytes:25008 acl:0 sco:0 events:1215 errors:0
    TX bytes:2486 acl:0 sco:0 commands:159 errors:0
    Features: 0xff 0xff 0x8f 0xfe 0xdb 0xff 0x5b 0x87
    Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
    Link policy: RSWITCH HOLD SNIFF PARK
    Link mode: SLAVE ACCEPT
    Name: 'ev3dev #1'
    Class: 0x000000
    Service Classes: Unspecified
    Device Class: Miscellaneous,
    HCI Version: 4.0 (0x6)  Revision: 0x22bb
    LMP Version: 4.0 (0x6)  Subversion: 0x22bb
    Manufacturer: Cambridge Silicon Radio (10)

Temos 2 dispositivos Bluetooth: hci1 (UART) que é o nativo (interno) do EV3, ainda 2.0 e que por isso de pouco serve; e hci0 (USB) que é o nosso.

Podemos ver na informação acima que hci0 já está em estado “UP RUNNING” apesar de na maioria dos artigos que encontrei referirem sempre ser necessário activá-lo

hciconfig hci0 up

Inicialmente hci0 não está a anunciar-se em modo Low Energy pelo que é necessário ativar esse modo. Além disso o artigo em que me inspirei refer ser conveniente desactivar a procura de outros dispositivos por poder interferir e apesar de achar isso estranho para este exemplo mal não faz por isso

hciconfig hci0 leadv
hciconfig hci0 noscan

Resta apenas configurar hci0 para funcionar se anunciar como beacon e fingir ser da Apple:

root@ev3dev:~# hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C8 00
< HCI Command: ogf 0x08, ocf 0x0008, plen 32
  1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F
  17 D1 AD 07 A9 61 00 00 00 00 C8 00
> HCI Event: 0x0e plen 4
  01 08 20 00

Se no nosso Android instalarmos uma aplicação como a “Bluetooth LE Scan” (e o telemóvel/tablet tiver BT 4.0 e Android 4.3 ou superior) após um SCAN encontramos o nosso Beacon:

ev3dev #1
MAC: 00:1A:7D:DA:71:13
Updated: 2014-09-12 17:46:50
RSSI: -60.0db / -60.0 dB
UUID: ....
Major:   0     Minor:  0
Tx Power: -56  Distance: 1.64m
Descriptor: NEAR

A precisão deixa um pouco a desejar: vários SCAN consecutivos deram valores entre 1m [NEAR] e 7m [FAR] quando o telemóvel estava a cerca de 1m40 do dongle.

Podemos ajustar um pouco mudando um valor de referência Tx Power enviado pelo Beacon no penúltimo byte do comando hcitool (“C8” acima) que corresponde ao valor RSSI medido em laboratória a distâncias de um metro, escrito em complemento para 2 (C8 = -56 portanto corresponde a uma medição de RSSI de -56 dB a um metro)

Seguem algures valores próximos de -56 dB:

-54 dB = CA
-55 dB = C9
-56 dB = C8
-57 dB = C7
-58 dB = C6
-59 dB = C5

Portanto se colocarmos o dispositivo Android a exactamente um metro do dongle USB, o valor RSSI medido será o nosso valor ideal de Tx Power.

Infelizmente obtive valores entre -55 e -63 dB (com o EV3 alimentado por carregador para garantir uma alimentação constante) por isso fiquei na mesma. Após algumas experiências com algumas medições a várias distâncias fiquei-me por -57 dB = C7, pareceu-me o menos flutuante.

hcitool -i hci0 cmd 0x08 0x0008 1E 02 01 1A 1A FF 4C 00 02 15 E2 0A 39 F4 73 F5 4B C4 A1 2F 17 D1 AD 07 A9 61 00 00 00 00 C7 00

Quando tiver um Beacon a sério vou poder calibrar melhor este meu EV3 B3AC0N (e até pode ser que a flutuação seja do software, vou procurar outras aplicações semelhantes).

Falta agora o inverso: utilizar hci0 para encontrar outros Beacons. Ou talvez outros B3AC0Ns.

Nota: podemos não fingir ser Apple, substituindo 4C 00 pelo Company ID de outro fabricante (infelizmente a LEGO não aparece) como por exemplo ​FE 00 (reserved)… só que deixa de ser reconhecido como um Beacon.

 

ev3dev – autorun script

Se quiser lançar um programa automaticamente sempre que o EV3 com ev3dev arranca (sem ter que aceder remotamente por ssh nem ter um teclado ligado) a única solução funcional que encontrei foi implementar um serviço:

nano /etc/init.d/startup.sh

conteúdo do ficheiro:

#!/bin/bash
cd /root
python /root/joy6.py &

Dar permissões de execução ao script:

chmod +x /etc/init.d/startup.sh

Instalar o script como serviço:

update-rc.d startup.sh defaults

Se o nosso script não terminar por si e depois o quisermos terminar então temos mesmo de aceder por ssh, procurar o pid do processo e matá-lo:

pgrep pyhton
12345
kill -9 12345

(ou implementar uma forma de parar o serviço mas sou demasiado preguiçoso para investigar isso)

Um efeito secundário deste método é que a minha forma de implementar beeps (um shell script que envia o caracter BELL para a consola) deixou de funcionar. Como os beeps dão-me muito jeito para perceber o que alguns programas estão a fazer optei por arranjar um ficheiro ‘beep.wav’ com um toque curto de campaínha e tocá-lo com o comando ‘aplay’

#echo -en "07" > /dev/tty1
aplay -q beep.wav

 

AD4M4ST0R – controlo por joystick

This post is part 7 of 9 of  AD4M4ST0R - um rover LEGO

Apenas uma breve explicação do modo como o rover é controlado quando em modo ‘joystick’:

É utilizado apenas o joystick da direita, correspondente ao par de eixos 2 (horizontal) e 3 (vertical). A «potência» aplicada aos motores vai ser proporcional ao movimento do joystick portanto aos valores lidos nos eixos e que podem variar entre -1.0 e +1.0 sendo que no caso do eixo vertical estão invertidos (o sentido positivo é para baixo). Assim:

(x,y) = (axis2, -axis3)

Defini uma zona morta ao redor da posição central (10% portanto entre -10.0 e +10.0 para ambos os eixos) de modo a que pequenos toques no joytick sejam ignorados:

Diagram2

Além disso para evitar acelerações bruscas optei por dar mais importância às amplitudes menores que às maiores (como numa torneira em que se tem que rodar cada vez mais a torneira para se aumentando o caudal de água). Isso consegue-se aplicando uma função logarítmica:

f(x,y) = log10(100x,100y)

Não existe log10(0) mas isso já está prevenido a priori porque o centro está incluído na zona morta. O menor valor fora da zona morta será 0.1 e o maior valor será 1.0 [primeiro quadrante apenas]. Ora

  • log10(100 x 0.1) = 1
  • log10(100 x 1.0) = 2

Como os motores aceitam valores entre -100 e +100 basta aplicar um factor de escala de 50x para cobrir a gama toda. Mas no meu programa usei um factor de 25x apenas porque achei que o rover movia-se demasiado rápido [estou a pensar definir um botão do gamepad para comutar entre 25x e 50x para movimentos de precisão vs. corridas].

As curvas ocorrem quando o valor de x é não-nulo e nessas ocasiões reduzo o motor correspondente em 33% do valor de x (se quisermos curvas mais apertadas basta aumentar este valor).

O resto do código é apenas identificação dos quadrantes de modo a acertar os sinais dados aos motores.

AD4M4ST0R – medindo luminosidade

This post is part 6 of 9 of  AD4M4ST0R - um rover LEGO

A ideia é utilizar um sensor de cor para medir a luminosidade de modo a acender os LEDs de iluminação apenas o necessário para garantir uma imagem razoável.

O sensor utilizado é um EV3 Color Sensor (45506). Na wiki do projecto ev3dev há 2 artigos com a informação suficiente:

Após a ligação o sensor é imediatamente reconhecido:

root@ev3dev:~# dmesg
(...)
ev3-uart-host in2:ev3-uart-host: Started.
Registered LEGOEV3 line discipline. (29)
msensor sensor0: Mindstorms sensor registered.

Sendo o único sensor ligado aparece referenciado como ‘sensor0’ debaixo da classe ‘msensor’:

root@ev3dev:~# ls /sys/class/msensor/ -la
lrwxrwxrwx  1 root root 0 Aug 24 00:46 sensor0 -> ../../devices/platform/serial8250.0/tty/ttyS0/ttyS0:ev3-uart-sensor/msensor/sensor0

Temos a seguinte informação disponível:

root@ev3dev:~# ls /sys/class/msensor/sensor0
bin_data     device  mode    num_values  power      type_id    units    value1    value3    value5    value7
bin_data_format  dp     modes    port_name   subsystem  uevent    value0    value2    value4    value6

O sensor é inicializado no modo ‘COL-REFLECT’ mas podemos mudar para um dos seguintes modos disponíveis:

root@ev3dev:~# cat /sys/class/msensor/sensor0/mode
COL-REFLECT

root@ev3dev:~# cat /sys/class/msensor/sensor0/modes
COL-REFLECT COL-AMBIENT COL-COLOR REF-RAW RGB-RAW COL-CAL

Pretendo operar no modo ‘COL-AMBIENT’:

root@ev3dev:~# echo COL-AMBIENT > /sys/class/msensor/sensor0/mode

Neste modo o sensor gera apenas um valor:

root@ev3dev:~# cat /sys/class/msensor/sensor0/num_values
1

que será portanto ‘value0’:

root@ev3dev:~# cat /sys/class/msensor/sensor0/value0
3

Infelizmente a gama de valores lida pelo sensor deixa muito a desejar: no mesmo local na minha sala observei de ‘1’ (de noite com luz fraca) a ’17’ (numa tarde típica de Agosto sendo a sala virada a Sul embora não haja incidência directa no sensor). E com uma lanterna a apontar directamente ao sensor obtenho ’68’.

Tirei algumas fotos com vários valores de leitura e vários duty cycles aplicados a 3 pares de LEDs Power Functions para tentar elaborar uma regra (o rover e a minifig sempre no mesmo local com cerca de 15 cm entre a webcam e a minifig, a iluminação dos LEDs nunca alterou o valor medido pelo sensor e todas as fotos foram apenas reduzidas dos 640×480 originais para 160×120)

Brightness Duty Cycle
(%) 0% 25% 33% 50% 66% 75% 100%
1 bright01-duty000 bright01-duty100
3 bright03-duty000 bright03-duty025 bright03-duty033 bright03-duty050 bright03-duty066 bright03-duty075 bright03-duty100
4 bright04-duty000 bright04-duty025 bright04-duty033 bright04-duty050 bright04-duty066 bright04-duty075 bright04-duty100
5 bright05-duty000 bright05-duty025 bright05-duty033 bright05-duty050 bright05-duty066 bright05-duty075 bright05-duty100
6 bright06-duty000 bright06-duty025 bright06-duty033 bright06-duty050 bright06-duty066 bright06-duty075 bright06-duty100
13 bright13-duty000 bright13-duty033 bright13-duty066 bright13-duty100
17 bright17-duty000 bright17-duty066 bright17-duty066 bright17-duty100

 

Com esta informação optei pela seguinte função ‘fuzzy logic’:

  • Brightness <6  => Duty Cycle = 100%
  • Brightness ∈[6, 7] => Duty Cycle = 75%
  • Brightness ∈[8, 9] => Duty Cycle = 66%
  • Brightness ∈[10,11] => Duty Cycle = 50%
  • Brightness ∈[12,13] => Duty Cycle = 33%
  • Brightness >13 => Duty Cycle = 0%

AD4M4ST0R – controlando LEDs

This post is part 5 of 9 of  AD4M4ST0R - um rover LEGO

A qualidade das fotos tiradas pela webcam Logitech C170 não é má… desde que haja suficiente luz ambiente. Por isso lembrei-me de ligar LED’s Power Functions a uma das portas de saída ainda disponíveis para funcionar como «flashes» ao tirar a fotografia.

Infelizmente a versão actual do ev3dev ainda não permite o controlo individual das portas (foi aberto hoje um issue, depois de uma troca de ideias) por isso a única maneira é enganar o ev3dev fazendo-o acreditar que tem um motor.

Os motores e os sensores EV3 têm uma funcionalidade «Auto-ID» que permite ao EV3 identificar que dispositivo foi ligado a cada porta (género “Plug and Play”). Nos videos do utilizador “TecnicRobot” ele utiliza um par de resistências para que os seus dispositivos de saída pareçam ser motores EV3.

Para isso fiz um cabo semelhante a este (não tinha resistências de 1KΩ e 10 KΩ à mão mas como o que interessa é a proporção usei de 2.2KΩ e 22KΩ) e funciona.

(o pinout do cabo EV3 foi baseado neste)

    EV3 Cable                             PF Cable
                                           - 9V
White    Pin 1 (M1)     -------------------- C2 (middle)
Black    Pin 2 (M2)     -------------------- C1 (middle)
Red      Pin 3 (GND)    ----[R1]---x       - GND
Green    Pin 4 (POWER)  ----[R2]---x
Yellow   Pin 5 (TACHO0) -----------x
Blue     Pin 6 (TACHO1) -------- Pin 3 (GND)

R2 ~10xR1
R1 = 1K, 2K2, 3K3...
R2 = 10K, 22K, 33K...
dmesg:
tacho-motor tacho-motor2: Tacho motor registered.
ev3-tacho-motor outC:motor: A Tacho Motor connected to port outC gpio 93 irq 194

Para acender os LEDs a meia intensidade:

echo 50 > /sys/class/tacho-motor/tacho-motor2/duty_cycle_sp
echo 1 > /sys/class/tacho-motor/tacho-motor2/run

Para acender na máxima intensidade:

echo 100 > /sys/class/tacho-motor/tacho-motor2/duty_cycle_sp
echo 1 > /sys/class/tacho-motor/tacho-motor2/run

ad4m4st0r-02 (parece-me que é altura de fazer um cabo decente e dar uma arrumação aos cabos do AD4M4ST0R).

Durante a noite, numa sala com pouca iluminação (halogéneo)

ad4m4st0r-03Durante a manhã, no mesmo local com iluminação média (natural mas indirecta):

ad4m4st0r-04A terceira imagem foi tirada com um segundo par de LED’s Power Functions o que me levou na noite seguinte a acrescentar um terceiro par:

ad4m4st0r-06As 4 imagens correspondem a Duty Cycles de 0%, 33%, 67% e 100% (ou seja os LED’s apagados, o equivalente a um par aceso, o equivalente a dois pares acesos e os três pares acesos).

Assim que me for possível vou tentar medir a luminosidade ambiente com um EV3 Color Sensor (45506) para determinar automaticamente o Duty Cycle a aplicar aos LEDs antes de cada fotografia.

AD4M4ST0R – o código

This post is part 4 of 9 of  AD4M4ST0R - um rover LEGO

Para terminar a apresentação do AD4M4ST0R faltava apenas o código.

O programa principal é um script escrito em python ‘joy.py’ que lida com o gamepad e os motores. O controlo da webcam e do buzzer é delegado em dois bash scripts (‘saycheese.sh’ e ‘beep.sh’).

O programa principal, “joy.py’:

import sys, traceback, os
os.environ['SDL_VIDEODRIVER'] = 'dummy'

# from math import sqrt
from math import log10
from subprocess import call
from time import sleep
from pygame import joystick, event, display

### botoes ###
B_TRIANG = 0
B_CIRC = 1
B_CROSS = 2
B_SQUARE = 3
B_LTRIG2 = 4
B_RTRIG2 = 5
B_LTRIG = 6
B_RTRIG = 7
B_SELECT = 8
B_LJOY = 10
B_RJOY = 11
B_START = 9


def main():
    try:
    display.init();
    joystick.init();
    js=joystick.Joystick(0);
    js.init();

    ### arranca em modo Joystick ###
    control_by_JOY=True;

    num_axes=js.get_numaxes();
    num_buttons=js.get_numbuttons();
    num_hats=js.get_numhats();

    ### sabemos a priori que tem 4 axes, 13 buttons e 1 hat

    ### inicializa motores com Duty Cycle=0
        call("echo 0 > /sys/class/tacho-motor/tacho-motor0/duty_cycle_sp", shell=True);
        call("echo 0 > /sys/class/tacho-motor/tacho-motor1/duty_cycle_sp", shell=True);
        call("echo 1 > /sys/class/tacho-motor/tacho-motor0/run", shell=True);
        call("echo 1 > /sys/class/tacho-motor/tacho-motor1/run", shell=True);

    flag=False;

    while True:
      x=y=motor_direito=motor_esquerdo=0.0;
      event.pump();

      button_mode=js.get_button(B_SELECT);
      button_shot=js.get_button(B_SQUARE);

      if button_mode ==1:

        call("./beep.sh" , shell=True);

        if control_by_JOY==True:
          control_by_JOY=False;
              print 'Control Mode=HAT';
        else:
          control_by_JOY=True;
              print 'Control Mode=JOYSTICK';

      if button_shot==1:
        print 'Cheese!!!';
        call("/root/saycheese.sh", shell=True);


      ### eixo dos joysticks entre -1 e +1
        ### x=axis2 , y=-axis3
      ### ignorar valores abaixo de 0.1 (dead zone)
      ### aplicar log10(100x) aos restantes (para dar enfase aos mais baixos)
      ### o resultado e' 0 ou 1 a 2 por isso
      ###    multiplicar por 25 da' valores Duty Cycle nulos ou entre 25 e 50

      if control_by_JOY==True:

            # Control by Right Joystick, Axis 2 e 3

        axis2=js.get_axis(2);
        axis3=js.get_axis(3);

        if axis2>0:
          if axis2<0.1:
            x=0;
          else:
            x=log10(axis2*100);
        elif axis2<0:
          if axis2>-0.1:
            x=0;
          else:
            x=-log10(-axis2*100);

        if axis3>0:
          if axis3<0.1:
            y=0;
          else:
            y=-log10(axis3*100);
        elif axis3<0:
          if axis3>-0.1:
            y=0;
          else:
            y=log10(-axis3*100);
        else:
          y=0;

        if y<>0:
          if x<0:
            motor_direito=50*y/2;
            # rodar para a esquerda => reduzir motor esquerdo
               motor_esquerdo=motor_direito*(1+x/3);
          else:
            motor_esquerdo=50*y/2;
            # rodar para a direita => reduzir motor direito
            motor_direito=motor_esquerdo*(1-x/3);
        elif x<>0:
           # y=0, apenas roda
           motor_esquerdo=25*x;
           motor_direito=-motor_esquerdo;

        else:

         # Control by HAT keys

         hat=js.get_hat(0);

         if hat==(0,1):
            ### anda em frente ###
            motor_direito=50;
            motor_esquerdo=50;
          elif hat==(1,0):
            ### roda para a direita ###
            motor_esquerdo=50;
            motor_direito=-50;
         elif hat==(0,-1):
            ### recua ###
            motor_direito=-50;
            motor_esquerdo=-50;
         elif hat==(-1,0):
            ### roda para a esquerda ###
            motor_esquerdo=-50;
            motor_direito=50;
         elif hat==(1,1):
            ### frente e roda para a direita ###
            motor_esquerdo=50;
            motor_direito=25;
         elif hat==(-1,1):
            ### frente e roda para a esquerda ###
            motor_esquerdo=25;
            motor_direito=50;
         elif hat==(-1,-1):
            ### recua e roda para a esquerda ###
            motor_esquerdo=-50;
            motor_direito=-25;
         elif hat==(1,-1):
            ### recua e roda para a direita ###
            motor_esquerdo=-25;
            motor_direito=-50;

       comando_motordir = "echo " + repr(motor_direito) + " > /sys/class/tacho-motor/tacho-motor0/duty_cycle_sp";
       comando_motoresq = "echo " + repr(motor_esquerdo) + " > /sys/class/tacho-motor/tacho-motor1/duty_cycle_sp";

       call(comando_motordir, shell=True);
       call(comando_motoresq, shell=True);

    # end while

    except (KeyboardInterrupt, SystemExit):
        print "Exiting...";
    except Exception:
        traceback.print_exc(file=sys.stdout);

    call("echo 0 > /sys/class/tacho-motor/tacho-motor0/duty_cycle_sp", shell=True);
    call("echo 0 > /sys/class/tacho-motor/tacho-motor1/duty_cycle_sp", shell=True);
    call("echo 0 > /sys/class/tacho-motor/tacho-motor0/run", shell=True);
    call("echo 0 > /sys/class/tacho-motor/tacho-motor1/run", shell=True);

    js.quit();
    joystick.quit();
    display.quit();
    sys.exit(0);

if __name__ == "__main__":
    main()

O controlo da webcam, ‘saycheese.sh’:

#!/bin/sh
filename=$(date +"%d-%m-%y_%Hh%Mm%Ss")
fswebcam -d /dev/video0 -p MJPEG -r 640x480 -q -D 1 -S 3 -s brightness=10 -s contrast=20  $filename.jpg

O controlo do buzzer, ‘beep.sh’:

#!/bin/bash
echo -en "07" > /dev/tty1

A quem o considerar útil, esteja à vontade para utilizar / feel free to use as you wish.

Computer Vision com LEGO

Com a webcam a funcionar e com tanta coisa em comum com a comunidade Raspberry Pi ocorreu-me experimentar o OpenCV e testar as capacidades do EV3  no campo da Computer Vision.

E afinal nem é muito difícil começar: segundo o artigo ‘Face Recognition With Python, in Under 25 Lines of Code‘ são mesmo suficientes menos de 25 linhas de código em Python.

Em vez de instalar o opencv todo, instalei apenas o módulo para python:

root@ev3dev:~# apt-get install python-opencv

Depois foi só criar o script ‘teste-opencv.py’:

import numpy as np
import cv2

imagePath = "/root/teste-face.jpg"
cascPath = "/root/haarcascade_frontalface_default.xml"
resultPath ="/root/resultado.jpg"

faceCascade = cv2.CascadeClassifier(cascPath)

image = cv2.imread(imagePath)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

faces = faceCascade.detectMultiScale(
    gray,
    scaleFactor=1.1,
    minNeighbors=5,
    minSize=(30, 30),
    flags = cv2.cv.CV_HAAR_SCALE_IMAGE
)

print "Found {0} faces!".format(len(faces))

# Draw a rectangle around the faces
for (x, y, w, h) in faces:
    cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)

cv2.imwrite(resultPath,image)

Como não instalei o opencv não tenho os ficheiros xml com regras de classificação. Para este exemplo o autor do artigo referido disponibiliza também o ficheiro ‘haarcascade_frontalface_default.xml‘.

Para esta foto ‘teste-face.jpg’:

teste-face02O resultado do script, ao fim de 3 a 5 minutos de CPU a 80% foi este:

resultado02Abre um mundo de possibilidades, não abre?