LEGO laser harp – part II

This post is part 2 of 2 of  LEGO Laser Harp

About 10 years ago I offered my wife a M-Audio USB MIDI Keyboard and installed Ubuntu Studio on a computer so she could play some piano at home. She was so amazed with the possibility to generate music sheet while playing that almost accepted the idea of using Linux… almost 🙂

I remember that at that time I used timidity++ as a software MIDI synthesizer, tuned ALSA (one of the several Linux sound systems, perhaps the most generally used) and the preemptive kernel to work almost flawlessly with the Creative Labs sound card. My wife didn’t enjoy the KDE experience, Firefox was OK for her but OpenOffice were terribly with the spreadsheets she used and finally, when our first kid was born, she attended some English lessons at Wall Street Institute and we found out that the online lessons required an odd combination of an old version on Java, ActiveX and IE… so she returned to Windows XP and never looked back.

10 years is a LOT of time in computer systems but ALSA is still around, even on ev3dev. So I installed timidity++ and tried to play a MIDI file… to find that an ALSA module that is not currently available in ev3dev kernel is required just for MIDI.

I googled for alternatives and found fluidsynth with an unexpected bonus: there is a quite interesting python library, mingus, that works with fluidsynth. So I installed it in my Ubuntu laptop and in a few minutes I was playing harp – amazing!

sudo apt-get install fluidsynthsudo easy_install mingus
python
>>> from mingus.midi import fluidsynth
>>> from mingus.containers.note import Note
>>> fluidsynth.init("/usr/share/sounds/sf2/FluidR3_GM.sf2", "alsa")
>>> fluidsynth.set_instrument(1, 46)
>>> fluidsynth.play_Note(Note("C-3"))

In the previous example I just import the fluidsynth and Note parts of the library, initialize fluidsynth to work with ALSA loading the soundfount that cames with it, choose harp (instrument number 46) and play C3.

Well and polyphony? The correct way is to use a NoteContainer

from mingus.containers import NoteContainer
fluidsynth.play_NoteContainer(NoteContainer(["B-3", "C-3", "F-3"]))

but the lazy way is… just play several notes in a fast sequence.

So, let’s do it in the ev3dev!

Oops, fluidsynth also needs an ALSA module not available in current ev3dev kernel.

I’m not a linux music expert. Not even a linux expert! So after some more googling I gave up and asked for help in ev3dev’ GitHub project. And once again David accepted to include ALSA MIDI suport in future kernels, so I’ll just wait a bit.

Oh, but I can’t wait…

And if I read the color sensors in ev3dev and play the music in my laptop?

ALSA, once again, suports something like client/server MIDI communication with “aseqnet” and “aconnect” commands and some people are already using it with Raspberry Pi!

Yeah, I should have guessed… “aconnect” requires an ALSA MIDI module that is not available in current ev3dev kernel.

OK, let’s use MQTT: configure my EV3 as a publisher and my Ubuntu laptop as a subscriber and just send some notes as messages.

On the EV3:

sudo apt-get install mosquitto
sudo easy_install paho-mqtt

The publisher script is “harp-mqtt-pub.py”:

#!/usr/bin/env python

from ev3dev.auto import *
from time import sleep
import paho.mqtt.client as mqtt

DELAY = 0.01

# should have an auto-calibrate function
AMB_THRESHOLD = 9

sensor1 = ColorSensor('in1:i2c80:mux1')
sensor1.mode = 'COL-AMBIENT'
sensor2 = ColorSensor('in1:i2c81:mux2')
sensor2.mode = 'COL-AMBIENT'
sensor3 = ColorSensor('in1:i2c82:mux3')
sensor3.mode = 'COL-AMBIENT'
sensor4 = ColorSensor('in2')
sensor4.mode = 'COL-AMBIENT'
sensor5 = ColorSensor('in3')
sensor5.mode = 'COL-AMBIENT'
sensor6 = ColorSensor('in4')
sensor6.mode = 'COL-AMBIENT'

# there is no sensor7 yet, I need another MUX

s1 = 0
s2 = 0
s3 = 0
s4 = 0
s5 = 0
s6 = 0
s7 = 0

client = mqtt.Client()
client.connect("localhost",1883,60)

print 'Running...'

while True:
    key_touched = False
    s1 = sensor1.value(0)
    s2 = sensor2.value(0)
    s3 = sensor3.value(0)
    s4 = sensor4.value(0)
    s5 = sensor5.value(0)
    s6 = sensor6.value(0)
#    s7 = sensor7.value(0)

    if s1 < AMB_THRESHOLD:
        client.publish("topic/Harp", "C-3")
        key_touched=True
    if s2 < AMB_THRESHOLD:
        client.publish("topic/Harp", "D-3")
        key_touched=True
    if s3 < AMB_THRESHOLD:
        client.publish("topic/Harp", "E-3")
        key_touched=True
    if s4 < AMB_THRESHOLD:
        client.publish("topic/Harp", "F-3")
        key_touched=True
    if s5 < AMB_THRESHOLD:
        client.publish("topic/Harp", "G-3")
        key_touched=True
    if s6 < AMB_THRESHOLD:
        client.publish("topic/Harp", "A-3")
        key_touched=True
#    if s7 < AMB_THRESHOLD:
#        client.publish("topic/Harp", "B-3")
#        key_touched=True

    if key_touched == True:
        sleep(DELAY)

On the Ubuntu laptop side:

sudo easy_install paho-mqtt

The subscriber script is “harp-mqtt-sub.py”

#!/usr/bin/env python

import paho.mqtt.client as mqtt
from mingus.midi import fluidsynth
from mingus.containers.note import Note

EV3_IP = "192.168.43.35"

SOUNDFONT = 'Concert_Harp.sf2'
INSTRUMENT = 46 # Harp

NOTES = ['C-3','D-3','E-3','F-3','G-3','A-3','B-3']

def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))
    client.subscribe("topic/Harp")

def on_message(client, userdata, msg):
    global i
    if (msg.payload in NOTES):
        print msg.payload
        fluidsynth.play_Note(Note(msg.payload))
    
client = mqtt.Client()
client.connect(EV3_IP,1883,60)

client.on_connect = on_connect
client.on_message = on_message

fluidsynth.init(SOUNDFONT, "alsa")
fluidsynth.set_instrument(1, INSTRUMENT)

client.loop_forever()

And guess what? It works!!! I just love linux and open source!

I will keep waiting for David Lechner to include ALSA MIDI support in ev3dev’ kernel. I’m not so sure if there is enough horsepower in the EV3 to load a soundfont and play it with acceptable latency but if I can at least use the MIDI client/server functionality I can drop MQTT.

An interesting possibility that this client/server design allows is to scale my harp easily: with just a second EV3 (2 MUX each) I can make a 13-string harp with almost no modification on my code.

LEGO laser harp – part I

This post is part 1 of 2 of  LEGO Laser Harp

This is an idea I’ve been postponing for several months but the time has finally come: an laser harp.

After tinkering with lasers, fog, sound, color sensors and python I found myself wondering how to give a proper use to all that. Then I remembered Jean-Michel Jarre and how his laser harp made such a big impression on me at late 80’s when I finally decided “hey, i wanna study Electronics!”

For a first version, let’s call it “a proof of concept”, I just want a simple 7-string harp that can play the basic 7 notes. Polyphony would be great but I doubt that the EV3 sound capabilities allow that (and I cannot afford the brute force solution of using 7 EV3 so that each one plays only a single note).

So in the last months I’ve been buying EV3 color sensors and I finally have 7. Since the EV3 only has 4 input ports I need some kind of sensor multiplexer but thanks to mindsensors.com I already have one EV3SensorMux (and a second one is on the way, from an european distributor – portuguese customs DO stink!)

With 2 MUX it’s possible to connect up to 8 sensors to the EV3. Since I just want 7 “strings” I am considering using an 8th sensor to control the amplitude of the notes. I’ll try an ultrasonic sensor but I’m not sure if it has enough “wideness” to cover the whole harp, let’s see.

So of course I’ll be using ev3dev and python.

Using the EV3SensorMux is easy: just plug it to an input port and ev3dev immediately recognizes it:

lego-port port8: Registered 'in1:i2c80:mux1' on '3-0050'.
lego-port port8: Added new device 'in1:i2c80:mux1:lego-ev3-color'
lego-sensor sensor0: Registered 'ms-ev3-smux' on 'in1:i2c80'.
lego-port port9: Registered 'in1:i2c81:mux2' on '3-0051'.
lego-port port9: Added new device 'in1:i2c81:mux2:lego-ev3-color'
lego-sensor sensor1: Registered 'ms-ev3-smux' on 'in1:i2c81'.
lego-port port10: Registered 'in1:i2c82:mux3' on '3-0052'.
lego-port port10: Added new device 'in1:i2c82:mux3:lego-ev3-color'
lego-sensor sensor2: Registered 'ms-ev3-smux' on 'in1:i2c82'.
lego-sensor sensor3: Registered 'lego-ev3-color' on 'in1:i2c80:mux1'.
lego-sensor sensor4: Registered 'lego-ev3-color' on 'in1:i2c81:mux2'.
lego-sensor sensor5: Registered 'lego-ev3-color' on 'in1:i2c82:mux3'.

Even better: by default all 3 mux ports are configured for the EV3 color sensor, just as I wanted!

NOTE: as of today (kernel version ‘4.4.17-14-ev3dev-ev3’) my EV3 autodetection only works when booting with a non-default configuration:

sudo nano /etc/default/flash-kernel

 LINUX_KERNEL_CMDLINE="console=ttyS1,115200"

sudo flash-kernel
sudo reboot

this was suggested to me by David Lechner in another issue, hope will be fixed soon.

To use the color sensors in python I just need to know their ports. With the MUX in port ‘in1’ and 6 color sensors connected, these are the ports to use:

in1:i2c80:mux1
in1:i2c80:mux2
in1:i2c80:mux3
in2
in3
in4

And to play a note in python I just need to know it’s frequency to use with Sound.tone() function, so:

C3 = [(130.81, TONE_LENGHT)] 
D3 = [(146.83, TONE_LENGHT)] 
E3 = [(164.81, TONE_LENGHT)] 
F3 = [(174.61, TONE_LENGHT)] 
G3 = [(196.00, TONE_LENGHT)] 
A3 = [(220.00, TONE_LENGHT)] 
B3 = [(246.94, TONE_LENGHT)]

And so this was the first script for my harp:

#!/usr/bin/env python

from ev3dev.auto import *

TONE_LENGHT = 150

C4 = [(261.64, TONE_LENGHT)]   #Do4
D4 = [(293.66, TONE_LENGHT)]   #Re4
E4 = [(329.63, TONE_LENGHT)]   #Mi4
F4 = [(349.23, TONE_LENGHT)]   #Fa4
G4 = [(392.00, TONE_LENGHT)]   #Sol4
A4 = [(440.00, TONE_LENGHT)]   #La4
B4 = [(493.88, TONE_LENGHT)]   #Si4

AMB_THRESHOLD = 9

sensor1 = ColorSensor('in1:i2c80:mux1')
sensor1.mode = 'COL-AMBIENT'
sensor2 = ColorSensor('in1:i2c81:mux2')
sensor2.mode = 'COL-AMBIENT'
sensor3 = ColorSensor('in1:i2c82:mux3')
sensor3.mode = 'COL-AMBIENT'
sensor4 = ColorSensor('in2')
sensor4.mode = 'COL-AMBIENT'
sensor5 = ColorSensor('in3')
sensor5.mode = 'COL-AMBIENT'
sensor6 = ColorSensor('in4')
sensor6.mode = 'COL-AMBIENT'

# there is no sensor7 yet, I need another MUX

s1 = 0
s2 = 0
s3 = 0
s4 = 0
s5 = 0
s6 = 0
s7 = 0

while True:
    s1 = sensor1.value(0)
    s2 = sensor2.value(0)
    s3 = sensor3.value(0)
    s4 = sensor4.value(0)
    s5 = sensor5.value(0)
    s6 = sensor6.value(0)
#    s7 = sensor7.value(0)
  
    if s1 < AMB_THRESHOLD:
        Sound.tone(C4).wait()
    if s2 < AMB_THRESHOLD:
        Sound.tone(D4).wait()
    if s3 < AMB_THRESHOLD:
        Sound.tone(E4).wait()
    if s4 < AMB_THRESHOLD:
        Sound.tone(F4).wait()
    if s5 < AMB_THRESHOLD:
        Sound.tone(G4).wait()
    if s6 < AMB_THRESHOLD:
        Sound.tone(A4).wait()
#    if s7 < AMB_THRESHOLD:
#        Sound.tone(B4).wait()

So whenever the light level over one of the color sensor drops bellow AMB_THRESHOLD a note will play for TONE_LENGHT milliseconds.

Unfortunately the sound is monophonic (just one note can be played at a time) and it doesn’t sound like an harp at all – it sounds more like my BASIC games on the ZX Spectrum in the 80’s.

So I tried Sound.play(Wave File) instead. Found some harp samples, converted them to .wav files at 44100 Hz and it really sounds much better… but the length of the samples I found is to big so the “artist” have to wait for the note to stop playing before moving the hand to another “string”. Not good and also not polyphonic.

Next post I’ll show a better approach for both quality and polyphony: MIDI.

ev3dev – using IRLink with python

I got myself a HiTechnic IRLink sensor.

As of today (August 2016) ev3dev already recognizes the IRLink as a nxt-i2c sensor but there’s no language support for it. David Lechner suggested me using the “direct” attribute to communicate directly with the IRLink at I2C level.

Last time I wrote something mildly related to I2C was about 20 years ago for a Microchip PIC project but well… why not?

So after lots of trial and error, reading several times the LEGO Power Functions RC Protocol and shamelessly copying code from Mike Hatton (“Parax”), Xander Soldaat and Lawrie Griffiths I found on GitHub, RobotC forum and LeJOS forum I fanally managed to control a PF motor in ComboPWM mode.

In the following video, I’m increasing the motor speed (all 7 steps) then decreasing it again until it stops:

This is the python script running in the EV3:

#!/usr/bin/python

#
# based mainly on RobotC code from Mike Hatton ("Parax") and Xander Soldaat
# but also on LeJOS code from Lawrie Griffiths
#

# assumes IRLink at Input 1 as sensor0

import sys
from time import sleep

# channel: 0..3
# motorA, motorB: 0..7

channel = 0
for motorA in (1,1,2,2,3,3,4,4,5,5,6,6,7,7,6,6,5,5,4,4,3,3,2,2,1,1,0,0):

  motorB = motorA

  iBufferSize=2
  iBuffer = bytearray(iBufferSize)

  iBuffer[0] = ((0x04 | channel) << 4) | motorB
  iBuffer[1] = motorA << 4
  check = 0xF ^ (0x04 | channel) ^ motorB ^ motorA
  iBuffer[1] = iBuffer[1] | check

  oBufferSize=14
  oBuffer = bytearray(oBufferSize)

  # clear all positions
  for i in range (0,oBufferSize):
    oBuffer[i]=0

  oBuffer[0]=0x80    # Start Bit

  oBufferIdx = 0

  for iBufferByte in range (0,2):
    for iBufferIdx in range (0,8):
      oBuffer[1 + (oBufferIdx / 8)] |= (0x80 >> (oBufferIdx % 8) )
      if ( ( ( iBuffer[iBufferByte] ) & (0x80 >> (iBufferIdx % 8) ) ) != 0 ) :
        oBufferIdx = oBufferIdx + 6
      else:
        oBufferIdx = oBufferIdx + 3

# Stop bit
  oBuffer[1+ (oBufferIdx / 8)] |= (0x80 >> (oBufferIdx % 8) )

  tailIdx = 1 + (oBufferIdx / 8) + 1

  # Tail


  if (tailIdx == 10):
    oBuffer[tailIdx]= 0x10 # IRLink message payload length
    register = 0x43
  else:
    oBuffer[tailIdx]= 0x11
    register = 0x42

  oBuffer[tailIdx+1]= 0x02 # IRLink in Power Functions Mode
  oBuffer[tailIdx+2]= 0x01 # IRLInk Start transmission 


# clear IRLink (not sure if correct but seems to improve)

  fd = open("/sys/class/lego-sensor/sensor0/direct", 'wb',0)
  fd.seek(0x41)
  fd.write(chr(0x46))
  fd.write(chr(0x44))
  fd.write(chr(0x4C))
  fd.write(chr(0x50))
  fd.close()
  sleep(0.1)

  for i in range(0,5):
    fd = open("/sys/class/lego-sensor/sensor0/direct", 'wb',0)
    fd.seek(register)
    for oBufferIdx in range (0,oBufferSize):
      fd.write(chr(oBuffer[oBufferIdx]))
    fd.close()

    # Power Functions timings (for a 5-command burst)
    if (i==1):
      sleep(0.064)
    elif (i==5):
      sleep(0.096)
    else:
      sleep(0.080)

 

Running ev3dev on a Raspberry Pi 3

A few days ago the ev3dev project launched a great feature: nightly image builds. Right after that I got a received a notice that they included in the image for Raspberry Pi 2/3 support for onboard the Bluetooth and needed to test it.

So I did test it. And found out that onboard Bluetooth indeed works… as also onboard Wi-Fi… as also the Brick Pi, no need to disable BT. Yeah, no more USB dongles!

The procedure is very simple – the really important step is freeing the hardware serial port for the BrickPi (both the onboard Bluetooth and the BrickPi need a UART so a soft UART (“miniuart”) is used for BT instead of the default one.

  • get the latest nightly image build for the Pi2/Pi3 (mine was 26 July 2016) and restore it to a microSD card
  • insert the card in the Pi3
  • connect an USB keyboard and a HDMI display to the Pi3
  • power up the Pi
  • login (robot + maker) – if you cannot see the login prompt change to the proper console with Alt+F1 or Alt+F2 or Alt+F[n]
  • run ‘sudo connmanctl’ to configure BT and Wi-Fi (see this tutorial on how to configure Wi-Fi from command line; for BT just run ‘sudo connmanctl enable bluetooth’)
  • edit the ‘/boot/flash/config.txt’ and uncomment these 4 lines:
    • dtoverlay=brickpi
    • init_uart_clock=32000000
    • dtoverlay=pi3-miniuart-bt
    • core_freq=250
  • sudo reboot
  • remove the display and the keyboard and from now on just connect through Wi-Fi

To test that both Bluetooth and the BrickPi work properly I used a python script to read the NXT ultrasonic sensor (in the first input port) and change the color of my WeDo 2.0 Smart Hub from green to red:

#!/usr/bin/python

# run with sudo
# assumes NXT Ultrasonic at INPUT #1

from ev3dev.auto import *
from gattlib import GATTRequester
from time import sleep

BTdevice = "hci0"       # BlueTooth 4.0 BLE capable device

WeDo2HubAddress  = "A0:E6:F8:1E:58:57"

InputCommand_hnd = 0x3a
OutputCommand_hnd  = 0x3d

RGBAbsoluteMode_cmd = str(bytearray([01,02,06,17,01,01,00,00,00,02,01]))
RGBAbsoluteOutput_cmd = str(bytearray([06,04,03]))  # or "\x06\x04\x03"

DELAY      = 0.3

# DO NOT FORGET TO CONFIG FOR US sensor:
# sudo echo nxt-i2c > /sys/class/lego-port/port0/mode
# sudo echo "lego-nxt-us 0x01" > /sys/class/lego-port/port0/set_device
#
us = UltrasonicSensor('ttyAMA0:S1:i2c1')
assert us.connected

req = GATTRequester(WeDo2HubAddress,True,BTdevice)
sleep(DELAY)

# configure RBG LED to Absolute Mode (accepts 3 bytes for RGB instead of default Index Mode)
req.write_by_handle(InputCommand_hnd,RGBAbsoluteMode_cmd)

while(True):
  if (us.value() < 10):
    print("!")
    req.write_by_handle(OutputCommand_hnd, RGBAbsoluteOutput_cmd+chr(255)+chr(0)+chr(0))
    sleep(DELAY)
  else:
    print("-")
    req.write_by_handle(OutputCommand_hnd, RGBAbsoluteOutput_cmd+chr(0)+chr(255)+chr(0))
    sleep(DELAY)

My script need the gattlib library to talk with Bluetooth Low Energy devices. You can install this library with ‘pip’ but first need to install some dependencies:

sudo apt-get install pkg-config libboost-python-dev libboost-thread-dev libbluetooth-dev libglib2.0-dev python-dev

then

sudo pip install gattlib

EV3DEV na BRInCKa 2015

O controlo dos meus módulos MFL é feito por intermédio de um computador LEGO Mindstorms EV3 a correr ev3dev – Debian Linux for the EV3, uma versão de Debian Linux criada de propósito para o EV3.

EV3DEV inside

O tile à frente do pinguim foi-me oferecido pelos responsáveis do projecto em reconhecimento pelo Rasperry Pi 2 que contribuí para o projecto, que está agora numa fase de ser portado para outros dispositivos similares como o Pi ou o Cubieboard.

SBrick beta testing

Na campanha Kickstarter do SBrick (ou SmartBrick) inscrevi-me como beta tester. O meu SBrick chegou finalmente esta semana, vindo da Hungria numa encomenda verdadeiramente espartana, sem qualquer folheto ou instruções:

É ainda uma versão muito tosca e com alguns defeitos (um dos quatro canais parece estar avariado e a ficha de outro não encaixa com firmeza nos cabos Power Functions) mas já permite ensaiar a conectividade com o Linux e em especial com o ev3dev – o meu propósito como beta tester é sobretudo ajudar na ligação entre o Mindstorms EV3 e o Sbrick.

O SBrick expõe 6 serviços Bluetooth BT4.0/BLE: Generic Access, Device Information e mais 4 específicos do fabricante. Os 2 primeiros expõem esta informação:

Device Name = SBrick
Appearance = Generic Remote Control
Model Number  = 4.0
Firmware Revision = 4.1
Hardware Revision = 4.0
Software Revision = 4.1
Manufacturer Name String = Vengit Ltd.

Os outros 4 serviços são específicos da Vengit e expõem ao todo 8 campos:
– 5 Read Only
– 1 Write Only
– 1 Read/Write
– 1 Unknown

Entretanto recebi de um dos engenheiros da Vengit a informação mínima mas suficiente para controlar um motor ligado a um dos 4 canais: o UUID do serviço de controlo remoto é ‘4dc591b0-857c-41de-b5f1-15abda665b0c’, sendo a caracteristica do controlo remoto ‘2b8cbcc-0e25-4bda-8790-a15f53e6010f’.

Isso em termos práticos, utilizando o comando gatttool que vem com o BlueZ 5 (o stack de Bluetooth para Linux) corresponde a escrever no handle 0x0025 os comandos reconhecidos pelo firmware.

Até agora apenas me foram apresentados dois comandos (00h = BRAKE e 01h = DRIVE)

  • BRAKE Channel
  • DRIVE Channel Direction DutyCycle

Channel corresponde a uma das 4 portas disponíveis, podendo ser 00h, 01h, 02h ou 03h.

Direction corresponde ao sentido da rotação, podendo ser clokwise (00h) ou anticlockwise (01h).

DutyCycle corresponde à potência transmitida ao motor, variando entre 00h (“Coast”) e FFh (full power).

Assim para enviar um comando a partir de um sistema Linux (PC com Ubuntu, Raspberry Pi com Raspbian ou Mindstorms EV3 com ev3dev) equipado com dongle USB BT4.0/BLE basta invocar o comando gatttool:

$ gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010000FF

No exemplo acima é enviado para o dispositivo BT com identificativo 00:07:80:7F:28:E1 (o meu SBrick) a partir do device hci0 (o meu dongle BT4.0/BLE) o comando

DRIVE Channel#0 Clockwise 100%

Como este comando não mantém a sessão bluetooth aberta, ao fim de cerca de 3 segundos ela fecha e os canais deixam de comandar os motores (se invocarmos o comando gatttool em modo interactivo com a opção “-I” ou “–interactive” e dermos a partir daí os comandos equivalentes a sessão persiste e os motores continuam activos indefinidamente).

No video abaixo são mostrados dois motores a rodar em sentidos diferentes:

  • DRIVE Channel#0 Clockwise 22%
  • DRIVE Channel#2 Anticlockwise 17%

 

No próximo video é mostrado um motor EV3 a servir de referência a um motor Servo Power Functions:

E finalmente uma versão um pouco mais refinada do mesmo exemplo com introdução de um factor de escala de modo a que os movimentos sejam equiparáveis e ainda uma limitação ao intervalo [-90º,+90º]

 

Para este último video foi usado o script abaixo, em python:

import traceback
from sys import exit
from time import sleep
from subprocess import call
from subprocess import check_output
from math import trunc

def reset_ev3motor():
  call ("echo 0 > /sys/class/tacho-motor/motor0/position", shell=True);
  return;

def rd_ev3motor():

  v=check_output("cat /sys/class/tacho-motor/motor0/position",shell=True);
  return(trunc(float(v)));

def sbrick_drive(channel,direction,dutycycle):
  " calls gatttool command from BlueZ, the official Linux Bluetooth protocol stack"

  if(dutycycle > 255):
    dt_hexa="0xFF";
  else:
    dt_hexa=str(hex(int(dutycycle)));

  command="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01"+channel+direction+dt_hexa[2:];
#  print(command);
  call (command, shell=True);

  return;

def main():
  try:
    pos=0.0;
    SCALE=255.0/90.0;

    reset_ev3motor();

    while(True):
      pos=rd_ev3motor()*SCALE;

      if(pos>0):
        sbrick_drive("02","00",pos);
      else:
        sbrick_drive("02","01",-pos)

      sleep(0.1);

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

  exit(0);

if __name__ == "__main__":
  main()

Recorro à função ‘call’ da library ‘subprocess’ para invocar o comando gatttool. É feio mas funciona, ainda não tive tempo de procurar uma library específica para bluetooth BLE.

SBrick beta testing

At SBrick (or SmartBrick) Kickstarter campaign I pledged for beta tester. My beta SBrick arrived at last from Hungary:

As it’s still an early version it has some issues (one of  the four channels seems  to be damaged and LEGO Power Function cables don’t fit tight to another channel’s plug) but it’s good enough for testing connectivity with Linux, particularly with ev3dev – my main goal as beta tester is to help in connecting LEGO Mindstorms EV3 to the Sbrick.

SBrick exposes 6 Bluetooth 4.0 Low Energy «services» (I am not familiar with the BLE jargon yet): Generic Access, Device Information and another 4 specific from the vendor. The first and second services expose this information:

Device Name = SBrick
Appearance = Generic Remote Control
Model Number = 4.0
Firmware Revision = 4.1
Hardware Revision = 4.0
Software Revision = 4.1
Manufacturer Name String = Vengit Ltd.

The other 4 services are specific from Vengit and expose a total of 8 «fields»:
– 5 Read Only
– 1 Write Only
– 1 Read/Write
– 1 Unknown

Meanwhile I got from Vengit the minimal information needed for controlling a motor connected to one of the 4 channels: the reomote control service UUID is ‘4dc591b0-857c-41de-b5f1-15abda665b0c’ and the remote control characteristic is ‘2b8cbcc-0e25-4bda-8790-a15f53e6010f’.

For practical uses, using the ‘gatttool’ command from BlueZ 5 (the Linux Bluetooth stack), the above information translates to writing to handle 0x0025 the commands supported by the SBrick firmware.

I only know two of those commands (00h = BRAKE e 01h = DRIVE)

  • BRAKE Channel
  • DRIVE Channel Direction DutyCycle

‘Channel’ is one of the 4 output ports available and can be 00h, 01h, 02h or 03h.

‘Direction’ can be clockwise (00h) or anticlockwise (01h).

‘DutyCycle’ is the power pretended for the motor, can go from 00h (none or “Coast”) to FFh (“full power”).

So to send a command from Linux (a Ubuntu PC, a Raspberry Pi with Raspbian or a Mindstorms EV3 with ev3dev) with a USB BT4.0/BLE dongle one just need to use the ‘gatttool’:

$ gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=010000FF

The exemple above, sends to the Bluetooth device with address ’00:07:80:7F:28:E1′ (my SBrick) from the BT controller ‘hci0’ (my dongle) the command

DRIVE Channel#0 Clockwise 100%

As this command doesn’t keep the bluetooth connection open, the motor spins for around  3 secondss, then the connection drops and it stops (but if we use ‘gatttool’ in interactive mode with option “-I” or “–interactive” and send the equivalent commands, the motor will keep spinning).

Next video shows 2 motors spinning in opposite directions:

  • DRIVE Channel#0 Clockwise 22%
  • DRIVE Channel#2 Anticlockwise 17%

 

Next video shows an EV3 motor actiing as the reference for a Power Functionn Servo:

And a little better version of the same example, with a scale factor for matching the motor positions and a limitation of range to [-90º,+90º].

 

For that last video it was used this python script:

import traceback
from sys import exit
from time import sleep
from subprocess import call
from subprocess import check_output
from math import trunc

def reset_ev3motor():
  call ("echo 0 > /sys/class/tacho-motor/motor0/position", shell=True);
  return;

def rd_ev3motor():

  v=check_output("cat /sys/class/tacho-motor/motor0/position",shell=True);
  return(trunc(float(v)));

def sbrick_drive(channel,direction,dutycycle):
  " calls gatttool command from BlueZ, the official Linux Bluetooth protocol stack"

  if(dutycycle > 255):
    dt_hexa="0xFF";
  else:
    dt_hexa=str(hex(int(dutycycle)));

  command="gatttool -b 00:07:80:7F:28:E1 -i hci0 --char-write --handle=0x0025 --value=01"+channel+direction+dt_hexa[2:];
#  print(command);
  call (command, shell=True);

  return;

def main():
  try:
    pos=0.0;
    SCALE=255.0/90.0;

    reset_ev3motor();

    while(True):
      pos=rd_ev3motor()*SCALE;

      if(pos>0):
        sbrick_drive("02","00",pos);
      else:
        sbrick_drive("02","01",-pos)

      sleep(0.1);

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

  exit(0);

if __name__ == "__main__":
  main()

I use the ‘call’ function from library ‘subprocess’ to get to ‘gatttool’. It’s an ugly trick but it works, I did’t find a python bluetooth BLE library yet.

LEGO MOC: Xutos & Pontapés ao vivo no Braga BRInCKa 2014

Longe de estar terminado mas já suficientemente funcional para supreender alguns visitantes:

[actualização] com mais algumas minifigs e um backstage a disfarçar a electrónica [ideia da mulher, claro 😉 ]

Logitech c170 – MOD

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

A câmara utilizada no AD4MAST0R, uma webcam Logitech c170, não foi prevista para montagem em robots autónomos. Dois pontos que poderão ser vistos como vantajosos para o seu propósito original acabam por atrapalhar:

  • O clip é pouco útil, demasiado grande e não foi previsto como amovível
  • O cabo USB é longo (cerca de 1.2m) quando se podia ter colocado uma ficha no corpo da webcam para ser utilizada com um cabo USB do tamanho que se pretendesse

Felizmente não é difícil contornar estas limitações:

_________________________________________________________________________

ATENÇÃO!

A execução dos procedimentos abaixo VIOLA a garantia do produto. Além disso estes procedimentos estão descritos para um género de leitores que os compreende – se não for esse o caso NÂO tente proceder a estas modificações sob risco de danificar de foram definitiva a sua webcam e o bus USB do seu EV3 ou PC.

_________________________________________________________________________

1. Remoção do clip

Para remover o clip é necessária uma chave de relojoeiro (fenda ou Philips, desde que pequena). Também é conveniente uma chave de fendas maior (género busca-pólos) ou um canivete de lâmina fina mas forte.

  1. desapertar o parafuso da metade do clip junto à webcam e remover a tampa
  2. desapertar os 2 parafusos do corpo da webcam e destapar
  3. desapertar os 2 parafusos que fixam o cabo e os 2 parafusos da placa de circuito impresso
  4. soltar a placa de circuito impresso
  5. por baixo desta há um parafuso que fixa o eixo de plástico que mantém o clip agarrado ao corpo da webcam: desaparafusar
  6. através do furo deixado vago, tentar empurrar o eixo para fora até ser possível enfiar uma chave de fendas ou uma lâmina na ranhura entre a ponta do eixo e o corpo da webcam para poder puxar o parafuso para fora
  7. voltar a montar a placa de circuito impresso, apertar os restantes parafusos, alinhar o microfone, tapar a webcam e apertar os parafusos
  8. confirmar que a webcam ainda funciona 🙂

No final deste processo sobram o clip, a tampa do clip, o eixo e 2 parafusos

2. Redução do cabo

Bastou seguir o artigo “Connect USB with other devices by short Wire” do autor Indiana67 no site “Instructables”. A única diferença foi não ter tubo termoretráctil suficientemente largo, o que utilizei não consegui puxar para cima da zona cortada e por isso acabei enrolando fita de electricista preta. Feio mas igualmente funcional.

E confirmei novamente que ainda funciona 🙂

 

3. LEGO-lização da webcam

No lugar deixado vago pelo eixo de fixação ao clip pode-se montar um eixo LEGO Technic, basta alargar um pouco [e com cuidado] o furo e colar com uma cola resistente:

O eixo permite inúmeras possibilidades de montagem, comecei por esta:

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)