Piloter un panneau à led par une application Kivy-Android

(actualisé le ) par wlaidet

Projets, matériels et librairies :

De la même façon, vous pouvez piloter un robot motorisé Arduino : voir ici

Projets :

Vous trouverez dans cet article :

  • Comment créer les motifs et les faire varier (p.1)

Trois façons de piloter le panneau à LED :

  • Par USB (p.2)
  • Par Bluetooth classique (mode EDR) avec Android et Python-Kivy (p.3)
  • Par BluetoothLE (mode BLE) avec Android et Python-Kivy (p.4)

Voici une vidéo de Théo (il utilise ici une carte bluetooth EDR) :

<iframe class="player" type="text/html" width="600" height="480" src="http://www.youtube.com/embed/SPuXReKNHoI?enablejsapi=1&wmode=transparent&modestbranding=1&rel=0&fs=1&showinfo=1" frameborder="0" allowfullscreen webkitallowfullscreen mozallowfullscreen >

Matériels :

  • Carte Arduino Uno
  • Panneau à led 8x8 : ici
  • Un Grove base Shield

Pour la connexion Bluetooth, deux possibilités (mais préférez le mode EDR, c’est plus simple) :

  • Une carte bluetooth HC-06 (ou HC-05) pour une connexion en mode EDR
  • Un Grove BLE_v1 pour une connexion en mode BLE.
    (Il existe des cartes bluetooth dual-mode...)

Librairies Arduino :

Il vous faudra une version récente d’ardublock et des librairies augmentées (au moins 0.57 de mars 2016) : ici

Créer des motifs :

Pour mieux comprendre le fonctionnement du panneau à LED côté Arduino, Voici un premier exemple permettant de faire varier les motifs de façon autonome (sans connexion) :

Le panneau est fixé sur les pins D7, D8 et D9.

Une vidéo :

Le lien pour créer facilement les motifs : ici

Le code Arduino :

#include <LedControl.h>
#include <Duinoedu_LedControlAdd.h>

const int DIN_PIN = 7;
const int CS_PIN =  8;
const int CLK_PIN = 9;
const byte MOTIFS[3][8] = {
  {  
    B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000   }
  ,{  
    B10000001,   B01000010,   B00100100,   B00011000,   B00011000,   B00100100,   B01000010,   B10000001   }
  ,{  
    B00000000,   B00000000,   B00000000,   B00011000,   B00011000,   B00000000,   B00000000,   B00000000   }
};
const int MOTIFS_LEN = sizeof(MOTIFS)/8;
Duinoedu_LedControlAdd maMatrice;
int i=0;

void setup()
{
  maMatrice.brancher(7,8,9); // DIN, CS, CLK
}

void loop()
{
  maMatrice.afficherImage(MOTIFS[i]);
  i=i+1;
  i=i%3;
  delay(1000);
}

Télécharger

++++

Connexion USB

Création des motifs :

http://duinoedu.com/tools/matrix/

Le panneau est fixé sur les pins D7, D8 et D9.

import serial
import kivy
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label

class ChatApp(App):
 
    def build(self):
        #reconnaissance de la carte Arduino:
        self.Arduino = serial.Serial('/dev/ttyACM0', 9600)
       
        #On cree une disposition pour l'affichage:
        Layout=BoxLayout(orientation='vertical',spacing=40,padding=(200,20))
        #On cree un label:
        self.Label1=Label(text='Choisis ton motif', font_size=20)
        Layout.add_widget(self.Label1)
        #On cree deux boutons reponses:
        self.Bouton1=Button(text='Motif 1')
        self.Bouton1.id='1'
        self.Bouton1.bind(on_press=self.send)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.Bouton1)
        self.Bouton2=Button(text='Motif 2')
        self.Bouton2.id='2'
        self.Bouton2.bind(on_press=self.send)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.Bouton2)
        self.Bouton3=Button(text='Motif 3')
        self.Bouton3.id='3'
        self.Bouton3.bind(on_press=self.send)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.Bouton3)
 
        #On renvoie l'affichage:
        return Layout
 
    def send(self,instance):
        self.Arduino.write(instance.id)
 
if __name__ == '__main__':
    ChatApp().run()

Télécharger

//Librairies pour le panneau à led:
#include <LedControl.h>
#include <Duinoedu_LedControlAdd.h>

const int DIN_PIN = 7;
const int CS_PIN =  8;
const int CLK_PIN = 9;

//Tableau de motifs:
const byte MOTIFS[3][8] = {
  {  
    B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000   }
  ,{  
    B10000000,   B01000000,   B00100000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000   }
,{
  B00000000,  B01000010,  B01100110,  B01011010,  B01000010,  B01000010,  B01000010,  B00000000 }
};
const int MOTIFS_LEN = sizeof(MOTIFS)/8;
Duinoedu_LedControlAdd maMatrice;
 
word w = '0'; //mot qui reçoit les trames émises
 
//Initialisation des E/S et communication
void setup() {
    Serial.begin(9600); //vitesse de transmission
    maMatrice.brancher(7,8,9); // DIN, CS, CLK panneau à led
    maMatrice.afficherImage(MOTIFS[1]);
}
 
//Boucle principale
void loop() {
  recevoir();
  if (w=='1'){
    maMatrice.afficherImage(MOTIFS[0]);
  }
  if (w=='2'){
    maMatrice.afficherImage(MOTIFS[1]);
  }
  if (w=='3'){
    maMatrice.afficherImage(MOTIFS[2]);
  }
  delay(500);
}
 
//procédure qui lit les trames recues
void recevoir(){
  if (Serial.available()) {
    w=Serial.read();//On lit la valeur recue
    Serial.flush();
    }
}

Télécharger

++++

Connexion Bluetooth EDR

Création des motifs :

http://duinoedu.com/tools/matrix/

Le panneau est fixé sur les pins D7, D8 et D9.

On créé l’application Android avec Python-Kivy. (Evidemment, le code donné par Théo dans cet article ne fonctionne que sur Android...)

Le principe de la connexion vient d’ici : https://gist.github.com/tito/7432757

Remarques :

  • Le fichier buildozer.spec contient le nom de l’application, les droits BLUETOOTH et BLUETOOTH_ADMIN
  • La carte bluetooth s’appelle "HC-06" donc, si vous avez une autre carte, vous pouvez changer ce nom dans le code Python puis repackager votre application avec buildozer oubien simplement changer le nom de votre carte en "HC-06" sur la tablette.
  • Vous pouvez améliorer cette application en proposant dans votre interface utilisateur une liste de tous les périphériques bluetooth appairés sur la tablette afin que l’utilisateur choisisse le bon. (Cela permet de se passer du nom "HC-06" figé).
    La liste paired_devices dans le code Python est la liste de tous les périphériques bluetooth appairés sur la tablette... Ce n’est pas très compliqué de lister les noms...
  • Pour appairer la carte "HC-06" avec la tablette, il faut entrer le code "1234".

Voici le résultat du projet de Théo en vidéo (il améliore actuellement son application) :

L’ensemble du projet dans un zip :

  • buildozer.spec
  • main.py
  • images
  • l’application android (.apk)
  • le fichier ardublock (.abp)
  • le code arduino (.ino) :

Tous les codes en ligne :

Le code Python Kivy :

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from jnius import autoclass

BluetoothAdapter = autoclass('android.bluetooth.BluetoothAdapter')
BluetoothDevice = autoclass('android.bluetooth.BluetoothDevice')
BluetoothSocket = autoclass('android.bluetooth.BluetoothSocket')
UUID = autoclass('java.util.UUID')

#Connexion HC-06:
def get_socket_stream(name):
    paired_devices = BluetoothAdapter.getDefaultAdapter().getBondedDevices().toArray()
    socket = None
    for device in paired_devices:
        if device.getName() == name:
            socket = device.createRfcommSocketToServiceRecord(
                UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"))
            recv_stream = socket.getInputStream()
            send_stream = socket.getOutputStream()
            break
    socket.connect()
    return recv_stream, send_stream
 
class Application(App):
    def build(self):
        #Pour la connexion EDR :
        self.recv_stream, self.send_stream=None,None
        #Interface graphique :
        Layout=BoxLayout(orientation='vertical',spacing=20,padding=(0,20))
        self.Haut_Layout=GridLayout(cols=1,size_hint=(0.80,0.7),pos_hint={'x':.1, 'y':.1})
        self.orientation='vertical' #Nombre maximum de colonnes
       
        #On l'ajoute a la fenetre principal:
        Layout.add_widget(self.Haut_Layout)
       
        self.BoutonConnect=Button(text='Connect')
        self.BoutonConnect.bind(on_press=self.connect)
        #On ajoute le bouton dans l'affichage:
        self.Haut_Layout.add_widget(self.BoutonConnect)
       
       
        self.Bas_Layout=GridLayout(cols=3,size_hint=(0.80,0.7),pos_hint={'x':.1, 'y':.1},spacing=20)
        self.orientation='horizontal' #Nombre maximum de colonnes
        self.spacing=8 #Espace entre les objets contenus
        self.padding=30 #Marges interieures du Layout
       
        #On l'ajoute a la fenetre principal:
        Layout.add_widget(self.Bas_Layout)
       
        #On cree un bouton:
        self.Bouton1=Button()
        #On lui donne des proprietes:
        #Une taille en pourcentages:
        self.Bouton1.size_hint=(0.5,0.5)
        #Une position:
        self.Bouton1.pos_hint={0.5: 0.5}
        #Une couleur de fond:
        self.Bouton1.background_normal="image1.png"
        self.Bouton1.bind(on_press=self.send)
        self.Bouton1.id='1'
        #On l'ajoute au layout principal:
        self.Bas_Layout.add_widget(self.Bouton1)
       
        #On cree un bouton:
        self.Bouton2=Button()
        #On lui donne des proprietes:
        #Une taille en pourcentages:
        self.Bouton2.size_hint=(0.5,0.5)
        #Une position:
        self.Bouton2.pos_hint={0.6: 0.6}
        #Une couleur de fond:
        self.Bouton2.background_normal="image2.png"
        self.Bouton2.bind(on_press=self.send)
        self.Bouton2.id='2'
        #On l'ajoute au layout principal:
        self.Bas_Layout.add_widget(self.Bouton2)
       
         #On cree un bouton:
        self.Bouton3=Button()
        #On lui donne des proprietes:
        #Une taille en pourcentages:
        self.Bouton3.size_hint=(0.5,0.5)
        #Une position:
        self.Bouton3.pos_hint={0.9: 0.9}
        #Une couleur de fond:
        self.Bouton3.background_normal="image3.png"
        self.Bouton3.bind(on_press=self.send)
        self.Bouton3.id='3'
        #On l'ajoute au layout principal:
        self.Bas_Layout.add_widget(self.Bouton3)
       
       
        return Layout
 
    def connect(self,instance):
        try:
            self.recv_stream, self.send_stream = get_socket_stream('HC-06')
        except:
            instance.background_color=[1,0,0,1]
        if self.send_stream!=None:
            instance.background_color=[0,1,0,1]
            instance.text="connected"
 
    def send(self, instance):
        if self.send_stream!=None:
            self.send_stream.write(int(instance.id))
            self.send_stream.flush()
 
if __name__ == '__main__':
    Application().run()

Télécharger

L’image ardublock :

Le code Arduino correspondant :

#include <LedControl.h>
#include <Duinoedu_LedControlAdd.h>
#include <SoftwareSerial.h>

const int DIN_PIN = 7;
const int CS_PIN =  8;
const int CLK_PIN = 9;
const byte MOTIFS[][8] = {
  {  
    B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000   }
  ,{  
    B00000000,   B01100110,   B01100110,   B00000000,   B00000000,   B11000011,   B11111111,   B11111111   }
  ,{  
    B11111111,   B11111111,   B11111111,   B11111111,   B11111111,   B11111111,   B11111111,   B11111111   }
  ,{  
    B00011000,   B00011000,   B01111110,   B10011001,   B10000001,   B10000001,   B10000001,   B01111110   }
};
const int MOTIFS_LEN = sizeof(MOTIFS)/8;
Duinoedu_LedControlAdd maMatrice;
SoftwareSerial mySerial(2,3);
int _ABVAR_1_Recu = 0 ;

void setup()
{
  maMatrice.brancher(7,8,9); // DIN, CS, CLK
  mySerial.begin(9600);



  _ABVAR_1_Recu = 0 ;

  maMatrice.afficherImage(MOTIFS[3]);
}

void loop()
{
  _ABVAR_1_Recu = mySerial.read() ;
  if (( ( _ABVAR_1_Recu ) == ( 1 ) ))
  {
    maMatrice.afficherImage(MOTIFS[0]);
  }
  if (( ( _ABVAR_1_Recu ) == ( 2 ) ))
  {
    maMatrice.afficherImage(MOTIFS[1]);
  }
  if (( ( _ABVAR_1_Recu ) == ( 3 ) ))
  {
    maMatrice.afficherImage(MOTIFS[2]);
  }
}

Télécharger

++++

Connexion BluetoothLE

La connexion BLE est plus compliquée. Il vous faudra implémenter la classe BluetoothGattCallback et renseigner ce fichier java dans le buildozer.spec.

Vous trouverez un résumé de ce fonctionnement : ici

L’ensemble du projet dans un zip :

  • Le fichier buildozer.spec
  • Le main.py
  • Le fichier BluetoothGattImplem.java
  • L’application Android (.apk)
  • Le code Arduino (.ino)
LED_BLE
Tous les fichiers pour piloter un panneau à LED Arduino avec une application Android (KIvy) en Buetooth Low Energy

Voici le résultat en vidéo :

Création des motifs :

http://duinoedu.com/tools/matrix/

Le panneau est fixé sur les pins D7, D8 et D9.

On créé l’application Android avec Python-Kivy. (Evidemment, le code ne fonctionne que sur Android...)

Remarques :

  • Le fichier buildozer.spec contient le nom de l’application, les droits BLUETOOTH et BLUETOOTH_ADMIN ainsi que le lien vers le fichier .java
  • La carte bluetooth s’appelle "HMSoft" donc, si vous avez une autre carte, vous pouvez changer ce nom dans le code Python puis repackager votre application avec buildozer oubien simplement changer le nom de votre carte en "HMSoft" sur la tablette.
  • Vous pouvez améliorer cette application en proposant dans votre interface utilisateur une liste de tous les périphériques bluetooth appairés sur la tablette afin que l’utilisateur choisisse le bon. (Cela permet de se passer du nom "HMSoft" figé).
    La liste paired_devices dans le code Python est la liste de tous les périphériques bluetooth appairés sur la tablette... Ce n’est pas très compliqué de lister les noms...
  • Pour appairer la carte "HMSoft" avec la tablette, il faut entrer le code "000000".
'''
Bluetooth/Pyjnius example
=========================
This was used to send some bytes to an arduino BLE.
The app must have BLUETOOTH and BLUETOOTH_ADMIN permissions.
Connect your device to your phone, via the bluetooth menu. After the
pairing is done, you'll be able to use it in the app.
'''


import kivy
kivy.require('1.8.0')

from jnius import PythonJavaClass, java_method
from jnius import autoclass
from jnius import cast
from kivy.lang import Builder
from kivy.app import App
from kivy.logger import Logger
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import time
import struct

BluetoothAdapter = autoclass('android.bluetooth.BluetoothAdapter')
BluetoothDevice = autoclass('android.bluetooth.BluetoothDevice')
BluetoothSocket = autoclass('android.bluetooth.BluetoothSocket')
BluetoothGatt= autoclass('android.bluetooth.BluetoothGatt')
BluetoothGattCallback = autoclass('android.bluetooth.BluetoothGattCallback')
UUID = autoclass('java.util.UUID')
List = autoclass('java.util.List')
Context = autoclass('android.content.Context')
PythonActivity = autoclass('org.renpy.android.PythonActivity')
BluetoothGattService=autoclass('android.bluetooth.BluetoothGattService')
activity = PythonActivity.mActivity
btManager = activity.getSystemService(Context.BLUETOOTH_SERVICE)
Intent = autoclass('android.content.Intent')
BluetoothProfile = autoclass('android.bluetooth.BluetoothProfile')
Service=autoclass('android.app.Service')
etatconnexion=0

class PyBluetoothGattCallback(PythonJavaClass):
    __javainterfaces__ =["org/myapp/BluetoothGattImplem$OnBluetoothGattCallback"]
    __javacontext__ = 'app'

    @java_method('(Landroid/bluetooth/BluetoothGatt;II)V')
    def onConnectionStateChange(self, gatt, status, newstate):
        global etatconnexion
        Logger.info('%s' % newstate)
        etatconnexion=newstate
   
    @java_method('(Landroid/bluetooth/BluetoothGatt;I)V')
    def onServicesDiscovered(self, gatt, status):
        global servicesdiscovered
        Logger.info('%s' % status)
        servicesdiscovered=status#0 si discovered


BluetoothGattImplem = autoclass('org/myapp/BluetoothGattImplem')
pycallback = PyBluetoothGattCallback()
bg = BluetoothGattImplem()
bg.setCallback(pycallback)

def try_connect(name):
    global etatconnexion,servicesdiscovered
   
    servicesdiscovered=1
    BluetoothAdapter=btManager.getAdapter()
   
    paired_devices = BluetoothAdapter.getDefaultAdapter().getBondedDevices().toArray()
   
    if len(paired_devices)==0: #Existe-t-il des peripheriques appaires
        return None, None
   
    for device in paired_devices:
        if device.getName() == name:
            break
   
    if device.getName()!=name: #Existe-t-il un peripherique du nom 'HMSoft'
        return None, None
   
    BluetoothGatt=None
   
    BluetoothGatt = device.connectGatt(Service, 0, bg)
   
    compteur=0
    while etatconnexion!=2:
        compteur+=1
        time.sleep(0.1)
        if compteur==100:#Delai de connexion depasse
            Logger.info('%s' % "Toto1")
            Logger.info('%s' % etatconnexion)
            return None, None
   
    BluetoothGatt.discoverServices()#Il faut que ca marche pour recup un service
   
    compteur=0
    while servicesdiscovered!=0:
        compteur+=1
        time.sleep(0.1)
        if compteur==100:#Delai de decouverte des services depasse
            return None, None
   
    service = BluetoothGatt.getService(UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb"))
    try:
        characteristic = service.getCharacteristic(UUID.fromString("0000ffe1-0000-1000-8000-00805F9B34FB"))
    except:
        return None, None
   
    return BluetoothGatt, characteristic

class BluetoothApp(App):
    def build(self):
        #On créé une disposition pour l'affichage:
        Layout=BoxLayout(orientation='vertical',spacing=20,padding=(200,20))
        self.BoutonConnect=Button(text='Connect')
        self.BoutonConnect.bind(on_press=self.connect)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.BoutonConnect)
        #On cree des boutons pour les motifs:
        self.Bouton1=Button(text='Motif 1')
        self.Bouton1.id='1'
        self.Bouton1.bind(on_press=self.send)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.Bouton1)
        self.Bouton2=Button(text='Motif 2')
        self.Bouton2.id='2'
        self.Bouton2.bind(on_press=self.send)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.Bouton2)
        self.Bouton3=Button(text='Motif 3')
        self.Bouton3.id='3'
        self.Bouton3.bind(on_press=self.send)
        #On ajoute le bouton dans l'affichage:
        Layout.add_widget(self.Bouton3)
        self.gatt=None
        self.charac=None
       
        return Layout
   
    def connect(self,instance):
        try:
            BluetoothGatt.disconnect()
        except:
            pass
        instance.background_color=[1,1,0,1]
        instance.text="Wait for connexion"
        self.gatt, self.charac = try_connect('HMSoft')
        if self.charac!=None:
            instance.background_color=[0,1,0,1]
            instance.text="connected"
        else:
            instance.background_color=[1,0,0,1]
            instance.text="echec de connexion : un nouvel essai"

    def send(self, instance):
        global etatconnexion
        if self.charac!=None:
            nb=int(instance.id)
            if etatconnexion==2:
                self.charac.setValue(nb,17,0)
                BluetoothGatt.writeCharacteristic(self.charac)
            else:
                self.BoutonConnect.background_color=[1,0,0,1]
                self.BoutonConnect.text="echec de connexion : un nouvel essai"

if __name__ == '__main__':
    BluetoothApp().run()

Télécharger

Le code Arduino :

//Pour le Grove_BLE:
#include <SoftwareSerial.h>
 
#define RxD 2
#define TxD 3
 
SoftwareSerial bleShield(RxD,TxD);

//Librairies pour le panneau à led:
#include <LedControl.h>
#include <Duinoedu_LedControlAdd.h>

const int DIN_PIN = 7;
const int CS_PIN =  8;
const int CLK_PIN = 9;

//Tableau de motifs:
const byte MOTIFS[3][8] = {
  {  
    B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000   }
  ,{  
    B10000000,   B01000000,   B00100000,   B00000000,   B00000000,   B00000000,   B00000000,   B00000000   }
,{
  B00000000,  B01000010,  B01100110,  B01011010,  B01000010,  B01000010,  B01000010,  B00000000 }
};
const int MOTIFS_LEN = sizeof(MOTIFS)/8;
Duinoedu_LedControlAdd maMatrice;
 

//Preparation des variables pour recevoir les valeurs:
unsigned char tab[256];
int index = 0;
boolean dataAvailable = false;
 
void setup()
{
  Serial.begin(19200);
  pinMode(RxD, INPUT);//R
  pinMode(TxD, OUTPUT);//T
  bleShield.begin(38400);
  bleShield.print("AT+CLEAR");
  maMatrice.brancher(7,8,9); // DIN, CS, CLK panneau à led
  maMatrice.afficherImage(MOTIFS[1]);
}
 
void loop()
{  
  delay(200);
 
  while(bleShield.available()>0)//On lit tous les chiffres et on stock dans tab
  {
    tab[index++] = bleShield.read()-0x80;
  }
  if (index > 0)
  {
    for(int i = 0 ; i < index ; i++)
    {
      int chiffre = tab[i];//conversion en int
        if (chiffre==1){
    maMatrice.afficherImage(MOTIFS[0]);
        }
        if (chiffre==2){
    maMatrice.afficherImage(MOTIFS[1]);
        }
        if (chiffre==3){
    maMatrice.afficherImage(MOTIFS[2]);
        }
      delay(1000);//attente de 1seconde par affichage
    }
 
    index = 0;
  }
}

Télécharger