Skip to content

I have Q! ESP32-h2 + PN532 NFC Tag reader ( Expose The Tag UID to HA using Zigbee and return response what tasks to do next) #10667

@KMTsvetanov

Description

@KMTsvetanov

Related area

Zigbee, NFC tag reader, DAC Decoder, Micro Mini SD Storage Expansion Board

Hardware specification

ESP32-h2, PN532 I2C mode, I2S PCM5102 DAC Decoder, Micro Mini SD Storage Expansion Board

Describe the solution you'd like

Hello. Over the past 3 weeks I'm looking for a way to make this setup:

My idea is to make a Zigbee coordinator (ESP32-h2) that can:

Steps:

  1. Connect ESP32-h2 to HA - using Zigbee - DONE (library: espressif/arduino-esp32)

  2. Connect ESP32-h2 to PN532 NFC - using I2C - DONE (library: adafruit/Adafruit-PN532)
    By following this video

################
################ Q: 1 ### How to populate the TAG ID to Home Assistant using Zigbee ? ################
################

If you can't store it here:
image
Maybe just add it as:
image

There are 2 options to make the tag reader work:

  • If tag # 1,2,3,... detected, do task # 1,2,3,... - Simple and I will skip it
  • If tag # 1,2,3,... detected, pass the tag id to the main Zigbee controller (my HA brain that controlls all my automation). Than based on some automations will return to the ESP32-h2 a response and based on that response do task # 1,2,3,...

################
################ Q: 2### After Q1 is done, how do i return some data to ESP32-h2 ? ################
################

  1. Connect ESP32-h2 to PCM5102 DAC Decoder - using I2S - DONE (following XTronical youtube videos)
    I added a WavData.h for now until i implement the SD Expansion Board
    WavData.h containes 16 bit mono (16000Hz) or 16 bit steroe (16000Hz) made from Audacity and HxD programs.
    with code starting like that:
    unsigned const char WavData16BitMono[288982] = { 0x52, 0x49, 0x46, 0x46, 0xCE, 0x68, 0x04...
    Will remove it later.

  2. Connect ESP32-h2 to Micro Mini SD Storage Expansion Board - using SPI - TODO

  3. Added a pasive Buzzer - DONE

  4. Added a vibrating motor - DONE

Can someone help me with my Q? I think that adding custom Exposes (or NFC Tag) to Zigbee devices will be a good addition to this libraly. That way I can even triger an automation to play music from just opening a door or reading a tag.

Note* In the end I will provite the full steps and code!

Here is the code so far:


// ************************************** DAC **************************************
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s.h"                       // Library of I2S routines, comes with ESP32 standard install
#include "WavData.h"                          // The Wav file stored in memory, should be in folder with this file
static const i2s_port_t i2s_num = I2S_NUM_0;  // i2s port number
unsigned const char* TheData;
uint32_t DataIdx=0;                           // index offset into "TheData" for current  data t send to I2S

bool isMusicPlaying = false;

struct WavHeader_Struct
{
  //   RIFF Section    
  char RIFFSectionID[4];      // Letters "RIFF"
  uint32_t Size;              // Size of entire file less 8
  char RiffFormat[4];         // Letters "WAVE"
  
  //   Format Section    
  char FormatSectionID[4];    // letters "fmt"
  uint32_t FormatSize;        // Size of format section less 8
  uint16_t FormatID;          // 1=uncompressed PCM
  uint16_t NumChannels;       // 1=mono,2=stereo
  uint32_t SampleRate;        // 44100, 16000, 8000 etc.
  uint32_t ByteRate;          // =SampleRate * Channels * (BitsPerSample/8)
  uint16_t BlockAlign;        // =Channels * (BitsPerSample/8), effectivly the size of a single sample for all chans.
  uint16_t BitsPerSample;     // 8,16,24 or 32

  // Data Section
  char DataSectionID[4];      // The letters "data"
  uint32_t DataSize;          // Size of the data that follows
}WavHeader;

// I2S configuration structures

static const i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
    .sample_rate = 44100,                            // Note, this will be changed later
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,       // high interrupt priority
    .dma_buf_count = 8,                             // 8 buffers
    .dma_buf_len = 1024,                            // 1K per buffer, so 8K of buffer space
    .use_apll=0,
    .tx_desc_auto_clear= true, 
    .fixed_mclk=-1    
};

static const i2s_pin_config_t pin_config = {
    .bck_io_num = 10,                                 // The bit clock connectiom, goes to pin 27 of ESP32
    .ws_io_num = 25,                                  // Word select, also known as word select or left right clock
    .data_out_num = 12,                               // Data out from the ESP32, connect to DIN on 38357A
    .data_in_num = I2S_PIN_NO_CHANGE                  // we are not interested in I2S data into the ESP32
};

TaskHandle_t musicTaskHandle;

void musicTask(void* parameter) {
  while (true) {
    DAC_start();
  }
}

void DAC_setup() {
  const unsigned char *WavFile=WavData16BitMono;
  // const unsigned char *WavFile=WavData16BitStereo;
  Serial.begin(115200);
  memcpy(&WavHeader,WavFile,44);                      // Copy the header part of the wav data into our structure
  DumpWAVHeader(&WavHeader);                          // Dump the header data to serial, optional!
  if(ValidWavData(&WavHeader))
  {
    Serial.println("Initializing DAC...");
    i2s_driver_install(i2s_num, &i2s_config, 0, NULL);        // ESP32 will allocated resources to run I2S
    Serial.println("I2S Driver Installed");
    i2s_set_pin(i2s_num, &pin_config);                        // Tell it the pins you will be using
    i2s_set_sample_rates(i2s_num, WavHeader.SampleRate);      //set sample rate 
    TheData=WavFile+44;                                       // set to start of data  
  }                 
  else {
    Serial.println("Invalid WAV file. Exiting setup...");
    return;  // Or handle the error gracefully
  }
}

void DAC_start() {
  uint8_t Mono[4];                             // This holds the data we actually send to the I2S if mono sound
  const unsigned char *Data;                   // Points to the data we are going to send                                              
  size_t BytesWritten;                         // Returned by the I2S write routine, we are not interested in it

  // The WAV Data could be mono or stereo but always 16 bit, that's a data size of 2 byte or 4 bytes  
  // Unfortunatly I2S only allows stereo, so to send mono we have to send the mono sample on both left and right
  // channels. It's a bit of a faf really!
  if(WavHeader.NumChannels==1)     // mono
  {
    Mono[0]=*(TheData+DataIdx);                 // copy the sample to both left and right samples, this is left
    Mono[1]=*(TheData+DataIdx+1);      
    Mono[2]=*(TheData+DataIdx);                 // Same data to the right channel
    Mono[3]=*(TheData+DataIdx+1);     
    Data=Mono;    
  }
  else                            // stereo
    Data=TheData+DataIdx;
  
  i2s_write(i2s_num,Data,4,&BytesWritten,portMAX_DELAY); 
  DataIdx+=WavHeader.BlockAlign;                            // increase the data index to next next sample
  if(DataIdx>=WavHeader.DataSize) {              // If we gone past end of data reset back to beginning
    DataIdx=0;   
    // After breaking out of the loop, delete the task
    vTaskDelete(NULL);  // Pass NULL to delete the current task (musicTask)
  }
}
// ************************************** Micro Mini SD Storage Expansion Board *****************************


// // **************************************** factory reset ****************************************
#define BUTTON_PIN            9  // ESP32-C6/H2 Boot button
// Init button for factory reset

void factoryReset_setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

// **************************************** Zigbee ****************************************
#ifndef ZIGBEE_MODE_ZCZR
#error "Zigbee coordinator mode is not selected in Tools->Zigbee mode"
#endif

#include "Zigbee.h"

#define LED_PIN               RGB_BUILTIN
#define ZIGBEE_LIGHT_ENDPOINT 10

ZigbeeLight zbLight = ZigbeeLight(ZIGBEE_LIGHT_ENDPOINT);

void setLED(bool value) {

  Serial.print("TEST");
  Serial.println(value ? "1" : "0");
  digitalWrite(LED_PIN, value);
}

void ZIGBEE_setup() {  
  // Init LED and turn it OFF (if LED_PIN == RGB_BUILTIN, the rgbLedWrite() will be used under the hood)
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH);

  //Optional: set Zigbee device name and model
  zbLight.setManufacturerAndModel("Espressif", "ZBLightBulb");

  // Set callback function for light change
  zbLight.onLightChange(setLED);

  //Add endpoint to Zigbee Core
  log_d("Adding ZigbeeLight endpoint to Zigbee Core");
  Zigbee.addEndpoint(&zbLight);

  // When all EPs are registered, start Zigbee. By default acts as ZIGBEE_END_DEVICE
  log_d("Calling Zigbee.begin()");
  Zigbee.begin();
}







// ************************************** NFC CARD READER *****************************

// NFC Card Reader
#include <Wire.h>
#include <Adafruit_PN532.h>

#define PN532_IRQ  (2)
#define PN532_RESET (3)
#define SDA_PIN 4  // Choose SDA pin
#define SCL_PIN 5 // Choose SCL pin
unsigned long nfcCheckInterval = 2000;  // Interval for checking NFC (in milliseconds)
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void NFC_setup() {
  Wire.begin(SDA_PIN, SCL_PIN);  // Initializes I2C communication  
  nfc.begin();
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // halt
  }
  // Got ok data, print it out!
  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);

  // configure board to read FRID tags
  nfc.SAMConfig();

  Serial.println("Waiting for an ISO14443A Card ...");
}

// Global variables to store the last scanned UID
uint8_t lastUid[7] = {0};  // Buffer to store the last UID
uint8_t lastUidLength = 0;  // Length of the last UID

// Function to compare two UIDs
bool isSameUID(uint8_t* uid1, uint8_t length1, uint8_t* uid2, uint8_t length2) {
  if (length1 != length2) {
    return false;  // Different length, so UIDs are different
  }
  for (int i = 0; i < length1; i++) {
    if (uid1[i] != uid2[i]) {
      return false;  // Found different byte, so UIDs are different
    }
  }
  return true;  // UIDs are the same
}




// ************************************** BUZZER *****************************
const int buzzerPin = 22; // "+" passive buzzer

int buzzerMelody[] = { 262, 392, 523, 440, 466, 466, 440, 392, 523, 440, 523, 440, 349, 392, 523, 523, 523 };
int noteDurations[] = { 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500 };

// Start playing the buzzer (initialize the state)
void startBuzzer() {
  for (int i = 0; i < sizeof(buzzerMelody) / sizeof(buzzerMelody[0]); i++) {
    // Play the current note
    tone(buzzerPin, buzzerMelody[i], noteDurations[i]);   
    // Wait for the note to finish playing
    delay(noteDurations[i]);
  }
  // After the melody is finished, stop the buzzer
  noTone(buzzerPin);
}

void BUZZER_setup() {
  pinMode(buzzerPin, OUTPUT);
  noTone(buzzerPin); // Ensure buzzer is off at the start
}





// ************************************** VIBRATOR *****************************

const int vibratorPin = 1; // "+" passive buzzer

// Variables for managing vibrator timing
unsigned long vibratorStartTime = 0;
unsigned long vibratorCycleStartTime = 0;
bool isVibratorOn = false;
bool isVibratorActive = false;  // Tracks whether the vibrator should be active

// Start the vibrator (initialize the state)
void startVibrator() {
  digitalWrite(vibratorPin, HIGH);
  delay(500);
  digitalWrite(vibratorPin, LOW);
}

void VIBRATOR_setup() {
  pinMode(vibratorPin, OUTPUT);
  noTone(vibratorPin); // Ensure buzzer is off at the start
}





void setup() {
  Serial.begin(115200);
  while (!Serial) delay(1000);

  factoryReset_setup();
  ZIGBEE_setup();

  NFC_setup();
  BUZZER_setup();
  VIBRATOR_setup();
  DAC_setup();
  Serial.println("Hello from setup!");
}

void loop() {
  Serial.println("Hello from loop!");

  // Checking button for factory reset
  if (digitalRead(BUTTON_PIN) == LOW) {  // Push button pressed
    // Key debounce handling
    delay(100);
    int startTime = millis();
    while (digitalRead(BUTTON_PIN) == LOW) {
      delay(50);
      if ((millis() - startTime) > 3000) {
        // If key pressed for more than 3secs, factory reset Zigbee and reboot
        Serial.printf("Resetting Zigbee to factory settings, reboot.\n");
        Zigbee.factoryReset();
      }
    }
  } else {
    uint8_t success;
    uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the UID
    uint8_t uidLength;  // Length of the UID
    
    success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);   
    if (success) {  
      // Compare current UID with the last scanned UID
      if (isSameUID(uid, uidLength, lastUid, lastUidLength)) {
        // If the same UID as the last scan, skip processing
        // Serial.println("Same UID detected, skipping...");

        delay(nfcCheckInterval);  // Wait for 1 second before repeating (to avoid overloading the DAC)k
        
      } else {
        isMusicPlaying = true;
        // Save the current UID as the last UID
        memcpy(lastUid, uid, uidLength);  // Copy current UID to lastUid
        lastUidLength = uidLength;  // Save the length of the current UID

        // When an NFC card is detected, start both the melody and the vibrator
        // Display some basic information about the card
        Serial.println("Found an ISO14443A card");
        Serial.print("  UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
        Serial.print("  UID Value: ");
        nfc.PrintHex(uid, uidLength);

        startBuzzer();
        startVibrator();


        // Create a task to run DAC_start() continuously
        xTaskCreate(musicTask, "Music Task", 4096, NULL, 1, &musicTaskHandle);

        delay(nfcCheckInterval);
      }      
    }
  }
}









bool ValidWavData(WavHeader_Struct* Wav)
{
  
  if(memcmp(Wav->RIFFSectionID,"RIFF",4)!=0) 
  {    
    Serial.print("Invlaid data - Not RIFF format");
    return false;        
  }
  if(memcmp(Wav->RiffFormat,"WAVE",4)!=0)
  {
    Serial.print("Invlaid data - Not Wave file");
    return false;           
  }
  if(memcmp(Wav->FormatSectionID,"fmt",3)!=0) 
  {
    Serial.print("Invlaid data - No format section found");
    return false;       
  }
  if(memcmp(Wav->DataSectionID,"data",4)!=0) 
  {
    Serial.print("Invlaid data - data section not found");
    return false;      
  }
  if(Wav->FormatID!=1) 
  {
    Serial.print("Invlaid data - format Id must be 1");
    return false;                          
  }
  if(Wav->FormatSize!=16) 
  {
    Serial.print("Invlaid data - format section size must be 16.");
    return false;                          
  }
  if((Wav->NumChannels!=1)&(Wav->NumChannels!=2))
  {
    Serial.print("Invlaid data - only mono or stereo permitted.");
    return false;   
  }
  if(Wav->SampleRate>48000) 
  {
    Serial.print("Invlaid data - Sample rate cannot be greater than 48000");
    return false;                       
  }
  if(Wav->BitsPerSample!=16) 
  {
    Serial.print("Invlaid data - Only 16 bits per sample permitted.");
    return false;                        
  }
  return true;
}


void DumpWAVHeader(WavHeader_Struct* Wav)
{
  if(memcmp(Wav->RIFFSectionID,"RIFF",4)!=0)
  {
    Serial.print("Not a RIFF format file - ");    
    PrintData(Wav->RIFFSectionID,4);
    return;
  } 
  if(memcmp(Wav->RiffFormat,"WAVE",4)!=0)
  {
    Serial.print("Not a WAVE file - ");  
    PrintData(Wav->RiffFormat,4);  
    return;
  }  
  if(memcmp(Wav->FormatSectionID,"fmt",3)!=0)
  {
    Serial.print("fmt ID not present - ");
    PrintData(Wav->FormatSectionID,3);      
    return;
  } 
  if(memcmp(Wav->DataSectionID,"data",4)!=0)
  {
    Serial.print("data ID not present - "); 
    PrintData(Wav->DataSectionID,4);
    return;
  }  
  // All looks good, dump the data
  Serial.print("Total size :");Serial.println(Wav->Size);
  Serial.print("Format section size :");Serial.println(Wav->FormatSize);
  Serial.print("Wave format :");Serial.println(Wav->FormatID);
  Serial.print("Channels :");Serial.println(Wav->NumChannels);
  Serial.print("Sample Rate :");Serial.println(Wav->SampleRate);
  Serial.print("Byte Rate :");Serial.println(Wav->ByteRate);
  Serial.print("Block Align :");Serial.println(Wav->BlockAlign);
  Serial.print("Bits Per Sample :");Serial.println(Wav->BitsPerSample);
  Serial.print("Data Size :");Serial.println(Wav->DataSize);
}

void PrintData(const char* Data,uint8_t NumBytes)
{
    for(uint8_t i=0;i<NumBytes;i++)
      Serial.print(Data[i]); 
      Serial.println();  
}

Work in progress!!!
image

Metadata

Metadata

Assignees

Labels

Area: ZigbeeIssues and Feature Request about ZigbeeType: Feature requestFeature request for Arduino ESP32

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions