Skip to content

Instantly share code, notes, and snippets.

@JpEncausse
Created March 2, 2024 15:40
Show Gist options
  • Save JpEncausse/cb1dbcca156784ac1e0804243da8e481 to your computer and use it in GitHub Desktop.
Save JpEncausse/cb1dbcca156784ac1e0804243da8e481 to your computer and use it in GitHub Desktop.
PixelDice ESP-32 work in progress
#include "pixelsdice.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#
const std::string serviceUUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const char* notifUUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const char* writeUUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
// ------------------------------------------
// PIXEL DICE
// ------------------------------------------
void PixelDice::print(){
Serial.print("PixelId : "); Serial.println(pixelId );
Serial.print("Name: "); Serial.println(name.c_str());
Serial.print("LEDs: "); Serial.println(ledCount);
Serial.print("Design & Color: "); Serial.println(designColor);
Serial.print("Roll State: "); Serial.println(rollState);
Serial.print("Current Face: "); Serial.println(currentFace);
Serial.print("Batterie Level: "); Serial.println(batteryLevel);
Serial.print("isCharging : "); Serial.println(batteryCharge);
Serial.print("Timestamp : "); Serial.println(timestamp);
Serial.print("FirmwareDate : "); Serial.println(firmwareDate );
}
void PixelDice::notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
/*
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("Data type: ");
Serial.println(pData[0]);
*/
if (pData[0] == 3){
Serial.print("Roll state: "); Serial.print(pData[1]); Serial.print(" face: "); Serial.println(pData[2]);
if (pData[1] == 1){ // On Face
currentFace = pData[2];
timestamp = millis();
}
}
else if (pData[0] == 34){
//Serial.print("Battery level: "); Serial.print(pData[1]); Serial.print(" state: "); Serial.println(pData[2]);
}
}
bool PixelDice::blinkDice(){
bool isConnected = client->isConnected();
if (!isConnected){ connect(); }
BLERemoteService* pRemoteService = client->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.println("Failed to find our service UUID");
disconnect();
return false;
}
BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(writeUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.println("Failed to find our characteristic UUID");
disconnect();
return false;
}
if (!pRemoteCharacteristic->canWrite()) {
Serial.println("Failed to write");
disconnect();
return false;
}
Blink blink;
uint8_t *dataPtr = (uint8_t *)&blink;
//Serial.println("Write pRemoteCharacteristic");
pRemoteCharacteristic->writeValue(dataPtr, sizeof(blink), true);
delay(3000);
if (!isConnected){ disconnect(); }
return true;
}
bool PixelDice::disconnect() {
if (!client->isConnected()){ return true; }
Serial.print("Disconnect from "); Serial.println(name.c_str());
client->disconnect();
return true;
}
bool PixelDice::connect() {
long t0 = millis();
if (client->isConnected()) {
//Serial.println("Device already connected");
return true;
}
Serial.println("Connecting");
client->connect(&device);
if (!client->isConnected()){
Serial.println("Failed to connect to server");
return false;
}
BLERemoteService* pRemoteService = client->getService(serviceUUID);
if (pRemoteService == nullptr) {
Serial.print("Failed to find our service UUID");
disconnect();
return false;
}
BLERemoteCharacteristic* pRemoteCharacteristic = pRemoteService->getCharacteristic(notifUUID);
if (pRemoteCharacteristic == nullptr) {
Serial.print("Failed to find our characteristic UUID");
disconnect();
return false;
}
if (pRemoteCharacteristic->canNotify()) {
auto callback = std::bind(&PixelDice::notifyCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4);
pRemoteCharacteristic->registerForNotify(callback);
} else {
Serial.println("Failed to register for notifications");
disconnect();
return false;
}
Serial.print("Connected to "); Serial.print(name.c_str()); Serial.print(" in "); Serial.print(millis() - t0); Serial.println("ms");
return true;
}
// ------------------------------------------
// PIXEL DICE MANAGER
// ------------------------------------------
PixelDice* PixelDiceManager::findDice(uint32_t pixelId){
for(int i = 0; i < diceList.size(); i++){
if (diceList[i].pixelId == pixelId) return &diceList[i];
}
Serial.println(">>> CREATE NEW DICE <<<");
PixelDice dice;
dice.pixelId = pixelId;
dice.client = BLEDevice::createClient();
diceList.insert(diceList.begin(), dice);
return &diceList.front();
}
void PixelDiceManager::unshiftDice(PixelDice* dice){
for(int i = 0; i < diceList.size(); i++){
if (dice->pixelId != diceList[i].pixelId) continue;
dice->timestamp = millis();
PixelDice temp = *dice;
diceList.erase(diceList.begin() + i);
diceList.insert(diceList.begin(), temp);
}
}
long PixelDiceManager::getTimestamp(uint8_t index){
if (index < diceList.size()){
PixelDice* dice = &diceList[index];
return dice->timestamp;
}
return 0;
}
void PixelDiceManager::extractPixelsData(BLEAdvertisedDevice advertisedDevice){
if (!advertisedDevice.haveServiceUUID()){ return; }
for (size_t i = 0; i < advertisedDevice.getServiceUUIDCount(); i++){
BLEUUID service = advertisedDevice.getServiceUUID(i);
if (service.toString() != serviceUUID){ continue; }
if (!advertisedDevice.haveServiceData()){ Serial.print("No Service Data "); continue; }
std::__cxx11::string rawData = advertisedDevice.getServiceData();
uint32_t pixelId = *(uint32_t*)&rawData[0];
std::string name = advertisedDevice.getName();
std::string addr = advertisedDevice.getAddress().toString();
PixelDice* dice = findDice(pixelId);
delay(500); // seems dice override their position in the list
//Serial.print("Find Dice: "); debugDice(dice);
dice->pixelId = pixelId;
dice->firmwareDate = *(uint32_t*)&rawData[4];
dice->device = advertisedDevice;
dice->name = name;
dice->addr = addr;
if (advertisedDevice.haveManufacturerData()){
uint8_t* pointer = (uint8_t*)advertisedDevice.getManufacturerData().data();
std::__cxx11::string rawData = advertisedDevice.getManufacturerData();
dice->ledCount = pointer[2];
dice->designColor = pointer[3];
dice->batteryLevel = pointer[6] & 0x7f;
dice->batteryCharge = (pointer[6] & 0x80) > 0;
uint8_t rollState = pointer[4];
if (rollState == 1){ // On Face
dice->rollState = rollState;
uint8_t currentFace = pointer[5];
if (currentFace != dice->currentFace){
dice->currentFace = currentFace;
//unshiftDice(dice);
}
}
}
}
}
void startScan(void* parameter) {
BLEScan* pScan = static_cast<BLEScan*>(parameter);
while (true){
Serial.println(">>> SCAN START <<<");
pScan->start(10);
pScan->clearResults();
Serial.println(">>> SCAN END <<<");
PixelDiceManager::getInstance().debugDiceList();
delay(2000);
}
vTaskDelete(NULL); // Supprimez cette tâche une fois le scan terminé
}
void PixelDiceManager::setup() {
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(new PixelAdvertiseCallbacks());
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
pBLEScan->setInterval(100);
pBLEScan->setWindow(99); // less or equal setInterval value
xTaskCreate(startScan, "ScanTask", 10000, pBLEScan, 1, NULL);
}
void PixelDiceManager::debugDiceList(){
for(int i = 0; i < diceList.size(); i++){
PixelDice* dice = &diceList[i];
debugDice(dice);
}
}
void PixelDiceManager::debugDice(PixelDice* dice){
Serial.print("PixelDice : ");
Serial.print(dice->pixelId); Serial.print(" | ");
Serial.print(dice->name.c_str()); Serial.print(" | ");
Serial.print(dice->addr.c_str()); Serial.print(" | ");
Serial.print(dice->currentFace+1); Serial.print(" | ");
Serial.print(dice->client->isConnected() ? "CONNECTED" : "DISCONNECTED");
Serial.println();
}
void PixelDiceManager::loop() {
uint8_t t0 = getTimestamp();
for(int i = 0; i < diceList.size(); i++){
PixelDice* dice = &diceList[i];
dice->connect();
//dice->disconnect();
//if (dice->currentFace == 9){ dice->blinkDice(); }
}
uint8_t t1 = getTimestamp();
if (t1 > t0){ Serial.println("SCAN UPDATE"); }
//Serial.print("LOOP");
//delay(10000);
}
PixelDiceManager& PixelDiceManager::getInstance() {
static PixelDiceManager instance; // Crée une seule instance
return instance;
}
// ------------------------------------------
// DICE ADVERTISEMENT
// ------------------------------------------
void PixelAdvertiseCallbacks::onResult(BLEAdvertisedDevice advertisedDevice) {
PixelDiceManager::getInstance().extractPixelsData(advertisedDevice);
}
#ifndef PIXELSDICE_H
#define PIXELSDICE_H
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include <vector>
// ------------------------------------------
// DICE EFFECTS
// https://github.com/GameWithPixels/.github/blob/main/doc/CommunicationsProtocol.md#iamadie
// ------------------------------------------
struct Blink
{
uint8_t type = 29;
uint8_t count = 2;
uint16_t duration = 2000;
uint32_t color = 0xff0000;
uint32_t mask = 0xffffffff;
uint8_t fade = 0;
uint8_t loop = 0;
};
// ------------------------------------------
// DICE
// https://github.com/GameWithPixels/.github/blob/main/doc/CommunicationsProtocol.md#iamadie
// Max connections : https://github.com/espressif/arduino-esp32/issues/8823#issuecomment-1789924653
// ------------------------------------------
class PixelDice {
public:
uint8_t ledCount = 0;
uint8_t designColor = 0;
uint8_t rollState = 0; // 0:Unknown - 1:OnFace - 2:Handling - 3:Rolling - 4:Crooked
uint8_t currentFace = 0;
uint8_t batteryLevel = 0;
uint8_t batteryCharge = 0;
long timestamp = 0;
uint32_t pixelId = 0;
uint32_t firmwareDate = 0;
std::string name;
std::string addr;
BLEAdvertisedDevice device;
BLEClient* client;
/**
* @brief Print the dice data.
*/
void print();
/**
* @brief Notify the dice data.
*/
void notifyCallback(BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify);
/**
* @brief Connect to the dice.
*/
bool connect();
/**
* @brief Connect to the dice.
*/
bool disconnect();
/**
* @brief Blink the dice.
*/
bool blinkDice();
};
class PixelAdvertiseCallbacks: public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertisedDevice) override;
};
class PixelDiceManager {
private:
PixelDiceManager() {} // Constructeur privé
PixelDiceManager(const PixelDiceManager&) = delete; // Supprime le constructeur de copie
PixelDiceManager& operator=(const PixelDiceManager&) = delete; // Supprime l'opérateur d'affectation
std::vector<PixelDice> diceList;
int scanTime = 10;
/**
* @brief Move the dice to the top of the list.
*/
void unshiftDice(PixelDice* dice);
public:
BLEScan* pBLEScan;
/**
* @brief Find an exisitng dice for the given pixelIdD or create a new one.
*/
PixelDice* findDice(uint32_t pixelId);
/**
* @brief Return the timestamp of the dice at the given index or the latest timestamp.
*/
time_t getTimestamp(uint8_t index = 0);
/**
* @brief Return the dice list.
*/
std::vector<PixelDice> getDiceList(){
return diceList;
}
// ------------------------------------------
// SINGLETON
// ------------------------------------------
static PixelDiceManager& getInstance();
// ------------------------------------------
// DEBUG
// ------------------------------------------
void debugDiceList();
void debugDice(PixelDice* dice);
// ------------------------------------------
// DICE ADVERTISEMENT
// ------------------------------------------
/**
* @brief Extract and Update the dice data from the advertised device.
*/
void extractPixelsData(BLEAdvertisedDevice advertisedDevice);
// ------------------------------------------
// DICE WORKFLOW
// ------------------------------------------
void setup();
void loop();
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment