diff --git a/src/ModelingToolkitSampledData.jl b/src/ModelingToolkitSampledData.jl index 4ae6fcb..6e6039c 100644 --- a/src/ModelingToolkitSampledData.jl +++ b/src/ModelingToolkitSampledData.jl @@ -9,6 +9,7 @@ export DiscreteIntegrator, DiscreteDerivative, Delay, Difference, ZeroOrderHold, DiscretePIDParallel, DiscretePIDStandard, DiscreteStateSpace, DiscreteTransferFunction, NormalNoise, UniformNoise, Quantization, ExponentialFilter +export DiscreteOnOffController include("discrete_blocks.jl") end diff --git a/src/discrete_blocks.jl b/src/discrete_blocks.jl index bc69f5f..94e50cf 100644 --- a/src/discrete_blocks.jl +++ b/src/discrete_blocks.jl @@ -971,3 +971,43 @@ Exponential filtering with input-output relation ``y(z) ~ (1 - a) y(z-1) + a u(z end end +""" + DiscreteOnOffController(b = 0.1, bool = true) + +Discrete-time On-Off controller with hysteresis. The controller switches between two states based on the error signal `reference-input`. The controller is in the on-state if the error signal is within the bandwidth `b` around the reference signal, and in the off-state otherwise. + +# Connectors: +- `reference`: The reference signal to the controller +- `input`: The measurement feedback +- `output`: The control signal output + +# Parameters: +- `b`: Bandwidth around reference signal within which the controller does not react +- `bool`: (structural) If true (default), the controller switches between 0 and 1. If false, the controller switches between -1 and 1. +- `k`: Controller gain. The output of the contorller is scaled by this gain, i.e., `k = 2, bool = false` will result in an output of -2 or 2. +""" +@mtkmodel DiscreteOnOffController begin + @extend u, y = siso = SISO() + @components begin + reference = RealInput() + end + @structural_parameters begin + z = ShiftIndex() + bool = true + end + @parameters begin + b = 0.1, [description = "Bandwidth around reference signal"] + k = 1, [description = "Controller gain"] + end + @variables begin + s(t)=true, [description = "Internal variable"] + end + @equations begin + s(z) ~ (y(z-1) == k) & (u(z) < reference.u(z) + b/2) | (u(z) < reference.u(z) - b/2) + if bool + y(z) ~ k*s(z) + else + y(z) ~ k*(2*s(z) - 1) + end + end +end \ No newline at end of file diff --git a/test/test_discrete_blocks.jl b/test/test_discrete_blocks.jl index 1b46287..c6c0dbd 100644 --- a/test/test_discrete_blocks.jl +++ b/test/test_discrete_blocks.jl @@ -461,6 +461,33 @@ end @test 0 ∈ uy end +@testset "OnOff" begin + @info "Testing OnOff" + cl = Clock(0.1) + z = ShiftIndex(cl) + @mtkmodel OnOffModel begin + @components begin + onoff = DiscreteOnOffController(; z, bool=false) + c = Constant(k=1) + end + @variables begin + x(t) = 0 + end + @equations begin + onoff.u ~ Sample(cl)(x) + connect(c.output, onoff.reference) + D(x) ~ 0.1x + Hold(onoff.y) + end + end + @named m = OnOffModel() + m = complete(m) + ssys = structural_simplify(IRSystem(m)) + prob = ODEProblem(ssys, [m.onoff.y(z-1) => 0], (0.0, 4.0)) + sol = solve(prob, Tsit5(), dtmax=0.1) + # plot(sol, idxs=[m.x, m.onoff.y], title="On-off control of an unstable first-order system") + @test 0.89 <= sol(4, idxs=m.x) <= 1.11 +end + @testset "ExponentialFilter" begin @info "Testing ExponentialFilter"