2019b/Member

課題3の概要

課題:ボール運搬ロボット
青と赤のボールを運搬して、空き缶の上に載せる

フィールド図

空き缶を6本用意し、そのうち3本をC,E,Fの交差点に描いた半径4cmの円内に置き、 その上にボールを載せておく
同様に残りの空き缶をC', E', F' 地点に置く

  • 基本ルール
    • 競技時間は審判が続行不能と判断するまで、あるいはリタイアするまで
    • 図のA地点または(および)A'地点からスタートする。ただし接地している部分はそれぞれの領域内に収まるものとする(線上はOK)。上空部分は領域からはみ出していてもよい
    • 開始の合図から5秒以内にスタートボタンを押す作業を完了すること
    • 競技が終了するまで、ロボットに触ったり人間が遠隔で操作してはならない
    • 途中でうまく動かなくなった場合、1回限り再スタートすることができる(再スタートの際に別プログラムで起動してよい)
    • 競技終了後、ロボットが、空き缶に触れていてはいけない
  • 基本得点の計算方法
    • ボールを1個運べば8点。3つとも運べば24点。
    • 競技終了後、ゴールの空き缶が半分以上円からはみ出しまった場合、3点減点。
    • 競技終了後、ゴールの空き缶が完全に円から出しまった場合、4点減点。
    • 競技終了後、空き缶にロボットが触れていれば、もとの基本点の3減点。
  • 技術点の計算方法
    以下の動作の精度・スピード・確実性などを含めた技術的な工夫や芸術性について他の全てのチーム(5チーム)が20点満点で採点し、その平均点を求める。 得点の目安:
    • ボールを探し取りにいくまでの動作 (3点)
    • ボールを掴む動作 (3点)
    • ボールを運ぶ動作 (2点)
    • ボールを容器の中に入れる動作 (2点)
    • 2台のNXT、EV3の連携の良さ(2点)
    • 自立型のロボットとしての形や動作の美しさ、斬新さ(2点)
    • その他 (3点)
       
       
  • 我々の班は以下のルートでボールをキャッチし、置くようにした。
    ルート図

ロボットの説明

  • 以下がロボットの全体像である
    全体図
     
     
  • ボールをキャッチする機構は以下のように作った
    キャッチ図
    このようにすることによって少しずれていてもゴムの柔軟性により高確率でキャッチできる。キャッチした後はキャッチする部分を回転させて上にあげることでレーンにボールを落とし、これを三回繰り返しレーンにボールをため込む。走行中はここが上がってボールをせき止める。
     
     
  • ボールを置く機構について
    でっぱり図
    キャッチする機構を下げて缶の上に押さえつけると同時に、垂直についているでっぱりによりレーンからボールが一つだけ送り出される。
     
     
    置く図
    そしてアームを下げることで図のようにボールを押さえつける形となり、この状態からキャッチする機構を上にあげればボールは缶の上に置かれる。
     
     
  • ライントレースについて
    二つのセンサーで線を挟み込むことでずれを少なくした。
    センサー図
     
     
  • 缶の感知には超音波センサーを使った。
    超音波図
    超音波センサーは水平に置くと感度が悪く、垂直に置く必要があるのでボールを押すアームに取り付けた。走行中は図のような定位置にセットされて缶を検知する。

プログラムの説明

  • 今回の課題では二台のNXTを通信させて制御した。
    マスター側が缶を感知したり、ボールをキャッチし、置くタスクをつかさどり、スレイブ側が運転をつかさどる。

接続の確認

  • まずマスター側のスイッチを押すと以下のプログラムにより、スレイブの応答を待つ。応答があれば音が鳴る。
     void m_con(){//接続
    	int x;
    	while(x!=4){
    	 ReceiveRemoteNumber(MAILBOX1,false,x);
    	}
    	PlayTone(700,200);
     }
     
  • スレイブ側のスイッチを押せば以下のプログラムにより、コネクション番号である4をマスターのメールボックス1に送る。
    #define con 4
    void s_con(){//接続
    	SendResponseNumber(MAILBOX1,con);PlayTone(700,200);
    }
    これらにより通信が正常かどうかを確かめられる。

初期位置にする

  • マスター側はアームやレーン、キャッチする機構などを以下のプログラムで定位置にセットする
    void reset(){//初期位置
    	ResetTachoCount(OUT_BC);
    	OnFwd(OUT_B,20);Wait(800);//キャッチ機構をめいっぱい後ろに倒す
    	OnFwd(OUT_C,45);Wait(1200);//アームをめいっぱい後ろに倒す
    	RotateMotor(OUT_B,-20,10);//キャッチ機構を少し前に倒す
    	OnFwd(OUT_C,-20);Wait(1700);//アームを前に倒す
    	OnFwd(OUT_B,-20);Wait(400);//キャッチ機構がアームに引っかかるまで前に倒す
    	Off(OUT_BC);
    }

缶の観測

  • 初期位置になった後、マスター側は以下のプログラムにより、スレイブにgo命令番号である1を送信し、超音波センサーと地面までの距離の十回平均を測定し、超音波センサーの値がその平均の半分以下になるまで測定し続け、そうなったらstop命令番号である2をスレイブに送る。
    #define go 1
    #define st 2
    void observe(){
    	int c=0;
    	long sum=0;
    	float a;
    	SendRemoteNumber(1,MAILBOX1,go);//go命令
    	SetSensorLowspeed(S4);
    	while(c!=10){
    	sum=sum+SensorUS(S4);
    	c=c+1;
    	}
    	a=sum/10;//十回平均
    	while(SensorUS(S4)>0.5*a){
    	}//while
    	SendRemoteNumber(1,MAILBOX1,st);//stop命令
    }//void
    レーン上のボールの数により走行中の定位置が少し違うので、超音波センサーの値を〜cmなどと絶対値で判断してしまうと正常に缶を認識できない。したがって初めに平均をとりその値を基準に判断するようにした。

待機

  • スレイブ側は以下のプログラムでマスター側からgo命令がくるまで待機する。
    #define go 1
    int y;
    void waiting(){
    	while(y!=go){ReceiveRemoteNumber(MAILBOX1,true,y);}
    }

ライントレース

  • stop命令が来るか、センサーが同時に黒を認識する(交差点を認識する)までライントレース。しかし、経過時間が引数t_min以下の時は交差点を無視。
    #define st 2
    #define t 52
    #define sp 40
    int y;
    
    void gf (long x,long y){
    	OnFwd(OUT_B,x);OnFwd(OUT_C,y);
    }
    
    void line_trace(long t_min) {//止まる命令が来るまでライントレース
    	SetSensorLight(S1);
    	SetSensorLight(S2);
    	long t_start=CurrentTick();PlayTone(700,200);
    	while(y!=st){
    	 ReceiveRemoteNumber(MAILBOX1,true,y);
    
    
    	 if(SENSOR_1<t-7){
    
    	  if(SENSOR_2<t-7){
    	   if((CurrentTick()-t_start)<t_min){gf(sp,sp);}
    	   else{break;}
    	  }
    
    	  else if(SENSOR_2<t+7){
    	   gf(0,sp);
    	  }
    
    	  else {
    	   gf(-sp,sp);
    	  }
    
    	 }//if1
    
    
    	 else if(SENSOR_1<t+7){
    	  if(SENSOR_2<t-7){
    	   gf(sp,0);
    	  }
    	  else if(SENSOR_2<t+7){
    	   gf(sp,sp);
    	  }
    	  else {
    	   gf(0,sp);
    	  }
    	 }//if2
    
    
    	 else{
    	  if(SENSOR_2<t-7){
    	   gf(sp,-sp);
    	  }
    	  else if(SENSOR_2<t+7){
    	   gf(sp,sp);
    	  }
    	  else {
    	   gf(sp,sp);
    	  }
    	 }//if3
    
    
    	}//while
    
    	gf(0,0);
    	PlayTone(1400,200);
    }//trace
    このライントレースについて閾値t=52としてセンサーの値がt-7以下を黒、t-7以上t+7以下を並、t+7以上を白として、左右それぞれのセンサーの組み合わせ全9通りについて各処理を決めた。
    樹形図にすると以下のようになる。
    樹形図
    ループに入る前にt_startに現在時刻を記録すればCurrentTick()-t_startはループに入ってからの経過時間となる。これがt_min以下の時はセンサーが黒ー黒でも止まらない。つまり引数としてt_minを指定すれば、始まってからこの時間までは交差点を無視する。

ボールを取る

  • マスター側は、缶を感知したらスレイブにstop命令を出し、自身はボールをつかむプログラムを実行する。以下がボールを取るプログラムである。
    #define go 1
    void catch(){
    
    	Wait(100);
    	ResetTachoCount(OUT_BC);
    	OnFwd(OUT_B,20);Wait(800);//キャッチ部分をめいっぱい後ろに倒す
    	OnFwd(OUT_C,45);Wait(1200);//アームをめいっぱい後ろに倒す
    	OnFwd(OUT_B,-20);Wait(1700);//キャッチ部分を缶に押し当てる、ここでボールがゴムに挟まれる
    	RotateMotor(OUT_B,20,150);//キャッチ部分を持ち上げてレーンにボールを置く
    	OnFwd(OUT_C,-35);Wait(700);//アームを前に倒す
    	RotateMotor(OUT_B,-20,30);//キャッチ部分も前に少し倒す
    	SendRemoteNumber(1,MAILBOX1,go);//スレイブにgo命令を出す
    }

缶をどかす

  • スレイブ側は、ボールを取った後の缶がまだあるとそれを認識して止まってしまう。したがってライントレースを行う前に缶をどかす。以下がそのプログラムである。
    #define sp 40
    void move_can(){
    	OnFwdSync(OUT_BC,sp,0);Wait(3500);
    	OnFwdSync(OUT_BC,-sp,0);Wait(2900);
    }

缶にボールを置く

  • マスター側はボールを三つ取り、C', E', F' 地点の缶まで来たら以下のプログラムを実行してボールを置く。
    #define spd 30
    #define go 1
    void put_ball(){
       OnFwd(OUT_B,spd);Wait(1000);//キャッチ部分をめいっぱい後ろに倒す
       OnFwd(OUT_C,2 * spd);//アームをめいっぱい後ろに倒す
       RotateMotor(OUT_B,4 * spd,-5);Wait(8000);//キャッチ部分を少し前に倒す、この揺れにより引っ掛かりをなくしボールを転がす
       Off(OUT_BC);
       RotateMotor(OUT_B,spd,-120);Wait(600);//空き缶の上にキャッチ部分をセット
       RotateMotor(OUT_C,spd,-65);Wait(400);//突き
       RotateMotor(OUT_B,spd + 10,50);Wait(500);//アームを下ろしたままキャッチ部分を上げる
       OnFwd(OUT_C,2 * spd);Wait(1000);//アームを後ろに倒す
       RotateMotor(OUT_B,spd,70);//キャッチ部分も後ろに倒す
       RotateMotor(OUT_C,spd,-40);//アームを前に倒す
       RotateMotor(OUT_B,spd,-30);//キャッチ部分を少し前に倒す
       SendRemoteNumber(1,MAILBOX1,go);//スレイブにgo命令を出す
    }

その他細かい動作

  • スレイブ側が線を越えるときの決まった動作を以下で定義した。
    #define tt 700//四半回転時間
    void turn_l(){
    	gf(sp,sp);Wait(300);gf(-sp,sp);Wait(tt);
    }
    
    void turn_r(){
    	gf(sp,sp);Wait(300);gf(sp,-sp);Wait(tt);
    }

メインプログラム

以上でプログラムの部品はそろった。

  • マスター側は以下の流れで課題を実行する。
    #define tt 700//四半回転時間
    #define bt 1500//半回転時間
    task main(){
    
    m_con();//スレーブとの接続を確認
    reset();//初期位置にセット
    observe();//go命令を出し、ボール1を観測したらstop命令
    catch();//C地点のボールをつかみ、go命令を出す
    Wait(7000);//C地点の缶がどかされるまで待つ
    
    observe();//go命令を出し、ボール2を観測したらstop命令
    catch();//E地点のボールをつかみ、go命令を出す
    Wait(7000);//E地点の缶がどかされるまで待つ
    
    observe();//go命令を出しボール3を観測したらstop命令
    catch();//F地点のボールをつかみ、go命令を出す
    Wait(7000);//F地点の缶がどかされるまで待つ
    SendRemoteNumber(1,MAILBOX1,go);//go命令を出す
    
    
    Wait(10000);//少し待つ
    observe();//F’地点の缶を観測したらstop命令
    put_ball();//F’地点の缶にボールを置く
    
    observe();//E’地点の缶を観測したらstop命令
    put_ball();//E’地点の缶にボールを置く
    
    observe();//C’地点の缶を観測したらstop命令
    put_ball();//C’地点の缶にボールを置く
    
    }
     
  • スレイブ側は以下の流れで課題を実行する。
    task main(){
    
    s_con();//マスターとの接続を確認
    waiting();//go命令待ち
    line_trace(10000);//C地点の缶までライントレース
    waiting();//マスターがC地点のボールを取りgo命令が来るまで待つ
    move_can();//C地点の缶をどかす
    
    waiting();//go命令待ち
    line_trace(20000);//E地点の缶までライントレース
    waiting();//マスターがE地点のボールを取りgo命令が来るまで待つ
    move_can();//E地点の缶をどかす
    
    waiting();//go命令待ち
    gf(sp,sp);Wait(400);turn_r();//E地点の缶をどかした後、線に戻る
    line_trace(8000);//F地点の缶までトレース
    waiting();//マスターがF地点のボールを取りgo命令が来るまで待つ
    move_can();//F地点の缶をどかす
    
    
    
    
    waiting();//go待ち
    gf(sp,sp);Wait(400);turn_r();//F地点の缶をどかした後、線に戻る
    line_trace(3000);//交差点Gまでライントレース
    turn_l();line_trace(1500);//G-H
    turn_l();line_trace(1000);//H-H'
    turn_r();line_trace(1000);//H'-G'
    gf(sp,sp);Wait(300);turn_r();line_trace(5000);//G'-F'
    waiting();//マスターがF'地点にボールを置きgo命令が来るまで待つ
    
    OnFwdSync(OUT_BC,-sp,0);Wait(1000);gf(-sp,sp);Wait(tt);;line_trace(1000);//F'-G'-D'
    turn_r();line_trace(1000);//D'-E'
    waiting();//マスターがE'地点にボールを置きgo命令が来るまで待つ
    
    OnFwdSync(OUT_BC,-sp,0);Wait(bt);//E'-D'
    gf(sp,sp);Wait(300);gf(sp,-sp);Wait(tt*2);line_trace(10000);//D'-C'
    
    }

全プログラム

参考として実際にNXTに入れたプログラムを以下に示す。

  • マスタープログラム
    #define go 1
    #define st 2
    #define con 4
    #define spd 30
    void m_con(){//接続
    	int x;
    	while(x!=4){
    	 ReceiveRemoteNumber(MAILBOX1,false,x);
    	}
    	PlayTone(700,200);
    }
    void reset(){//初期位置
    	ResetTachoCount(OUT_BC);
    	OnFwd(OUT_B,20);Wait(800);
    	OnFwd(OUT_C,45);Wait(1200);
    	RotateMotor(OUT_B,-20,10);
    	OnFwd(OUT_C,-20);Wait(1700);
    	OnFwd(OUT_B,-20);Wait(400);
    	Off(OUT_BC);
    }
    
    void observe(){
    	int c=0;
    	long sum=0;
    	float a;
    	SendRemoteNumber(1,MAILBOX1,go);
    	SetSensorLowspeed(S4);
    	while(c!=10){
    	sum=sum+SensorUS(S4);
    	c=c+1;
    	}
    	a=sum/10;
    	while(SensorUS(S4)>0.5*a){
    	}//while
    	SendRemoteNumber(1,MAILBOX1,st);
    }//void
    
    
    
    void catch(){
    
    	Wait(100);
    	ResetTachoCount(OUT_BC);
    	OnFwd(OUT_B,20);Wait(800);
    	OnFwd(OUT_C,45);Wait(1200);
    	OnFwd(OUT_B,-20);Wait(1700);
    	RotateMotor(OUT_B,20,150);
    	OnFwd(OUT_C,-35);Wait(700);
    	RotateMotor(OUT_B,-20,30);
    	SendRemoteNumber(1,MAILBOX1,go);
    }
    
    
    void put_ball()
    {/*
    	ResetTachoCount(OUT_BC);
    	RotateMotor(OUT_B,spd,30);
    	RotateMotor(OUT_C,spd,50);
    	Wait(1500);
    	RotateMotor(OUT_C,spd,-10);
    	Wait(1500);
    	RotateMotor(OUT_B,spd,-100);     //空き缶の上にゴム枠をセット
    	Wait(600);
    	RotateMotor(OUT_C,spd,-30);    //突き後半
    	Wait(400);
    	RotateMotor(OUT_B,spd * 2,30);   //センサを下ろしたままゴム枠を上げる
    	RotateMotor(OUT_C,spd * 2,60);
    	RotateMotor(OUT_B,spd,50);
    */
        OnFwd(OUT_B,spd);
        Wait(1000);
        OnFwd(OUT_C,2 * spd);
        RotateMotor(OUT_B,4 * spd,-5); 
        Wait(8000);
        Off(OUT_BC);
        RotateMotor(OUT_B,spd,-120);     //空き缶の上にゴム枠をセット
        Wait(600);
        RotateMotor(OUT_C,spd,-65);      //突き
        Wait(400);
        RotateMotor(OUT_B,spd + 10,50);   //センサを下ろしたままゴム枠を上げる
        Wait(500);
        OnFwd(OUT_C,2 * spd);
        Wait(1000);
        RotateMotor(OUT_B,spd,70);  
        RotateMotor(OUT_C,spd,-40);
        RotateMotor(OUT_B,spd,-30);
    	SendRemoteNumber(1,MAILBOX1,go);
    }
    
    
    task main(){
    
    m_con();
    reset();
    observe();//ボール1を観測
    catch();//ボール1つかむ
    Wait(7000);//缶1どかされるまで待つ
    
    observe();//ボール2を観測
    catch();//ボール2をつかむ
    Wait(7000);//缶2どかされるまで待つ
    
    observe();//ボール3を観測
    catch();//ボール3をつかむ
    Wait(7000);//缶3どかされるまで待つ
    SendRemoteNumber(1,MAILBOX1,go);
    Wait(10000);
    observe();
    put_ball();//1
    
    observe();
    put_ball();//2
    
    observe();
    put_ball();//3
    
    
    
    
    }
     
     
  • スレイブプログラム
    #define t 52
    #define sp 40
    #define go 1
    #define st 2
    #define con 4
    #define tt 700
    #define bt 1500
    int y;
    void gf (long x,long y){
    	OnFwd(OUT_B,x);OnFwd(OUT_C,y);
    }
    
    void s_con(){//接続
    	SendResponseNumber(MAILBOX1,con);PlayTone(700,200);
    }
    
    
    void line_trace(long t_min) {//止まる命令が来るまでライントレース
    	SetSensorLight(S1);
    	SetSensorLight(S2);
    	long t_start=CurrentTick();PlayTone(700,200);
    	while(y!=st){
    	 ReceiveRemoteNumber(MAILBOX1,true,y);
    
    
    	 if(SENSOR_1<t-7){
    
    	  if(SENSOR_2<t-7){
    	   if((CurrentTick()-t_start)<t_min){gf(sp,sp);}
    	   else{break;}
    	  }
    
    	  else if(SENSOR_2<t+7){
    	   gf(0,sp);
    	  }
    
    	  else {
    	   gf(-sp,sp);
    	  }
    
    	 }//if1
    
    
    	 else if(SENSOR_1<t+7){
    	  if(SENSOR_2<t-7){
    	   gf(sp,0);
    	  }
    	  else if(SENSOR_2<t+7){
    	   gf(sp,sp);
    	  }
    	  else {
    	   gf(0,sp);
    	  }
    	 }//if2
    
    
    	 else{
    	  if(SENSOR_2<t-7){
    	   gf(sp,-sp);
    	  }
    	  else if(SENSOR_2<t+7){
    	   gf(sp,sp);
    	  }
    	  else {
    	   gf(sp,sp);
    	  }
    	 }//if3
    
    
    	}//while
    
    	gf(0,0);
    	PlayTone(1400,200);
    }//trace
    
    
    void move_can(){
    	OnFwdSync(OUT_BC,sp,0);Wait(3500);
    	OnFwdSync(OUT_BC,-sp,0);Wait(2900);
    }
    
    void waiting(){
    	while(y!=go){ReceiveRemoteNumber(MAILBOX1,true,y);}
    }
     
    void turn_l(){
    
    	gf(sp,sp);Wait(300);gf(-sp,sp);Wait(tt);
    
    }
    
    void turn_r(){ 
    
    	gf(sp,sp);Wait(300);gf(sp,-sp);Wait(tt);
    
    }
    
    
    task main(){
    s_con();//返事
    waiting();//go待ち
    line_trace(10000);//缶1までトレース
    waiting();//ボール1catch待ち
    move_can();//缶1どかす 
    
    waiting();//go待ち
    line_trace(20000);//缶2までトレース
    waiting();//ボール2キャッチ待ち
    move_can();//缶2どかす
    
    waiting();//go待ち
    gf(sp,sp);Wait(400);turn_r();line_trace(8000);//缶3までトレース
    waiting();//ボール3キャッチ待ち
    move_can();//缶3どかす
    
    
    
    
    waiting();//go待ち
    gf(sp,sp);Wait(400);turn_r();line_trace(3000);//缶3どかしたあとfg
    turn_l();line_trace(1500);//gh
    turn_l();line_trace(1000);//hh'
    turn_r();line_trace(1000);//h'g'
    gf(sp,sp);Wait(300);turn_r();line_trace(5000);//g'f'
    waiting();//go待ち
    OnFwdSync(OUT_BC,-sp,0);Wait(1000);gf(-sp,sp);Wait(tt);;line_trace(1000);//g'd'
    turn_r();line_trace(1000);//d'e'
    waiting();//go待ち
    OnFwdSync(OUT_BC,-sp,0);Wait(bt);//e'd'
    gf(sp,sp);Wait(300);gf(sp,-sp);Wait(tt*2);line_trace(10000);//d'c'
    }

反省点

全体的にレーンやアームの動作、缶をどかす動作などをWaitなどで時間指定で実行するようにしてしまい、本番に電池の出力が変わったことが原因で正常に動かなくなってしまった。モーターの作動はRotateMotorをもっと使うべきであったかもしれないが、RotateMotorは指定された角度になるまで次の工程に進まないので、何らかの引っ掛かりなどにより負荷がかかり止まってしまったらプログラムが進まない。なので、ある動作の経過時間が一定時間を超えれば強制終了するようなプログラムを作ってみるという改良点も考えられる。

感想

今回の課題は前回の課題のメインであるライントレースを使って、3つのボールを運搬するというものであった。前回とは異なり、NXTを二台分使うのでかなり自由度は高くなっていた。
まずロボットを二台に分割してするのか、一つにまとめてするのかで別れる。二台使うことの利点としては、それぞれが独立して動けるのでかなり、自由にルートを決定できるという点である。デメリットとしては、二台に分けてしまうとライントレース用のモーターを計4つ使うことになってしまいボールのために使えるモーターは2つだけになってしまう点である。逆に1台にまとめればボールのために使えるモータは4つとなりボールのために自由な動きができる。我々の班は一台にまとめてロボットを作ったが結局使ったモーターは二個だけであった。ボールをキャッチしたり貯めたボールをせき止めるためのモーターとレーンの傾きの調節とボールを押さえつけるためのモーターの計二個である。このように一つのモータの役割を多くしたためボールを取る動作や置く動作が複雑になってしまい調節に時間がかかりすぎてしまった。
また、アームに超音波センサーを付けたため重くなってしまい弱い出力ではアームは持ち上がらず、倒すときは自重で勝手に落ちてしまうので調節が大変であった。
ボールをつかむ機構の輪ゴムをつかうという工夫はかなりうまくできていたと思う。少なくとも9割はつかめていた。
最後に、今までプログラムにあまり触れてこなかったが、この授業でプログラムで何かの計算をしたり、何かをを動かしたりすることをしてプログラムに今までよりも身近になれたと思う。将来においておそらくプログラムに関連したことをすることになると思うので、この授業での成功や失敗の経験は将来の大きな糧になると思う。


添付ファイル: files樹形図.jpg 15件 [詳細] file2019b-mission3 (2).png 15件 [詳細] files横図.jpg 13件 [詳細] filesライントレース.jpg 11件 [詳細] filesプット.jpg 13件 [詳細] filesでっぱり.jpg 17件 [詳細] filesキャッチ.jpg 13件 [詳細] files全体図.jpg 14件 [詳細] file2019b-mission3 (1).png 13件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2020-02-13 (木) 15:55:25