I2c and analog sensor take data together

i make a project to take a raw ppg's data with infrared led that take from max30105 sensor and green's led data from heart rate pulse sensor. I take data simultaneously with frequency sampling 100 Hz in different esp32 microcontroller. When i take data for 1 minute, i get 2900 data from max30105 and 5790 data from heart rate pulse sensor. This different sample data from max30105 and pulse sensor making the data difficult to process. Can you help me how to fix this?
This is my analog program

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>

const char* ssid = "xxxx";
const char* password = "vvvv";

const char* mqtt_server = "a20555bc.s1.eu.hivemq.cloud";
const int mqtt_port = 8883;
const char* mqtt_user = "pulupulu";
const char* mqtt_pass = "12345";

WiFiClientSecure wifiClient;
PubSubClient client(wifiClient);

// Buffer untuk 10 data
int buffer[10];
int bufferIndex = 0;

void setup_wifi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
}

void reconnect() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
if (client.connect("ESP32_Analog", mqtt_user, mqtt_pass)) {
Serial.println("connected");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds...");
delay(5000);
}
}
}

void setup() {
Serial.begin(115200);
setup_wifi();

wifiClient.setInsecure(); // Tanpa SSL sertifikat manual
client.setServer(mqtt_server, mqtt_port);
}

void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();

// Baca sensor analog
int value = analogRead(32);
buffer[bufferIndex] = value;
bufferIndex++;

// Jika sudah 10 data, kirim sekaligus
if (bufferIndex >= 10) {
String payload = "[";
for (int i = 0; i < 10; i++) {
payload += String(buffer);
if (i < 9) payload += ","; // Tambah koma kecuali terakhir
}
payload += "]";

// Kirim ke HiveMQ
client.publish("sensor/analog", payload.c_str());

Serial.print("Kirim data: ");
Serial.println(payload);

bufferIndex = 0; // Reset buffer
}

delay(10); // Sampling 100Hz
}

this is my max30105 program

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "MAX30105.h"

// Ganti dengan SSID dan password WiFi kamu
const char* ssid = "xxxx";
const char* password = "vvvv";

// Konfigurasi HiveMQ Cloud
const char* mqtt_server = "a040555bc.s1.eu.hivemq.cloud";
const int mqtt_port = 8883;
const char* mqtt_user = "pulupulu";
const char* mqtt_pass = "12345";

// Objek koneksi
WiFiClientSecure wifiClient;
PubSubClient client(wifiClient);
MAX30105 particleSensor;

// Buffer untuk menampung data IR
String irBuffer = "[";
int count = 0;

// Sertifikat root untuk SSL
static const char *root_ca PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
-----END CERTIFICATE-----
)EOF";

// Setup WiFi
void setup_wifi() {
Serial.print("Menghubungkan ke WiFi");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nβœ… WiFi tersambung.");
}

// Setup awal
void setup() {
Serial.begin(115200);
setup_wifi();

// SSL root certificate
wifiClient.setCACert(root_ca);
client.setServer(mqtt_server, mqtt_port);

Serial.print("🩺 Inisialisasi sensor MAX30105... ");
if (!particleSensor.begin()) {
Serial.println("❌ Gagal! Periksa koneksi sensor.");
while (1); // Berhenti
} else {
Serial.println("βœ… Berhasil.");
}

particleSensor.setup(); // Konfigurasi default
}

// Koneksi ulang jika putus
void reconnect() {
if (!client.connected()) {
Serial.print("πŸ”Œ Menghubungkan ke MQTT... ");
if (client.connect("ESP32_MAX30105", mqtt_user, mqtt_pass)) {
Serial.println("βœ… Terhubung ke MQTT.");
} else {
Serial.print("❌ Gagal, rc=");
Serial.print(client.state());
Serial.println(" (cek dokumentasi PubSubClient)");
}
}
}

// Sampling tiap 10ms (100Hz)
unsigned long lastSampleTime = 0;
const int sampleInterval = 10;

void loop() {
if (!client.connected()) {
reconnect();
return;
}

client.loop();

unsigned long currentMillis = millis();
if (currentMillis - lastSampleTime >= sampleInterval) {
lastSampleTime = currentMillis;

long ir = particleSensor.getIR();

// Tambahkan ke buffer JSON
irBuffer += String(ir);
count++;

if (count < 10) {
irBuffer += ",";
} else {
irBuffer += "]";
Serial.println("πŸ“€ Mengirim data: " + irBuffer);
client.publish("sensor/max30105", irBuffer.c_str());
irBuffer = "[";
count = 0;
}
}
}

Replace delay() with precise millis()-based sampling
For both ESP32s, change the logic to something like this:

unsigned long previousMillis = 0;
const int sampleInterval = 10; // 100 Hz

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= sampleInterval) {
    previousMillis += sampleInterval; // prevents drift
    // Your sampling and publishing code
  }
}

How are you synchronizing the two ESP?

i use 2 com in my laptop

what is 2 com?

Do you understand what I meant about synchronizing the two ESPs?

i'm sorry i new in this field. I just use 2 esp32. and then take the data with python

import serial
import threading

# Konfigurasi COM port
COM_IR = 'COM11'       # Port for MAX30102 (IR)
COM_ANALOG = 'COM3'   # Port for sensor analog
BAUDRATE = 115200

# Buffer data sementara
data_ir = None
data_analog = None

# Lock untuk sinkronisasi antar thread
lock = threading.Lock()

# Buka file log
log_file = open("data_bandinghmq.csv", "w")
log_file.write("IR\tAnalog\n")  # Header

# Fungsi tulis jika kedua data tersedia
def try_write_line():
    global data_ir, data_analog
    if data_ir is not None and data_analog is not None:
        log_file.write(f"{data_ir}\t{data_analog}\n")
        print(f"{data_ir}\t{data_analog}")
        log_file.flush()
        data_ir = None
        data_analog = None

# Thread pembaca IR (MAX30102)
def read_ir():
    global data_ir
    try:
        ser_ir = serial.Serial(COM_IR, BAUDRATE)
        while True:
            line = ser_ir.readline().decode().strip()
            if line.isdigit():
                with lock:
                    data_ir = line
                    try_write_line()
    except Exception as e:
        print(f"[ERROR IR] {e}")

# Thread pembaca Analog
def read_analog():
    global data_analog
    try:
        ser_analog = serial.Serial(COM_ANALOG, BAUDRATE)
        while True:
            line = ser_analog.readline().decode().strip()
            if line.isdigit():
                with lock:
                    data_analog = line
                    try_write_line()
    except Exception as e:
        print(f"[ERROR ANALOG] {e}")

# Jalankan thread
thread_ir = threading.Thread(target=read_ir)
thread_analog = threading.Thread(target=read_analog)

thread_ir.start()
thread_analog.start()

try:
    thread_ir.join()
    thread_analog.join()
except KeyboardInterrupt:
    print("Program stop.")
    log_file.close()

why use two ESP32s?
did you attempt to use a single ESP32 to sample both devices?

using multiple microcontrollers makes the overall project much more complex, e.g. programming two microcontrollers, synchronizing them (pointed out by @jim-p), communications between the devices, etc

I would suggest getting the data sampling working as your project requirements specifies then look at the mqtt communications etc

i use 2 esp32s because if i use single esp32, the peaks of the analog data will be clipped

You will never make this work with two ESPs
If you can't make it work with one then it is not possible.

are you sure the ADC input(s) were within the range of the ESP32 ADCs?

upload a schematic and your test code?

can you give me an advice how to synchronize it?

and my test code is just as same as with the first i sent

Connect a wire between GPIOs of the two ESPs (and GND)
When one starts sampling have it send a HIGH to the other.
The other should wait and only start sampling when it receives the HIGH.

which GPIO that you mean?
do i also have to set it in the program?

Any GPIO
You set it in one program and read it in the other.