Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*
*/

#include <Wire.h>
#include <ads129x.h>
#include <adscommand.h>
#include <wscommand.h>
Expand All @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down
124 changes: 124 additions & 0 deletions test/main.py
Original file line number Diff line number Diff line change
@@ -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)