Varinha Gestual Inteligente: Controle Mágico com IA e UNIHIKER K10

Você já sonhou em ter uma varinha mágica capaz de reconhecer gestos? Como a varinha do Harry Potter.

 

 

Com o UNIHIKER K10, a plataforma Edge Impulse e alguns componentes eletrônicos, você pode criar uma varinha que reconhece gestos usando IA! Este projeto utiliza o acelerômetro embutido do K10 para treinar um modelo de machine learning e fazer inferências em tempo real, além de empregar o ESP NOW para comunicação entre múltiplos dispositivos UNIHIKER K10.

Prepare-se para mergulhar em um mundo onde gestos controlam a magia – ou, pelo menos, dispositivos inteligentes!

 

HARDWARE LIST
3 UNIHIKER K10
1 Fita LED RGB Digital 120 LED - Preto
1 Gravity: Botão de pressão digital
2 Gravity: Módulo de relé digital 10A
1 Cartão de memória MicroSD
1 Suporte de bateria de íons de lítio CR123A

 

ℹ️ Antes de começarmos...

⚠️ Atenção: a plataforma Edge Impulse ainda não possui interface em português. Sugerimos utilizar extensões de tradução automática do navegador — como o tradutor integrado do Google Chrome

 

 

Passo 1: Preparando o ambiente com Edge Impulse

O Edge Impulse é uma plataforma gratuita para criar modelos de IA. Com ela, você pode capturar dados via porta serial, enviá-los para um computador e depois usar o Edge Impulse CLI para processá-los diretamente na plataforma.

 

Passos necessários no computador:

- Registrar-se no Edge Impulse – Crie uma conta gratuita em https://edgeimpulse.com/

- Instalar Python 3 – Baixe a versão mais recente em https://www.python.org/

- Instalar Node.js (v14 ou superior) – Disponível em https://nodejs.org/en

- Terminal PowerShell com permissões de administrador

 

1. Abra o PowerShell como administrador: Clique com o botão direito no ícone do PowerShell no Menu Iniciar. Selecione "Executar como administrador".

 

2. Execute o comando de instalação: npm install -g edge-impulse-cli --force

 

 

Passo 2: Enviando dados do K10 para o Edge Impulse

1. Para enviar os dados do sensor para o K10 para treinamento, é necessário enviar o código de reencaminhamento de dados para o K10.

CODE
#include "unihiker_k10.h"
 
volatile float mind_n_test;
 
UNIHIKER_K10 k10;
 
 
void setup() {
        k10.begin();
        Serial.begin(9600);
}
void loop() {
        if ((k10.isGesture(TiltForward))) {
                mind_n_test = 1;
        }
        else {
                if ((k10.isGesture(TiltBack))) {
                        mind_n_test = 2;
                }
                else {
                        if ((k10.isGesture(TiltLeft))) {
                                mind_n_test = 3;
                        }
                        else {
                                if ((k10.isGesture(TiltRight))) {
                                        mind_n_test = 4;
                                }
                                else {
                                        if ((k10.isGesture(ScreenUp))) {
                                                mind_n_test = 5;
                                        }
                                        else {
                                                if ((k10.isGesture(ScreenDown))) {
                                                        mind_n_test = 6;
                                                }
                                                else {
                                                        mind_n_test = 0;
                                                }
                                        }
                                }
                        }
                }
        }
        Serial.print(mind_n_test);
        Serial.print(",");
        Serial.print((k10.getAccelerometerX()));
        Serial.print(",");
        Serial.print((k10.getAccelerometerY()));
        Serial.print(",");
        Serial.print((k10.getAccelerometerZ()));
        Serial.print(",");
        Serial.println((k10.getStrength()));
        delay(100);
}

 

2. No PowerShell, execute o seguinte comando para enviar dados do K10 para o Edge Impulse:

edge-impulse-data-forwarder --frequency 100

 

 

3. Insira sua conta EdgeImpulse e nomeie sua varinha mágica. Por fim, atribua nomes diferentes às cinco variáveis de saída do código acima. Aqui, eu as nomeei k, x, y, z e v.

 

 

 

📊 Passo 3: Coleta de dados e treinamento do modelo

1. Faça login na sua conta Edge Impulse e selecione o seu dispositivo de coleta de dados.

 

 

2. Selecione "Data acquisition", preencha a etiqueta dos dados do sensor de movimento, selecione o tempo de amostragem de 2000 ms e comece a amostragem. Após clicar em "Start sampling", você terá 2 segundos para agitar o K10 em suas mãos. Você pode agitar o K10 para desenhar círculos, triângulos, quadrados, etc. Antes de agitar, certifique-se de que o mesmo tipo de operação corresponda à mesma "Label".

 

 

Dica: É altamente recomendável coletar dados suficientes nos conjuntos de dados de treinamento e teste. O Edge Impulse usará os dados de treinamento para treinar e substituirá os dados de teste no modelo para validação.

 

 

 

Passo 4: Criando e treinando o modelo

1. Após coletar os dados, é possível acessar a janela de configuração "Create Impulse" para definir o tamanho e a frequência da coleta de valores característicos.

 

 

Os valores característicos podem ser gerados conforme mostrado na figura abaixo.

 

 

 

2. Em seguida, você pode entrar no "Classfier" para treinar o modelo. É possível definir o número de ciclos de treinamento. Aqui, defini 100 ciclos e selecionei a versão do modelo. O modelo float32 é um pouco maior, mas a precisão é muito maior.

 

 

3. Após a conclusão do treinamento, você poderá ver a precisão do modelo de treinamento no lado direito. Essa precisão é verificada substituindo o modelo pelo conjunto de dados de teste que coletamos anteriormente. Quando estiver satisfeito com a precisão, podemos exportar o modelo e implantá-lo. Clique em "Deployment". Selecione a "Arduino library", "TensorFlow Lite", em seguida, "Build". Uma biblioteca Arduino seria baixada.

 

 

 

Passo 5: Código

icon conv&depthwise.zip 27KB Download(0)

1. Depois de baixar o repositório, copie-o para a pasta libraries do Arduino IDE 1.8.19 e descompacte-o.

Copie os arquivos conv.cpp e depthwise_conv.cpp para:

src → edge-impulse-sdk → tensorflow → lite → micro → kernels

 

 

2. Carregue o código a seguir no Magic Wand. Devido ao modelo Edge Impulse, o tempo de compilação pode levar até 40 minutos.

 

👇 Acompanham os arquivos de biblioteca necessários para a compilação, bem como o material gráfico exibido na tela do Magic Wand K10.

icon RMT&DFRobot_NeoPixel lib.zip 24KB Download(0)
icon TrasmitterPic.zip 11KB Download(0)
CODE
	#include <esp_now.h>
#include <WiFi.h>
#include "unihiker_k10.h"
#include <DFRobot_NeoPixel.h>
#include <magic-xhl_inferencing.h> //Need to change to your own library
 
UNIHIKER_K10 k10;
DFRobot_NeoPixel neoPixel_P1;
uint8_t      screen_dir=2;
 
//MAC
uint8_t MAC1[] = {0x7C, 0xDF, 0xA1, 0xFE, 0xEF, 0xC4};//Magic wand mac address
uint8_t MAC0[] = {0x7C, 0xDF, 0xA1, 0xFD, 0x67, 0xB8};//First HAT mac address
uint8_t MAC2[] = {0x68, 0xB6, 0xB3, 0x22, 0x06, 0x34};//Second HAT mac address
 
typedef struct struct_message {
  uint8_t ID;
  char data[50];
} struct_message;
struct_message sendData;
struct_message recvData;
esp_now_peer_info_t peerInfo;
int x,y,z,v;
int mind_n_test = 0;
int i = 0;
int max_probability_class = 0; 
 
 
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", 
           mac_addr[0], mac_addr[1], mac_addr[2], 
           mac_addr[3], mac_addr[4], mac_addr[5]);
  if(status == ESP_NOW_SEND_SUCCESS){
    Serial.print("Send Success to ");
    Serial.println(macStr);
  }else{
    Serial.print("Send Fail to ");
    Serial.println(macStr);
  }
}
 
// Callback when data is received 
void OnDataRecv(const uint8_t * mac, const uint8_t *Data, int len) {
  memcpy(&recvData, Data, sizeof(recvData));
  Serial.println("=========");
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.println(recvData.ID);
  Serial.println(recvData.data);
  Serial.println("---------");
}
 
static float features[100];
 
int featureIndex = 0;
 
int raw_feature_get_data(size_t offset, size_t length, float *out_ptr) {
    memcpy(out_ptr, features + offset, length * sizeof(float));
    return 0;
}
 
void print_inference_result(ei_impulse_result_t result);
 
void setup()
{
    Serial.begin(9600);
    k10.begin();
    k10.initScreen(screen_dir);
    k10.creatCanvas();
    k10.setScreenBackground(0xFFFFFF);
    k10.rgb->write(-1, 0xFF0000);
    k10.initSDFile();
    k10.canvas->canvasDrawImage(0, 0, "S:/Fail.png");
    k10.canvas->updateCanvas();
    neoPixel_P1.begin(2, 7);
    pinMode(1, INPUT);
    WiFi.mode(WIFI_STA);
    //Init ESP-NOW
    if (esp_now_init() != ESP_OK) {
        Serial.println("Error initializing");
        return;
    }
 
    //Register the send callback function
    esp_now_register_send_cb(OnDataSent);
 
    peerInfo.channel = 0;  
    peerInfo.encrypt = false;
    //Register MAC0 devices
    memcpy(peerInfo.peer_addr, MAC0, 6);
    if (esp_now_add_peer(&peerInfo) != ESP_OK){
        Serial.println("Failed to add peer0");
        return;
    }
    //Register MAC2 devices
    memcpy(peerInfo.peer_addr, MAC2, 6);
    if (esp_now_add_peer(&peerInfo) != ESP_OK){
        Serial.println("Failed to add peer0");
        return;
    }
 
    //Register the receive callback function
    esp_now_register_recv_cb(OnDataRecv);
    sendData.ID = 1;
    k10.rgb->write(-1, 0x000000);
}
 
void loop()
{
    if (digitalRead(1)) {
        Serial.println("====================");
        k10.canvas->canvasDrawImage(0, 0, "S:/Effective.png");
        k10.canvas->updateCanvas();
        featureIndex = 0;
        neoPixel_P1.setRangeColor(0, 13, 0xFF0000);
        delay(100);
        
    while (featureIndex < 100) {
      if ((k10.isGesture(TiltForward))) {
        mind_n_test = 1;
      }
      else {
        if ((k10.isGesture(TiltBack))) {
          mind_n_test = 2;
          }
          else {
            if ((k10.isGesture(TiltLeft))) {
              mind_n_test = 3;
              }
              else {
                if ((k10.isGesture(TiltRight))) {
                  mind_n_test = 4;
                  }
                  else {
                    if ((k10.isGesture(ScreenUp))) {
                      mind_n_test = 5;
                      }
                      else {
                        if ((k10.isGesture(ScreenDown))) {
                          mind_n_test = 6;
                          }
                          else {
                            mind_n_test = 0;
                            }
                          }
                        }
                      }
                    }
                  }
    // Store data in the features array
    //Serial.print(features[featureIndex]);Serial.print(","); 
    features[featureIndex++] = mind_n_test;
    //Serial.print(features[featureIndex]);Serial.print(","); 
    features[featureIndex++] = k10.getAccelerometerX();
    //Serial.print(features[featureIndex]);Serial.print(","); 
    features[featureIndex++] = k10.getAccelerometerY();
    //Serial.print(features[featureIndex]);Serial.print(","); 
    features[featureIndex++] = k10.getAccelerometerZ();
    //Serial.print(features[featureIndex]);Serial.print(","); 
    features[featureIndex++] = k10.getStrength();
    delay(100);
    }
 
        ei_printf("Edge Impulse standalone inferencing (Arduino)\n");
 
        if (sizeof(features) / sizeof(float) != EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE) {
            ei_printf("The size of your 'features' array is not correct. Expected %lu items, but had %lu\n",
                EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE, sizeof(features) / sizeof(float));
            delay(1000);
            return;
        }
 
        ei_impulse_result_t result = { 0 }; 
 
        signal_t features_signal;
        features_signal.total_length = sizeof(features) / sizeof(features[0]);
        features_signal.get_data = &raw_feature_get_data;
 
        EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, false /* debug */);
        if (res != EI_IMPULSE_OK) {
            ei_printf("ERR: Failed to run classifier (%d)\n", res);
            return;
        }
 
        ei_printf("run_classifier returned: %d\r\n", res);
        print_inference_result(result);
        //neoPixel_P1.setRangeColor(0, 6, 0x00FF00);
        // for (int index = 0; index < 7; index++) {
        //     neoPixel_P1.shift(1);
        //     delay(20);
        // }
        if(max_probability_class == 1){
            strcpy(sendData.data, "Action_1");
            for (int i = 0; i < 7; i++) {
              neoPixel_P1.setRangeColor(i, i, 0x00FF00);
              neoPixel_P1.setRangeColor(13-i, 13-i, 0x00FF00);
              delay(200);
            }
            esp_now_send(0, (uint8_t *)&sendData, sizeof(sendData));  
            k10.canvas->canvasDrawImage(0, 0, "S:/Action_1.png");    
            Serial.println("esp_now_send");
            
        }else if(max_probability_class == 2){
            strcpy(sendData.data, "Action_2");
            for (int i = 0; i < 7; i++) {
              neoPixel_P1.setRangeColor(i, i, 0x00FF00);
              neoPixel_P1.setRangeColor(13-i, 13-i, 0x00FF00);
              delay(20);
            }
            esp_now_send(0, (uint8_t *)&sendData, sizeof(sendData));
            k10.canvas->canvasDrawImage(0, 0, "S:/Action_2.png"); 
            Serial.println("esp_now_send");
        }else if(max_probability_class == 3){
            strcpy(sendData.data, "Action_3");
            for (int i = 0; i < 7; i++) {
              neoPixel_P1.setRangeColor(i, i, 0x00FF00);
              neoPixel_P1.setRangeColor(13-i, 13-i, 0x00FF00);
              delay(20);
            }
            esp_now_send(0, (uint8_t *)&sendData, sizeof(sendData));
            k10.canvas->canvasDrawImage(0, 0, "S:/Action_3.png");
            Serial.println("esp_now_send");
        }  
        k10.canvas->updateCanvas();
        delay(2000);
        k10.canvas->canvasDrawImage(0, 0, "S:/Fail.png");
        k10.canvas->updateCanvas();
        neoPixel_P1.clear();
    }
}
 
 
void print_inference_result(ei_impulse_result_t result) {
    ei_printf("Timing: DSP %d ms, inference %d ms, anomaly %d ms\r\n",
            result.timing.dsp,
            result.timing.classification,
            result.timing.anomaly);
 
#if EI_CLASSIFIER_OBJECT_DETECTION == 1
    ei_printf("Object detection bounding boxes:\r\n");
    for (uint32_t i = 0; i < result.bounding_boxes_count; i++) {
        ei_impulse_result_bounding_box_t bb = result.bounding_boxes[i];
        if (bb.value == 0) {
            continue;
        }
        ei_printf("  %s (%f) [ x: %u, y: %u, width: %u, height: %u ]\r\n",
                bb.label,
                bb.value,
                bb.x,
                bb.y,
                bb.width,
                bb.height);
    }
#else
    ei_printf("Predictions:\r\n");
    float max_value = 0.0;
    for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
        ei_printf("  %s: ", ei_classifier_inferencing_categories[i]);
        ei_printf("%.5f\r\n", result.classification[i].value);
        if (result.classification[i].value > max_value) {
            max_value = result.classification[i].value;
            max_probability_class = i + 1; 
        }
    }
 
    ei_printf("Max Probability Class: %d\n", max_probability_class);
#endif
 
#if EI_CLASSIFIER_HAS_ANOMALY
    ei_printf("Anomaly prediction: %.3f\r\n", result.anomaly);
#endif
}

 

Após carregar o código, instale o botão e a fita de LED WS2812 conforme mostrado na imagem.

 

 

 

Passo 6: Código e montagem do “Chapéu Mágico” (receptor)

Outro K10 servirá como receptor de mensagens ESPNOW e acionará a faixa de luz LED WS2812 e o relé. O relé controla a alimentação do servidor, e o servidor que comprei no chapéu começa a funcionar assim que é ligado.

Dica: se você comprou um servidor de chapéu que precisa de sinal PWM ou outro sinal para funcionar, talvez seja necessário modificar o código abaixo adequadamente.

CODE
	#include <esp_now.h>
#include <WiFi.h>
#include "unihiker_k10.h"
 
//On black hat and orange hat K10 upload need to open the corresponding macro definition, comment out another macro definition
#define blackHAT
//#define orangeHAT
 
UNIHIKER_K10 k10;
uint8_t      screen_dir=2;
static int status = 0;
float startTime = 0;
float endTime = 0;
 
//MAC
uint8_t MAC1[] = {0x7C, 0xDF, 0xA1, 0xFE, 0xEF, 0xC4};//Magic Wand Mac Address
uint8_t MAC0[] = {0x7C, 0xDF, 0xA1, 0xFD, 0x67, 0xB8};//First hat Mac Address
uint8_t MAC2[] = {0x68, 0xB6, 0xB3, 0x22, 0x06, 0x34};//Orange hat Mac Address
 
typedef struct struct_message {
  uint8_t ID;
  char data[50];
} struct_message;
struct_message sendData;
struct_message recvData;
esp_now_peer_info_t peerInfo;
 
int recv_action;
 
// Callback when data is sent 
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", 
           mac_addr[0], mac_addr[1], mac_addr[2], 
           mac_addr[3], mac_addr[4], mac_addr[5]);
  if(status == ESP_NOW_SEND_SUCCESS){
    Serial.print("Send Success to ");
    Serial.println(macStr);
  }else{
    Serial.print("Send Fail to ");
    Serial.println(macStr);
  }
}
 
/* 
Callback when data is received  
ACTION 1 means circle, first hat move
ACTION 2 means shake, both hat stop
ACTION 3 means triangle, second hat move
The two HAT's K10's need to be uploaded with different programs, the program switching is achieved by the #define macro definition at the beginning of that program
*/
void OnDataRecv(const uint8_t * mac, const uint8_t *Data, int len) {
  memcpy(&recvData, Data, sizeof(recvData));
  Serial.println("=========");
  Serial.print("Bytes received: ");
  Serial.println(len);
  Serial.println(recvData.ID);
  Serial.println(recvData.data);
  if (String(recvData.data) == "Action_1") {
      recv_action = 1;
      k10.canvas->canvasText("Action_1", 0, 0, 0x0000FF, k10.canvas->eCNAndENFont24, 50, true);
      #ifdef blackHAT
      if(status = 0)
      {
        digitalWrite(P0, HIGH);
        status = 1;
      }
      else if(status = 1)
      {
        digitalWrite(P0, LOW);
        delay(1000);
        digitalWrite(P0, HIGH);
        status = 1;
      }
      #endif
  } else if (String(recvData.data) == "Action_2") {
      recv_action = 2;
      k10.canvas->canvasText("Action_2", 0, 0, 0x0000FF, k10.canvas->eCNAndENFont24, 50, true);
      digitalWrite(P0, LOW);
      status = 0;
  } else if (String(recvData.data) == "Action_3") {
      recv_action = 3;
      k10.canvas->canvasText("Action_3", 0, 0, 0x0000FF, k10.canvas->eCNAndENFont24, 50, true);
      #ifdef orangeHAT
      if(status = 0)
      {
        digitalWrite(P0, HIGH);
        status = 1;
      }
      else if(status = 1)
      {
        digitalWrite(P0, LOW);
        delay(1000);
        digitalWrite(P0, HIGH);
      }
      status = 0;
      #endif
  }
  Serial.println(recv_action);
  k10.canvas->updateCanvas();
  Serial.println("---------");
}
 
void setup() {
  Serial.begin(9600);
  k10.begin();
  pinMode(P0, OUTPUT);
  k10.initScreen(screen_dir);
  k10.creatCanvas();
  k10.setScreenBackground(0xFFFFFF);
  k10.rgb->write(-1, 0xFF0000);
  WiFi.mode(WIFI_STA);
  //Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing");
    return;
  }
 
 
  esp_now_register_send_cb(OnDataSent);
 
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;
  memcpy(peerInfo.peer_addr, MAC1, 6);
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer0");
    return;
  }
 
  //注册接收回调函数
  esp_now_register_recv_cb(OnDataRecv);
  k10.rgb->write(-1, 0x000000);
 
  delay(3000);
  digitalWrite(P0, HIGH);
  delay(1000);
  digitalWrite(P0, LOW);
}
 
void loop() {
  if(digitalRead(P0) == HIGH)
  {
    endTime = millis();
    if(endTime - startTime >= 15000)
    {
      startTime = endTime;
      delay(500);
      digitalWrite(P0, LOW);
      delay(1000);
      digitalWrite(P0, HIGH);
    }
  }
  else if(digitalRead(P0) == LOW)
  {
    startTime = millis();
  }
  Serial.print("Start Time: ");
  Serial.println(startTime);
  Serial.print("End Time: ");
  Serial.println(endTime);
}

Conecte o K10 ao seu módulo relé e use COM/NA para ligar e desligar o motor HAT.

Passo 7: Imprima o corpo da varinha em 3D

Imprima em 3D um dispositivo varinha mágica para instalar o K10. A parte traseira do dispositivo varinha mágica está equipada com uma tampa que pode ser usada para instalar uma caixa de bateria CR123A. A tampa pode ser equipada com ímãs para ativar a função de magnetização do compartimento da bateria.

icon WandCase.rar 4.72MB Download(0)

Passo 8 Ligue os chapéus e a varinha e, em seguida, agite a varinha.

Pressione o botão na Varinha Mágica e desenhe um triângulo em dois segundos. Quando a Varinha Mágica reconhecer o triângulo - movimento de desenho. A faixa de luz fica verde e o Chapéu Mágico balança de um lado para o outro.
Se você balançar a Varinha Mágica horizontalmente, o Chapéu Mágico vai parar de balançar.

Quer ajuda para adaptar esse projeto? Tem dúvidas na instalação?

💬 Entre no grupo do WhatsApp — estamos sempre à disposição para ajudá-lo!

License
All Rights
Reserved
licensBg
0