diff --git a/README.md b/README.md index dd93048788ce2d675a333bfc276530fb2806e234..b14e865906b50a1d8cdb872e5b30f04e413b78e0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ -# cotc-rsmpc -Examples of rate switcing MPC +# Control-over-the-Cloud - Rate-Switching MPC +Evaluation of a rate switching, state and input constrained, MPC, derived from a continuous time optimal design. This code has been tested with [Matlab Simulink v 9.2 (R2018b)](https://www.mathworks.com/products/matlab.html) and [TrueTime 2.0](http://www.control.lth.se/research/tools-and-software/truetime). + +### Primary files +- init.m Configures the example. +- rsmpc.slx Simulink model. +- tt\_rsmpc\_init.m RS-MPC init file, TrueTime. +- tt\_rsmpc\_main.m RS-MPC implementation, TrueTime. +- simrsmpc.m Runs simulation. See file header for options. +- freqinfo.m Finds freq. range from singular values. diff --git a/execinfo.slx b/execinfo.slx new file mode 100644 index 0000000000000000000000000000000000000000..c2f35bb6b78ecdeb9f0955f1661a662480588159 Binary files /dev/null and b/execinfo.slx differ diff --git a/freqinfo.m b/freqinfo.m new file mode 100644 index 0000000000000000000000000000000000000000..a4af5da84dfefdc4203caf600d80ccb9c555b996 --- /dev/null +++ b/freqinfo.m @@ -0,0 +1,15 @@ +figure; +w = linspace(0.1, 1000, 10000); +[sv,w] = sigma(LQc,w); +svn = sv/max(sv); +larger = find(svn>0.71); % -3dB +smaller = find(svn<=0.71); +bf = [w(larger(end)) w(smaller(1))]; +bsv = [svn(larger(end)) svn(smaller(1))]; +step=(bsv(1)-0.71)/(bsv(1)-bsv(2)); +bw = bf(1)+(bf(2)-bf(1))*step +sample_h=[0.15 0.6]/bw +sample_f=1./sample_h +%csvwrite('sigma.csv', [w svn']); +loglog(w, svn); +grid() diff --git a/getdlq.m b/getdlq.m new file mode 100644 index 0000000000000000000000000000000000000000..78991539c10ac8b7f33e25ae05c3a0db70e9110f --- /dev/null +++ b/getdlq.m @@ -0,0 +1,9 @@ +function [SysD,Q,R,N,K,S] = getdlq(SysC, Qc, Rc, h) +% Get a discrete time LQ, sampled system and cost matrices, from a continous definition + [SysD,Q,R,N,R1,Qconst] = lqgsamp(SysC,h,Qc,Rc,zeros(size(SysC.B)),zeros(size(SysC.A))); + SysD = modred(SysD, 4); + Q = Q(1:size(SysD.A,1),1:size(SysD.A,1)); + N = N(1:size(SysD.A,1), 1:size(SysD.B,2)); + [K,S] = dlqr(SysD.A, SysD.B, Q, R, N); +end + diff --git a/init.m b/init.m new file mode 100644 index 0000000000000000000000000000000000000000..349ec5b5b71c542b0aea9b9050140ecd2f63e80b --- /dev/null +++ b/init.m @@ -0,0 +1,28 @@ +zetaVariant = 0; + +f_hf = 30; +f_lf = f_hf/2; +h_hf = 1/f_hf; +h_lf = 1/f_lf; +N_lf = 10; +N_hf = 2*N_lf; +mpc_mode = 1; % 1 = A, 2 = B, 3 = C + +rng(1); + +SysC = sysmodel(); +Qc = diag([1500 20 0]); +Rc = 1; + +[Kc, Sc, Ec] = lqr(SysC, Qc, Rc); +LQc = ss(SysC.A-SysC.B*Kc, SysC.B, SysC.C, 0); +[SysD, Qd, Rd, Nd, Kd, Sd] = getdlq(SysC, Qc, Rc, h_hf); + +t = 10; +xinit = [0 0 0]; +simtime = h_hf * [0:1/h_hf * t] - 1/60; +simin_ref = [simtime; ones(1, length(simtime))*8]'; +simin_drop = [simtime; zeros(1, length(simtime))]'; +simin_au = [simtime; zeros(3,length(simtime))]'; + +experiment = {}; diff --git a/init_mpc.m b/init_mpc.m new file mode 100644 index 0000000000000000000000000000000000000000..ac3d8deb50e834c8bbdb1ee568df3129367c7365 --- /dev/null +++ b/init_mpc.m @@ -0,0 +1,22 @@ +% Prepares an MPC configuration to use with quadprog +function [mpc] = init_mpc(h, SysC, Qc, Rc) + [SysD, Qd, Rd, Nd, Kd, Sd] = getdlq(SysC, Qc, Rc, h); + model = ss(SysD, h); + mpc.h = h; + mpc.A = model.A; + mpc.B = model.B; + mpc.C = model.C; + mpc.D = []; + mpc.Cx = repmat([0.55; 10; pi/4], 2, 1); + mpc.CX = [diag([1 1 1]); diag([-1 -1 -1])]; + mpc.Cu = [10; 10]; + mpc.CU = [1; -1]; + mpc.Q = Qd; + mpc.R = Rd; + mpc.N = Nd; + mpc.Xf = []; + mpc.Qf = Sd; + mpc.K = Kd; + mpc.S = Sd; + + diff --git a/lqgsamp.m b/lqgsamp.m new file mode 100644 index 0000000000000000000000000000000000000000..f6d3d5d6e3a8fe25bf5afb1aea1ca1c0919abdf3 --- /dev/null +++ b/lqgsamp.m @@ -0,0 +1,57 @@ +function [sysd,Q1e,Q2e,Q12e,R1,Qconst] = lqgsamp(sysc,h,Q1c,Q2c,Q12c,R1,tau) +% +% [sysd,Q1,Q2,Q12,R1,Qconst] = lqgsamp(sysc,h,Q1c,Q2c,Q12c,R1c) +% [sysd,Q1,Q2,Q12,R1,Qconst] = lqgsamp(sysc,h,Q1c,Q2c,Q12c,R1c,tau) +% +% Sample a continous time LQ design + +if nargin < 7 + tau = 0; +end + +[A,B,C] = ssdata(sysc); + +totsize = size(A,2)+size(B,2); +a1 = size(A,1); % number of states +b2 = size(B,2); % number of inputs + +inttau = max(0,ceil(tau/h-1)); % whole samples extra delay +fractau = tau - inttau*h; % fractional delay + +% Sample the plant, the cost, and the noise +Q = [Q1c Q12c; Q12c' Q2c]; +[Phi2,r,Q2] = calcc2d([A B;zeros(b2,a1+b2)],blkdiag(R1,zeros(b2)),Q,fractau); +Gamma2 = Phi2(1:a1,a1+1:a1+b2); +[X,r,Q1] = calcc2d([A B;zeros(b2,a1+b2)],blkdiag(R1,zeros(b2)),Q,h-fractau); +Gamma2 = X(1:a1,1:a1)*Gamma2; +Gamma1 = X(1:a1,a1+1:a1+b2); +[Phi,R1,Q,Qconst] = calcc2d([A B;zeros(b2,a1+b2)],blkdiag(R1,zeros(b2)),Q,h); +R1 = R1(1:a1,1:a1); +Phi = Phi(1:a1,1:a1); + +% Build extended state-space model +Phie = [Phi Gamma2; zeros(b2,a1+b2)]; +Gammae = [Gamma1; eye(b2)]; +Ce = [C zeros(size(C,1),b2)]; +Ge = [eye(a1);zeros(b2,a1)]; +Q1e = Q2+Phi2(1:a1,:)'*Q1(1:a1,1:a1)*Phi2(1:a1,:); +Q2e = Q1(a1+1:end,a1+1:end); +Q12e = [Phi2(1:a1,:)'*Q1(1:a1,a1+1:end)]; + +% Add additional integer delays (if tau > h) +if inttau > 0 + Phie = blkdiag([Phie Gammae],eye((inttau-1)*b2)); + Gammae = zeros(size(Phie,1),b2); + Phie = [Phie; zeros(b2,size(Phie,2))]; + Gammae = [Gammae; eye(b2)]; + Ce = [Ce zeros(size(Ce,1),inttau*b2)]; + Ge = [Ge; zeros(inttau*b2,size(Ge,2))]; + Q1e = [Q1e Q12e; Q12e' Q2e]; + Q1e = blkdiag(Q1e,zeros((inttau-1)*b2)); + Q2e = zeros(size(Q2e)); + Q12e = zeros(size(Q1e,1),size(Q2e,2)); +end + +R1 = blkdiag(R1,zeros(size(Phie)-size(A))); + +sysd = ss(Phie,Gammae,Ce,0,h); diff --git a/metrics.slx b/metrics.slx new file mode 100644 index 0000000000000000000000000000000000000000..5b5905c6b4da86209a0ce23d8c6bfd808f049291 Binary files /dev/null and b/metrics.slx differ diff --git a/rsmpc.slx b/rsmpc.slx new file mode 100644 index 0000000000000000000000000000000000000000..ed4b33f5dd3535e9bbce7c45e2ebcc6ef23201c8 Binary files /dev/null and b/rsmpc.slx differ diff --git a/runquadprog.m b/runquadprog.m new file mode 100644 index 0000000000000000000000000000000000000000..d5dc8e09055680ceb79ead2350a49c209b3a44d9 --- /dev/null +++ b/runquadprog.m @@ -0,0 +1,53 @@ +% Run quadprog optimizer +% mpc is the mpc configuration with the following fields: +% A,B System model matrices +% Q State cost +% Qf Terminal cost +% R Control signal cost +% N Cross-couping cost (may be [], which is typical in discrete design) +% CX,Cx State constraints +% CU,Uc Input constraints +% Xf Terminal constraints +% x is the current state estimate +% xref is the target state +% N is the prediction horizon +function [optx, fval, exitflag, output] = runquadprog(mpc, x, xref, Np, Nc) + ns = size(mpc.A, 1); % Number of states + ni = size(mpc.B, 2); % Number of inputs + + dx = x-xref; + + % Setup costs. f is linear costs which is not used. + H = blkdiag(kron(eye(Np-1), mpc.Q), mpc.Qf, kron(eye(Np), mpc.R)); + f = zeros(length(H),1); + + if isfield(mpc, 'N') == 1 && isempty(mpc.N) == 0 + H(1:size(mpc.Q,1)*Np, size(mpc.Q,1)*Np+1:end) = kron(eye(Np), mpc.N); + H(size(mpc.Q,1)*Np+1:end, 1:size(mpc.Q,1)*Np) = kron(eye(Np), mpc.N'); + end + + % Setup create constraint inequality + A1 = kron(eye(Np), mpc.CX); + A4 = kron(eye(Np), mpc.CU); + A2 = zeros(size(A1,1), size(A4, 2)); + A3 = zeros(size(A4,1), size(A1, 2)); + A = [A1 A2; A3 A4]; + + if isfield(mpc, 'Xf') == 0 || isempty(mpc.Xf) + mpc.Xf = mpc.Cx - mpc.CX*xref; + end + b = [repmat(mpc.Cx - mpc.CX*xref, Np-1,1); mpc.Xf; repmat(mpc.Cu, Np, 1)]; + % Setup equality constraints (the model) + Aeq1 = [zeros(ns,ns*Np); kron(eye(Np-1), -mpc.A) zeros(ns*(Np-1), ns)] + kron(eye(Np), eye(ns)); + Aeq2def = [eye(Nc, Nc) zeros(Nc, Np-Nc); zeros(Np-Nc, Nc-1) ones(Np-Nc, 1) zeros(Np-Nc, Np-Nc)]; + Aeq2 = kron(Aeq2def, -mpc.B); + Aeq = [Aeq1, Aeq2]; + beq_ = mpc.A * dx; + beq = [beq_; zeros((Np-1)*ns, 1)]; + + % Set optimizer options + options = optimoptions('quadprog', 'Display', 'off', 'MaxIterations', 200, 'OptimalityTolerance', 1e-8); + + x0 = dx; + [optx,fval,exitflag,output] = quadprog(H, f, A, b, Aeq, beq, [], [], x0, options); + diff --git a/simrsmpc.m b/simrsmpc.m new file mode 100644 index 0000000000000000000000000000000000000000..b0df266a2dd1f897619d76f8d72fbfa8e7f7d9c1 --- /dev/null +++ b/simrsmpc.m @@ -0,0 +1,124 @@ +function [experiment] = simcdc(name, mpcmode, zetaVariant) +% Runs named experiments. First run init.m. +% +% The named experiments are: +% 'singlestep' Performs a single step 0->0.50 and runs 5 seconds. Drops are random. +% 'repstep' Repeated step changes. +% 'largerepstep' Performs large steps close to the constraint. Note: Keeps u, does not use OL data. +% 'largerepstepol' Same as above but uses open loop when switching. +% 'disturb' A noisy disturbance is applied through the input u. +% +% mpc_mode is one of: +% 0: Local (LF) control only +% 1: Mode A +% 2: Mode B +% 3: Mode C +% 4: Disregard drop and run HF controller only (local HF control) +% +% zetaVariant is one of: +% 0: LQ +% 1: u(0) +% 2: Open loop, u(k-i) +% 3: u(1) +% +% After finished experiment use savesim(experiment) to store the result on disk. +% The result is stored in sim/{name}/{mode}/. + +h_hf = evalin('base', 'h_hf'); +refscale = 10/0.55; +rng(1); + +assignin('base', 'zetaVariant', zetaVariant); + +if strcmp(name, 'singlestep') + sp=0.35; + xinit = [-0.401 0 0]; % Small difference from reference allows Matlab to solve algebraic loop for LQc + T = h_hf * [0:1/h_hf * 2] - 1/60; + % Delay the reference so LQ doesn't kick in for all modes at the first step! + simin_ref = [T; ones(1, 5)*-0.4*refscale ones(1, length(T)-5) * sp*refscale]'; + simin_drop = [T; randi(2, 1, length(T))-1]'; + simin_au = [T; zeros(3,length(T))]'; + stopTime=T(end); +elseif strcmp(name, 'repstep') + xinit = [-0.401 0 0]; % Small difference from reference allows Matlab to solve algebraic loop for LQc + T = h_hf * [0:1/h_hf * 2] - 1/60; + % Delay the reference so LQ doesn't kick in for all modes at the first step! + simin_ref = [ones(1, 5)*-0.4*refscale ones(1, length(T)-5) * 0.4*refscale]; + simin_ref = kron(repmat([1 -1], 1, 90), simin_ref); + simin_drop = randi(2, 1, length(simin_ref))-1; + simin_au = zeros(3,length(simin_ref)); + T = h_hf * [0:length(simin_ref)-1]; + stopTime=T(end); + simin_ref = [T; simin_ref]'; + simin_drop = [T; simin_drop]'; + simin_au = [T; simin_au]'; +elseif strcmp(name, 'largerepstep') || strcmp(name, 'largerepstepol') + xinit = [-0.501 0 0]; % Small difference from reference allows Matlab to solve algebraic loop for LQc + T = h_hf * [0:1/h_hf * 2] - 1/60; + % Delay the reference so LQ doesn't kick in for all modes at the first step! + simin_ref = [ones(1, 5)*-0.5*refscale ones(1, length(T)-5) * 0.5*refscale]; + simin_ref = kron(repmat([1 -1], 1, 90), simin_ref); + simin_drop = randi(2, 1, length(simin_ref))-1; + simin_au = zeros(3,length(simin_ref)); + T = h_hf * [0:length(simin_ref)-1]; + stopTime=T(end); + simin_ref = [T; simin_ref]'; + simin_drop = [T; simin_drop]'; + simin_au = [T; simin_au]'; + if strcmp(name, 'largerepstepol') + assignin('base', 'zetaVariant', 2); + end +% else +% assignin('base', 'zetaVariant', 1); +% end +elseif strcmp(name, 'disturb') +% assignin('base', 'zetaVariant', 2); + t = 360; + xinit = [0.541 0 0]; % Small difference from reference allows Matlab to solve algebraic loop for LQc + T = h_hf * [0:1/h_hf * t] - 1/60; + % Delay the reference so LQ doesn't kick in for all modes at the first step! + simin_ref = [ones(1, length(T))*0.54*refscale]; + simin_drop = randi(2, 1, length(simin_ref))-1; + simin_au = 0 + 1.*randn(1, length(T)); + T = h_hf * [0:length(simin_ref)-1]; + stopTime=T(end); + simin_ref = [T; simin_ref]'; + simin_drop = [T; simin_drop]'; + simin_au = [T; simin_au; zeros(2, length(simin_au))]'; + +elseif strcmp(name, 'basic') + xinit = [-0.401 0 0]; % Small difference from reference allows Matlab to solve algebraic loop for LQc + stopTime = 5; + T = h_hf * [0:1/h_hf * stopTime] - 1/60; + ref=(10/0.55)*0.5; + simin_ref = []; + steplen=3; + for sec = 1:steplen:stopTime + l=min(steplen, stopTime-sec); + simin_ref = [simin_ref ref*ones(1, 1/h_hf*l)]; + ref = -ref; + end + simin_ref = [simin_ref ones(1, length(T)-length(simin_ref))*simin_ref(end)]; + simin_ref = [T; simin_ref]'; + simin_drop = [T; randi(2, 1, length(T))-1]'; + simin_au = [T; zeros(1,length(T))]'; +else + ERROR +end + +SimStopTime=sprintf('%d', stopTime); +assignin('base', 'xinit', xinit); +assignin('base', 'simin_drop', simin_drop); +assignin('base', 'simin_au', simin_au); +assignin('base', 'simin_ref', simin_ref); +assignin('base', 'mpc_mode', mpcmode); +simout = sim('rsmpc', 'ReturnWorkspaceOutputs', 'on', 'StopTime', SimStopTime); + +experiment = {}; +experiment.sim = simout; +modes = {'LF', 'A', 'B', 'C', 'HF'}; +experiment.mpc_mode = modes{evalin('base', 'mpc_mode')+1}; +experiment.zeta_variant = sprintf('%d', evalin('base', 'zetaVariant')); +experiment.name = name; + +end diff --git a/sysmodel.m b/sysmodel.m new file mode 100644 index 0000000000000000000000000000000000000000..7d32c073e31d2ffc4240244a6002ee11971709e6 --- /dev/null +++ b/sysmodel.m @@ -0,0 +1,16 @@ +function [SysC] = sysmodel() +l = 1.1; +g = 9.80665; +g1 = 0.44; +g2 = 10/(pi/4); +g3 = g; +g4 = 5/7; +g5 = 10/(l/2); +g34 = -g3*g4; +A = [ 0 1 0; 0 0 g34; 0 0 0; ]; +B = [ 0; 0; g1; ]; +C = [ g5 0 0; 0 0 g2 ]; +SysC = ss(A, B, C, []); +SysC.InputName= {'AngularVelocityV'}; +SysC.OutputName= {'PositionV', 'AngleV'}; +SysC.StateName= {'Position', 'Velocity', 'Angle'}; diff --git a/tt_rsmpc_init.m b/tt_rsmpc_init.m new file mode 100644 index 0000000000000000000000000000000000000000..2234121c5aa8d1d4dab98bafc97b158ebf2f4838 --- /dev/null +++ b/tt_rsmpc_init.m @@ -0,0 +1,55 @@ +% Input args: +% 1: Frequency +% 2: Horizon +function tt_rsmpc_init(args) + ttInitKernel('prioFP'); + + SysC = sysmodel(); + Qc = diag([args(7); args(8); args(9)]); + Rc = args(10); + + data.mpcL = init_mpc(1.0/args(1), SysC, Qc, Rc); + data.mpcR = init_mpc(1.0/args(3), SysC, Qc, Rc); + + data.NL = args(2); + data.NR = args(4); + data.u = 0; + data.Lu = 0; + data.pend_Lu = 0; + data.pend_j = -1; + data.Ru = zeros(1,3); + data.LQu = 0; + data.which = 0; + data.localExec = 0; + + + % Here are parameters from the paper algorithm + % NOTE: At this point j does not correspond. j in the paper is j+2 here. + % NOTE: At this point i does not correspond. i in the paper is i+1 here. + data.Delta = zeros(1,4) + data.sigma = 2; + data.applyi = -100; + data.applyj = -100; + data.r = -1 + + data.u_i = zeros(1,3); % Data from remote + data.flag_i = -2; % Flag from remote + data.flag_j = -2; % Flag from local + + data.k = -1; % The HF time step + data.i = -100; % Remote data timestamp, (time when opt was requested) + data.j = -100; % Local data timestamp, (time when opt was requested) + data.local_run_at = -1; % A timestamp (k) when local mode shall be run + + data.zetaVariant = args(5); % 0 = LQ, 1 = u(0), 2 = OpenLoop, 3 = u(1) + + data.mode = args(6) % 1=A, 2=B, 3=C + + if data.mode == 0 || data.mode == 4 + data.zetaVariant = 0; % Always use LQ backup for these to not make things confusing + end + + ttCreatePeriodicTask('rsmpc', 0, data.mpcR.h, 'tt_rsmpc_main', data); + ttSetPriority(1, 'rsmpc'); +end + diff --git a/tt_rsmpc_main.m b/tt_rsmpc_main.m new file mode 100644 index 0000000000000000000000000000000000000000..d628c0f0b7e182aedb69a303ab9954b047b8035a --- /dev/null +++ b/tt_rsmpc_main.m @@ -0,0 +1,223 @@ +function [exectime, data] = tt_switching_main(segment, data) + + exectime = 0; + switch segment + case 1 % Step time and read input + data.k = data.k + 1; + + sp = ttAnalogIn(1); + pos = ttAnalogIn(2); + vel = ttAnalogIn(3); + ang = ttAnalogIn(4); + + data.xk = [pos vel ang]'; + data.xref = [sp 0 0]'; + + data.drop = ttAnalogIn(5); + if data.mode == 4 + data.drop = 0; + end + data.Delta = zeros(1,4); % Selection vector + + case 2 % Update remote response. Mode 0 is local only. Flag_i checks feasibility. Drop=0 means network was ok. + + if data.flag_i == 1 % Feasible result + ttAnalogOut(4, 1); + else + ttAnalogOut(4, 0); + end + + if data.mode > 0 && data.flag_i == 1 && data.drop == 0 + data.Delta(1) = 1; + data.i = data.k-1; % Set the time of start for successful HF optimization. + data.applyi = data.k; + data.Ru = data.u_i; + if data.mode == 1 || data.mode == 3 % Stop local execution + data.local_run_at = -1; + if data.mode == 1 + data.r = data.k + 3; + else + data.r = data.k + 2; + end + else + data.r = data.k + mod(data.k,data.sigma)+data.sigma; + end + elseif data.drop == 0 && data.flag_i ~= 1 + disp(sprintf('HF infeasible, k=%d', data.k)); + end + + case 3 % Update local response and determine recovery + if data.pend_j > -1 && data.pend_j == data.k-2 + if data.flag_j == 1 % Feasible + ttAnalogOut(5, 1); + data.j = data.pend_j; + data.applyj = data.k; + data.Lu = data.pend_Lu; + else + ttAnalogOut(5, 0); + end + end + + if data.applyj-data.applyi >= data.sigma && data.k - data.applyj < data.sigma + data.Delta(2) = 1; + end + + DeltaBar = 0; + if data.k ~= 0 && data.k < data.r % What if there is no initial networked response? + DeltaBar = 1; + end + + data.Delta(3) = DeltaBar*(1-sum(data.Delta(1,1:2))); + data.Delta(4) = (1-sum(data.Delta(1,1:3))); + + if sum(data.Delta) == 0 || sum(data.Delta) > 1 + ME = MException('SwitchingMPC:DeltaError', 'Invalid Delta vector %i%i%i%i', data.Delta); + throw(ME) + end + + case 4 % Select and apply control signal + % HF selector function \Delta_\alpha + deltaAlpha = zeros(1,3); + if data.i > -1 && data.k-data.i <= length(deltaAlpha) + deltaAlpha(data.k-data.i) = 1; + end + deltaB = zeros(1,2); + if data.j > -1 && data.k-data.j > 1 && data.k-data.j-1 <= length(deltaB) + deltaB(data.k-data.j-1) = 1; + end + deltaG = (1-sum(deltaAlpha(1:2)))*deltaB(1) + (1-sum(deltaAlpha(2:3)))*deltaB(2); + + deltaOne = deltaAlpha(1); + + deltaB = 0; + + if deltaOne == 1 % Remote closed loop control + data.u = data.Ru(1); + data.which = 2; + elseif deltaG == 1 + data.u = data.Lu; + data.which = 1; + elseif deltaAlpha(2) == 1 + if data.zetaVariant == 2 + data.u = data.Ru(2); + else + data.u = data.Ru(1); + end + data.which = 1.75; + elseif deltaAlpha(3) == 1 + if data.zetaVariant == 2 + data.u = data.Ru(3); + else + data.u = data.Ru(1); + end + data.which = 1.5; + else + data.u = data.mpcR.K*(data.xref-data.xk); % Calculate HF LQ for recovery mode + data.which = 0; + end + + zeta_which = 0; + % The paper logic + if data.zetaVariant == 0 % LQ control + zeta_1 = data.mpcR.K*(data.xref-data.xk); + zeta_which = 0; + elseif data.zetaVariant == 1 % Hold control signal + zeta_1 = data.Ru(1); + zeta_which = 2; + elseif data.zetaVariant == 2 % Open loop control + if data.k - (data.i+1) < length(data.Ru) + zeta_1 = data.Ru(data.k-data.i); % Note: i is not according to definition + zeta_which = 2-0.25*(data.k-(data.i+1)); + else + zeta_1 = 0; + zeta_which = -1; + end + else % Hold during MPC_2 execution + if data.mode == 1 % Mode A + % Applies one step open loop since MPC_2 is started in this time step + zeta_1 = data.Ru(2); + zeta_which = 1.5; + elseif data.mode == 2 % Mode B + if data.k == data.local_run_at + zeta_1 = data.Ru(2); + zeta_which = 1.5; + else + zeta_1 = data.Ru(1); + zeta_which = 1.75; + end + else % Mode C + % Holds control signal since MPC_2 was started in previous time step + zeta_1 = data.Ru(1); + zeta_which = 1.75; + end + end + zeta_2 = data.mpcR.K*(data.xref-data.xk); + data.zeta2cost = 0.5*(data.xref-data.xk)'*data.mpcR.S*(data.xref-data.xk); + selections = [data.Ru(1) data.Lu zeta_1 zeta_2]; + newu = data.Delta*selections'; + + if data.Delta(1) == 1 + data.which = 2; + elseif data.Delta(2) == 1 + data.which = 1; + elseif data.Delta(3) == 1 + data.which = zeta_which; + else + data.which = 0; + end + + data.u = newu; + + ttAnalogOut(1, data.u); + ttAnalogOut(2, data.which); + + if data.mode == 1 && data.local_run_at == -1 % Mode A, shut down local on remote response + data.localExec = 0; + end + + case 5 + % Always run the HF controller + x = data.mpcR.A*data.xk + data.mpcR.B*data.u; % HF state prediction + [optx, fval, exitflag, output] = runquadprog(data.mpcR, x, data.xref, data.NR, data.NR); + if exitflag == 1 + data.u_i = optx(data.NR*3+1:data.NR*3+3); % Three steps of control signal from HF + end + data.flag_i = exitflag; + + case 6 + if data.mode == 1 && data.drop == 1 % Mode A, start Local MPC when drop occurs + if data.local_run_at == -1 % New drop + data.local_run_at = data.k; + end + elseif data.mode == 2 || data.mode == 3 + if data.local_run_at == -1 + data.local_run_at = data.k; + end + elseif data.mode == 0 && data.local_run_at == -1 % Local only, start and keep running + data.local_run_at = data.k; + end + + case 7 + if data.local_run_at == data.k + x = data.mpcL.A*data.xk + data.mpcL.B*data.u; + [ox, fv, fl, op] = runquadprog(data.mpcL, x, data.xref, data.NL, data.NL); + data.flag_j = fl; + data.pend_j = data.k; + if fl == 1 + data.pend_Lu = ox(data.NL*3+1); + else + data.u = 0; % Hm.... + end + data.local_run_at = data.k+2; + data.localExec = 1; + end + ttAnalogOut(3, data.localExec); + data.localExec = -1*data.localExec; + + case 8 + exectime = 0.01; + + case 9 + exectime = -1; + end +end