【PyFMI】最小構成のMBD事例 第2章 その126【リアルタイム描画⑳】

【PyFMI】最小構成のMBD事例 第2章 その126【リアルタイム描画⑳】 事例

バックナンバーはこちら。
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イベントとかタイマハンドラで関数が分離しているので仕方ない面もある。
  • 折角なのでクラスとしてまとめてみようと試みる。
    • 基本的なロジックは出来ているのでそれほど修正時間はかからない見込み。

バックナンバーはこちら。

コメント

タイトルとURLをコピーしました