Skip to content
Snippets Groups Projects
Select Git revision
  • 877a014fd711656d6fbea6406bf9a07e87a85096
  • master default protected
  • v1.0
3 results

Rt2b.m

Blame
  • doubletank.jl 7.43 KiB
    #export DoubleTankSimulator, DoubleTank
    
    using LabConnections.Computer #for LabStream
    
    const uppertankempty = 0.0
    const lowertankempty = 0.0
    const uppertankfull  = 10.07
    const lowertankfull  = 9.39
    const venturimin     = 0.0
    const venturimax     = 14.0
    using Parameters
    
    
    #This should be changed in LabConnections in some reasonably manner
    ############################################################
    function LabConnections.Computer.getwritecommand(stream::ComediStream, input::AnalogOutput10V, val::Float64)
        abs(val) <= 10 || error("Volatage $val not in range [-10,10]")
        return (Int32(0),Int32(3),input.i,val)
    end
    function LabConnections.Computer.getreadcommand(stream::ComediStream, input::AnalogInput10V)
        return (Int32(0),Int32(2),input.i) 
    end
    ############################################################
    
    # This allows calibration without having to toggle the pump controller
    function LabConnections.Computer.send(n::Void, a::Number)
        nothing
    end
    
    @with_kw mutable struct PID
        b::Float64     = 1.0
        K::Float64     = 1.0
        h::Float64     = 0.05
        Ti::Float64    = 1.0
        Td::Float64    = 1.0
        y_old::Float64 = 0.51
        N::Int64 = 10
        P::Float64   = 0
        I::Float64   = 0
        D::Float64   = 0
        Tot::Float64 = 0
    end
    function PID(b, K, h, Ti, Td, y_old, N)
        PID(b, K, h, Ti, Td, y_old, N, 0.0, 0.0, 0.0, 0.0)
    end
    
    
    function (p::PID)(r, y, onv=(1,1,1))
        #The PID should operate entirely on normalized values
        @unpack b, K, h, Ti, Td, N, y_old, P, I, D, Tot = p
    
        ad = Td/(N*h+Td)
        bd = N*K*Td/(N*h+Td)
    
        Tot = 0.0
        P = K*(b*r-y)
        if onv[1]==1
            Tot += P
        end
        if onv[3]==1
            D = ad*D - bd*(y-y_old)
            Tot += D
        end
        if onv[2]==1 && Ti !=0
            Ichange = K/Ti*h*(r-y)
            if (Tot + I < 1 && Ichange > 0)||
                (Tot + I > 0 && Ichange < 0)
                I += Ichange
            end
            Tot += I
        end
        y_old = y
        @pack p = P, I, D, Tot, y_old
        Tot
    end
    
    @with_kw mutable struct DoubleTankSimulator <: SimulatedProcess
        h::Float64         = 0.05
        x::Vector{Float64} = [0.5, 0.5]
        n::Int             = 1
        α::Float64         = 2.1e-5
        A::Float64         = 4.9e-4
        a1::Float64        = 3.1e-6
        a2::Float64        = 3.1e-6
        g::Float64         = 9.8
        scale::Float64     = 0.16
        σ::Float64         = 0.0 #in meters
    end
    
    
    
    mutable struct Pump <: PhysicalProcess
        h::Float64
        stream::LabStream
        u::Float64
        v::Float64
        pid::PID
        venturirange::Array{Float64, 1}
        measure::AnalogInput10V
        control::Union{AnalogOutput10V, Void}
    end
    
    function Pump(stream)
        pid    = PID()
        pid.K  = 0.21
        pid.Ti = 0.06
        pid.b  = 0.0
        pid.h  = 0.01 
        Pump(pid.h, stream, 0.0, 0.0, pid, [venturimin, venturimax], AnalogInput10V(4), AnalogOutput10V(0))
    end
    
    outputrange(p::Pump) = [0.,10.] 
    inputrange(p::Pump)  = [(0.,1.)] 
    
    function control(p::Pump, yref)
        p.u = yref
    end
    
    function initialize(p::Pump)
        @async while true
            @periodically p.h begin
                #normalized venturi
                venturi = (read(p.measure)-p.venturirange[1])/(p.venturirange[2]-p.venturirange[1])
                flow = sqrt(max(venturi,0.0)) 
                p.v = p.pid(p.u, flow, (1,1,0))
                send(p.control, 10*clamp(p.v, 0.0, 1.0)) 
            end
        end
    end
    
    measure(p::Pump) = read(p.measure)
    
    struct DoubleTank <: PhysicalProcess
        h::Float64
        stream::LabStream
        measure::Array{AnalogInput10V, 1} 
        uprange::Array{Float64, 1}
        lowrange::Array{Float64, 1}
        pump::Pump
    end
    
    function DoubleTank(;
                        h::Float64               = 0.05,
                        stream::LabStream        = ComediStream(),
                        measure = [AnalogInput10V(0), AnalogInput10V(1)], 
                        uprange = [uppertankempty, uppertankfull],
                        lowrange = [lowertankempty, lowertankfull],
                        pump::Pump =  Pump(stream)) #Change
        p = DoubleTank(Float64(h),stream,measure, uprange, lowrange, pump)
        init_devices!(p.stream, p.measure..., pump.measure, pump.control)
        initialize(pump)
        p
    end
    
    function control(p::DoubleTank, u)
        control(p.pump, u)
    end
    
    
    """
    Calibrates the tanks. Sets the pump to max for filltime seconds, during which the flow is measured calpts times. The tank voltage is then measured, when they are presumably full. The pump is then set to min for emptytime and the flow is again measured calpts time. The tank voltage is then again measured, when they're presumably empty. These values are then used to set the parameters for the DoubleTank measure function and the DoubleTank.pump controller.
    """
    function calibrate(p::DoubleTank)
        print("Calibrating...\n")
        filltime = 40
        emptytime = 30
        calpts = 20
    
        #Override pump control
        c = p.pump.control
        p.pump.control = nothing
    
        measurements = zeros(1:calpts, Float64)
    
        send(c, 10.0) #just a number > 10
        for i in 1:calpts
            sleep(filltime/calpts)
            measurements[i] = measure(p.pump)
        end
        fulltanks  = read.(p.measure)
        venturimax = mean(measurements)
    
        print("Upper Full Voltage:  $(fulltanks[1])\n")
        print("Lower Full Voltage:  $(fulltanks[2])\n")
        print("Venturi Avg max:     $venturimax\n")
    
        send(c, 0.0)
        for i in 1:calpts
            sleep(emptytime/calpts)
            measurements[i] = measure(p.pump)
        end
        emptytanks = read.(p.measure)
        venturimin = mean(measurements)
    
        print("Upper Empty Voltage: $(emptytanks[1])\n")
        print("Lower Empty Voltage: $(emptytanks[2])\n")
        print("Venturi Avg min:     $venturimin\n")
    
        p.uprange[1] = emptytanks[1]
        p.uprange[2] = fulltanks[1]
        p.lowrange[1] = emptytanks[2]
        p.lowrange[2] = fulltanks[2]
        p.pump.venturirange = [venturimin, venturimax]
    
        #Return pump control
        p.pump.control = c
        print("Finished calibration\n")
        nothing
    end
    
    function calibrate(p::DoubleTankSimulator)
        print("Simulator can't be calibrated!\n")
    end
    
    function measure(p::DoubleTank)
        #This should give an array of two values in the range [0,1]
        minv = [p.uprange[1], p.lowrange[1]]
        maxv = [p.uprange[2], p.lowrange[2]]
        clamp.((read.(p.measure)-minv)./(maxv-minv), 0.0, 1.0)
    end
    
    
    
    const AbstractDoubleTank = Union{DoubleTank, DoubleTankSimulator}
    
    num_outputs(p::AbstractDoubleTank) = 2
    num_inputs(p::AbstractDoubleTank)  = 1
    outputrange(p::AbstractDoubleTank) = [(0.0,1.0),(0.0,1.0)] 
    inputrange(p::AbstractDoubleTank)  = [(0.,1.)] 
    isasstable(p::AbstractDoubleTank)  = true
    sampletime(p::AbstractDoubleTank)  = p.h
    bias(p::DoubleTankSimulator)       = 0
    
    function qu(p, u)
        u*p.α
    end
    
    function control(p::DoubleTankSimulator, u::Number)
        @unpack a1, a2, g, x, A, h, scale = p
        x *= scale
        #The simulator should operate on physical values but u should be in [0,1]
    
        u = clamp(u, inputrange(p)[1]...)
    
        qut = a1*sqrt(2*g*x[1]) #m^3/s
        dA1 = qu(p,u) - qut #m^3/s
    
        if -dA1*h>x[1]*A
            qut = x[1]*A/h-qu(p, u)
            dA1 = qu(p, u) - qut #m^3/s
        elseif x[1] + (dA1*h)/A > scale*outputrange(p)[1][2]
            dA1 = (scale*outputrange(p)[1][2] - x[1])*A/h
        end
    
        dA2 = qut - a2*sqrt(2*g*x[2]) #m^3/s
        if(-dA2*h>x[2]*A)
            dA2 = -x[2]*A/h
        end
        x[1] += (dA1*h)/A 
        x[2] += (dA2*h)/A
    
        x /= scale
        @pack p = a1, a2, g, x, A, h 
    end
    
    
    measure(p::DoubleTankSimulator)    = p.x + p.σ*p.scale*randn(2) 
    
    
    LabProcesses.initialize(p::DoubleTankSimulator) = nothing
    LabProcesses.finalize(p::DoubleTankSimulator)   = nothing
    
    LabProcesses.initialize(p::DoubleTank) = nothing 
    LabProcesses.finalize(p::DoubleTank)   = nothing