My new project: an EKG simulator for educational purposes

Hello! I choose Codea to make an old project: an EKG simulator (and multiparameter monitor simulator) for educational purposes. I am a nurse, working and living in Chile. I work on an hospital and teaching on a University to Nursing students. So, i use a lot an early prototype and now i started from draft because i want it to put on AppStore. I also learning to code directly on XCode but i choose Codea because aircode and i can test on the fly my program. I want to show you some photos of my first prototype, working but very buggy.

Copy and paste this code, if program stops just put play again,LANDSCAPE only.

The new version is more object oriented. The precision is very low, so 60BPM actually wont run at 60/minute.

-- Monitor Sim


-- Use this function to perform your initial setup
function setup()
    backingMode(RETAINED)
    deltaCumulative=0
    ecgVal=0
    ecgLastX=0
    ecgLastY=0
    smooth()
    startTime= os.clock()
    deltaFps=0
    fpsDelta= os.clock()
    intFps=0
    currentFrecState=0
    valFps=0
    Sist = 0
    Diast = 0
    deltaEcgBox=10
    ecgBoxWidth = 3
    parameter.integer("FC",0,180,70)
    parameter.integer("FR",0,50,17)
    parameter.integer("Sist",0,240,125, function() if Sist < Diast then Diast = Sist end end)
    parameter.integer("Diast",0,140,80, function() if Diast > Sist then Sist = Diast end end)
    parameter.integer("Sat",0,100,95)
    parameter.action("Sano",function() setPar(70, 17, 125, 80, 90) end)
    parameter.action("Sepsis", function() setPar(140, 24, 94, 50, 92) end)
    parameter.action("Bradi. Asintomática", function() setPar(41, 19, 123, 87, 94) end)
    parameter.action("Bradi. Sintomática", function() setPar(34, 23, 86, 43, 85) end)
    parameter.action("Crisis HTA", function() setPar(90, 21, 210, 102, 93) end)
    parameter.action("Insuf. Resp I", function() setPar(142, 47, 115, 72, 72) end)
    parameter.action("Insuf. Resp II", function() setPar(45, 5, 0, 0, 30) end)
    parameter.action("Vaciar", function() setPar(0,0,0,0,0) end)
    intResolution = 6
    XCuad = (WIDTH / intResolution)-30
    YCuad = (HEIGHT / intResolution)-10
    textAlign(RIGHT)
    font("Futura-Medium")
    textMode(CORNER)
    sep="- -"
end
function setPar(intFC, intFR, intSist, intDiast, intSat)
    FC=intFC
    FR=intFR
    Sist=intSist
    Diast=intDiast
    Sat=intSat
end
-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    -- background(0, 0, 0, 255)
    stroke(0, 0, 0, 255)
    fill(25, 25, 25, 255)
    rect(WIDTH/2,0,WIDTH/2,HEIGHT)
    rect(0,0,12, HEIGHT)
    fps()
    -- Formatting
    strokeWidth(1)
    stroke(69, 69, 69, 0)
    for ix=1, intResolution - 1 do
        line(XCuad*ix,0,XCuad*ix,HEIGHT)
    end
    for iy=1, intResolution - 1 do
        line(0, YCuad*iy,WIDTH, YCuad*iy)
    end
    if FC == 0 then sFC = sep else sFC = FC end
    if FR == 0 then sFR = sep else sFR = FR end
    if Sist == 0 then sSist = sep else sSist = Sist end
    if Diast == 0 then sDiast = sep else sDiast = Diast end
    if Sat == 0 then sSat = sep else sSat = Sat end
    txt("FREC CARDIACA",10,"G",XCuad * (intResolution - 3), YCuad * (intResolution - 1))
    txt(sFC , 120,"G", XCuad * (intResolution - 2), YCuad * (intResolution - 1)-30)
    
    txt("FREC RESP", 10, "W", XCuad * (intResolution - 3), YCuad * (intResolution - 2))
    txt(sFR , 100, "W", XCuad * (intResolution - 2), YCuad * (intResolution - 2)-30)
    if Sist == sep or Diast == sep then
        PAM = sep
    else
        PAM = math.floor((Sist + Diast + Diast)/ 3)
    end
    if Sist == Diast then PAM = sep end
    txt("PANI", 10, "R" , XCuad * (intResolution - 3), YCuad * (intResolution - 3))
    txt(sSist .. "/" .. sDiast , 90, "R", XCuad * (intResolution - 2), YCuad * (intResolution - 3)-5)
    txt("(" .. PAM  .. ")", 30, "R", XCuad * (intResolution) +30, YCuad * (intResolution - 3)-30)
    
    txt("SpO2", 10, "Y", XCuad * (intResolution - 3), YCuad * (intResolution - 4))
    txt(sSat, 120, "Y", XCuad * (intResolution - 2), YCuad * (intResolution - 4)-30)
    debugFpsData()
    eraseBg()
    drawECG()
end

function drawECG()
    -- Mostrar corazon
    if currentFrecState <= 0.2 then 
        txt("SYMBOL",20,"W",XCuad * (intResolution) + 40, YCuad * (intResolution - 1) +50)
    end
    -- Trazar linea
    -- fill(40, 255, 0, 255)
    -- ecgVal=HEIGHT -120 + (math.sin(currentFrecState*2*math.pi)*30)
    ecgVal=HEIGHT -120 + (getEcgVal())
    --ecgVal=g
    -- ellipse(deltaEcgBox -5,ecgVal,5,5)
    strokeWidth(2)
    stroke(58, 255, 0, 255)
    if ecgLastX ==  0 then 
    else
        line(ecgLastX,ecgLastY,deltaEcgBox,ecgVal) 
    end
    ecgLastX=deltaEcgBox
    ecgLastY=ecgVal
    fill(255, 255, 255, 255)
    ellipse(deltaEcgBox +4,ecgVal,4,4)
end
-- tiempo = 60' = 120'
-- 60 seg = 1   = 2
--          
function eraseBg()
    fill(0, 0, 0, 255)
    noStroke()
    rect(deltaEcgBox,0,ecgBoxWidth,HEIGHT)
    fill(20, 20, 20, 255)
    rect(deltaEcgBox+ecgBoxWidth,0,ecgBoxWidth,HEIGHT)
    deltaEcgBox = deltaEcgBox + ecgBoxWidth -1
    if deltaEcgBox >= WIDTH/2 then 
        deltaEcgBox=10 
        ecgLastX=0
    end 
end
function debugFpsData()
    deltaTime = os.clock() - startTime
    currentFrecState = currentFrecState +  (deltaTime * FC/ 60)
    startTime = os.clock()
    deltaCumulative = deltaCumulative + 1
    if currentFrecState>=1 then 
        print(currentFrecState)
        currentFrecState = 0.001
        print(deltaCumulative)
        deltaCumulative = 0
    end
    
  
    txt("fps=" .. valFps, 12, "Y", XCuad * (intResolution - 1), YCuad * (intResolution - 4)-70)
end



function getEcgVal()
    local ecgStringId = math.ceil(currentFrecState*(string.len(ecgData)/3))
    local eValue = tonumber(string.sub(ecgData,(ecgStringId*3),(ecgStringId*3)+2))/10
    txt(eValue, 12, "Y", XCuad * (intResolution - 1), YCuad * (intResolution - 3))
    return eValue
end
function txt(strString, intSize, strColor, intX, intY) -- Imprime un texto en la pantalla
    cR = 255
    cG = 255
    cB = 255
    if strColor == "R" then
        cR=255
        cG=0
        cB=0
    elseif strColor == "G" then
        cR=0
        cG=255
        cB=0
    elseif strColor == "Y" then
        cR = 255
        cG = 255
        cB = 0
    elseif strColor == "W" then
        cB = 255
        cR = 255
        cG = 255
    end
    fill(cR,cG,cB,255)
    fontSize(intSize)
    text(strString,intX, intY)
end
function fps()
    intFps = intFps + 1
    local finalFps = deltaFps +1
    if os.clock()>=finalFps then 
        deltaFps = os.clock()
        valFps=intFps
        intFps=0
        
    end
end

--ECG DATABANK
ecgData="00500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550550551552553554555556557557558558559559560560560559559558558557556555554552550547543539535531528526524522520518517516515514512510509508507506505504503502501500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500499498497496495494493492491490488486484482480478476474472470467464461458455452449446442438434430426422420435450465480495510525540555570585600615630645660675690705720735750765780795810825840855870885900915930945930915900885870855840825810795780765750735720705690675660645630615600585570555540525510495480465450435420405390375360345375405435450460468474480485489492494496497498499500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630630631631632632633633632632631631630629628627625622620618616614612610608606604602600598596594592590588586584582580578576574572570568566564562560558556554552550548546544542540538536534532530528526524522520518516515514513512511510509509508508507507506506505505504504504503503503502502502501501501500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500501501501502502502503503504504505505506506507508509510511512513514515516516517517518518518519519519519520520520520520520519519519518518518517517517516515514513512511510509508507506505504504503503503502502502502501501501501501500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500500"

@Andresan That is really neat. I’m looking forward to seeing the progress. Nicely done.

Very cool! Keep us posted.


There is a recurring error on line 152 (attempt to perform arithmetic on a nil value) Hope this helps!

Thank you! I already had wrote it from scratch, so i wont check this code again. This error happened because a out of bounds value, maybe a division by zero.

I already have a framework, three clases: one is the main screen area, completely resizable and responding to orientation change events. The next class is the Curve Area class, and it have its own presence on its parent. The last class, is the curve itself.

I have one main problem:
the way i sync the representation with the data. I have a curve data, but when the display refresh the value obtained skips some important points, like the highest point of the curve, that result in not all the complexes having the same heights. This could be resolved drawing all the pasts values (not promediating, because it would be the same). In fact, real monitors draw segments of data based of measured ones, both of events happening asyncronously.

In medical matters, is really important the constant of heights of the complexes.

A image of the current state of my proyect. It draws by now a sinus curve for testing purposes.

https://www.dropbox.com/s/li9yxa7108hjprx/IMG_0199.PNG