バックナンバーはこちら。
https://www.simulationroom999.com/blog/model-based-of-minimum-2-backnumber/
はじめに
前回、とりあえずtkinter上でリアルタイム制御をしていたところ。
おおよそOKだが、ソースコード周りがちょっとヤッツケな感じが出ている。
まぁちょっとグローバル変数が多い・・・。
(クラス化した方が良いかもしれん)
登場人物
博識フクロウのフクさん
イラストACにて公開の「kino_k」さんのイラストを使用しています。
https://www.ac-illust.com/main/profile.php?id=iKciwKA9&area=1
エンジニア歴8年の太郎くん
イラストACにて公開の「しのみ」さんのイラストを使用しています。
https://www.ac-illust.com/main/profile.php?id=uCKphAW2&area=1
現状のソースコードはどうなってる?
太郎くん
で、前回の課題のソースコード側はどんな感じになったのかな?
ヤッツケ感が強いとか言ってたけど?
フクさん
現状はこうなってる。
動作確認用コード
import tkinter
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import time
import numpy as np
from pyfmi import load_fmu, FMUModelCS2, Master
from pyfmi.tests.test_util import Dummy_FMUModelCS2
import collections
root = tkinter.Tk()
root.title("DC Motor Control")
w = root.winfo_screenwidth()
h = root.winfo_screenheight()
root.geometry(str(int(w/2))+"x"+str(int(h/2))+"+"+str(int(w/2))+"+0")
model_sub1 = FMUModelCS2( "PID.fmu", "", _connect_dll=True)
model_sub2 = FMUModelCS2( "Motor.fmu", "", _connect_dll=True)
model_dummy = Dummy_FMUModelCS2([], "Dummy.fmu", "", _connect_dll=False)
model_dummy.values[model_dummy.get_variable_valueref("y")] = 0
def do_dummy( current_t, step_size, new_step=True):
model_dummy.values[model_dummy.get_variable_valueref("y")] = _y
model_dummy.completed_integrator_step()
return 0
_y = 0
model_dummy.do_step = do_dummy
models = [model_sub1, model_sub2, model_dummy]
connections = [(model_dummy,"y", model_sub1,"target" ),
(model_sub1,"y",model_sub2,"voltage"),
(model_sub2,"speed",model_sub1,"u")]
master = Master(models, connections)
step_size = 0.001
queue_max = int(8.5/step_size) # 8.5秒分のqueueを用意
# define queues
deque_voltage = collections.deque(maxlen=queue_max)
deque_current = collections.deque(maxlen=queue_max)
deque_speed = collections.deque(maxlen=queue_max)
deque_loadTorqueStep_tau = collections.deque(maxlen=queue_max)
deque_target = collections.deque(maxlen=queue_max)
deque_time = collections.deque(maxlen=queue_max)
deque_cpuload = collections.deque(maxlen=queue_max)
fig = plt.figure()
fig.set_size_inches(8.4, 4.5)
ax = plt.subplot(1,1,1)
ax.set_xlabel('Time')
ax.set_ylabel('Value')
# define plots
ax.plot([], [], label="target[rad/s]", color='Magenta',linewidth=3)
ax.plot([], [], label="voltage[V]", color='Red')
ax.plot([], [], label="speed[rad/s]", color='Blue',linewidth=0.9)
ax.plot([], [], label="loadTorqueStep.tau[N m]", color='Cyan')
ax.plot([], [], label="current[A]", color='Green',linestyle='--',linewidth=0.8)
ax.plot([], [], label="cpu_load[ms]", color='Black',linestyle='--',linewidth=1)
ax.legend(bbox_to_anchor=(1, 1), borderaxespad=0, fontsize=10)
ax.grid(which='both')
canvas = FigureCanvasTkAgg(fig, root)
canvas.get_tk_widget().pack(side = tkinter.RIGHT)
toolbar=NavigationToolbar2Tk(canvas, root)
toolbar.place(x=0, y=h/2-40)
scalbln = tkinter.BooleanVar()
scalbln.set(True)
chk = tkinter.Checkbutton(root, variable=scalbln, text="Enable Scale bar")
chk.place(x=0, y=10)
cpuloadbln = tkinter.BooleanVar()
cpuloadbln.set(False)
chk = tkinter.Checkbutton(root, variable=cpuloadbln, text="Enable Cpu Load")
chk.place(x=0, y=30)
pausebln = tkinter.BooleanVar()
pausebln.set(False)
chk = tkinter.Checkbutton(root, variable=pausebln, text="pause")
chk.place(x=0, y=50)
sinbln = tkinter.BooleanVar()
sinbln.set(False)
chk = tkinter.Checkbutton(root, variable=sinbln, text="sin wave")
chk.place(x=0, y=70)
sawtoothbln = tkinter.BooleanVar()
sawtoothbln.set(False)
chk = tkinter.Checkbutton(root, variable=sawtoothbln, text="Sawtooth wave")
chk.place(x=0, y=90)
def change(value):
global _y
if scalbln.get():
_y = float(value)
scale = tkinter.Scale(
root,
label = "target Speed",
orient=tkinter.VERTICAL,
command=change,
length = 300,
from_ = 100,
to = 0
)
scale.pack(side = tkinter.LEFT)
# define timers
start_tick = time.perf_counter()
opts = master.simulate_options()
opts["step_size"] = step_size
opts["initialize"] = 1
currenttime = 0
def FMU_handler():
global start_tick
global currenttime
current_tick = time.perf_counter()
delta_tick = current_tick - start_tick;
start_tick = current_tick
delta_simulate = delta_tick
res = master.simulate(start_time=currenttime, final_time=currenttime+delta_simulate-step_size/100, options=opts)
opts["initialize"] = 0
currenttime = currenttime + delta_simulate
deque_voltage.extend(res[model_sub2]['voltage'])
deque_current.extend(res[model_sub2]['current'])
deque_speed.extend(res[model_sub2]['speed'])
deque_loadTorqueStep_tau.extend(res[model_sub2]['loadTorqueStep.tau'])
deque_target.extend(res[model_sub1]['target'])
deque_time.extend(res[model_sub2]['time'])
deque_cpuload.extend(np.ones(len(res[model_sub2]['time']))*delta_simulate*1000)
root.after(1, FMU_handler)
def plot_handler():
if pausebln.get() == False:
ax.lines[0].set_data( np.array(deque_time), np.array(deque_target) )
ax.lines[1].set_data( np.array(deque_time), np.array(deque_voltage) )
ax.lines[2].set_data( np.array(deque_time), np.array(deque_speed) )
ax.lines[3].set_data( np.array(deque_time), np.array(deque_loadTorqueStep_tau) )
ax.lines[4].set_data( np.array(deque_time), np.array(deque_current) )
if cpuloadbln.get():
ax.lines[5].set_data( np.array(deque_time), np.array(deque_cpuload) )
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
ax.set_ylim(-70,200)
ax.set_xlim(deque_time[-1]-8,deque_time[-1])
canvas.draw()
root.after(200, plot_handler)
FMU_handler()
plot_handler()
root.mainloop()
コード修正が必要?
太郎くん
結構な規模になってたんだなぁ。
フクさん
ちょっと暗黙的なものも含めてグローバル変数が多いんだよねー。
太郎くん
それは仕方ないんじゃない?
GUIイベントとかタイマハンドラとかで関数が分離されてるし。
フクさん
折角だからクラスにまとめるか。
太郎くん
まじか。
フクさん
基本的なロジックはできてるから、次回までには作れると思う。
太郎くん
だったら、やってもらうか。
まとめ
フクさん
まとめだよ。
- 現状のソースコード確認。
- 暗黙的なものも含めてグローバル変数が点在している。
- GUIイベントとかタイマハンドラで関数が分離しているので仕方ない面もある。
- 暗黙的なものも含めてグローバル変数が点在している。
- 折角なのでクラスとしてまとめてみようと試みる。
- 基本的なロジックは出来ているのでそれほど修正時間はかからない見込み。
バックナンバーはこちら。
コメント