diff --git a/src/main.cpp b/src/main.cpp index fbf6c57..b0e69b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ * */ +#include #include #include #include @@ -40,9 +41,21 @@ const char *password = ""; #define ADS_STATUS_SIZE 3 #define BLOCK_SIZE 32 // Data + Timestamp + Counter #define SAMPLES_PER_BUFFER 250 -#define PACKET_SIZE (BLOCK_SIZE * SAMPLES_PER_BUFFER) +#define PACKET_SIZE (BLOCK_SIZE * SAMPLES_PER_BUFFER + 1) // +1 for the battery percentage byte #define NUM_BUFFERS 20 #define MAX_PAYLOAD_SIZE 256 +#define BATTERY_ADDRESS 0x55 // I2C address of BQ27546 +#define BATTERY_SOC_REGISTER 0x2C // Register for State of Charge +#define BATTERY_READ_INTERVAL 1000 // Read battery every 1 second + +union +{ + uint8_t battery_byte; + uint8_t battery_percentage; +} battery_union; + +uint8_t latest_battery_percentage = 0; +unsigned long last_battery_read = 0; uint8_t data_buffers[NUM_BUFFERS][PACKET_SIZE]; volatile int current_buffer_index = 0; @@ -112,8 +125,44 @@ void readRegisterCommand(unsigned char unused1, unsigned char unused2); void writeRegisterCommand(unsigned char register_number, unsigned char register_value); void helpCommand(unsigned char unused1, unsigned char unused2); +uint8_t readBatteryPercentage() { + Wire.beginTransmission(BATTERY_ADDRESS); + Wire.write(BATTERY_SOC_REGISTER); + if (Wire.endTransmission(false) != 0) { + return 0xFF; // Transmission failed, return invalid value + } + + Wire.requestFrom(BATTERY_ADDRESS, 2); + if (Wire.available() < 2) { + return 0xFF; // Not enough data received + } + + // Read low and high byte (little-endian format) + uint8_t soc_low = Wire.read(); + uint8_t soc_high = Wire.read(); + uint16_t soc = soc_low | (soc_high << 8); + + // SOC is typically returned as percentage (0-100) + // If it's over 100, return 0xFF as error + return (soc > 100) ? 0xFF : (uint8_t)soc; +} + void setup() { + Wire.begin(4, 5); // Initialize I2C with SDA=4, SCL=5 + + uint8_t battery_level = readBatteryPercentage(); + while (battery_level <= 10 && battery_level != 0xFF) { + pixels.begin(); + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.show(); + delay(500); + pixels.setPixelColor(0, pixels.Color(0, 0, 0)); + pixels.show(); + delay(500); + battery_level = readBatteryPercentage(); + } + Serial.begin(BAUD_RATE); while (!Serial) { @@ -239,6 +288,13 @@ void setup() void loop() { + // Read battery percentage periodically (not in ISR) + unsigned long current_time = millis(); + if (current_time - last_battery_read >= BATTERY_READ_INTERVAL) { + latest_battery_percentage = readBatteryPercentage(); + last_battery_read = current_time; + } + if (buffer_completed[buffer_to_send]) { // Send the current buffer via WebSocket @@ -468,10 +524,17 @@ void sdatacCommand(unsigned char unused1, unsigned char unused2) { using namespace ADS129x; is_rdatac = false; + + // Wait a bit to ensure no more ISR calls + delayMicroseconds(100); + + current_buffer_index = 0; current_sample_index = 0; memset((void*)buffer_completed, 0, sizeof(buffer_completed)); buffer_to_send = 0; + // Reset sample numbering + sample_number_union.sample_number = 0; adcSendCommand(SDATAC); using namespace ADS129x; send_response_ok(); @@ -521,7 +584,7 @@ void IRAM_ATTR DRDY_ISR(void) if (buffer_completed[current_buffer_index]) { // The current buffer is still full and not sent yet, we skip this write to avoid overflow - ESP_LOGD("ERROR", "Buffer Overflow detected at buffer %d", current_buffer_index); + // ESP_LOGD("ERROR", "Buffer Overflow detected at buffer %d", current_buffer_index); return; } // Get a pointer to the current position in the buffer @@ -546,6 +609,9 @@ void IRAM_ATTR DRDY_ISR(void) if (current_sample_index >= SAMPLES_PER_BUFFER) { + // Use the cached battery percentage (read in main loop) + data_buffers[current_buffer_index][PACKET_SIZE - 1] = latest_battery_percentage; + // Mark the current buffer as completed buffer_completed[current_buffer_index] = true; @@ -614,6 +680,7 @@ void adsSetup() void espSetup() { using namespace ADS129x; + // Add I2C initialization at the beginning // prepare pins to be outputs or inputs // pinMode(PIN_SCLK, OUTPUT); //optional - SPI library will do this for us // pinMode(PIN_DIN, OUTPUT); //optional - SPI library will do this for us diff --git a/test/main.py b/test/main.py new file mode 100644 index 0000000..f636934 --- /dev/null +++ b/test/main.py @@ -0,0 +1,124 @@ +import websocket +import socket +import json +import time +import datetime +import math +from pylsl import StreamInfo, StreamOutlet + +def calculate_rate(data_size, elapsed_time): + rate = data_size / elapsed_time + return rate + +blockSize = 32 +data_size = 0 +sample_size = 0 +packet_size = 0 +previousSampleNumber = -1 +previousTimeStamp = -1 +previousData = [] +strean_name = 'ORIC2' +data = StreamInfo(strean_name, 'EEG', 8, 250, 'float32', 'uid007') +outlet = StreamOutlet(data) + +ws = websocket.WebSocket() +ws.connect("ws://"+socket.gethostbyname("oric.local")+":81") +# ws.connect("ws://192.168.0.23:81") + +ws.send_text(json.dumps({"command":"sdatac", "parameters":[]})) +# ws.send_text(json.dumps({"command":"wreg", "parameters":[0x01, 0b10010100]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x01, 0x96]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x02, 0xC0]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x03, 0xEC]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x15, 0b00100000]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x05, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x06, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x07, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x08, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x09, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x0A, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x0B, 0x00]})) +ws.send_text(json.dumps({"command":"wreg", "parameters":[0x0C, 0x00]})) +ws.send_text(json.dumps({"command":"status", "parameters":[]})) +ws.send_text(json.dumps({"command":"rdatac", "parameters":[]})) + +print("Setup Done!") +start_time = time.time() + +while 1: + data = ws.recv() + data_size += len(data) + # print(data) # REMOVE THIS LINE - this was printing raw bytes + current_time = time.time() + elapsed_time = current_time - start_time + if elapsed_time >= 1.0: + samples_per_second = calculate_rate(sample_size, elapsed_time) + refresh_rate = calculate_rate(packet_size, elapsed_time) + bytes_per_second = calculate_rate(data_size, elapsed_time) + # Get the current local time + local_time = datetime.datetime.now() + # Extract hours, minutes, and seconds + hours = local_time.hour + minutes = local_time.minute + seconds = local_time.second + # print(f"Local Time: {hours:02d}:{minutes:02d}:{seconds:02d}") + # print(f"Bytes per second: {bytes_per_second} BPS") + # print(f"Samples per second: {math.ceil(samples_per_second)}") + # print(f"SPS Refresh rate: {math.ceil(refresh_rate)}") + print(f"{math.ceil(refresh_rate)} Packets : {math.ceil(samples_per_second)} Samples : {math.ceil(bytes_per_second)} Bytes") + packet_size = 0 + sample_size = 0 + data_size = 0 + start_time = current_time + # print(len(data)) + if data and (type(data) is list or type(data) is bytes): + # print(len(data)) + # ws.send_text(json.dumps({"command":"status", "parameters":[]})) + # status = ws.recv() + # print(status) + packet_size += 1 + # print("Packet size: ", len(data), "Bytes") + + # Extract and print battery percentage after each packet + # if len(data) > 8000: + battery_percentage = data[-1] + print(f"Battery: {battery_percentage}%") + + for blockLocation in range(0, len(data)-1, blockSize): + sample_size += 1 + block = data[blockLocation:blockLocation + blockSize] + # data_hex = ":".join("{:02x}".format(c) for c in data) + timestamp = int.from_bytes(block[0:4], byteorder='little') + sample_number = int.from_bytes(block[4:8], byteorder='little') + channel_data = [] + for channel in range(0, 8): + channel_offset = 8 + (channel * 3) + sample = int.from_bytes(block[channel_offset:channel_offset + 3], byteorder='big', signed=True) + channel_data.append(sample) + + if previousSampleNumber == -1: + previousSampleNumber = sample_number + previousTimeStamp = timestamp + previousData = channel_data + else: + if sample_number - previousSampleNumber > 1: + print("Error: Sample Lost") + exit() + elif sample_number == previousSampleNumber: + print("Error: Duplicate sample") + exit() + elif sample_number - previousSampleNumber < 1: + print("Error: Sample order missed") + exit() + else: + # print(timestamp - previousTimeStamp) + previousTimeStamp = timestamp + previousSampleNumber = sample_number + previousData = channel_data + outlet.push_sample(channel_data) + if(all(v == 0 for v in channel_data[:3]) and all(v > 0 for v in channel_data[4:])): + print("Blank Data: ",timestamp, sample_number, channel_data[0], channel_data[1], channel_data[2], channel_data[3], channel_data[4], channel_data[5], channel_data[6], channel_data[7]) + exit() + else: + print("EEG Data: ",timestamp, sample_number, channel_data[0], channel_data[1], channel_data[2], channel_data[3], channel_data[4], channel_data[5], channel_data[6], channel_data[7]) # UNCOMMENT THIS LINE + outlet.push_sample(channel_data)