Code source - ayaabidghozy13/Syst-me-Embarqu-Biom-dicaux GitHub Wiki

Code source

Tout le code du projet se trouve dans le dépôt GitHub :

Syst-me-Embarqu-Biom-dicaux/
├── firmware/
│   └── cardiotrack/
│       └── cardiotrack.ino   ← sketch Arduino IDE (Arduino UNO R4 WiFi)
├── node-red/
│   └── flows.json            ← flow Node-RED + dashboard (à importer)
└── README.md

📁 Liens directs :

Environnement

Le firmware a été développé avec l'Arduino IDE 2 (carte : Arduino UNO R4 WiFi).

Librairie Auteur Équivalent PlatformIO (lib_deps)
MFRC522 GithubCommunity (miguelbalboa) miguelbalboa/MFRC522
PubSubClient Nick O'Leary knolleary/PubSubClient

WiFiS3 et SPI sont fournies avec le paquet de cartes Arduino UNO R4.

Avant de téléverser

  1. Ouvrez firmware/cardiotrack/cardiotrack.ino dans l'Arduino IDE et sélectionnez la carte Arduino UNO R4 WiFi.
  2. Remplacez VOTRE_SSID et VOTRE_MOT_DE_PASSE par les identifiants de votre réseau Wi-Fi.
  3. Si plusieurs groupes utilisent le broker public en même temps, changez le préfixe des topics (health/... → cardiotrack-groupeX/...) dans le firmware et dans le flow Node-RED.
  4. Importez node-red/flows.json dans Node-RED (Menu → Import), puis cliquez sur Deploy.

Organisation du firmware

Fonction Rôle
gererWiFi() / gererMQTT() Connexion et reconnexion non bloquantes, Last Will MQTT
recevoirCommande() Lecture de lamp=..;buzzer=..;frequency=.. et pilotage des actionneurs
lireRFID() Lecture de l'UID, anti-répétition 3 s, passage en Patient identifié
lireBouton() / actionBouton() Anti-rebond puis action selon l'état (refused / start / stop)
verifierDelaiIdentification() Retour en attente après 60 s sans appui
envoyerECG() Échantillonnage 50 Hz et publication JSON pendant l'acquisition
envoyerHeartbeat() Publication de l'état toutes les 2 s

Organisation du flow Node-RED

Fonction Node-RED Rôle
ECG processing + BPM estimate Ligne de base, détection des pics R, BPM, état des électrodes
BPM alarm — lamp + buzzer Compare le BPM au seuil et publie health/command
Button event (start/stop/refused) Texte du bouton, efface le graphe au start, remet tout à zéro au stop
Arduino connection + session En ligne si un message est reçu dans les 5 dernières secondes
Patient card state État de la carte patient (UID, heure, session)
Manual command (full state) Commandes manuelles de la lampe et du buzzer
Save BPM threshold Mémorise le seuil d'alarme (40–180 BPM)

firmware/cardiotrack/cardiotrack.ino

#include <SPI.h>
#include <MFRC522.h>
#include <WiFiS3.h>
#include <PubSubClient.h>
#include <string.h>
#include <stdlib.h>

// =====================================================
// PINS - ARDUINO UNO R4 WIFI
// =====================================================
const int LAMPE = 9;
const int BOUTON = 2;
const int BUZZER = 3;
const int ECG = A0;
const int LO_PLUS = 5;
const int LO_MOINS = 4;

#define RFID_SS 10
#define RFID_RST 7
MFRC522 rfid(RFID_SS, RFID_RST);

// =====================================================
// WIFI / MQTT
// =====================================================
const char* WIFI_SSID = "VOTRE_SSID";
const char* WIFI_PASSWORD = "VOTRE_MOT_DE_PASSE";
const char* MQTT_SERVER = "broker.emqx.io";
const int MQTT_PORT = 1883;

WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);

const char* TOPIC_ECG = "health/ecg";
const char* TOPIC_RFID = "health/rfid";
const char* TOPIC_BUTTON = "health/button";
const char* TOPIC_STATUS = "health/status";
const char* TOPIC_COMMAND = "health/command";

// =====================================================
// TIMING
// =====================================================
const unsigned long PERIODE_ECG = 20;                // 50 Hz
const unsigned long DEBOUNCE_BOUTON = 50;
const unsigned long DELAI_WIFI = 10000;
const unsigned long DELAI_MQTT = 3000;
const unsigned long ANTI_REPETITION_RFID = 3000;
const unsigned long DELAI_IDENTIFICATION = 60000;    // 60 s pour appuyer après le badge
const unsigned long PERIODE_STATUT = 2000;           // heartbeat vers Node-RED

unsigned long dernierEchantillon = 0;
unsigned long derniereTentativeWiFi = 0;
unsigned long derniereTentativeMQTT = 0;
unsigned long dernierChangementBouton = 0;
unsigned long dernierAppui = 0;
unsigned long dernierBadgeMillis = 0;
unsigned long debutIdentification = 0;
unsigned long dernierStatut = 0;

int dernierBoutonLu = HIGH;
int etatStableBouton = HIGH;

char dernierUID[32] = "";
char uidPatient[32] = "";

bool etatLampe = false;
bool etatBuzzer = false;
int frequenceBuzzer = 600;

// =====================================================
// MACHINE D'ÉTATS
// ATTENTE_BADGE  --badge-->  PATIENT_IDENTIFIE  --bouton-->  ACQUISITION
//       ^                         |  (timeout 60 s)               |
//       +-------------------------+<----------bouton (stop)-------+
// =====================================================
enum EtatSysteme { ATTENTE_BADGE, PATIENT_IDENTIFIE, ACQUISITION };
EtatSysteme etat = ATTENTE_BADGE;

const char* texteEtat() {
  switch (etat) {
    case ATTENTE_BADGE:     return "Attente badge";
    case PATIENT_IDENTIFIE: return "Patient identifie";
    case ACQUISITION:       return "Acquisition en cours";
  }
  return "Inconnu";
}

void publierStatut() {
  if (mqtt.connected()) mqtt.publish(TOPIC_STATUS, texteEtat());
  dernierStatut = millis();
}

void changerEtat(EtatSysteme nouvelEtat) {
  etat = nouvelEtat;
  Serial.print("Etat -> ");
  Serial.println(texteEtat());
  publierStatut();
}

// Bip court sans couper une alarme déjà active.
void bip(int frequence, unsigned long duree) {
  if (!etatBuzzer) tone(BUZZER, frequence, duree);
}

// =====================================================
// WIFI - NON BLOQUANT
// =====================================================
void gererWiFi() {
  if (WiFi.status() == WL_CONNECTED) return;

  unsigned long maintenant = millis();
  if (maintenant - derniereTentativeWiFi < DELAI_WIFI) return;

  derniereTentativeWiFi = maintenant;
  Serial.println("Tentative de connexion Wi-Fi...");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
}

// =====================================================
// COMMANDES NODE-RED
// Format: lamp=1;buzzer=0;frequency=800
// =====================================================
void recevoirCommande(char* topic, byte* payload, unsigned int length) {
  if (strcmp(topic, TOPIC_COMMAND) != 0) return;

  char message[128];
  if (length == 0 || length >= sizeof(message)) return;

  memcpy(message, payload, length);
  message[length] = '\0';

  char* champLampe = strstr(message, "lamp=");
  char* champBuzzer = strstr(message, "buzzer=");
  char* champFrequence = strstr(message, "frequency=");

  if (champLampe != nullptr) etatLampe = atoi(champLampe + 5) != 0;
  if (champBuzzer != nullptr) etatBuzzer = atoi(champBuzzer + 7) != 0;

  if (champFrequence != nullptr) {
    int f = atoi(champFrequence + 10);
    if (f >= 100 && f <= 5000) frequenceBuzzer = f;
  }

  digitalWrite(LAMPE, etatLampe ? HIGH : LOW);

  if (etatBuzzer) tone(BUZZER, frequenceBuzzer);
  else noTone(BUZZER);

  Serial.print("Commande Node-RED : ");
  Serial.println(message);
}

// =====================================================
// MQTT - NON BLOQUANT (+ Last Will)
// =====================================================
void gererMQTT() {
  if (WiFi.status() != WL_CONNECTED || mqtt.connected()) return;

  unsigned long maintenant = millis();
  if (maintenant - derniereTentativeMQTT < DELAI_MQTT) return;
  derniereTentativeMQTT = maintenant;

  char clientId[40];
  snprintf(clientId, sizeof(clientId), "UNO-R4-%lu", (unsigned long)millis());

  Serial.print("Connexion MQTT...");
  // Le broker publiera "Arduino deconnecte" si la carte disparaît brutalement.
  if (mqtt.connect(clientId, TOPIC_STATUS, 0, false, "Arduino deconnecte")) {
    Serial.println(" OK");
    mqtt.subscribe(TOPIC_COMMAND);
    publierStatut();
  } else {
    Serial.print(" echec, code = ");
    Serial.println(mqtt.state());
  }
}

// Heartbeat : sans lui, Node-RED afficherait "déconnecté"
// dès qu'aucun échantillon ECG n'est envoyé (avant le badge / après stop).
void envoyerHeartbeat() {
  if (millis() - dernierStatut >= PERIODE_STATUT) publierStatut();
}

// =====================================================
// RFID - IDENTIFICATION DU PATIENT
// =====================================================
void lireRFID() {
  if (!rfid.PICC_IsNewCardPresent()) return;
  if (!rfid.PICC_ReadCardSerial()) return;

  char uid[32] = "";
  char octet[4];

  for (byte i = 0; i < rfid.uid.size; i++) {
    snprintf(octet, sizeof(octet), "%02X", rfid.uid.uidByte[i]);
    if (i > 0) strncat(uid, ":", sizeof(uid) - strlen(uid) - 1);
    strncat(uid, octet, sizeof(uid) - strlen(uid) - 1);
  }

  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();

  // Pendant l'acquisition, on ne change pas de patient.
  if (etat == ACQUISITION) {
    Serial.println("Badge ignore : acquisition en cours");
    return;
  }

  unsigned long maintenant = millis();
  bool nouveauBadge = (strcmp(uid, dernierUID) != 0) ||
                      (maintenant - dernierBadgeMillis >= ANTI_REPETITION_RFID);
  if (!nouveauBadge) return;

  strncpy(dernierUID, uid, sizeof(dernierUID) - 1);
  dernierUID[sizeof(dernierUID) - 1] = '\0';
  strncpy(uidPatient, uid, sizeof(uidPatient) - 1);
  uidPatient[sizeof(uidPatient) - 1] = '\0';
  dernierBadgeMillis = maintenant;
  debutIdentification = maintenant;

  Serial.print("Patient identifie, UID : ");
  Serial.println(uid);

  if (mqtt.connected()) mqtt.publish(TOPIC_RFID, uid);
  bip(1800, 120);

  if (etat != PATIENT_IDENTIFIE) changerEtat(PATIENT_IDENTIFIE);
  else publierStatut();   // re-badge : on relance simplement le délai
}

// =====================================================
// ACTION DU BOUTON SELON L'ÉTAT
// =====================================================
void actionBouton() {
  switch (etat) {
    case ATTENTE_BADGE:
      Serial.println("Appui refuse : badge RFID requis");
      if (mqtt.connected()) mqtt.publish(TOPIC_BUTTON, "refused");
      bip(400, 300);
      break;

    case PATIENT_IDENTIFIE:
      if (!mqtt.connected()) {
        Serial.println("Demarrage refuse : MQTT non connecte");
        bip(400, 300);
        break;
      }
      mqtt.publish(TOPIC_BUTTON, "start");
      dernierEchantillon = millis();
      bip(2200, 80);
      changerEtat(ACQUISITION);
      break;

    case ACQUISITION:
      if (mqtt.connected()) mqtt.publish(TOPIC_BUTTON, "stop");
      uidPatient[0] = '\0';
      bip(1000, 150);
      changerEtat(ATTENTE_BADGE);   // le patient suivant doit se badger
      break;
  }
}

// =====================================================
// BOUTON - ANTI-REBOND
// =====================================================
void lireBouton() {
  int lecture = digitalRead(BOUTON);
  unsigned long maintenant = millis();

  if (lecture != dernierBoutonLu) {
    dernierBoutonLu = lecture;
    dernierChangementBouton = maintenant;
  }

  if (maintenant - dernierChangementBouton >= DEBOUNCE_BOUTON &&
      lecture != etatStableBouton) {
    etatStableBouton = lecture;

    if (etatStableBouton == LOW && maintenant - dernierAppui >= 300) {
      dernierAppui = maintenant;
      Serial.println("Bouton appuye");
      actionBouton();
    }
  }
}

// =====================================================
// DÉLAI D'IDENTIFICATION
// =====================================================
void verifierDelaiIdentification() {
  if (etat != PATIENT_IDENTIFIE) return;
  if (millis() - debutIdentification < DELAI_IDENTIFICATION) return;

  Serial.println("Delai depasse : nouveau badge requis");
  uidPatient[0] = '\0';
  changerEtat(ATTENTE_BADGE);
}

// =====================================================
// ECG - UNIQUEMENT PENDANT L'ACQUISITION
// =====================================================
void envoyerECG() {
  if (etat != ACQUISITION) return;

  unsigned long maintenant = millis();
  if (maintenant - dernierEchantillon < PERIODE_ECG) return;
  dernierEchantillon = maintenant;

  int valeurECG = analogRead(ECG);
  int loPlus = digitalRead(LO_PLUS);
  int loMoins = digitalRead(LO_MOINS);

  char message[100];
  snprintf(message, sizeof(message),
           "{\"raw\":%d,\"lo_plus\":%d,\"lo_moins\":%d,\"time\":%lu}",
           valeurECG, loPlus, loMoins, maintenant);

  if (mqtt.connected()) mqtt.publish(TOPIC_ECG, message);
}

// =====================================================
// SETUP
// =====================================================
void setup() {
  pinMode(LAMPE, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(BOUTON, INPUT_PULLUP);
  pinMode(LO_PLUS, INPUT);
  pinMode(LO_MOINS, INPUT);

  digitalWrite(LAMPE, LOW);
  noTone(BUZZER);

  Serial.begin(9600);
  delay(1000);
  Serial.println("SYSTEME ECG + RFID + MQTT");

  SPI.begin();
  rfid.PCD_Init();
  delay(100);

  Serial.print("Version RC522 : 0x");
  Serial.println(rfid.PCD_ReadRegister(MFRC522::VersionReg), HEX);

  mqtt.setServer(MQTT_SERVER, MQTT_PORT);
  mqtt.setCallback(recevoirCommande);
  mqtt.setBufferSize(256);
  mqtt.setKeepAlive(30);
  mqtt.setSocketTimeout(2);

  derniereTentativeWiFi = millis() - DELAI_WIFI;
  gererWiFi();

  Serial.println("En attente d'un badge RFID...");
}

// =====================================================
// LOOP
// =====================================================
void loop() {
  gererWiFi();
  gererMQTT();

  if (mqtt.connected()) {
    mqtt.loop();
    envoyerHeartbeat();
  }

  lireRFID();
  lireBouton();
  verifierDelaiIdentification();
  envoyerECG();
}
⚠️ **GitHub.com Fallback** ⚠️