【UiPath】フルリモのお供。マウスを動かし続けるロボット

## 皆もすなるQiitaといふものを、儂もしてみむとてするなり。

と言う訳で、こん＊＊は！はなっち！です。

この記事はUiPathブログ発信チャレンジ2023サマーの3日目の記事です。

#### 【初めに】
お客様から貸与されたパソコン。無論セキュリティレベルは高く、検索して出てきたリンク先が制限が掛かったりすると、ガッカリしてしまう。
仕方なく、個人ネットワークに接続した自前のパソコンを使って、その制限が掛かっているサイトを見て、あぁ、この情報なら知っていたと思ってガッカリしてしまう。
まぁ、自分なりの考えでやってみよう！と気を取り直してお客様から貸与されたパソコンに向かうと、スクリーンセーバーが働いていて、そこから復帰するのにログインIDとパスワードを入力して...と意外と手間、そのストレス...

マウスが動いていればスクリーンセーバーが働かず、復帰するための再度ログインする操作は不要となる。ならば、**マウスを動かし続けるロボット**を作ってしまえ！

今回は、単に**マウスを動かし続けるロボット**を作った！だけでは面白くないので、**どのようにロボットを成長させたか**も併せて書こうと思います。
- - - 
## １ マウスは動かせるのか。
ロボットがマウスを動かす事が出来なければ、意味はありません。
まずは、**「ホバー」アクティビティ**を使って、ロボットがマウスを動かせるか確認しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/d71046f9-9e5d-6666-ac05-56dbcbd9ccbb.png)
ターゲット要素は特定せず、カーソルの位置を(X,Y)=(200,400)にしてみます。
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/f919b3cf-6f24-3ee3-46f5-69d40202c8e5.png">
実行してみましょう。
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/862672e9-72b0-52ac-a1ed-bc05cf9027b4.png">
ちゃんと移動しました。

## ２ マウスを動かし続ける。
マウスを動かす事はできました。ただ、これは一回だけなので、複数回動かすように改造します。
#### ２－1 移動させる座標を決める。
最初のカーソルの位置は(X,Y)=(200,400)としました。次は **(X,Y)=(X+α,Y+α) α=1** とすると、いずれは画面サイズを超えたところにカーソル位置を持っていくことになります。

スクリーンセーバーが起動されなければ、カーソル位置を常に新しい場所にする必要はなく、最低２点あれば十分ですので。**int32型配列として保持**する事にしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/62da0423-da98-006c-d9f5-d3ee242c98c8.png)
#### ２－2 繰り返し回数を決める。
スクリーンセーバーが起動する時間に到達する以前に、マウスを動かします。例えば５分だとしたら、４分毎に動かし、２回目の８分後にスクリーンセーバーが動いていなければ、大成功です。

繰り返し回数を決めたら、**「繰り返し (コレクションの各要素)」アクティビティ**を配置し、その中に、**「ホバー」アクティビティ**を配します。例では3回としています。
```
繰り返し:currentItem（Object型）
次のコレクションの要素：Enumerable.range(0,3)
```
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/e98ec327-4e49-435a-26b2-8bfb576963de.png">

カーソルの位置は、先の図のように、変数Xと変数Yを宣言、定義してあるので、それを利用します。
繰り返しで指定した変数を利用すると、要素2の配列に対して3回目の繰り返しの際に、**配列の範囲外の例外**を起こしてしまいます。ですので、剰余(mod)を使って、配列外の参照が行われないようにします。
```
Xのオフセット：X(CInt(currentItem.ToString) mod X.Count)
Yのオフセット：Y(CInt(currentItem.ToString) mod Y.Count)
```
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/5846c7ae-085d-9911-b282-949238262e1d.png">

#### ２－3 繰り返し間隔を決める。
このままですと、 **「ホバー」アクティビティ**が実行した直後に再び **「ホバー」アクティビティ**を実行してしまい、忙しないマウス操作になってしまいます。 **「ホバー」アクティビティ**にも「実行後の待機時間」パラメタがありますので、それを活用します。例では、60000ミリ秒で、つまり１分です。
```
実行後の待機時間：60000
```
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/aa1b798f-8a46-c3b5-117f-31718fe7d23a.png">

こうする事で、**「3回の繰り返し＝3分」** と言う事になります。

## ３ 処理が終わったあとの処理を決める。
**「繰り返し (コレクションの各要素)」アクティビティ**で指定回数、**「ホバー」アクティビティ**でマウスを動かし終わったら、当然その処理は終了します。それを気が付かずにまたスクリーンセーバーが起動して、使おうと思ったログインする操作を...これでは元も子もありません。

#### ３－1 音で終了を報せる。
ここでは、**「警告音」アクティビティ**を使って、サウンドを鳴らすようにしましょう。一回では心もとないので、**「繰り返し (コレクションの各要素)」アクティビティ**で3回鳴らすようにします。ただ、**「警告音」アクティビティ**が連続して実行されても、一回目のサウンドが鳴り終わるまで、次のサウンドはなりません。ですので、**「Delay」アクティビティ**を入れています。
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/bdee4f24-ce4d-8037-1cfe-2e2b373698ec.png">

## ４ 汎用的に使えるように処理を決める。
**「ホバー」アクティビティ**を繰返して処理させる回数は、固定にしていました。例えば、ちょっと銀行へ行って帰ってくる30分、マウスを動かし続けてほしいなぁ。なんて時に、ロボットを`修正`するとか、`CONFIGファイル`を用意して…なんて事は非常にまだるっこしいですね。

:::note warn
Attendedライセンスの場合、長時間の離席での運用を想定していません。記事の中でのお話です。
:::
#### ４－1 固定数のホバー処理を可変にする。
固定で決めていた値は、変数を宣言、定義しての大丈夫です。そこに都度値を代入してあげ、それを参照してあげれば、汎用的になります。

その変数にどのようにして値を渡すか...

そういう時は、**「入力ダイアログ」アクティビティ**の出番です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/8c3406d5-272e-f581-7645-c8f25fde6c93.png)
実行すると、以下ののような画面が出てきて、入力を促されます。入力した値は、String型変数に格納されるので、未入力であったり、Int32型変数に変換した時のエラーの際は処理しないように制御できますね。
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/40e5fafd-aa78-b745-d50e-42709b01a870.png">

#### ４－2 ホバー処理を回数入力ではなく、○時○分までとする。
現在の時刻（９時２０分）から、午後２時４５分からの打合せの時間まで、５時間２５分あるから、５(時間)＊６０(分)＋２５(分)＝３２５(分)を入力すればいいや。。。でも毎回こんな計算するの大変だよなぁ。。。だったら、先の **「入力ダイアログ」アクティビティ**にホバー処理を終了させる時刻、例えば「１３：４５」と入れて、現在の時刻から**分単位の差分**を取ればいいか！

となると、**「入力ダイアログ」アクティビティ**に入力される値を想定し、
- `数値` ... これはそのまま繰り返し回数とする。
- `HH:mm` ... これは繰り返し終了までの時刻とする。
- `上記以外` ... 想定していない入力なので、この時点で処理終了とする。

`数値`かどうかを判断してからInt32型の変数に入れるというような二段階ではなく、`数値`をInt32型に変換して、エラーでなければ`数値`、エラーだったら、`HH:mm`の入力なのかもしれないと考え、ここでは、**「Try～Catch」アクティビティ**を利用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/e5c75bc1-1914-9c5c-e45a-cfb6af99c271.png)
```
- SECOND 代入   `←表示名がちがっていますね`
    Minute ← CINT(INPUTVALUE)
```
もしExceptionが発生したら、`数値`ではなく、`HH:mm`が入力された処理を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/d3743b91-4501-0dff-470b-6e6edc70da83.png)
```
- NOWTIMESPAN 代入
    NOWTIMESPAN ← New TimeSpan(now.Hour, now.Minute,0)
- INPUTTIMESPAN 代入
    INPUTTIMESPAN ← New TimeSpan(CINT(INPUTVALUE.split(":"c)(0)), CINT(INPUTVALUE.split(":"c)(1)), 0)
- Minute 代入
    Minute ← CINT(INPUTTIMESPAN.Subtract(NOWTIMESPAN).TotalMinutes)
```
ちょっと汚いやり方ですが、INPUTTIMESPANを代入する際、 **「入力ダイアログ」アクティビティ**から入力された値を、コロン(:)でSplitし、その配列(0)が`HH:mm`の**時**部分、その配列(1)が`HH:mm`の**分**部分、秒は**0**としてTimeSpanを求めています。

入力したTimeSpan値から、現在のTimeSpan値を減じたTimeSpan値の**総分数**を得るのに、.TotalMinutesメソッドを使っています。


## 【まとめ】
・ホバーを繰返すことで、目的とする機能が実現出来たら、前後左右に機能を考えてみる事が、**ロボット開発の醍醐味**です。
・ロボットの内容に、正解はありません。あらゆる手法で目的とする機能が出来たら、それが正解です。いろいろと**試行錯誤**してましょう。

- - - 

##おわりに
いかがでした？

今回も読んでいただきありがとうございました！
是非UiPathでのロボ開発の一助になればと思っています。

ありがとうございました！