Comunicazione Bluetooth Low Energy tra Raspberry Pi 3 e The Tactigon

Bluetooth Low Energy tra Raspberry Pi 3 e The TactigonCiao! Qualche settimana fa abbiamo visto come installare BlueZ e bluepy sul computer Raspberry Pi 3, ed usufruire quindi del Bluetooth Low Energy. Oggi andremo ad instaurare una comunicazione Bluetooth Low Energy tra Raspberry Pi 3 e The Tactigon tramite uno script Python.

Lo sketch che andremo a caricare sulla nostra scheda The Tactigon verificherà la sua inclinazione (questa volta semplicemente analizzando l’accelerazione) e, ad un cambio di stato, invierà il comando tramite la caratteristica Bluetooth Low Energy. Lo script Python in esecuzione su Rasbperry Pi 3 B, alla ricezione del messaggio, ne effettuerà il parse per poi eseguire un’azione, nel nostro caso il cambio di stato di 4 GPIO.

Comunicazione Bluetooth Low Energy tra Raspberry Pi 3 e The Tactigon

Requisiti

Oggi avremo bisogno del nostro computer Raspberry Pi 3 con BlueZ e bluepy installati, come mostrato nell’articolo Bluetooth Low Energy con Raspberry Pi e Python pubblicato qualche settimana fa. Per programmare la scheda The Tactigon, invece, avremo bisogno solamente dell’IDE Arduino configurato come abbiamo visto nell’articolo dedicato.

Sketch

Come anticipato, The Tactigon verifica l’attuale posizione, e la comunica tramite Bluetooth Low Energy.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/** Questo sketch invia dei messaggi tramite              **/
/** Bluetooth Low Energy in base all'inclinazione         **/
/** Michele Valentini - Red Sheep Labs - www.yarosia.it   **/
 
#include "tactigon_led.h"
#include "tactigon_IMU.h"
#include "tactigon_BLE.h"
 
T_Led rLed, bLed, gLed;
T_ACC accMeter;
T_AccData accData;
 
T_QUAT qMeter;
T_QData qData;
 
T_BLE bleManager;
UUID uuid;
T_BLE_Characteristic bleChar;
 
//Ultimo stato inviato
boolean lastInfZ = false;
boolean lastSupZ = false;
boolean lastInfY = false;
boolean lastSupY = false;
boolean lastInfX = false;
boolean lastSupX = false;
//Questa variabile viene utilizzata come "debounce" degli stati di inclinazione
boolean justSent = true;
//Soglia minima dell'accelerazione
float threshold = 9.6;
int ticks;
//Usati per il controllo ciclico dei LEDs
int  ticksLed, stp;
 
void setup() {
 
  ticks = 0;
  rLed.init(T_Led::RED);
  gLed.init(T_Led::GREEN);
  bLed.init(T_Led::BLUE);
 
  rLed.off();
  gLed.off();
  bLed.off();
  bleManager.InitRole(TACTIGON_BLE_PERIPHERAL);
  //bleManager.setName("TTGPS");
  //Aggiungiamo la caratteristica 
  uuid.set("7ac71000-503d-4920-b000-acc000000001");
  bleChar = bleManager.addNewChar(uuid, 2);
}
 
//Accende tutti e tre i colori
void flash() {
  rLed.on();
  gLed.on();
  bLed.on();
}
 
void loop() {
  //Buffer di trasmissione
  unsigned char buffData[2] = {0x00, 0x00};
  //Eseguiamo il codice a 50Hz
  if (GetCurrentMilli() >= (ticksLed + (1000 / 50))) {
    //Aggiorniamo i valori di accelerazione
    accData = accMeter.getAxis();
    //Se non sono stati appena inviati i dati
    if (!justSent) {
      //Verifico quale posizione è stata impostata
      if (accData.z > threshold && !lastSupZ) {
        lastSupZ = true;
        lastInfZ = false;
        lastInfX = false;
        lastSupX = false;
        lastInfY = false;
        lastSupY = false;
        buffData[0] = 0x00;
        buffData[1] = 0xFF;
        flash();
        //bleChar.update(buffData);
        justSent = true;
      }
      if (accData.z < -threshold && !lastInfZ) { 
        lastInfZ = true; 
        lastSupZ = false; 
        lastInfX = false; 
        lastSupX = false; 
        lastInfY = false; 
        lastSupY = false; 
        buffData[0] = 0xFF; 
        buffData[1] = 0xFF; flash(); 
        //bleChar.update(buffData); 
        justSent = true; 
      } 
      if (accData.y > threshold && !lastSupY) {
        lastSupY = true;
        lastInfY = false;
        lastInfZ = false;
        lastSupZ = false;
        lastInfX = false;
        lastSupX = false;
        lastInfY = false;
 
        buffData[0] = 0x5A;
        buffData[1] = 0x5A;
        flash();
        bleChar.update(buffData);
        justSent = true;
      }
      if (accData.y < -threshold && !lastInfY) { 
        lastInfY = true; 
        lastSupY = false; 
        lastInfZ = false; 
        lastSupZ = false; 
        lastInfX = false; 
        lastSupX = false; 
        lastSupY = false; 
        buffData[0] = 0x00; 
        buffData[1] = 0x88; flash(); 
        bleChar.update(buffData); 
        justSent = true; 
      } 
      if (accData.x > threshold && !lastSupX) {
        lastSupX = true;
        lastInfX = false;
        lastInfZ = false;
        lastSupZ = false;
        lastInfX = false;
        lastInfY = false;
        lastSupY = false;
        buffData[0] = 0x00;
        buffData[1] = 0xBB;
        flash();
        bleChar.update(buffData);
        justSent = true;
      }
      if (accData.x < -threshold && !lastInfX) { 
        lastInfX = true; 
        lastSupX = false; 
        lastInfZ = false; 
        lastSupZ = false; 
        lastSupX = false; 
        lastInfY = false; 
        lastSupY = false; 
        buffData[0] = 0x15; 
        buffData[1] = 0x15; 
        flash(); 
        bleChar.update(buffData); 
        justSent = true; 
      } 
   } 
 } 
//Controllo ciclico dei LEDs if (GetCurrentMilli() >= (ticksLed + (1000 / 2))) {
    //Resettiamo il flag justSent
    justSent = false;
    ticksLed = GetCurrentMilli();
    if (stp == 0) {
      rLed.on();
      gLed.off();
      bLed.off();
    }
    else if (stp == 1) {
      rLed.off();
      gLed.off();
      bLed.on();
    }
    else if (stp == 2) {
      rLed.off();
      gLed.on();
      bLed.off();
    }
    stp = (stp + 1) % 3;
  }
}

Descrizione

La variabile:

boolean justSent = true;

ha il compito di interrompere la comunicazione dei dati per un periodo di tempo (una sorta di debounce) successivo all’invio di un dato.

I dati inviati sono i seguenti:

  • 5A5A
  • 00FF
  • FFFF
  • 1515
  • 0088
  • 00BB

Script Python

Lo script python da eseguire su Raspberry Pi è il seguente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#Script Python per la ricezione di comandi tramite
#Bluetooth Low Energy da The Tactigon
#Michele Valentini - Red Sheep Labs - www.yarosia.it
 
import RPi.GPIO as GPIO
 
import struct
import binascii
import bluepy
from bluepy import btle
 
#Configurazione dei GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
gpios = [7, 11, 13, 15]
GPIO.setup(gpios, GPIO.OUT, initial=GPIO.LOW)
configA=[gpios[1],gpios[3]]
configB=[gpios[0],gpios[2]]
 
#Classe per gestione NOTIFICATION
class MyDelegate(btle.DefaultDelegate):
    def __init__(self):
        btle.DefaultDelegate.__init__(self)
 
    #Analisi del messaggio e azione
	def handleNotification(self, cHandle, data):
        print("handleNotification()")
        val=binascii.b2a_hex(data)
        val=binascii.unhexlify(val)
        val = struct.unpack('>H', val)[0]
        if(val==0x5A5A):
            GPIO.output(configB, GPIO.HIGH)
            GPIO.output(configA, GPIO.LOW)
            print("Configuration B ON")
        elif(val==0x0088):
            GPIO.output(configA, GPIO.HIGH)
            GPIO.output(configB, GPIO.LOW)
            print("Configuration A ON")
        elif(val==0x00BB):
            GPIO.output(gpios, GPIO.HIGH)
            print("All ON")
        elif(val==0x1515):
            GPIO.output(gpios, GPIO.LOW)
            print("All OFF")
 
#Impostazione device Bluetooth Low Energy
#Ricordiamoci di modificare gli UUID di service e characteristic 
#oltre al MAC Address del nostro dispositivo The Tactigon
tactigon_uuid = btle.UUID("7ac71000-503d-4920-b000-acc000000001")
service_uuid = btle.UUID("bea5760d-503d-4920-b000-101e7306b000")  
p = btle.Peripheral("BE:A5:7F:2F:76:68")
p.setDelegate(MyDelegate())
 
#Lista delle caratteristiche
tactigonService = p.getServiceByUUID(service_uuid)
#for ch in tactigonService.getCharacteristics():
        #print str(ch)
 
#Otteniamo il dato dalla caratteristica
tactigonSensorValue = tactigonService.getCharacteristics(tactigon_uuid)[0]
val = tactigonSensorValue.read()
#print "Characteristic value: ", binascii.b2a_hex(val)
 
#Impostiamo la NOTIFICATION 
notifyTactigon = tactigonService.getDescriptors(forUUID=0x2902)[0]
notifyTactigon.write(struct.pack('<bb', 0x01, 0x00))
 
#Attendiamo NOTIFICATIONS
try:
    while True:
        if p.waitForNotifications(1.0):
            continue
except:
    print("Terminato...")

Prima di tutto dobbiamo ricordarci di cambiare il MAC Address e gli UUID contenuti nel codice, in base a quello che abbiamo a disposizione o programmiamo nella scheda The Tactigon.

Lo script esegue la scansione dei device Bluetooth, una volta trovato The Tactigon (tramite MAC Address, appunto) verifica la presenza del servizio e quindi della caratteristica richiesta, e si sottoscrive alle notifiche. Non viene più eseguito il polling, ma viene lanciata la callback

def handleNotification(self, cHandle, data):

se ci sono dei dati ricevuti.

Questa funzione appena definita andrà ad analizzare il messaggio ricevuto, e azionerà quindi i GPIO corretti.

Conclusioni

Questo piccolo script apre le porte al controllo di luci, apparecchi, o anche software tramite gesture, che in questo caso sono solamente posizioni della scheda The Tactigon, ma con un po di lavoro potrebbero diventare movimenti complessi catturati ed elaborati dalla scheda stessa.

Download

Qui è possibile scaricare il file sorgente dello sketch analizzato in questo articolo.

Spread the love

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.