@@ -748,3 +748,172 @@ def read(self, duration):
748748 def close (self ):
749749 """Close TCPIP connections"""
750750 self .con .close ()
751+
752+
753+
754+
755+
756+ class Nonin3231USB :
757+ """Recording Nonin 3231 USB HR signal through USB connection
758+
759+ Parameters
760+ ----------
761+
762+ serial : pySerial object
763+ The `serial` instance interfacing with the USB port.
764+
765+
766+ Examples
767+ --------
768+ First, you will need to define a :py:func:`serial` instance, indexing the
769+ USB port where the Nonin 3231 Pulse Oximeter is plugged.
770+
771+ >>> import serial
772+ >>> ser = serial.Serial('COM4')
773+
774+ This instance is then used to create an :py:func:`Nonin3231USB` instance
775+ that will be used for the recording from a Nonin 3231 USB device.
776+
777+ >>> from ecg.recording import Nonin3231USB
778+ >>> exg = Nonin3231USB(serial).read(30)
779+
780+ Use the :py:func:`read` method to record some signal and save it in the
781+ `exg` dictionary.
782+
783+ .. warning:: The signals received fom the host are appened to a list. This
784+ process can require more time at each iteration as the signal length
785+ increase in memory. You should alway make sure that this will not
786+ interfer with other task and regularly save intermediate recording to
787+ save resources.
788+
789+ Notes
790+ -----
791+ """
792+
793+ def __init__ (
794+ self ,
795+ serial ,
796+ add_channels : Optional [int ] = 1 ,
797+ ):
798+ self .reset (serial , add_channels )
799+
800+ def reset (
801+ self ,
802+ serial ,
803+ add_channels : Optional [int ] = 1 ,
804+ ):
805+ """Initialize/restart the recording instance.
806+
807+ Parameters
808+ ----------
809+ serial : pySerial object
810+ The `serial` instance interfacing with the USB port.
811+
812+ Returns
813+ -------
814+ Nonin3231USB instance.
815+ """
816+ self .serial = serial
817+
818+ # Initialize recording with empty lists
819+ self .recording : List [float ] = []
820+ self .bpm : List [float ] = []
821+ self .SpO2 : List [float ] = []
822+ self .times : List [float ] = []
823+ self .n_channels : Optional [int ] = add_channels
824+ if add_channels is not None :
825+ self .channels : Optional [Dict [str , List ]] = {}
826+ for i in range (add_channels ):
827+ self .channels [f"Channel_{ i } " ] = []
828+ else :
829+ self .channels = None
830+
831+ # print('channels: '+str(self.channels))
832+
833+ return self
834+
835+ def setup (self ):
836+ self .reset (
837+ serial = self .serial ,
838+ )
839+
840+ return self
841+
842+ def read (self , duration : float ):
843+ """Read PPG signal for some amount of time.
844+
845+ Parameters
846+ ----------
847+ duration : int or float
848+ Length of the desired recording time.
849+ """
850+ # init start ime
851+ tstart = time .time ()
852+
853+ # read for length of duration
854+ while time .time () - tstart < duration :
855+ # assert one full line of data is there
856+ if self .serial .inWaiting () >= 12 :
857+ # Store line of data
858+ data = list (self .serial .readline ())
859+ # assert full data line
860+ if len (data ) < 12 :
861+ continue
862+ # get bpm reading
863+ self .bpm .append (data [8 ])
864+ # give bpm to recording as well
865+ self .recording .append (data [8 ])
866+ # get SpO2 reading (we don't use it)
867+ self .SpO2 .append (data [6 ])
868+ # get 'time' reading (seconds)
869+ self .times .append (data [5 ])
870+ # Add 0 to the additional channels
871+ if self .channels is not None :
872+ for ch in self .channels :
873+ self .channels [ch ].append (0 )
874+
875+ return self
876+
877+
878+ def readInWaiting (self , bool = False ):
879+ """Read in waiting Nonin data.
880+
881+ Parameters
882+ ----------
883+ stop : bool
884+ Stop the recording when an error is detected. Default is *False*.
885+ """
886+
887+ # nonin device reads out 12 bits per message
888+ while self .serial .inWaiting () >= 12 :
889+
890+ # Store full message line
891+ data = list (self .serial .readline ())
892+ # assert that there is a full line of data
893+ if len (data ) < 12 :
894+ continue
895+ # get bpm reading
896+ self .bpm .append (data [8 ])
897+ # give bpm to recording as well
898+ self .recording .append (data [8 ])
899+ # get SpO2 reading (we don't use it)
900+ self .SpO2 .append (data [6 ])
901+ # get 'time' reading (seconds)
902+ self .times .append (data [5 ])
903+ # Add 0 to the additional channels for triggers
904+ if self .channels is not None :
905+ for ch in self .channels :
906+ self .channels [ch ].append (0 )
907+
908+
909+
910+ def save (self , fname : str ):
911+ """
912+ Not saving the data as of now, only results
913+ """
914+ None
915+
916+
917+ def close (self ):
918+ """Close serial connections"""
919+ self .serial .close ()
0 commit comments