Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
Loading items

Target

Select target project
  • processes/LabProcesses.jl
  • martinheyden/LabProcesses.jl
2 results
Select Git revision
Loading items
Show changes
Commits on Source (31)
*.jl.cov *.jl.cov
*.jl.*.cov *.jl.*.cov
*.jl.mem *.jl.mem
docs/build/
docs/site/
docs/.documenter
...@@ -14,10 +14,10 @@ ...@@ -14,10 +14,10 @@
script: script:
# Let's run the tests. Substitute `coverage = false` below, if you do not # Let's run the tests. Substitute `coverage = false` below, if you do not
# want coverage results. # want coverage results.
- /opt/julia/bin/julia -e 'Pkg.rm("LabProcesses");Pkg.clone(pwd()); Pkg.test("LabProcesses",coverage = false)' - julia -e 'Pkg.rm("LabProcesses");Pkg.clone(pwd()); Pkg.test("LabProcesses",coverage = false)'
#- /opt/julia/bin/julia -e 'Pkg.update();Pkg.test("LabProcesses", coverage = true)' #- julia -e 'Pkg.update();Pkg.test("LabProcesses", coverage = true)'
# Comment out below if you do not want coverage results. # Comment out below if you do not want coverage results.
#- /opt/julia/bin/julia -e 'Pkg.add("Coverage"); cd(Pkg.dir("LabProcesses")); #- julia -e 'Pkg.add("Coverage"); cd(Pkg.dir("LabProcesses"));
# using Coverage; cl, tl = get_summary(process_folder()); # using Coverage; cl, tl = get_summary(process_folder());
# println("(", cl/tl*100, "%) covered")' # println("(", cl/tl*100, "%) covered")'
......
# LabProcesses ## News
This package contains an (programming- as well as connection-) interface to serve 2018-12-07: Updated to julia v1.0, see commit eac09291 for last julia v0.6 version
as a base for the implementation of lab-process software. The first example of
an implementaiton of this interface is for the ball-and-beam process, which is
used in Lab1 FRTN35: frequency response analysis of the beam. The lab is implemented
in [BallAndBeam.jl](https://gitlab.control.lth.se/processes/BallAndBeam.jl), a
package that makes use of `LabProcesses.jl` to handle the communication with
the lab process and/or a simulated version thereof. This way, the code written
for frequency response analysis of the beam can be run on another process
implementing the same interface (or a simulated version) by changeing a single
line of code :)
## Installation [![pipeline status](https://gitlab.control.lth.se/processes/LabProcesses.jl/badges/master/pipeline.svg)](https://gitlab.control.lth.se/processes/LabProcesses.jl/commits/master)
1. Start julia by typing `julia` in a terminal, make sure the printed info says it's [![coverage report](https://gitlab.control.lth.se/processes/LabProcesses.jl/badges/master/coverage.svg)](https://gitlab.control.lth.se/processes/LabProcesses.jl/commits/master)
`v0.6+` running. If not, visit [julialang.org](https://julialang.org/downloads/)
to get the latest release.
2. Install LabProcesses.jl using command `Pkg.clone("https://gitlab.control.lth.se/processes/LabProcesses.jl.git")` Lots of packages will now be installed, this will take some time. If this is your first time using Julia, you might have to run `julia> Pkg.init()` before you install any packages.
## How to implement a new process # LabProcesses
1. Locate the file [interface.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface.jl). When the package is installed, you find its directory under `~/.julia/v0.6/LabProcesses/`, if not, run `julia> Pkg.dir("LabProcesses")` to locate the directory. Documentation available at [Documentation](http://processes.gitlab.control.lth.se/documentation/labprocesses/)
(Alternatively, you can copy all definitions from [/interface_implementations/ballandbeam.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface_implementations/ballandbeam.jl) instead. Maybe it's easier to work from an existing implementaiton.)
2. Copy all function definitions.
3. Create a new file under `/interface_implementations` where you paste all the
copied definitions and implement them. See [/interface_implementations/ballandbeam.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface_implementations/ballandbeam.jl) for an example.
4. Above all function implementations you must define the process type, e.g,
```julia
struct BallAndBeam <: PhysicalProcess
h::Float64
bias::Float64
end
BallAndBeam() = BallAndBeam(0.01, 0.0) # Constructor with default value of sample time
```
Make sure you inherit from `PhysicalProcess` or `SimulatedProcess` as appropriate.
This type must contains fields that hold information about everything that is
relevant to a particular instance of the process. Different ballandbeam-process
have different biases, hence this must be stored. A simulated process would have
to keep track of its state etc. in order to implement the measure and control
methods. See [Types in julia documentation](https://docs.julialang.org/en/stable/manual/types/#Composite-Types-1)
for additional info regarding user defined types and (constructors)[https://docs.julialang.org/en/stable/manual/constructors/].
5. Documentation of all interface functions is available in the file [interface_documentation.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface_documentation.jl)
## Control a process
The interface `AbstractProcess` defines the functions `control(P, u)` and `measure(P)`.
These functions can be used to implement your own control loops. A common loop
with a feedback controller and a feedforward filter on the reference is implemented
in the function [`run_control_2DOF`](@ref), where the user can supply $G_1$ and $G_4$
in the diagram below, with the process $P=G_2$.
![block diagram](docs/feedback4.png)
The macro `@periodically` might come in handy if you want to implement your own loop. # Automatiskt genererad dokumentation
Consider the following example, in which the loop body will be run periodically Det finns i skrivande stund två stycken mer eller mindre automatgenererade hemsidor med dokumentation, en sida till repot BallAndBeam.jl
with a sample time of `h` seconds. http://processes.gitlab.control.lth.se/documentation/ballandbeam/
```julia
for (i,t) = enumerate(0:h:duration)
@periodically h begin
y[i] = measure(P)
r[i] = reference(t)
u[i] = calc_control(i,y,r)
control(P, u[i])
end
end
```
och en sida till (detta) repot LabProcesses.jl
http://processes.gitlab.control.lth.se/documentation/labprocesses
Often one finds the need to implement a stateful controller, i.e., a function Mitt förslag är att alla som känner sig ansvariga för ett repo som är skapat med syfte att till slut överlämnas till framtida kollegor sätter upp liknande
that has a memory or state. To this end, the function [`sysfilter`](@ref) is dokumentation för detta repo.
provided. This function is used to implement control loops where a signal is
filtered through a dynamical system, i.e., `U(z) = C(z)E(z)`.
Usage is demonstrated below, which is a simplified implementation of the block
diagram above (transfer function- and signal names corresponds to the figure).
```julia
stateG1 = init_sysfilter(G1)
stateG4 = init_sysfilter(G4)
function control(i)
rf = sysfilter!(stateG4, G4, r)
e = rf-y
u = sysfilter!(stateG1, G1, e)
end
```
`G1` and `G4` must here be represented by [`StateSpace`](http://juliacontrol.github.io/ControlSystems.jl/latest/lib/constructors/#ControlSystems.ss) types from [`ControlSystems.jl`](https://github.com/JuliaControl/ControlSystems.jl).
`TransferFunction` types can easily be converted to a `StateSpace` by `Gss = ss(Gtf)`.
Continuous time systems can be discretized using `Gd = c2d(Gc, h)[1]`. (The sample time of a process is available through `h = sampletime(P)`.)
## Generera dokumentation
Jag har byggt dokumentationen med [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl). Detta verktyg accepterar en markdown-fil (docs/src/index.md)
med lite text man skrivit samt macron som indikerar att man vill infoga docstrings från funktioner och typer i sitt juliapaket.
Outputen är en html-sida med tillbehör som man ska se till att hosta på gitlab pages.
# How to implement a Simulated Process Exempel på hur detta går till finns i repona LabProcesses.jl och BallAndBeam.jl. Man kan helt sonika kopiera docs/-mappen från ett av dessa repon och
## Linear process modifiera innehållet i alla filer så att det stämmer med ens nya repo.
This is very easy, just get a discrete time `StateSpace` model of your process
(if you have a transfer function, `Gss = ss(Gtf)` will do the trick, if you have continuous time, `Gd = c2d(Gc,h)[1]` is your friend).
You now have to implement the methods `control` and `measure` for your simulated type. ## Hosting
The implementation for `BeamSimulator` is shown below För hosting finns repot
```julia https://gitlab.control.lth.se/processes/documentation/
function control(p::BeamSimulator, u) Det man behöver göra är att editera filen
sysfilter!(p.state, p.sys, u) https://gitlab.control.lth.se/processes/documentation/blob/master/.gitlab-ci.yml
end och lägga till tre rader som
measure(P) = vecdot(p.sys.C,p.state)
```
The `control` method accepts a control signal (`u`) and propagates the system state
(`p.state`) forward using the statespace model (`p.sys`) of the beam. The function
[`sysfilter!`](@ref) is familiar from the "Control" section above. What it does
is essentially
```julia
function sysfilter!(state, sys, input)
state .= sys.A*state + sys.B*input
output = sys.C*state + sys.D*input
end
```
hence, it just performs one iteration of
```math
x' = Ax + Bu
```
```math
y = Cx + Du
```
The `measure` method performs the computation `y = Cx`, the reason for the call - Bygger dokumentationen i repot man vill dokumentera
to `vecdot` is that `vecdot` produces a scalar output, whereas `C*x` produces a - skapar en ny mapp med ett väl valt namn (myfoldername i exemplet nedan)
1-element `Matrix`. A scalar output is preferred in this case since the `Beam` - flyttar den byggda dokumentationen till den mappen
is SISO. - Det blir lätt att förstå hur man gör punkterna ovan när man kollar i filen .gitlab-ci.yml, bara kör mönstermatchning mot de repona som detta redan är gjort för.
It should now be obvious which fields are required in the `BeamSimulator` type. När .gitlab-ci.yml uppdateras i master triggas en pipline. Om denna lyckas kommer dokumentationen finnas under
It must know which sample time it has been discretized with, as well as its
discrete-time system model. It must also remember the current state of the system.
This is not needed in a physical process since it kind of remembers its own state.
The full type specification for `BeamSimulator` is given below
```julia
struct BeamSimulator <: SimulatedProcess
h::Float64
state::Vector{Float64} # states defined by the file define_beam_system
sys::StateSpace
BeamSimulator() = new(0.01, init_sysfilter(beam_system), c2d(beam_system, 0.01)[1])
BeamSimulator(h::Real) = new(Float64(h), init_sysfilter(beam_system), c2d(beam_system, h)[1])
end
```
It contains three fields and two inner constructors. The constructors initializes
the system state by calling `init_sysfilter`. The variable `beam_system` is already
defined outside the type specification.
One of the constructors provides a default value for the sample time, in case
the user is unsure about a reasonable value.
## Non-linear process http://processes.gitlab.control.lth.se/documentation/myfoldername/
Your first option is to linearize the process and proceed like above.
Other options include
1. Make `control` perform forward Euler, i.e., `x' = f(x,u)*h` for a general
system model ``x' = f(x,u); y = g(x,u)`` and sample time ``h``.
2. Integrate the system model using some fancy method like Runge-Kutta. See
[DifferentialEquations.jl](http://docs.juliadiffeq.org/stable/types/discrete_types.html)
for discrete-time solving of ODEs (don't be discuraged, this is almost as simple as
forward Euler above).
julia 0.6 julia 0.7
ControlSystems ControlSystems
Parameters
DSP
using Documenter, LabProcesses
# makedocs()
# deploydocs(
# deps = Deps.pip("pygments", "mkdocs", "python-markdown-math", "mkdocs-cinder"),
# repo = "gitlab.control.lth.se/processes/LabProcesses.jl",
# branch = "gh-pages",
# julia = "0.6",
# osname = "linux"
# )
makedocs(
format = :html,
sitename = "LabProcesses",
pages = [
"index.md",
]
)
site_name: LabProcesses.jl
repo_url: https://gitlab.control.lth.se/labdev/software/tree/master/julia_ballandbeam/LabProcesses.jl
site_description: Documentation for LabProcesses.jl
site_author: Fredri Bagge Carlson
theme: cinder
extra_css:
- assets/Documenter.css
extra_javascript:
- https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML
- assets/mathjaxhelper.js
markdown_extensions:
- extra
- tables
- fenced_code
- mdx_math:
enable_dollar_delimiter: True
docs_dir: 'build'
pages:
- Home: index.md
File moved
# LabProcesses
```@contents
Depth = 3
```
This package contains an (programming- as well as connection-) interface to serve
as a base for the implementation of lab-process software. The first example of
an implementaiton of this interface is for the ball-and-beam process, which is
used in Lab1 FRTN35: frequency response analysis of the beam. The lab is implemented
in [BallAndBeam.jl](https://gitlab.control.lth.se/processes/BallAndBeam.jl), a
package that makes use of `LabProcesses.jl` to handle the communication with
the lab process and/or a simulated version thereof. This way, the code written
for frequency response analysis of the beam can be run on another process
implementing the same interface (or a simulated version) by changeing a single
line of code :)
## Installation
1. Start julia by typing `julia` in a terminal, make sure the printed info says it's
`v0.6+` running. If not, visit [julialang.org](https://julialang.org/downloads/)
to get the latest release.
2. Install LabProcesses.jl using command `Pkg.clone("https://gitlab.control.lth.se/processes/LabProcesses.jl.git")` Lots of packages will now be installed, this will take some time. If this is your first time using Julia, you might have to run `julia> Pkg.init()` before you install any packages.
# How to implement a new process
### 1.
Locate the file [interface.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface.jl). When the package is installed, you find its directory under `~/.julia/v0.6/LabProcesses/`, if not, run `julia> Pkg.dir("LabProcesses")` to locate the directory.
(Alternatively, you can copy all definitions from [/interface_implementations/ballandbeam.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface_implementations/ballandbeam.jl) instead. Maybe it's easier to work from an existing implementaiton.)
### 2.
Copy all function definitions.
### 3.
Create a new file under `/interface_implementations` where you paste all the
copied definitions and implement them. See [/interface_implementations/ballandbeam.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface_implementations/ballandbeam.jl) for an example.
### 4.
Above all function implementations you must define the process type, e.g,
```julia
struct BallAndBeam <: PhysicalProcess
h::Float64
bias::Float64
end
BallAndBeam() = BallAndBeam(0.01, 0.0) # Constructor with default value of sample time
```
Make sure you inherit from `PhysicalProcess` or `SimulatedProcess` as appropriate.
This type must contains fields that hold information about everything that is
relevant to a particular instance of the process. Different ballandbeam-process
have different biases, hence this must be stored. A simulated process would have
to keep track of its state etc. in order to implement the measure and control
methods. See [Types in julia documentation](https://docs.julialang.org/en/stable/manual/types/#Composite-Types-1)
for additional info regarding user defined types and (constructors)[https://docs.julialang.org/en/stable/manual/constructors/].
### 5.
Documentation of all interface functions is available in the file [interface_documentation.jl](https://gitlab.control.lth.se/processes/LabProcesses.jl/blob/master/src/interface_documentation.jl)
# How to control a process
The interface `AbstractProcess` defines the functions `control(P, u)` and `measure(P)`.
These functions can be used to implement your own control loops. A common loop
with a feedback controller and a feedforward filter on the reference is implemented
in the function [`run_control_2DOF`](@ref), where the user can supply $G_1$ and $G_4$
in the diagram below, with the process $P=G_2$.
![block diagram](feedback4.png)
The macro `@periodically` might come in handy if you want to implement your own loop.
Consider the following example, in which the loop body will be run periodically
with a sample time of `h` seconds.
```julia
for (i,t) = enumerate(0:h:duration)
@periodically h begin
y[i] = measure(P)
r[i] = reference(t)
u[i] = calc_control(y,r)
control(P, u[i])
end
end
```
Often one finds the need to implement a stateful controller, i.e., a function
that has a memory or state. To this end, the type [`SysFilter`](@ref) is
provided. This type is used to implement control loops where a signal is
filtered through a dynamical system, i.e., `U(z) = G1(z)E(z)`.
Usage is demonstrated below, which is a simplified implementation of the block
diagram above (transfer function- and signal names corresponds to the figure).
First two `SysFilter` objects are created, these objects can now be used as
functions of an input, and return the filtered output. The `SysFilter` type takes
care of updating and remembering the state of the system when called.
```julia
G1f = SysFilter(G1)
G4f = SysFilter(G4)
function calc_control(y,r)
rf = G4f(r)
e = rf-y
u = G1f(e)
end
```
`G1` and `G4` must here be represented by [`StateSpace`](http://juliacontrol.github.io/ControlSystems.jl/latest/lib/constructors/#ControlSystems.ss) types
from [`ControlSystems.jl`](https://github.com/JuliaControl/ControlSystems.jl), e.g., `G1 = ss(A,B,C,D)`.
`TransferFunction` types can easily be converted to a `StateSpace` by `Gss = ss(Gtf)`.
Continuous time systems can be discretized using `Gd = c2d(Gc, h)[1]`. (The sample time of a process is available through `h = sampletime(P)`.)
# How to implement a Simulated Process
## Linear process
This is very easy, just get a discrete time `StateSpace` model of your process
(if you have a transfer function, `Gss = ss(Gtf)` will do the trick, if you have continuous time, `Gd = c2d(Gc,h)[1]` is your friend).
You now have to implement the methods `control` and `measure` for your simulated type.
The implementation for `BeamSimulator` is shown below
```julia
control(p::BeamSimulator, u) = p.Gf(u)
measure(P) = vecdot(p.Gf.sys.C, p.Gf.state)
```
The `control` method accepts a control signal (`u`) and propagates the system state
(`p.Gf.state`) forward using the statespace model (`p.Gf.sys`) of the beam. The object
`Gf` (of type [`SysFilter`](@ref)) is familiar from the "Control" section above. What it does
is essentially (simplified)
```julia
function Gf(input)
sys = Gf.sys
Gf.state .= sys.A*Gf.state + sys.B*input
output = sys.C*Gf.state + sys.D*input
end
```
hence, it just performs one iteration of
```math
x' = Ax + Bu
```
```math
y = Cx + Du
```
The `measure` method performs the computation `y = Cx`, the reason for the call
to `vecdot` is that `vecdot` produces a scalar output, whereas `C*x` produces a
1-element `Matrix`. A scalar output is preferred in this case since the `Beam`
is SISO.
It should now be obvious which fields are required in the `BeamSimulator` type.
It must know which sample time it has been discretized with, as well as its
discrete-time system model. It must also remember the current state of the system.
This is not needed in a physical process since it kind of remembers its own state.
The system model and its state is conveniently covered by the type [`SysFilter`](@ref),
which handles filtering of a signal through an LTI system.
The full type specification for `BeamSimulator` is given below
```julia
struct BeamSimulator <: SimulatedProcess
h::Float64
Gf::SysFilter
BeamSimulator() = new(0.01, SysFilter(beam_system, 0.01))
BeamSimulator(h::Real) = new(Float64(h), SysFilter(beam_system, h))
end
```
It contains three fields and two inner constructors. The constructors initializes
the system filter by creating a [`SysFilter`](@ref).
The variable `beam_system` is already defined outside the type specification.
One of the constructors provides a default value for the sample time, in case
the user is unsure about a reasonable value.
## Non-linear process
Your first option is to linearize the process and proceed like above.
Other options include
1. Make `control` perform forward Euler, i.e., `x[t+1] = x[t] + f(x[t],u[t])*h` for a general system model ``x' = f(x,u); y = g(x,u)`` and sample time ``h``.
2. Integrate the system model using some fancy method like Runge-Kutta. See [DifferentialEquations.jl](http://docs.juliadiffeq.org/stable/types/discrete_types.html) for discrete-time solving of ODEs (don't be discouraged, this is almost as simple as forward Euler above).
# Exported functions and types
```@autodocs
Modules = [LabProcesses]
Private = false
Pages = ["LabProcesses.jl", "controllers.jl", "reference_generators.jl", "utilities.jl"]
```
# Process interface specification
All processes must implement the following interface. See the existing
implementations in the folder `interface_implementations` for guidance.
```@autodocs
Modules = [LabProcesses]
Private = false
Pages = ["interface_documentation.jl"]
```
# Index
```@index
```
# __precompile__() # __precompile__()
using Pkg
installed_packages = Pkg.installed()
if "LabConnections" keys(installed_packages)
Pkg.clone("https://gitlab.control.lth.se/cont-frb/LabConnections.jl")
# Pkg.checkout("LabConnection","v0.0.1")
end
module LabProcesses module LabProcesses
using ControlSystems using ControlSystems, LabConnections.Computer, Parameters, DSP, LinearAlgebra
include("utilities.jl")
include("interface.jl") include("interface.jl")
include("interface_documentation.jl") include("interface_documentation.jl")
include("interface_implementations/ballandbeam.jl") include("interface_implementations/ballandbeam.jl")
include("interface_implementations/eth_helicopter.jl")
include("utilities.jl")
include("reference_generators.jl") include("reference_generators.jl")
include("controllers.jl") include("controllers.jl")
......
...@@ -8,7 +8,7 @@ Perform control experiemnt on process where the feedback and feedforward control ...@@ -8,7 +8,7 @@ Perform control experiemnt on process where the feedback and feedforward control
`reference` is a reference generating function that accepts a scalar `t` (time in seconds) and outputs a scalar `r`, default is `reference(t) = sign(sin(2π*t))`. `reference` is a reference generating function that accepts a scalar `t` (time in seconds) and outputs a scalar `r`, default is `reference(t) = sign(sin(2π*t))`.
The outputs `y,u,r` are the beam angle, control signal and reference respectively. The outputs `y,u,r` are the beam angle, control signal and reference respectively.
![block diagram](docs/feedback4.png) ![block diagram](feedback4.png)
""" """
function run_control_2DOF(P::AbstractProcess,sysFB, sysFF=nothing; duration = 10, reference = t->sign(sin(2π*t))) function run_control_2DOF(P::AbstractProcess,sysFB, sysFF=nothing; duration = 10, reference = t->sign(sin(2π*t)))
nu = num_inputs(P) nu = num_inputs(P)
...@@ -18,25 +18,25 @@ function run_control_2DOF(P::AbstractProcess,sysFB, sysFF=nothing; duration = 10 ...@@ -18,25 +18,25 @@ function run_control_2DOF(P::AbstractProcess,sysFB, sysFF=nothing; duration = 10
u = zeros(nu, length(0:h:duration)) u = zeros(nu, length(0:h:duration))
r = zeros(ny, length(0:h:duration)) r = zeros(ny, length(0:h:duration))
stateFB = init_sysfilter(sysFB) Gfb = SysFilter(sysFB)
if sysFF != nothing if sysFF != nothing
stateFF = init_sysfilter(sysFF) Gff = SysFilter(sysFF)
end end
function calc_control(i) function calc_control(i)
rf = sysFF == nothing ? r[:,i] : sysfilter!(stateFF, sysFF, r[:,i]) rf = sysFF == nothing ? r[:,i] : Gff(r[:,i])
e = rf-y[:,i] e = rf-y[:,i]
ui = sysfilter!(stateFB, sysFB, e) ui = Gfb(e)
ui + bias(P) ui .+ bias(P)
end end
simulation = isa(P, SimulatedProcess) simulation = isa(P, SimulatedProcess)
initialize(P) initialize(P)
for (i,t) = enumerate(0:h:duration) for (i,t) = enumerate(0:h:duration)
@periodically h simulation begin @periodically h simulation begin
y[:,i] = measure(P) y[:,i] .= measure(P)
r[:,i] = reference(t) r[:,i] .= reference(t)
u[:,i] = calc_control(i) # y,r must be updated before u u[:,i] .= calc_control(i) # y,r must be updated before u
control(P, [clamp.(u[j,i], inputrange(P)[j]...) for j=1:nu]) control(P, [clamp.(u[j,i], inputrange(P)[j]...) for j=1:nu])
end end
end end
......
...@@ -11,41 +11,75 @@ ...@@ -11,41 +11,75 @@
export Beam, BeamSimulator, AbstractBeam, BallAndBeam, BallAndBeamSimulator, AbstractBeamOrBallAndBeam export Beam, BeamSimulator, AbstractBeam, BallAndBeam, BallAndBeamSimulator, AbstractBeamOrBallAndBeam
# @with_kw allows specification of default values for fields. If none is given, this value must be supplied by the user. replaces many constructors that would otherwise only supply default values.
# Call constructor like Beam(bias=1.0) if you want a non-default value for bias
"""
Beam(;kwargs...)
Physical beam process
#Arguments (fields)
- `h::Float64 = 0.01`
- `bias::Float64 = 0.0`
- `stream::LabStream = ComediStream()`
- `measure::AnalogInput10V = AnalogInput10V(0)`
- `control::AnalogOutput10V = AnalogOutput10V(1)`
"""
struct Beam <: PhysicalProcess struct Beam <: PhysicalProcess
h::Float64 h::Float64
bias::Float64 bias::Float64
stream::LabStream
measure::AnalogInput10V
control::AnalogOutput10V
end
function Beam(;
h::Float64 = 0.01,
bias::Float64 = 0.,
stream::LabStream = ComediStream(),
measure::AnalogInput10V = AnalogInput10V(0),
control::AnalogOutput10V = AnalogOutput10V(1))
p = Beam(Float64(h),Float64(bias),stream,measure,control)
init_devices!(p.stream, p.measure, p.control)
p
end end
Beam() = Beam(0.01, 0.0)
include("define_beam_system.jl") include("define_beam_system.jl")
const beam_system, nice_beam_controller = define_beam_system() const beam_system, nice_beam_controller = define_beam_system()
# nice_beam_controller gives ϕₘ=56°, Aₘ=4, Mₛ = 1.6. Don't forget to discretize it before use # nice_beam_controller gives ϕₘ=56°, Aₘ=4, Mₛ = 1.6. Don't forget to discretize it before use
struct BeamSimulator <: SimulatedProcess struct BeamSimulator <: SimulatedProcess
h::Float64 h::Float64
state::Vector{Float64} # states defined by the file define_beam_system s::SysFilter
sys::StateSpace BeamSimulator(;h::Real = 0.01, bias=0) = new(Float64(h), SysFilter(beam_system, h))
BeamSimulator() = new(0.01, init_sysfilter(beam_system), c2d(beam_system, 0.01)[1])
BeamSimulator(h::Real) = new(Float64(h), init_sysfilter(beam_system), c2d(beam_system, h)[1])
end end
struct BallAndBeam <: PhysicalProcess struct BallAndBeam <: PhysicalProcess
h::Float64 h::Float64
bias::Float64 bias::Float64
stream::LabStream
measure1::AnalogInput10V
measure2::AnalogInput10V
control::AnalogOutput10V
end
function BallAndBeam(;
h = 0.01,
bias = 0.,
stream = ComediStream(),
measure1::AnalogInput10V = AnalogInput10V(0),
measure2::AnalogInput10V = AnalogInput10V(1),
control::AnalogOutput10V = AnalogOutput10V(1))
p = BallAndBeam(h,bias,stream,measure1,measure2,control)
init_devices!(p.stream, p.measure1, p.measure2, p.control)
p
end end
BallAndBeam() = BallAndBeam(0.01, 0.0)
struct BallAndBeamSimulator <: SimulatedProcess struct BallAndBeamSimulator <: SimulatedProcess
h::Float64 h::Float64
state::Vector{Float64} # pos,vel,angle,ω s::SysFilter
end end
BallAndBeamSimulator() = BallAndBeamSimulator(0.01, zeros(4))
const AbstractBeam = Union{Beam, BeamSimulator} const AbstractBeam = Union{Beam, BeamSimulator}
const AbstractBallAndBeam = Union{BallAndBeam, BallAndBeamSimulator} const AbstractBallAndBeam = Union{BallAndBeam, BallAndBeamSimulator}
const AbstractBeamOrBallAndBeam = Union{AbstractBeam, AbstractBallAndBeam} const AbstractBeamOrBallAndBeam = Union{AbstractBeam, AbstractBallAndBeam}
num_outputs(p::AbstractBeam) = 1 num_outputs(p::AbstractBeam) = 1
num_outputs(p::AbstractBallAndBeam) = 2 num_outputs(p::AbstractBallAndBeam) = 2
num_inputs(p::AbstractBeamOrBallAndBeam) = 1 num_inputs(p::AbstractBeamOrBallAndBeam) = 1
...@@ -60,32 +94,24 @@ bias(p::AbstractBeamOrBallAndBeam) = p.bias ...@@ -60,32 +94,24 @@ bias(p::AbstractBeamOrBallAndBeam) = p.bias
bias(p::BeamSimulator) = 0 bias(p::BeamSimulator) = 0
bias(p::BallAndBeamSimulator) = 0 bias(p::BallAndBeamSimulator) = 0
function control(p::AbstractBeamOrBallAndBeam, u::AbstractArray)
control(p::AbstractBeamOrBallAndBeam, u) = ccall((:comedi_write, comedipath),Int32, length(u) == 1 || error("Process $(typeof(p)) only accepts one control signal, tried to send u=$u.")
(Int32,Int32,Int32,Int32),0,1,1,num2io(u[1]+p.bias)) control(p,u[1])
function control(p::BeamSimulator, u)
sysfilter!(p.state, p.sys, u)
end end
control(p::BallAndBeamSimulator, u) = error("Not yet implemented") control(p::AbstractBeamOrBallAndBeam, u::Number) = send(p.control,u)
control(p::BeamSimulator, u::Number) = p.s(u)
control(p::BallAndBeamSimulator, u::Number) = error("Not yet implemented")
measure(p::Beam) = io2num(ccall((:comedi_read,comedipath), Int32, measure(p::Beam) = read(p.measure)
(Int32,Int32,Int32), 0,0,0)) measure(p::BallAndBeam) = [read(p.measure1), read(p.measure2)]
measure(p::BallAndBeam) = [io2num(ccall((:comedi_read,comedipath), Int32, measure(p::BeamSimulator) = dot(p.s.sys.C,p.s.state)
(Int32,Int32,Int32), 0,0,i)) for i = 0:1]
measure(p::BeamSimulator) = vecdot(p.sys.C,p.state)
measure(p::BallAndBeamSimulator) = error("Not yet implemented") measure(p::BallAndBeamSimulator) = error("Not yet implemented")
const comedipath = Pkg.dir("LabProcesses","c","comedi_bridge.so") initialize(p::Beam) = nothing
const conversion = 65535/20 initialize(p::BallAndBeam) = nothing
io2num(x) = x/conversion -10 # Converts from io to float finalize(p::AbstractBeamOrBallAndBeam) = foreach(close, p.stream.devices)
num2io(x) = round(Int32,x*100 + 2048) # Converts from regular number to io
initialize(p::AbstractBeamOrBallAndBeam) = ccall((:comedi_start, comedipath),Int32,(Int32,), 0)
finalize(p::AbstractBeamOrBallAndBeam) = (control(p,0);ccall((:comedi_stop, comedipath),Int32,(Int32,), 0))
initialize(p::BallAndBeamSimulator) = nothing initialize(p::BallAndBeamSimulator) = nothing
finalize(p::BallAndBeamSimulator) = nothing finalize(p::BallAndBeamSimulator) = nothing
initialize(p::BeamSimulator) = p.state .*= 0 initialize(p::BeamSimulator) = p.s.state .*= 0
finalize(p::BeamSimulator) = nothing finalize(p::BeamSimulator) = nothing
using ControlSystems using ControlSystems, DSP
""" """
beammodel, beamcontroller = define_beam_system(;doplot=false) beammodel, beamcontroller = define_beam_system(;doplot=false)
......
...@@ -7,11 +7,42 @@ ...@@ -7,11 +7,42 @@
export ETHHelicopter, ETHHelicopterSimulator, AbstractETHHelicopter export ETHHelicopter, ETHHelicopterSimulator, AbstractETHHelicopter
struct ETHHelicopter <: PhysicalProcess # @with_kw allows specification of default values for fields. If none is given, this value must be supplied by the user. replaces many constructors that would otherwise only supply default values.
# Call constructor like ETHHelicopter(bias=1.0) if you want a non-default value for bias
"""
ETHHelicopter(;kwargs...)
Physical ETH helicopter process
# Arguments (fields)
- `h::Float64 = 0.05`
- `bias::Float64 = 0.0`
- `stream::LabStream = ComediStream()`
- `measure1::AnalogInput10V = AnalogInput10V(0)`
- `measure2::AnalogInput10V = AnalogInput10V(1)`
- `control1::AnalogOutput10V = AnalogOutput10V(0)`
- `control2::AnalogOutput10V = AnalogOutput10V(1)`
"""
@with_kw struct ETHHelicopter <: PhysicalProcess
h::Float64 h::Float64
bias::Float64 bias::Float64
stream::LabStream
measure1::AnalogInput10V
measure2::AnalogInput10V
control1::AnalogOutput10V
control2::AnalogOutput10V
end end
ETHHelicopter() = ETHHelicopter(0.050, 0.) function ETHHelicopter(;
h = 0.05,
bias = 0.,
stream = ComediStream(),
measure1::AnalogInput10V = AnalogInput10V(0),
measure2::AnalogInput10V = AnalogInput10V(1),
control1::AnalogOutput10V = AnalogOutput10V(0),
control2::AnalogOutput10V = AnalogOutput10V(1))
p = ETHHelicopter(h,bias,stream,measure1,measure2,control1,control2)
init_devices!(p.stream, p.measure1, p.measure2, p.control1, p.control2)
p
end
struct ETHHelicopterSimulator <: SimulatedProcess struct ETHHelicopterSimulator <: SimulatedProcess
h::Float64 h::Float64
...@@ -32,22 +63,16 @@ bias(p::AbstractETHHelicopter) = p.bias ...@@ -32,22 +63,16 @@ bias(p::AbstractETHHelicopter) = p.bias
function control(p::ETHHelicopter, u) function control(p::ETHHelicopter, u)
ccall((:comedi_write, comedipath),Int32,(Int32,Int32,Int32,Int32), 0,1,0,num2io(u[1])) send(p.control1,u[1])
ccall((:comedi_write, comedipath),Int32,(Int32,Int32,Int32,Int32), 0,1,1,num2io(u[2])) send(p.control2,u[2])
end end
measure(p::ETHHelicopter) = [io2num(ccall((:comedi_read, comedipath),Int32,(Int32,Int32,Int32), 0,0,i)) for i = 0:1] #i=0 for pitch, i=1 for yaw measure(p::ETHHelicopter) = [read(p.measure1), read(p.measure2)]
control(p::ETHHelicopterSimulator, u) = error("Not yet implemented") control(p::ETHHelicopterSimulator, u) = error("Not yet implemented")
measure(p::ETHHelicopterSimulator) = error("Not yet implemented") measure(p::ETHHelicopterSimulator) = error("Not yet implemented")
const comedipath = Pkg.dir("LabProcesses","c","comedi_bridge.so")
const conversion = 65535/20
io2num(x) = x/conversion -10 # Converts from io to float
num2io(x) = round(Int32,(x + 10)*conversion) # Converts from regular number to io
initialize(p::ETHHelicopter) = ccall((:comedi_start, comedipath),Int32,(Int32,), 0) initialize(p::ETHHelicopter) = nothing
finalize(p::ETHHelicopter) = (control(p,0);ccall((:comedi_stop, comedipath),Int32,(Int32,), 0)) finalize(p::ETHHelicopter) = foreach(close, p.stream.devices)
initialize(p::ETHHelicopterSimulator) = nothing initialize(p::ETHHelicopterSimulator) = nothing
finalize(p::ETHHelicopterSimulator) = nothing finalize(p::ETHHelicopterSimulator) = nothing
...@@ -2,7 +2,7 @@ export PRBSGenerator ...@@ -2,7 +2,7 @@ export PRBSGenerator
#PRBS #PRBS
""" """
r = PRBSGenerator(state = 1) r = PRBSGenerator()
Generates a pseudo-random binary sequence. Call like `random_input = r()`. Generates a pseudo-random binary sequence. Call like `random_input = r()`.
""" """
mutable struct PRBSGenerator mutable struct PRBSGenerator
......
export @periodically, init_sysfilter, sysfilter! export @periodically, init_sysfilter, sysfilter!, SysFilter
""" """
@periodically(h, body) @periodically(h, body)
...@@ -9,7 +9,7 @@ macro periodically(h, body) ...@@ -9,7 +9,7 @@ macro periodically(h, body)
local start_time = time() local start_time = time()
$(esc(body)) $(esc(body))
local execution_time = time()-start_time local execution_time = time()-start_time
sleep(max(0,$(esc(h))-execution_time)) Libc.systemsleep(max(0,$(esc(h))-execution_time))
end end
end end
...@@ -23,10 +23,40 @@ macro periodically(h, simulation, body) ...@@ -23,10 +23,40 @@ macro periodically(h, simulation, body)
local start_time = time() local start_time = time()
$(esc(body)) $(esc(body))
local execution_time = time()-start_time local execution_time = time()-start_time
$(esc(simulation)) || sleep(max(0,$(esc(h))-execution_time)) $(esc(simulation)) || Libc.systemsleep(max(0,$(esc(h))-execution_time))
end end
end end
"""
Csf = SysFilter(sys_discrete::StateSpace)
Csf = SysFilter(sys_continuous::StateSpace, sampletime)
Csf = SysFilter(sys::StateSpace, state::AbstractVector)
Returns an object used for filtering signals through LTI systems.
Create a SysFilter object that can be used to implement control loops and simulators
with LTI systems, i.e., `U(z) = C(z)E(z)`. To filter a signal `u` through the filter,
call like `y = Csf(u)`. Calculates the filtered output `y` in `y = Cx+Du, x'=Ax+Bu`
"""
struct SysFilter{T<:StateSpace}
sys::T
state::Vector{Float64}
function SysFilter(sys::StateSpace, state::AbstractVector)
@assert !ControlSystems.iscontinuous(sys) "Can not filter using continuous time model."
@assert length(state) == sys.nx "length(state) != sys.nx"
new{typeof(sys)}(sys, state)
end
function SysFilter(sys::StateSpace)
@assert !ControlSystems.iscontinuous(sys) "Can not filter using continuous time model. Supply sample time."
new{typeof(sys)}(sys, init_sysfilter(sys))
end
function SysFilter(sys::StateSpace, h::Real)
@assert ControlSystems.iscontinuous(sys) "Sample time supplied byt system model is already in discrete time."
sysd = c2d(sys, h)[1]
new{typeof(sysd)}(sysd, init_sysfilter(sysd))
end
end
(s::SysFilter)(input) = sysfilter!(s.state, s.sys, input)
""" """
state = init_sysfilter(sys::StateSpace) state = init_sysfilter(sys::StateSpace)
Use together with [`sysfilter!`](@ref) Use together with [`sysfilter!`](@ref)
...@@ -36,6 +66,7 @@ function init_sysfilter(sys::StateSpace) ...@@ -36,6 +66,7 @@ function init_sysfilter(sys::StateSpace)
end end
""" """
output = sysfilter!(s::SysFilter, input)
output = sysfilter!(state, sys::StateSpace, input) output = sysfilter!(state, sys::StateSpace, input)
Returns the filtered output `y` in `y = Cx+Du, x'=Ax+Bu` Returns the filtered output `y` in `y = Cx+Du, x'=Ax+Bu`
...@@ -46,3 +77,5 @@ function sysfilter!(state::AbstractVector, sys::StateSpace, input) ...@@ -46,3 +77,5 @@ function sysfilter!(state::AbstractVector, sys::StateSpace, input)
state .= vec(sys.A*state + sys.B*input) state .= vec(sys.A*state + sys.B*input)
output = vec(sys.C*state + sys.D*input) output = vec(sys.C*state + sys.D*input)
end end
sysfilter!(s::SysFilter, input) = sysfilter!(s.state, s.sys, input)
using LabProcesses using LabProcesses, ControlSystems, DSP
using Base.Test using Test
# Reference generators # Reference generators
r = PRBSGenerator(Int(1)) r = PRBSGenerator(Int(4))
seq = [r() for i = 1:10] seq = [r() for i = 1:10]
@test all(seq .== [0, 0, 1, 0, 0, 1, 1, 0, 0, 1]) @test all(seq .== [1,0,1,0,0,0,0,0,0,0])
foreach(r,1:10_000) foreach(r,1:10_000)
function test_sysfilter()
N = 10
u = randn(N)
b = [1, 1]
a = [1, 0.1, 1]
sys = ss(tf(b,a,1))
state = init_sysfilter(sys)
yf = filt(b,a,u)
yff = similar(yf)
for i in eachindex(u)
yff[i] = sysfilter!(state, sys, u[i])[1]
end
@test sum(abs,yf - yff) < (eps())
sysfilt = SysFilter(sys)
for i in eachindex(u)
yff[i] = sysfilter!(sysfilt, u[i])[1]
end
@test sum(abs,yf - yff) < (eps())
sysfilt = SysFilter(sys)
for i in eachindex(u)
yff[i] = sysfilt(u[i])[1]
end
@test sum(abs,yf - yff) < (eps())
end
test_sysfilter()