More doxygen documentation. Added high bit for CMS50 heartrate

This commit is contained in:
Mark Watkins 2011-12-19 12:20:24 +10:00
parent 1279d0985c
commit d7a0b9fecb
8 changed files with 237 additions and 34 deletions

View File

@ -132,12 +132,22 @@ protected:
GLubyte * colors;
};
/*! \struct TextQue
\brief Holds a single item of text for the drawing queue
*/
struct TextQue
{
short x,y;
//! \variable contains the x axis screen position to draw the text
short x;
//! \variable contains the y axis screen position to draw the text
short y;
//! \variable the angle in degrees for drawing rotated text
float angle;
//! \variable the actual text to draw
QString text;
//! \variable the color the text will be drawn in
QColor color;
//! \variable a pointer to the QFont to use to draw this text
QFont *font;
};
@ -243,9 +253,11 @@ public:
//! \brief Draw all this layers custom GLBuffers (ie. the actual OpenGL Vertices)
virtual void drawGLBuf(float linesize);
//! \note not sure why I needed the reference counting stuff.
short m_refcount;
void addref() { m_refcount++; }
bool unref() { m_refcount--; if (m_refcount<=0) return true; return false; }
protected:
//! \brief Add a GLBuffer (vertex) object customized to this layer
void addGLBuf(GLBuffer *buf) { mgl_buffers.push_back(buf); }
@ -262,14 +274,21 @@ protected:
short m_Y;
short m_order; // order for positioning..
LayerPosition m_position;
//! \brief A vector containing all this layers custom drawing buffers
QVector<GLBuffer *> mgl_buffers;
// Default layer mouse handling = Do nothing
//! \brief Mouse wheel moved somewhere over this layer
virtual bool wheelEvent(QWheelEvent * event) { Q_UNUSED(event); return false; }
//! \brief Mouse moved somewhere over this layer
virtual bool mouseMoveEvent(QMouseEvent * event) { Q_UNUSED(event); return false; }
//! \brief Mouse left or right button pressed somewhere on this layer
virtual bool mousePressEvent(QMouseEvent * event) { Q_UNUSED(event); return false; }
//! \brief Mouse button released that was originally pressed somewhere on this layer
virtual bool mouseReleaseEvent(QMouseEvent * event) { Q_UNUSED(event); return false; }
//! \brief Mouse button double clicked somewhere on this layer
virtual bool mouseDoubleClickEvent(QMouseEvent * event) { Q_UNUSED(event); return false; }
//! \brief A key was pressed on the keyboard while the graph area was focused.
virtual bool keyPressEvent(QKeyEvent * event) { Q_UNUSED(event); return false; }
};
@ -285,9 +304,16 @@ public:
//! \brief Add Layer to this Layer Group
virtual void AddLayer(Layer *l);
//! \brief Returns the minimum time value for all Layers contained in this group (milliseconds since epoch)
virtual qint64 Minx();
//! \brief Returns the maximum time value for all Layers contained in this group (milliseconds since epoch)
virtual qint64 Maxx();
//! \brief Returns the minimum Y-axis value for all Layers contained in this group
virtual EventDataType Miny();
//! \brief Returns the maximum Y-axis value for all Layers contained in this group
virtual EventDataType Maxy();
//! \brief Check all layers contained and return true if none contain data
@ -303,9 +329,8 @@ public:
QVector<Layer *> & getLayers() { return layers; }
protected:
//! \brief Contains all Layer objects in this group
QVector<Layer *> layers;
//overide mouse handling to pass to sublayers..
};
class gGraph;
@ -321,7 +346,10 @@ public:
gThread(gGraphView *g);
~gThread();
//! \brief Start thread process
void run();
//! \brief Kill thread process
void die() { m_running=false; }
QMutex mutex;
protected:
@ -336,6 +364,7 @@ class gToolTip: public QObject
{
Q_OBJECT
public:
//! \brief Initializes the ToolTip object, and connects the timeout to the gGraphView object
gToolTip(gGraphView * graphview);
virtual ~gToolTip();
@ -372,9 +401,19 @@ class gGraph:public QObject
public:
friend class gGraphView;
/*! \brief Creates a new graph object
\param gGraphView * graphview if not null, links the graph to that object
\param QString title containing the graph Title which is rendered vertically
\param int height containing the opening height for this graph
\param short group containing which graph-link group this graph belongs to
*/
gGraph(gGraphView * graphview=NULL, QString title="", QString units="", int height=100,short group=0);
virtual ~gGraph();
//! \brief Tells all Layers to deselect any highlighting
void deselect();
//! \brief Starts the singleshot Timer running, for ms milliseconds
void Trigger(int ms);
/*! \fn QPixmap renderPixmap(int width, int height, float fontscale=1.0);
@ -400,6 +439,7 @@ public:
//! \brief Set the height element. (relative to the total of all heights)
void setHeight(float height) { m_height=height; }
// Can't remember what these are for..
int minHeight() { return m_min_height; }
void setMinHeight(int height) { m_min_height=height; }
@ -451,10 +491,10 @@ public:
//! \brief Sets the time range selected for this graph (in milliseconds since 1970 epoch)
virtual void SetXBounds(qint64 minx, qint64 maxx);
//! \brief Returns the physical Minimum time for all layers contained
//! \brief Returns the physical Minimum time for all layers contained (in milliseconds since epoch)
virtual qint64 MinX();
//! \brief Returns the physical Maximum time for all layers contained
//! \brief Returns the physical Maximum time for all layers contained (in milliseconds since epoch)
virtual qint64 MaxX();
//! \brief Returns the physical Minimum Y scale value for all layers contained
@ -463,9 +503,16 @@ public:
//! \brief Returns the physical Maximum Y scale value for all layers contained
virtual EventDataType MaxY();
//! \brief Sets the physical start of this graphs time range (in milliseconds since epoch)
virtual void SetMinX(qint64 v);
//! \brief Sets the physical end of this graphs time range (in milliseconds since epoch)
virtual void SetMaxX(qint64 v);
//! \brief Sets the physical Minimum Y scale value used while drawing this graph
virtual void SetMinY(EventDataType v);
//! \brief Sets the physical Maximum Y scale value used while drawing this graph
virtual void SetMaxY(EventDataType v);
//! \brief Forces Y Minimum to always select this value
@ -482,9 +529,12 @@ public:
//! \brief Set recommended Y minimum.. It won't go above this unless the data does. It won't go under this.
virtual void setRecMaxY(EventDataType v) { rec_maxy=v; }
//! \brief Returns the recommended Y minimum.. It won't go under this unless the data does. It won't go above this.
virtual EventDataType RecMinY() { return rec_miny; }
//! \brief Returns the recommended Y maximum.. It won't go under this unless the data does. It won't go above this.
virtual EventDataType RecMaxY() { return rec_maxy; }
//! \brief Called when main graph area is resized
void resize(int width, int height); // margin recalcs..
qint64 max_x,min_x,rmax_x,rmin_x;
@ -534,9 +584,14 @@ public:
m_marginleft=left; m_marginright=right;
m_margintop=top; m_marginbottom=bottom;
}
//! \brief Returns this graphs left margin
short marginLeft();
//! \brief Returns this graphs right margin
short marginRight();
//! \brief Returns this graphs top margin
short marginTop();
//! \brief Returns this graphs bottom margin
short marginBottom();
//! \brief Returns the main gGraphView objects GLShortBuffer line list.
@ -551,6 +606,8 @@ public:
QRect m_lastbounds;
QTimer * timer;
//! \brief Returns a Vector reference containing all this graphs layers
QVector<Layer *> & layers() { return m_layers; }
short m_marginleft, m_marginright, m_margintop, m_marginbottom;
@ -558,7 +615,6 @@ protected:
//void invalidate();
//! \brief Mouse Wheel events
virtual void wheelEvent(QWheelEvent * event);
//! \brief Mouse Movement events
@ -638,7 +694,10 @@ public:
//! \brief Add gGraph g to this gGraphView, in the requested graph-linkage group
void addGraph(gGraph *g,short group=0);
//! \brief The width of the vertical text Title area for all graphs
static const int titleWidth=30;
//! \brief The splitter is drawn inside this gap
static const int graphSpacer=4;
//! \brief Finds the top pixel position of the supplied graph
@ -646,6 +705,8 @@ public:
//! \brief Returns the scaleY value, which is used when laying out less graphs than fit on the screen.
float scaleY() { return m_scaleY; }
//! \brief Sets the scaleY value, which is used when laying out less graphs than fit on the screen.
void setScaleY(float sy) { m_scaleY=sy; }
//! \brief Returns the current selected time range
@ -695,12 +756,15 @@ public:
//! \brief Draw all Text in the text drawing queue, via QPainter
void DrawTextQue();
//! \brief Returns number of graphs contained (whether they are visible or not)
int size() { return m_graphs.size(); }
//! \brief Return individual graph by index value
gGraph * operator[](int i) { return m_graphs[i]; }
//! \brief Returns the custom scrollbar object linked to this gGraphArea
MyScrollBar * scrollBar() { return m_scrollbar; }
//! \brief Sets the custom scrollbar object linked to this gGraphArea
void setScrollBar(MyScrollBar *sb);
//! \brief Calculates the correct scrollbar parameters for all visible, non empty graphs.
@ -742,6 +806,7 @@ public:
//! \brief Hides the splitter, used in report printing code
void hideSplitter() { m_showsplitter=false; }
//! \brief Re-enabled the in-between graph splitters.
void showSplitter() { m_showsplitter=true; }
@ -770,18 +835,26 @@ protected:
//! \brief Set the Vertical offset (used in scrolling)
void setOffsetY(int offsetY);
//! \brief Set the Horizontal offset (not used yet)
void setOffsetX(int offsetX);
//! \brief Mouse Moved somewhere in main gGraphArea, propagates to the individual graphs
virtual void mouseMoveEvent(QMouseEvent * event);
//! \brief Mouse Button Press Event somewhere in main gGraphArea, propagates to the individual graphs
virtual void mousePressEvent(QMouseEvent * event);
//! \brief Mouse Button Release Event somewhere in main gGraphArea, propagates to the individual graphs
virtual void mouseReleaseEvent(QMouseEvent * event);
//! \brief Mouse Button Double Click Event somewhere in main gGraphArea, propagates to the individual graphs
virtual void mouseDoubleClickEvent(QMouseEvent * event);
//! \brief Mouse Wheel Event somewhere in main gGraphArea, propagates to the individual graphs
virtual void wheelEvent(QWheelEvent * event);
//! \brief Keyboard event while main gGraphArea has focus.
virtual void keyPressEvent(QKeyEvent * event);
//! \brief Add Graph to drawing queue, mainly for the benefit of multithreaded drawing code
void queGraph(gGraph *,int originX, int originY, int width, int height); // que graphs for drawing (used internally by paintGL)
void queGraph(gGraph *,int originX, int originY, int width, int height);
//! \brief the list of graphs to draw this frame
QList<gGraph *> m_drawlist;
@ -794,7 +867,11 @@ protected:
//! \brief List of all graphs contained, indexed by title
QHash<QString,gGraph*> m_graphsbytitle;
int m_offsetY,m_offsetX; // Scroll Offsets
//! \variable Vertical scroll offset (adjusted when scrollbar gets moved)
int m_offsetY;
//! \variable Horizontal scroll offset (unused, but can be made to work if necessary)
int m_offsetX;
//! \variable Scale used to enlarge graphs when less graphs than can fit on screen.
float m_scaleY;
bool m_sizer_dragging;
@ -811,8 +888,9 @@ protected:
bool m_graph_dragging;
int m_graph_index;
//! \brief List of all queue text to draw.. not sure why I didn't use a vector here.. Might of been a leak issue
TextQue m_textque[textque_max];
int m_textque_items;
int m_lastxpos,m_lastypos;

View File

@ -32,22 +32,50 @@ protected:
GLShortBuffer * lines;
};
/*! \class gLineChart
\brief Draws a 2D linechart from all Session data in a day. EVL_Waveforms typed EventLists are accelerated.
*/
class gLineChart:public Layer
{
public:
/*! \brief Creates a new 2D gLineChart Layer
\param code The Channel that gets drawn by this layer
\param col Color of the Plot
\param square_plot Whether or not to use square plots (only effective for EVL_Event typed EventList data)
\param disable_accel Whether or not to disable acceleration for EVL_Waveform typed EventList data
*/
gLineChart(ChannelID code,const QColor col=QColor("black"), bool square_plot=false,bool disable_accel=false);
virtual ~gLineChart();
//! \brief The drawing code that fills the vertex buffers
virtual void paint(gGraph & w,int left, int top, int width, int height);
//! \brief Set Use Square plots for non EVL_Waveform data
void SetSquarePlot(bool b) { m_square_plot=b; }
//! \brief Returns true if using Square plots for non EVL_Waveform data
bool GetSquarePlot() { return m_square_plot; }
//! \brief Set this if you want this layer to draw it's empty data message
//! \note They don't show anymore due to the changes in gGraphView. Should modify isEmpty if this still gets to live
void ReportEmpty(bool b) { m_report_empty=b; }
//! \brief Returns whether or not to show the empty text message
bool GetReportEmpty() { return m_report_empty; }
//! \brief Sets the ability to Disable waveform plot acceleration (which hides unseen data)
void setDisableAccel(bool b) { m_disable_accel=b; }
//! \brief Returns true if waveform plot acceleration is disabled
bool disableAccel() { return m_disable_accel; }
//! \brief Sets the Day object containing the Sessions this linechart draws from
virtual void SetDay(Day *d);
//! \brief Returns Minimum Y-axis value for this layer
virtual EventDataType Miny();
//! \brief Returns Maximum Y-axis value for this layer
virtual EventDataType Maxy();
protected:
@ -55,10 +83,16 @@ protected:
bool m_square_plot;
bool m_disable_accel;
QColor m_line_color;
GLShortBuffer * lines;
GLShortBuffer * outlines;
//! \brief Used by accelerated waveform plots. Must be >= Screen Resolution (or at least graph width)
static const int max_drawlist_size=4096;
//! \brief The list of screen points used for accelerated waveform plots..
QPoint m_drawlist[max_drawlist_size];
int subtract_offset;
};

View File

@ -11,18 +11,31 @@
enum GraphSegmentType { GST_Pie, GST_CandleStick, GST_Line };
/*! \class gSegmentChart
\brief Draws a PieChart, CandleStick or 2D Line plots containing multiple Channel 'slices'
*/
class gSegmentChart : public Layer
{
public:
gSegmentChart(GraphSegmentType gt=GST_Pie, QColor gradient_color=Qt::white,QColor outline_color=Qt::black);
virtual ~gSegmentChart();
//! \brief The drawing code that fills the Vertex buffers
virtual void paint(gGraph & w,int left, int top, int width, int height);
//! \brief Pre-fills a buffer with the data needed to draw
virtual void SetDay(Day *d);
//! \brief Returns true if no data available for drawing
virtual bool isEmpty();
//! \brief Adds a channel slice, and sets the color and label
void AddSlice(ChannelID code,QColor col,QString name="");
//! \brief Sets the fade-out color to make the graphs look more attractive
void setGradientColor(QColor & color) { m_gradient_color=color; }
//! \brief Sets the outline color for the edges drawn around the Pie slices
void setOutlineColor(QColor & color) { m_outline_color=color; }
const GraphSegmentType & graphType() { return m_graph_type; }
void setGraphType(GraphSegmentType type) { m_graph_type=type; }
@ -41,6 +54,10 @@ protected:
GLFloatBuffer *poly,*lines;
};
/*! \class gTAPGraph
\brief Time at Pressure chart, derived from gSegmentChart
\notes Currently unused
*/
class gTAPGraph:public gSegmentChart
{
public:

View File

@ -11,22 +11,44 @@
#include "gGraphView.h"
#include "gXAxis.h"
/*! \enum GraphType
\value GT_BAR Display as a BarGraph
\value GT_LINE Display as a line plot
\value GT_SESSIONS Display type for session times chart
*/
enum GraphType { GT_BAR, GT_LINE, GT_SESSIONS };
/*! \class SummaryChart
\brief The main overall chart type layer used in Overview page
*/
class SummaryChart:public Layer
{
public:
//! \brief Constructs a SummaryChart with QString label, of GraphType type
SummaryChart(QString label, GraphType type=GT_BAR);
virtual ~SummaryChart();
//! \brief Drawing code that fills the Vertex buffers
virtual void paint(gGraph & w,int left, int top, int width, int height);
//! \brief Precalculation code prior to drawing. Day object is not needed here, it's just here for Layer compatability.
virtual void SetDay(Day * day=NULL);
//! \brief Returns true if no data was found for this day during SetDay
virtual bool isEmpty() { return m_empty; }
//! \brief Adds a layer to the summaryChart (When in Bar mode, it becomes culminative, eg, the AHI chart)
void addSlice(ChannelID code, QColor color, SummaryType type, bool ignore_zeros) { m_codes.push_back(code); m_colors.push_back(color); m_type.push_back(type); m_zeros.push_back(ignore_zeros); }
//! \brief Deselect highlighting (the gold bar)
virtual void deselect() {
hl_day=-1;
}
//! \brief Sets the MachineType this SummaryChart is interested in
void setMachineType(MachineType type) { m_machinetype=type; }
//! \brief Returns the MachineType this SummaryChart is interested in
MachineType machineType() { return m_machinetype; }
protected:
Qt::Orientation m_orientation;
@ -59,9 +81,16 @@ class SummaryChart:public Layer
int tz_offset;
float tz_hours;
//! \brief Key was pressed that effects this layer
virtual bool keyPressEvent(QKeyEvent * event);
//! \brief Mouse moved over this layers area (shows the hover-over tooltips here)
virtual bool mouseMoveEvent(QMouseEvent * event);
//! \brief Mouse Button was pressed over this area
virtual bool mousePressEvent(QMouseEvent * event);
//! \brief Mouse Button was released over this area. (jumps to daily view here)
virtual bool mouseReleaseEvent(QMouseEvent * event);
};

View File

@ -10,10 +10,21 @@
#include <QtOpenGL/qgl.h>
#include <QColor>
/*! \brief Draw an outline of a rounded rectangle
\param radius Radius of corner rounding
\param lw Line Width
\param color Color of drawn lines
*/
void LinedRoundedRectangle(int x,int y,int w,int h,int radius,int lw,QColor color);
/*! \brief Draws a filled rounded rectangle
\param radius Radius of corner rounding
\param color Color of entire rectangle
*/
void RoundedRectangle(int x,int y,int w,int h,int radius,const QColor color);
#ifdef BUILD_WITH_MSVC
// Visual C++ doesn't have either of these in it's maths header.. I'm not surprised at Microsofts maths abilities..
const double M_PI=3.141592653589793;
double round(double number);

View File

@ -28,70 +28,91 @@ inline QString PrefMacro(QString s)
return "{"+s+"}";
}
//! \brief Returns a QString containing the Username, according to the Operating System
const QString & getUserName();
/*! \class Preferences
\author Mark Watkins <jedimark_at_users.sourceforge.net>
\brief Holds a group of preference variables
*/
class Preferences
{
public:
//! \brief Constructs a Preferences object 'name', and remembers sets the filename
Preferences(QString name,QString filename="");
Preferences();
virtual ~Preferences();
//! \brief Returns a QString containing preference 'name', processing any {} macros
const QString Get(QString name);
//const QString Get(const char * name) {
// return Get(name);
// };
/*const QString Get(int code) {
return Get(p_codes[code]);
}*/
// operator[] will not expand {} macros
//! \brief Returns the QVariant value of the selected preference.. Note, preference must exist, and will not expand {} macros
QVariant & operator[](QString name) {
return p_preferences[name];
}
/*QVariant & operator[](int code) {
return p_preferences[p_codes[code]];
}*/
//! \brief Sets the Preference 'name' to QVariant 'value'
void Set(QString name,QVariant value) {
p_preferences[name]=value;
}
/*void Set(int code,QVariant value) {
Set(p_codes[code],value);
}*/
//! \brief Returns true if preference 'name' exists
bool Exists(QString name) {
return (p_preferences.contains(name));
}
//! \brief Returns true if preference 'name' exists, and contains a boolean true value
bool ExistsAndTrue(QString name) {
QHash<QString,QVariant>::iterator i=p_preferences.find(name);
if (i==p_preferences.end()) return false;
return i.value().toBool();
}
//! \brief Removes preference 'name' from this Preferences group
void Erase(QString name) {
QHash<QString,QVariant>::iterator i=p_preferences.find(name);
if (i!=p_preferences.end())
p_preferences.erase(i);
}
//! \brief Derive from this to handle Loading of any custom XML sections
virtual void ExtraLoad(QDomElement & root) { root=root; }
//! \brief Derive from this to handle Saving of any custom XML sections
//! \return Must return a QDomElement to be inserted into the generated XML
virtual QDomElement ExtraSave(QDomDocument & doc) { doc=doc; QDomElement e; return e; }
//! \brief Opens, processes the XML for this Preferences group, loading all preferences stored therein.
//! \note If filename is empty, it will use the one specified in the constructor
//! \returns true if succesful
virtual bool Open(QString filename="");
//! \brief Saves all preferences to XML file.
//! \note If filename is empty, it will use the one specified in the constructor
//! \returns true if succesful
virtual bool Save(QString filename="");
//! \note Sets a comment string whici will be stored in the XML
void SetComment(const QString & str) {
p_comment=str;
}
//! \brief Finds a given preference.
//! \returns a QHash<QString,QString>::iterator pointing to the preference named 'key', or an empty end() iterator
inline QHash<QString,QVariant>::iterator find(QString key) { return p_preferences.find(key); }
//! \brief Returns an empty iterator pointing to the end of the preferences list
inline QHash<QString,QVariant>::iterator end() { return p_preferences.end(); }
//! \brief Returns an iterator pointing to the first item in the preferences list
inline QHash<QString,QVariant>::iterator begin() { return p_preferences.begin(); }
//int GetCode(QString name); // For registering/looking up new preference code.
//! \brief Stores all the variants indexed by a QString name for this Preferences object
QHash<QString,QVariant> p_preferences;
protected:
//QHash<int,QString> p_codes;
QString p_comment;
@ -101,6 +122,11 @@ protected:
};
enum PrefType { PT_Checkbox, PT_Integer, PT_Number, PT_Date, PT_Time, PT_DateTime, PT_LineEdit, PT_TextEdit, PT_Dropdown };
/*! \class Preference
\brief Holds a single preference
\note This is a work in progress to clean up preferences system
*/
class Preference
{
public:
@ -138,7 +164,10 @@ protected:
Q_DECLARE_METATYPE(Preference)
//! \brief Main Preferences Object used throughout the application
extern Preferences PREF;
//! \brief Layout Preferences Object used throughout the application
extern Preferences LAYOUT;
#endif // PREFERENCES_H

View File

@ -16,17 +16,20 @@ License: GPL
#include "preferences.h"
class Machine;
/*!
\class Profile
\author Mark Watkins
\date 28/04/11
\file profiles.h
\brief User profile system
\brief The User profile system, containing all information for a user, and an index into all Machine data
*/
class Profile:public Preferences
{
public:
//! \brief Creates a new Profile object 'name' (which is usually just set to "Profile", the XML filename is derived from this)
Profile(QString name);
//! \brief Create a new empty Profile object
Profile();
virtual ~Profile();
@ -51,7 +54,7 @@ public:
void DataFormatError(Machine *m);
/*! \brief Import Machine Data
\param path
\param path containing import location
*/
int Import(QString path);

View File

@ -433,7 +433,7 @@ void CMS50Serial::import_process()
return;
}
qDebug() << "CMS50 import complete. Processing" << data.size() << "bytes";
unsigned char a,pl,o2,lastpl=0,lasto2=0;
unsigned short a,pl,o2,lastpl=0,lasto2=0;
int i=0;
int size=data.size();
@ -452,9 +452,8 @@ void CMS50Serial::import_process()
qint64 lastpltime=0,lasto2time=0;
bool first=true;
while (i<(size-3)) {
a=data.at(i++);
Q_UNUSED(a);
pl=data.at(i++) ^ 0x80;
a=data.at(i++); // low bits are supposedly the high bits of the heart rate
pl=(data.at(i++) & 0x7f) | ((a & 3) << 7);
o2=data.at(i++);
if (pl!=0) {
if (lastpl!=pl) {
@ -571,8 +570,8 @@ void CMS50Serial::ReadyRead()
static int lastbytesize=0;
int size=bytes.size();
// Process all incoming serial data packets
unsigned char c;
unsigned char pl,o2;
unsigned short c;
unsigned short pl,o2;
if (!import_mode) {
QString data="Read: ";
@ -679,14 +678,17 @@ void CMS50Serial::ReadyRead()
//read data blocks..
}
} else {
static unsigned short hb=0;
if (bytes[i]&0x80) { // 0x80 == sync bit
EventDataType d=bytes[i+1] & 0x7f;
hb=(bytes[i+2] & 0x40) << 1;
addPlethy(lasttime,d);
lasttime+=20;
i+=3;
} else {
pl=bytes[i];
pl=(bytes[i] & 0x7f) | hb;
o2=bytes[i+1];
addPulse(lasttime,pl);
addSpO2(lasttime,o2);