@@ -748,3 +748,172 @@ def read(self, duration):
748
748
def close (self ):
749
749
"""Close TCPIP connections"""
750
750
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