aromring / MAX30102_by_RF

Arduino C code for MAX30102 pulse oximetry sensor (MAXIM Integrated, Inc.)
166 stars 73 forks source link

Heart-rate is always the same (83 Bpm) #8

Closed asemchenko closed 4 years ago

asemchenko commented 4 years ago

Hi!

I have used this library with Arduino Nano, and it looks like SpO2 is calculated right but heart-rate value is always 83 and does not changes. Can you help me to understand what is going wrong and how to deal with it, please? image

I am using the sketch below (there is no any changes, I've just commented some features (DEBUG, ADALOGGER, SAVE_ROW_DATA)

/********************************************************
*
* Project: MAXREFDES117#
* Filename: RD117_ARDUINO.ino
* Description: This module contains the Main application for the MAXREFDES117 example program.
*
* Revision History:
*\n 1-18-2016 Rev 01.00 GL Initial release.
*\n 12-22-2017 Rev 02.00 Significantlly modified by Robert Fraczkiewicz
*\n 08-22-2018 Rev 02.01 Added conditional compilation of the code related to ADALOGGER SD card operations
*
* --------------------------------------------------------------------
*
* This code follows the following naming conventions:
*
* char              ch_pmod_value
* char (array)      s_pmod_s_string[16]
* float             f_pmod_value
* int32_t           n_pmod_value
* int32_t (array)   an_pmod_value[16]
* int16_t           w_pmod_value
* int16_t (array)   aw_pmod_value[16]
* uint16_t          uw_pmod_value
* uint16_t (array)  auw_pmod_value[16]
* uint8_t           uch_pmod_value
* uint8_t (array)   auch_pmod_buffer[16]
* uint32_t          un_pmod_value
* int32_t *         pn_pmod_value
*
* ------------------------------------------------------------------------- */

#include "Arduino.h"
#include <Wire.h>
#include <SPI.h>
#include "algorithm_by_RF.h"
#include "max30102.h"

//#define DEBUG // Uncomment for debug output to the Serial stream
//#define USE_ADALOGGER // Comment out if you don't have ADALOGGER itself but your MCU still can handle this code
//#define TEST_MAXIM_ALGORITHM // Uncomment if you want to include results returned by the original MAXIM algorithm
//#define SAVE_RAW_DATA // Uncomment if you want raw data coming out of the sensor saved to SD card. Red signal first, IR second.

#ifdef USE_ADALOGGER
  #include <SD.h>
#endif

#ifdef TEST_MAXIM_ALGORITHM
  #include "algorithm.h" 
#endif

// Interrupt pin
const byte oxiInt = 10; // pin connected to MAX30102 INT

// ADALOGGER pins
#ifdef USE_ADALOGGER
  File dataFile;
  const byte chipSelect = 4;
  const byte cardDetect = 7;
  const byte batteryPin = 9;
  const byte ledPin = 13; // Red LED on ADALOGGER
  const byte sdIndicatorPin = 8; // Green LED on ADALOGGER
  bool cardOK;
#endif

uint32_t elapsedTime,timeStart;

uint32_t aun_ir_buffer[BUFFER_SIZE]; //infrared LED sensor data
uint32_t aun_red_buffer[BUFFER_SIZE];  //red LED sensor data
float old_n_spo2;  // Previous SPO2 value
uint8_t uch_dummy,k;

void setup() {

  pinMode(oxiInt, INPUT);  //pin D10 connects to the interrupt output pin of the MAX30102

#ifdef USE_ADALOGGER
  pinMode(cardDetect,INPUT_PULLUP);
  pinMode(batteryPin,INPUT);
  pinMode(ledPin,OUTPUT);
  digitalWrite(ledPin,LOW);
  pinMode(sdIndicatorPin,OUTPUT);
  digitalWrite(sdIndicatorPin,LOW);
#endif

  Wire.begin();

#if defined(DEBUG) || !defined(USE_ADALOGGER)
  // initialize serial communication at 115200 bits per second:
  Serial.begin(115200);
#endif

  maxim_max30102_reset(); //resets the MAX30102
  delay(1000);

  maxim_max30102_read_reg(REG_INTR_STATUS_1,&uch_dummy);  //Reads/clears the interrupt status register
  maxim_max30102_init();  //initialize the MAX30102
  old_n_spo2=0.0;

#ifdef USE_ADALOGGER
    // Measure battery voltage
  float measuredvbat = analogRead(batteryPin);
  measuredvbat *= 2;    // we divided by 2, so multiply back
  measuredvbat *= 3.3;  // Multiply by 3.3V, our reference voltage
  measuredvbat /= 1024; // convert to voltage

  char my_status[20];
  if(HIGH==digitalRead(cardDetect)) {
    // we'll use the initialization code from the utility libraries
    // since we're just testing if the card is working!
    if(!SD.begin(chipSelect)) {
      cardOK=false;
      strncpy(my_status,"CardInit!",9);
    } else cardOK=true;
  } else {
    cardOK=false;
    strncpy(my_status,"NoSDCard!",9);
  }

  if(cardOK) {
    long count=0;
    char fname[20];
    do {
//      if(useClock && now.month()<13 && now.day()<32) {
//        sprintf(fname,"%d-%d_%d.txt",now.month(),now.day(),++count);
//      } else {
        sprintf(fname,"data_%d.txt",++count);
//      }
    } while(SD.exists(fname));
    dataFile = SD.open(fname, FILE_WRITE);
    strncpy(my_status,fname,19);
  }

#ifdef DEBUG
  while(Serial.available()==0)  //wait until user presses a key
  {
    Serial.print(F("Vbatt=\t"));
    Serial.println(measuredvbat);
    Serial.println(my_status);
    Serial.println(dataFile,HEX);
    Serial.println(F("Press any key to start conversion"));
    delay(1000);
  }
  uch_dummy=Serial.read();
#endif

  blinkLED(ledPin,cardOK);

  k=0;
  dataFile.println(F("Vbatt=\t"));
  dataFile.println(measuredvbat);
  dataFile.println(my_status);
#ifdef TEST_MAXIM_ALGORITHM
  dataFile.print(F("Time[s]\tSpO2\tHR\tSpO2_MX\tHR_MX\tClock\tRatio\tCorr"));
#else // TEST_MAXIM_ALGORITHM
  dataFile.print(F("Time[s]\tSpO2\tHR\tClock\tRatio\tCorr"));
#endif // TEST_MAXIM_ALGORITHM
#ifdef SAVE_RAW_DATA
  int32_t i;
  // These are headers for the red signal
  for(i=0;i<BUFFER_SIZE;++i) {
    dataFile.print("\t");
    dataFile.print(i);
  }
  // These are headers for the infrared signal
  for(i=0;i<BUFFER_SIZE;++i) {
    dataFile.print("\t");
    dataFile.print(i);
  }
#endif // SAVE_RAW_DATA
  dataFile.println("");

#else // USE_ADALOGGER

  while(Serial.available()==0)  //wait until user presses a key
  {
    Serial.println(F("Press any key to start conversion"));
    delay(1000);
  }
  uch_dummy=Serial.read();
#ifdef TEST_MAXIM_ALGORITHM
  Serial.print(F("Time[s]\tSpO2\tHR\tSpO2_MX\tHR_MX\tClock\tRatio\tCorr"));
#else // TEST_MAXIM_ALGORITHM
  Serial.print(F("Time[s]\tSpO2\tHR\tClock\tRatio\tCorr"));
#endif // TEST_MAXIM_ALGORITHM
#ifdef SAVE_RAW_DATA
  int32_t i;
  // These are headers for the red signal
  for(i=0;i<BUFFER_SIZE;++i) {
    Serial.print("\t");
    Serial.print(i);
  }
  // These are headers for the infrared signal
  for(i=0;i<BUFFER_SIZE;++i) {
    Serial.print("\t");
    Serial.print(i);
  }
#endif // SAVE_RAW_DATA
  Serial.println("");

#endif // USE_ADALOGGER

  timeStart=millis();
}

//Continuously taking samples from MAX30102.  Heart rate and SpO2 are calculated every ST seconds
void loop() {
  float n_spo2,ratio,correl;  //SPO2 value
  int8_t ch_spo2_valid;  //indicator to show if the SPO2 calculation is valid
  int32_t n_heart_rate; //heart rate value
  int8_t  ch_hr_valid;  //indicator to show if the heart rate calculation is valid
  int32_t i;
  char hr_str[10];

  //buffer length of BUFFER_SIZE stores ST seconds of samples running at FS sps
  //read BUFFER_SIZE samples, and determine the signal range
  for(i=0;i<BUFFER_SIZE;i++)
  {
    while(digitalRead(oxiInt)==1);  //wait until the interrupt pin asserts
    maxim_max30102_read_fifo((aun_red_buffer+i), (aun_ir_buffer+i));  //read from MAX30102 FIFO
#ifdef DEBUG
    Serial.print(i, DEC);
    Serial.print(F("\t"));
    Serial.print(aun_red_buffer[i], DEC);
    Serial.print(F("\t"));
    Serial.print(aun_ir_buffer[i], DEC);    
    Serial.println("");
#endif // DEBUG
  }

  //calculate heart rate and SpO2 after BUFFER_SIZE samples (ST seconds of samples) using Robert's method
  rf_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_SIZE, aun_red_buffer, &n_spo2, &ch_spo2_valid, &n_heart_rate, &ch_hr_valid, &ratio, &correl); 
  elapsedTime=millis()-timeStart;
  millis_to_hours(elapsedTime,hr_str); // Time in hh:mm:ss format
  elapsedTime/=1000; // Time in seconds

#ifdef DEBUG
  Serial.println("--RF--");
  Serial.print(elapsedTime);
  Serial.print("\t");
  Serial.print(n_spo2);
  Serial.print("\t");
  Serial.print(n_heart_rate, DEC);
  Serial.print("\t");
  Serial.println(hr_str);
  Serial.println("------");
#endif // DEBUG

#ifdef TEST_MAXIM_ALGORITHM
  //calculate heart rate and SpO2 after BUFFER_SIZE samples (ST seconds of samples) using MAXIM's method
  float n_spo2_maxim;  //SPO2 value
  int8_t ch_spo2_valid_maxim;  //indicator to show if the SPO2 calculation is valid
  int32_t n_heart_rate_maxim; //heart rate value
  int8_t  ch_hr_valid_maxim;  //indicator to show if the heart rate calculation is valid
  maxim_heart_rate_and_oxygen_saturation(aun_ir_buffer, BUFFER_SIZE, aun_red_buffer, &n_spo2_maxim, &ch_spo2_valid_maxim, &n_heart_rate_maxim, &ch_hr_valid_maxim); 
#ifdef DEBUG
  Serial.println("--MX--");
  Serial.print(elapsedTime);
  Serial.print("\t");
  Serial.print(n_spo2_maxim);
  Serial.print("\t");
  Serial.print(n_heart_rate_maxim, DEC);
  Serial.print("\t");
  Serial.println(hr_str);
  Serial.println("------");
#endif // DEBUG
#endif // TEST_MAXIM_ALGORITHM

  //save samples and calculation result to SD card
#ifdef TEST_MAXIM_ALGORITHM
  if(ch_hr_valid && ch_spo2_valid || ch_hr_valid_maxim && ch_spo2_valid_maxim) {
#else   // TEST_MAXIM_ALGORITHM
  if(ch_hr_valid && ch_spo2_valid) { 
#endif // TEST_MAXIM_ALGORITHM
#ifdef USE_ADALOGGER
    ++k;
    dataFile.print(elapsedTime);
    dataFile.print("\t");
    dataFile.print(n_spo2);
    dataFile.print("\t");
    dataFile.print(n_heart_rate, DEC);
    dataFile.print("\t");
#ifdef TEST_MAXIM_ALGORITHM
    dataFile.print(n_spo2_maxim);
    dataFile.print("\t");
    dataFile.print(n_heart_rate_maxim, DEC);
    dataFile.print("\t");
#endif // TEST_MAXIM_ALGORITHM
    dataFile.print(hr_str);
    dataFile.print("\t");
    dataFile.print(ratio);
    dataFile.print("\t");
    dataFile.print(correl);
#ifdef SAVE_RAW_DATA
    // Save raw data for unusual O2 levels
    for(i=0;i<BUFFER_SIZE;++i)
    {
      dataFile.print(F("\t"));
      dataFile.print(aun_red_buffer[i], DEC);
    }
    for(i=0;i<BUFFER_SIZE;++i)
    {
      dataFile.print(F("\t"));
      dataFile.print(aun_ir_buffer[i], DEC);    
    }
#endif // SAVE_RAW_DATA
    dataFile.println("");
     // Blink green LED to indicate save event
    digitalWrite(sdIndicatorPin,HIGH);
    delay(10);
    digitalWrite(sdIndicatorPin,LOW);
    // FLush SD buffer every 10 points
    if(k>=10) {
      dataFile.flush();
      k=0;
    }
#else // USE_ADALOGGER
    Serial.print(elapsedTime);
    Serial.print("\t");
    Serial.print(n_spo2);
    Serial.print("\t");
    Serial.print(n_heart_rate, DEC);
    Serial.print("\t");
#ifdef TEST_MAXIM_ALGORITHM
    Serial.print(n_spo2_maxim);
    Serial.print("\t");
    Serial.print(n_heart_rate_maxim, DEC);
    Serial.print("\t");
#endif //TEST_MAXIM_ALGORITHM
    Serial.print(hr_str);
    Serial.print("\t");
    Serial.print(ratio);
    Serial.print("\t");
    Serial.print(correl);
#ifdef SAVE_RAW_DATA
    // Save raw data for unusual O2 levels
    for(i=0;i<BUFFER_SIZE;++i)
    {
      Serial.print(F("\t"));
      Serial.print(aun_red_buffer[i], DEC);
    }
    for(i=0;i<BUFFER_SIZE;++i)
    {
      Serial.print(F("\t"));
      Serial.print(aun_ir_buffer[i], DEC);    
    }
#endif // SAVE_RAW_DATA
    Serial.println("");
#endif // USE_ADALOGGER
    old_n_spo2=n_spo2;
  }
}

void millis_to_hours(uint32_t ms, char* hr_str)
{
  char istr[6];
  uint32_t secs,mins,hrs;
  secs=ms/1000; // time in seconds
  mins=secs/60; // time in minutes
  secs-=60*mins; // leftover seconds
  hrs=mins/60; // time in hours
  mins-=60*hrs; // leftover minutes
  itoa(hrs,hr_str,10);
  strcat(hr_str,":");
  itoa(mins,istr,10);
  strcat(hr_str,istr);
  strcat(hr_str,":");
  itoa(secs,istr,10);
  strcat(hr_str,istr);
}

#ifdef USE_ADALOGGER
// blink three times if isOK is true, otherwise blink continuously
void blinkLED(const byte led, bool isOK)
{
  byte i;
  if(isOK) {
    for(i=0;i<3;++i) {
      digitalWrite(led,HIGH);
      delay(200);
      digitalWrite(led,LOW);
      delay(200);
    }
  } else {
    while(1) {
      for(i=0;i<2;++i) {
        digitalWrite(led,HIGH);
        delay(50);
        digitalWrite(led,LOW);
        delay(50);
      }
      delay(500);
    }
  }
}
#endif
aromring commented 4 years ago

Hi Andrii, Yes, I will be glad to help. Please open the README file here https://github.com/aromring/MAX30102_by_RF/blob/master/README.md scroll down to HOW TO REPORT BUGS section and follow instructions therein.

akuznets0v commented 4 years ago

Hello, just wanted to chime in here that I ran the above code on an Arduino Uno and it's giving accurate-ish (resting, so around 55-60 bpm) heart rate as compared to a Santa Medical SM-110 @asemchenko

asemchenko commented 4 years ago

Hello @akuznets0v. That is interesting. Did you run it with MAX30102 sensor?

akuznets0v commented 4 years ago

@asemchenko Tested with Arduino Uno R3, MAXREFDES117# from Digikey, and using pin connection setup from Maxim (https://www.maximintegrated.com/en/design/reference-design-center/system-board/6300.html). Note that this is 3.3v.

(arduino pin) (sensor pin) SDA SDA SCL SCL 10 INT 3.3V VIN GND GND

supersuperfranz commented 4 years ago

I have the same issue. i'll collect data and sent it to you but i write here if you have yet solved.

aromring commented 4 years ago

@supersuperfranz, please do. As long as someone sends me raw data, I can debug the issue. Do you also use Arduino Nano?

supersuperfranz commented 4 years ago

Yes nano and brand new digikey sensor. Tomorrow I'll send you data! I would arrange it in case of needs due to corona issue here in Italy.

Thanks

supersuperfranz commented 4 years ago

I've collected some raw data, is enough? However both Adafruit and Maxim algorithms fails when usigng O2 mode. Only 1 led adafruit beatrate seems to work. Maybe something changed in configuration registers or fifo buffers? In the last max30102 datasheet i cannot find the pilot led instructions. is maybe deprecated in new chips?

thanks a lot for your help

Francesco Tripaldi max30102 RF raw data.txt

supersuperfranz commented 4 years ago

could be related to the maxim_max30102_read_fifo routine? Because Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno?

akuznets0v commented 4 years ago

Hello,

I found an old arduino nano clone (uses ATmega328P Old Bootloader) I had lying around and hooked things up.

No visible red LED from sensor and looks like everything is stagnant. For the others who have not been getting any readings, does the LED properly function? I'll double check my configuration but it might be something around that.

Time[s] SpO2 HR Clock Ratio Corr 0 80.14 100 0:0:0 1.00 1.00 4 80.14 100 0:0:4 1.00 1.00 9 80.14 100 0:0:9 1.00 1.00 13 80.14 100 0:0:13 1.00 1.00 (and so on)

aromring commented 4 years ago

@supersuperfranz, thank you so much! Thanks to your effort in collecting raw data coming from the sensor, as received by Arduino Nano, I can address this issue. First, take a look at plots of RED and IR signals plotted from your data; in ArduinoNano*.png files here:

https://www.dropbox.com/sh/5bdi49drt8p5uiu/AADyJRqmlbBmZ1ykzpq_33rva?dl=0

Both signals remain flat except either a sharp dip, or spike occurring every 18 samples (BTW, this interval corresponds to HR = 83 with default settings). Very strange, isn't it? These are a far cry from expected good signals coming from the Maxim sensor; see ExpectedGoodQualitySignals.png. As I wrote in the README, "don't expect a bad signal to produce correct results!" Why the signal is bad is another matter. My guess is that's a hardware problem caused by an underpowered MCU in Arduino Nano; issue 1) in README. You had a good hunch to look at the maxim_max30102_read_fifo() routine - Maxim has placed this comment in front: "//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format //To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data." You may try to replace uint32_t by uint16_t all over the code, but I am skeptical about this hack. Just get a better MCU! As @akuznets0v reported, Arduino Uno works fine.

I am about to close this "83 bpm" issue, unless I hear some new info in the next few days.

akuznets0v commented 4 years ago

Thanks so much for the deep dive. We'll probably want to follow up by trying these hacks/modifications out and work through a more efficient solution (that will likely work for the original devices). Would you prefer we fork or just start a new branch?

aromring commented 4 years ago

@akuznets0v, music to my ears! By all means, please investigate how to adopt Arduino Nano to get the right reading from MAX30102. Since I expect code modifications to be minor, with a few #ifdef's for Nano, please start a new branch.

asemchenko commented 4 years ago

@aromring Thanks for your answer, I will try with Arduino Uno. But I don't quite understand, how can it happen? As I know, Arduino Nano and Arduino Uno are both based on the same microcontroller (Atmega 328) aren't they?

akuznets0v commented 4 years ago

To add onto the point raised by @asemchenko, they have the same specs in terms of SRAM and etc. https://www.elprocus.com/wp-content/uploads/difference-between-arduino-uno-and-arduino-nano.jpg (from https://www.elprocus.com/an-overview-of-arduino-nano-board/). This might have to be taken into a different discussion, an initial test would be to see if sampling rarer or switching to uint16_t helps.

I remember reading something (will try to track it down) that not all MCUs can handle interrupting i2c. Might have to do with that.

aromring commented 4 years ago

@asemchenko I wish I could give you a meaningful answer, but all Nanos I've ever had were used in my projects long time ago and I've never owned an Uno. I am buying my MCUs almost exclusively from Adafruit... @akuznets0v I think I owe you a correction. The way GitHub works, only the project owner is able to create branches. All others have to fork, modify their version, then create a pull request to have these changes incorporated.

akuznets0v commented 4 years ago

aha! I think I have a good lead on the issue. In addition to the digikey one, I also received a MAX30102 module from a Chinese firm to compare and it needed a 10K 'pull up' resistor on SDA and SCL to get running so be aware of that.

I connected my arduino Uno to 3.3v and got readings slowly (once every 5 seconds) that were accurate (bpm, spo2 very stable at around 99.4-8). I connected it to 5v and I was getting measurements rapidly (once every 1-2 seconds) BUT the bpm was stuck at 83 and spo2 swung wide occassionally.

I'll try sending over output from both to @aromring soon.

aromring commented 4 years ago

Update: under the same link I've posted previously: https://www.dropbox.com/sh/5bdi49drt8p5uiu/AADyJRqmlbBmZ1ykzpq_33rva?dl=0 I have placed an updated Excel worksheet with new data from @akuznets0v (thank you very much, Andrew!). The data and exemplary plots are placed in the "FromAndrew" spreadsheet. And they are telling a lot. I short, Andrew took raw data from 2 sensors: an original MAXREFDES117# from Digikey and a cheap Chinese clone from Makerfabs under two voltage levels: 3.3 V and 5 V. Look at the graphs, the differences are startling. The Digikey sensor delivers the expected signals at both voltages with the usual distinction between low and high quality ones as defined by the algorithm settings. The Makerfabs clone, on the other hand, produces weird noise at 3.3 V and strange rectangular waves at 5V. Interestingly, at 5 V some of these waves were semi-periodic (last graph) and the algorithm did accept these and calculated HR = 55 bpm in one case. Soooo.... Does it mean the sensor is working fine at 5V and the problem is communication with the MCU?

akuznets0v commented 4 years ago

After diving into it, I think this circuit board issue may be at fault for the cheaper boards. I will be trying to implement this fix and will send over the logged data. https://reedpaper.wordpress.com/2018/08/22/pulse-oximeter-max30100-max30102-how-to-fix-wrong-board/

aromring commented 4 years ago

@akuznets0v, thank you for this amazing find. It's open source collaboration at its best! :) The classic "you reap what you sow" more than adequately summarizes the cheap crap boards that carry the MAX30102 sensor. One mystery remains, though. @supersuperfranz wrote that he used "brand new digikey sensor". Hmmm... But I have just checked digikey.com and found two evaluation boards: 1) The Maxim original MAXREFDES117# for $16.34 2) The SEN-15271 one from SparkFun Electronics for $10.95

@supersuperfranz, which one do you have? BTW, I am not trying to imply anything about the quality of SEN-15271. Its schematics seems fine, the 3.3 V logic is translated into 1.8 V logic via MOSFETs, but the ultimate test is to pair it with a capable MCU and record raw data again.

supersuperfranz commented 4 years ago

The first one! MAXIM original MAXREFDES117#.

I'm waiting samd21 m0 from Amazon for try. I suspect memory overflow, tried 16bit register and less sampling with no results. Tried both 5V and 3,3V supplies with no differencies.

akuznets0v commented 4 years ago

As an update on the cheaper chip, I implemented the 'cut' and 'add wire' fix and was able to avoid the 83 bpm at 5v. After I got it working I tested the following at 3.3v and 5v Time[s] SpO2 HR Clock Ratio Corr 24 99.70 57 0:0:24 0.57 0.96 29 99.74 57 0:0:29 0.73 0.98 38 99.66 62 0:0:38 0.88 0.98

Time[s] SpO2 HR Clock Ratio Corr 12 99.74 62 0:0:12 0.88 0.98 17 99.80 60 0:0:17 0.86 0.98 30 99.43 65 0:0:30 0.90 0.96

Unfortunately before getting the raw data I tried to check the voltage of the second regulator with my multimeter as recommended by the blogpost and fried something in my attempt. The LED has not turned on since. I have around 20 of these chips so I'll reattempt getting raw data soon.

jointothedarkside commented 4 years ago

@akuznets0v Did you calibrate your results with something else? are you getting the right value?

akuznets0v commented 4 years ago

@jointothedarkside I haven't recalibrated and am using the coefficients in the repository. Those bpm results were more or less accurate compared to the Santa Medical SM-110. I was just sitting in my chair when measuring.

According to the makerfabs folks, the voltage across the second regulator is 1.8v for their modules, so it seems just cutting and wiring is required for fixing their 30102 module. I'll be testing those when they arrive. I'll be doing my own fixing and tests in the meantime.

aromring commented 4 years ago

Since there is no further activity in this thread and the issue turned out to be related to a faulty hardware, I am closing it.