1. はじめに
はじめまして初投稿です。
お留守番の猫をX(twitter)で見れたらいいなぁと思い、作った仕組みについての記事です。
できるだけtwitterは X と書きます。
1.1 目的
仕事中も猫が見たい。
1.2 実際に X へ自動投稿したツイート
こんな感じにツイートされます。2fpsなのでカクカクですが、それでも様子を確認するには十分です。
test pic.twitter.com/mEugyyuXob
— まっきー🐈⬛ (@dmackey_2139) November 25, 2023
1.3 誰向けの記事か
ペットを飼っている方でこういうのに興味がある人の参考になればと思います。
2. 概要
2.1 構成図
2.2 必要になるもの
- FTP機能付きカメラ
- Xの鍵アカウント
- AWS EC2で作成するサーバ (Amazon Linux 2023)
- Linuxインフラ周りとpythonの簡単な知識
- 作業するパソコン(Windows10)
- wifi
2.3 メリット
- SDカードや外付けHDDといった記憶媒体を用意する必要がない。SNSに投稿するだけ
- SNSなので家族間で共有することが出来る
- GIFなので格安simの低速モードでも閲覧可
2.4 デメリット
自動でインターネット上に公開する仕組みです。意図せず風呂上りの写真をツイートするといった悲劇的な何かが起こる可能性はあります。
そのため絶対に、誰もフォローしていない or 親しい人だけフォローしている鍵アカを使ってください。
2.5 なぜXを採用したか?
SNSはXとLINEしかやってないのでこの時点で2択
XとLINEの比較 (APIについては無料枠)
X | LINE | |
---|---|---|
方式 | 垂れ流し | 全メッセージ受信 |
GIF 送信 | あり | なし |
画像,動画 送信 | あり | URL経由 |
API制限 | 1日50件 | 1時間1000件 |
LINEは溜まったメッセージをすべて受信する必要がある。Xだと最新情報だけサクっと見れる。ここが1番大きかった。
あとLINE Messaging APIは自分で画像や動画を送るときに自分でURLを用意する必要があるので面倒。
APIの利用回数についてはLINEの圧勝だけど
2.6 なぜAWSを採用したか?
EC2インスタンス1年間無料が魅力的だったので。
GCPのVMも試してみたけど、1番安いリージョンにしたせいかAWSに比べるとアクセスが遅く、精神衛生上よろしくなかったので止めた。
3. サーバーの準備 (AWS Amazon Linux 2023)
FTPとpythonが使えるLinuxサーバが必要になるのでAWSを利用します。
Windowsから開発を行いたいので、ターミナルソフトとSAMBAの設定もします。
3.1 アカウント作成
3.2 インスタンスの作成
後のssh接続で必要になるので、キーペアも作成して指定する。今回は「KEYPAIR」という名称で作成した。
作成すると「KEYPAIR.pem」が自動でダウンロードされるので大事に取っておく。
他はデフォルトのままでOK。「インスタンスを起動」をクリック
ちなみに1番小さいt2.nanoは無料利用枠がないみたい。何年も長く使うのならnanoの方がお得ってことか
3.3 Elastic IP
デフォルトだと再起動する度にグローバルIPアドレスが変わってしまい、カメラ側が困る。そのためElastic IPを利用して、IPアドレスを固定する。EC2インスタンスに関連付けていれば料金は掛からない。
以下のように関連付けが出来たら、インスタンスを再起動する。
以下のパブリックIPv4の項目がインスタンスを再起動しても変更しないことを確認する。
3.4 セキュリティグループ
ポート単位にアクセス可能なIPを指定できる。Anywhereは誰でも可、マイIPは自分だけ。
445・・Windows上で作業したいときに利用するSAMBAのポート番号。IPがバレると誰でもアクセス可になってしまうので、絶対にマイIPのみ
20,21・・FTPが使用するポート。誰でもアクセス可な0.0.0.0
22・・sshが使用するポート。SAMBAと同じでマイIPのみ
FTPも制限するべき説はあるけど、カメラのIP変わるかもだし今回の使い方で悪用されて問題になりようがない気がするので誰でも可にする。
3.5 teraterm
teraterm 5.0 をインストールする。インストール時にTeraTerm Menuにはチェックを入れる。
以下フォルダを作成し、大事にとっておいたキーペアファイルをこのフォルダに格納する。
D:\aws\teraterm
ttlファイルは以下内容で作成
HOSTADDR = '作成したインスタンスのホスト名'
USERNAME = 'ec2-user'
KEY_FILE = 'D:\aws\teraterm\KEYPAIR.pem'
COMMAND = HOSTADDR
strconcat COMMAND ':22 /ssh /2 /auth=publickey /user='
strconcat COMMAND USERNAME
strconcat COMMAND ' /keyfile='
strconcat COMMAND KEY_FILE
connect COMMAND
end
ec2ユーザーにログインできることを確認する。
新たにssh2login_root.ttlも作成して、USERNAMEのとこをrootに変更してからrootのログイン情報も追加する。
sshでいきなりrootログインはデフォルトで認められていないので、いったんec2ユーザで以下コマンド実行
sudo vi /root/.ssh/authorized_keys
これでrootもログインできるようことを確認する。
3.6 SAMBAの設定
rootで以下実行
# sambaインストール
yum -y install samba
# ユーザー追加し、パスワードはsambaにでもしとく
pdbedit -a ec2-user
# オプションLで確認
pdbedit -L
vi /etc/samba/smb.conf
# 末尾に以下追加しとく
[image]
path = /var/www/image
read only = No
----- vi おわり
# サービス起動と自動立ち上げ
systemctl start smb
systemctl enable smb
デスクトップ上で、右クリック->新規作成->ショートカットを選び、ホスト名を入力して以下のようにショートカットを作る。
開こうとしたら資格を聞かれるので、IDはec2-user、パスワードはさっき設定したsambaを入力して、アクセスできることを確認する。
3.7 FTPの設定
rootで以下実行
#ftp機能のインストール
yum -y install vsftpd
yum -y install ftp
# ユーザーとパスワードはどちらもftpuserにでもしとく
adduser ftpuser
passwd ftpuser
vi /etc/vsftpd/vsftpd.conf
# 末尾に以下1行追加して、ダウンロードは禁止にする
download_enable=NO
# ログを出力したいときは以下のコメントを外す
xferlog_file=/var/log/xferlog
----- vi おわり
mkdir -p /var/www/image
chmod 777 /var/www/image
usermod -d /var/www/image ftpuser
# サービス起動と自動立ち上げ
systemctl start vsftpd
systemctl enable vsftpd
以下のようにWindows PowerShellからFTPコマンドを実行して、ファイルのアップロードが可能なことを確認する。
3.8 Pythonのインストール
rootで以下実行。手元にあったメモが他のアプリとごちゃまぜだったのでちょっとここ自信ない。この後でプログラム動かしてエラーになったら適宜ライブラリをインストールしてください。いい加減でごめんなさい
yum -y install python-pip
yum -y install python-devel
pip install Pillow
pip install tweepy
pip install pyinotify
4. XのAPIを使う
準備したサーバー上でXのAPIを利用し、投稿出来るか確認します。
4.1 ディレクトリ構成
以下のようなディレクトリ構成とし、sns_cameraディレクトリの中にソースを置く。
4.2 ツイートするプログラム
「文字」をツイートする関数と、「文字とファイル」をツイートする関数の2つ作成します。
import tweepy
# Twitter APIキーとアクセストークン
CONSUMER_KEY = 'あなたのキー'
CONSUMER_SECRET = 'あなたのキー'
ACCESS_TOKEN = 'あなたのキー'
ACCESS_TOKEN_SECRET = 'あなたのキー'
# 指定テキスト内容でツイートする
def text_tweet(text):
client = tweepy.Client(consumer_key=CONSUMER_KEY,
consumer_secret=CONSUMER_SECRET,
access_token=ACCESS_TOKEN,
access_token_secret=ACCESS_TOKEN_SECRET)
try:
client.create_tweet(text=text)
except Exception as e:
print(e)
return False
return True
# 指定テキスト内容とファイルをツイートする
def image_tweet(text, image_path):
auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
api = tweepy.API(auth)
client = tweepy.Client(consumer_key=CONSUMER_KEY,
consumer_secret=CONSUMER_SECRET,
access_token=ACCESS_TOKEN,
access_token_secret=ACCESS_TOKEN_SECRET)
media = api.media_upload(filename=image_path)
try:
client.create_tweet(text=text, media_ids=[media.media_id])
except Exception as e:
print(e)
return False
return True
def main():
# text_tweet('hai')
image_tweet('こんにつは', '186.gif')
if __name__ == "__main__":
main()
4.3 必要になるXのAPIキーを入手
参考にしたサイト
https://programming-zero.net/how-to-start-twitter-api-basic-and-free/
ただし、無料枠での利用なのでクレジットカード情報なんて入力しない。
ちょっと手間取ったとこだけ記述
User authentication settingsを探してEditを選ぶ
デフォルト状態だと読み込み権限しかないので「Read and write」を選ぶ
CallbackURI、WebsiteURIが必須入力となっているが、使わないので適当なURLを入力
画面戻ったら
上の赤丸がCONSUMER_KEYとCONSUMER_SECRET
下の赤丸がACCESS_TOKENとACCESS_TOKEN_SECRET
(Read and Write permissionsを確認)
4.4 最後にテストしよう!
4つのAPIキーを入手できたらプログラムを動かしてみてXへ投稿できているか確認する。
4.5 気付いたAPIの制約
少なくとも以下はエラーになる
・まったく同じ内容でAPIを呼び出す
You are not allowed to create a Tweet with duplicate content.
(内容が重複してるよエラー)
・サイズが大きいファイルの送信
400 Bad Request
Your media IDs are invalid.
62MBの動画を送信しようとしたときに発生
でも13MBの動画は送信できたので、困ることはほとんど無いと思う。
APIの仕様はよく変わるので細かい閾値は調べても意味がないと思っている。
とりあえず13MBのGIFファイルは送れるのでヨシ!
5. GIFの作成
画像からGIFファイルを作成するプログラムを作ります。
5.1 仕様
引数に画像ファイルパスのリストを指定し、指定サイズ以内のGIFを作成する。
ディレクトリを指定したくなるところだが、今回はFTPで伝送されてくる画像を相手にするので、ファイルパスのリストを引数に指定することにした。ちなみにChatGPTが書いてくれたコードほぼそのまま
5.2 GIF作成プログラム
from PIL import Image
import os
import shutil
import tempfile
def create_gif(filepaths, output_gif_path, target_size, max_attempts=10):
# 指定ファイルリストをイメージオープンしてく
images = []
for filepath in filepaths:
img = Image.open(filepath)
images.append(img)
temp_dir = tempfile.mkdtemp()
temp_gif_path = os.path.join(temp_dir, "temp.gif")
images[0].save(temp_gif_path, save_all=True, append_images=images[1:], duration=500, loop=0)
# GIFの品質を調整して指定バイト数以下になるようにする
attempts = 0
while os.path.getsize(temp_gif_path) > target_size and attempts < max_attempts:
images[0].save(temp_gif_path, save_all=True, append_images=images[1:], duration=500, loop=0, optimize=True, quality=85)
attempts += 1
print(attempts, os.path.getsize(temp_gif_path))
# 指定サイズ内に収まらなかった
if os.path.getsize(temp_gif_path) > target_size:
shutil.rmtree(temp_dir)
return False
# 最終的なGIFをコピーして保存する
shutil.copy(temp_gif_path, output_gif_path)
shutil.rmtree(temp_dir)
return True
def main():
# 画像のファイルリスト
filepath_list = [
'001.png',
'002.png',
]
# 10メガバイト以内でgif作成
create_gif(filepath_list, 'output.gif', 10 * 1024 * 1024)
if __name__ == "__main__":
main()
5.3 最後にテストしよう!
ソースと同じディレクトリに適当な画像ファイルを用意してプログラムを動かしてみる。output.gifファイルが出来上がればOK
6. ディレクトリ監視(inotify)
FTP転送されるファイルを想定し、ディレクトリを監視するためにinotifyを利用する。
6.1 大雑把な処理の流れ
① 対象ディレクトリを監視
② ファイルが作成されたら起動
③ ある程度ファイルをまとめてGIFファイルを作成
④ Xへ投稿
⑤ ①へ戻る
6.2 プログラム
'''
特定ディレクトリを監視し、ファイル作成されたらtwitterに投稿する
'''
import os
import time
import sys
from datetime import datetime
from time import sleep
import pyinotify
import twitter
import make_gif
TARGET_DIR='/var/www/image'
extern_file_list = []
PICTURE_MAX = 30
GIF_FILE_NAME = 'x_post.gif'
# 指定ディレクトリ内の全ファイルを削除する
def delete_all_files(directory_path):
try:
# ディレクトリ内のファイル一覧を取得
file_list = os.listdir(directory_path)
# ファイルを順番に削除
for file_name in file_list:
file_path = os.path.join(directory_path, file_name)
if os.path.isfile(file_path):
os.remove(file_path)
except:
pass
# 指定ディレクトリ内の最も更新日付が新しいファイルを返却する
def get_newest_file_name(directory_path):
# 指定されたディレクトリ内のファイル一覧を取得
files = [f for f in os.listdir(directory_path) if os.path.isfile(os.path.join(directory_path, f))]
if not files:
return None
# ファイルごとの最終更新日時を取得
file_update_times = {file: os.path.getmtime(os.path.join(directory_path, file)) for file in files}
# 最終更新日時が最も新しいファイル名を取得
newest_file = max(file_update_times, key=file_update_times.get)
return newest_file
def handle_event(event):
global extern_file_list
# 今回発生ファイルをグローバル変数リストに追加
file_path = os.path.join(event.path, event.name)
extern_file_list.append(file_path)
# 2秒待ってディレクトリ内の最新ファイルと比較する
# 不一致 -> 後続があるので処理なし
# 一致 -> 後続ないので投稿を試みる
sleep(2)
latest = get_newest_file_name(event.path)
print(latest, event.name, len(extern_file_list))
if latest != event.name and PICTURE_MAX >= len(extern_file_list):
return
print('2秒経っても最新だった!')
# ツイート用に時間でも編集
now = datetime.now()
formatted_now = now.strftime("%Y年%m月%d日 %H時%M分%S秒") # 0埋めされるのは気にするな。意外とスマートな書き方がなさげ
# とりあえずテストも兼て毎回gifは作っとく(10MBまで)
if make_gif.create_gif(extern_file_list, GIF_FILE_NAME, 10 * 1024 * 1024):
# gifファイル作成に成功したら、twitterに投稿
twitter.image_tweet(formatted_now, GIF_FILE_NAME)
os.remove(GIF_FILE_NAME)
# 全部削除
for x in extern_file_list:
print(x, '削除')
try:
os.remove(x)
except:
pass
extern_file_list = []
def main(directory):
# 立ち上がり時に対象ディレクトリ内の全ファイルを削除する
delete_all_files(TARGET_DIR)
# インスタンスを作成
wm = pyinotify.WatchManager()
# イベントを処理するためのハンドラを作成
handler = pyinotify.ProcessEvent()
handler.process_default = handle_event
# イベントマスクを設定
mask = pyinotify.IN_CLOSE_WRITE # IN_CREATE だと書き込み完了前にイベント発生してしまうのでIN_CLOSE_WRITE
# ディレクトリを監視
wd = wm.add_watch(directory, mask, rec=True)
# イベントループ
notifier = pyinotify.Notifier(wm, handler)
try:
print(f"Watching directory: {directory}")
notifier.loop()
except KeyboardInterrupt:
print("Terminating...")
notifier.stop()
if __name__ == "__main__":
main(TARGET_DIR)
6.3 仕様
発生するファイル個数に依存したくなかったので、妥協してsleepとグローバル変数を使う。
今回トリガーのファイル名称は分かっているので、2秒スリープ後の該当ディレクトリの最新ファイルと
等しい -> 今回のファイルで終わりなのでGIF作ってツイート!
等しくない -> 後続にファイルが控えているので何もしない
という処理にした。これも大部分はChatGPT作
6.4 テストしよう!
bmpやjpgファイルを用意し、SAMBAでアクセス許可しているフォルダにコピーするだけでテストできる。
Xへ投稿されてコピーしたファイルは削除されればOK
6.5 OKならサービス登録
上手くいったらサーバー起動時に自動で実行されるように設定する。rootで以下コマンド
vi /etc/systemd/system/sns_camera_upload_service.service
# 中身を以下にする
[Unit]
Description=sns_camera upload Service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/ec2-user/project/sns_camera/inotify_main.py
WorkingDirectory=/home/ec2-user/project/sns_camera
Restart=always
[Install]
WantedBy=multi-user.target
----- viおわり
# 上記設定ファイルを作ったら以下コマンドでサービス登録
systemctl enable sns_camera_upload_service
systemctl start sns_camera_upload_service
systemctl daemon-reload
6.6 デバッグについて
サービスに登録してしまうと、print文による確認が出来なくなる。もちろんserviceファイルで設定すれば確認できるんだろうけど、ちょいと面倒だしやったことがねえ(笑)
サクっと確認したいだけのときは、rootで以下コマンド
systemctl stop sns_camera_upload_service
cd /home/ec2-user/project/sns_camera
python3 inotify_main.py
# テストが終わったら
systemctl start sns_camera_upload_service
重複起動チェックとかしてないので、サービスと手動実行で画像ファイルの取り合いになってハマるかもしれない。そんなときは落ち着いて上記を試す。
7. カメラを用意
やっとここまで来た・・
この記事を読んで自分もやってみよう! という方がいればとても嬉しいけど、カメラ購入は上記までを試してからにしてほしい。だってカメラ高かった!!
7.1 機種選定
FTP機能付きのカメラであれば何でもよいです。でもなかなか見つからない。中国メーカーで安いのはあるけど、勝手に首振り・中国語が聞こえてきた・誰かに見られているんじゃ、という怖いウワサがあるので使いたくなかった。そこでPanasonic
7.2 購入した機種
i-PRO アイプロ 防犯カメラ
型番:WV-B54300-F3W
購入してから気付いたけど、ペットの見守り用途がターゲットのカメラではない。
工場の中の様子を録画したり、防犯目的が主なカメラだ。しかしそんなことは気にしない。
7.3 wifiと接続
スマホに i-PROMoblie APP というアプリを入れて、カメラとwifiを接続する。
この辺は説明書と公式ページからpdfをダウンロードしてよく読む。
7.4 ちょっとハマる
wifi接続情報を元にQRコードを作成し、カメラに読ませるという方式だが何回やっても上手く行かない。
公式ページをよく見ると・・・2.4GHzしか対応していなかった。5GHzは接続不可な家電ってけっこうあるよね。
2.4GHz帯でQRコード作り直して読ませたら、緑色のランプがピコピコ光って接続完了
7.5 パソコンでの設定
wifiに接続できたら、パナソニックさんの設定ツールをインストールする。入れたのは、
「ツール ダウンロード IP簡単設定ソフトウェア EasyIpSetup_V4_80」
ブラウザ上からアクセスする。ライブ映像も見れるし、設定あり過ぎてうっひょー!!(嬉しい悲鳴)
なんか分からんけど一通り見てみないと気が済まない。どんな設定があるのか色々見て確認したりしてるとあっという間に3,4時間は過ぎる。あと少しなので焦らない。
7.6 時間合わせ
映像にはデフォルトで時刻情報が表示される。時間がズレていくと後で絶対気になる。最初から設定しておく。
NTPサーバーでググると情報通信研究機構さんが公開している ntp.nict.jp というのが見つかった。ありがたく利用させて頂く。NTPのポート番号は123
7.7 動作検知エリア
動作検知エリアを設定できる。猫ちゃんが通りそうな場所を指定しよう
7.8 アラーム無検知時間
アラーム無検知時間とは、動作検知等のアラームを検知してから次に検知するまでの間のこと。長く設定すれば当然無検知時間が増えるので、Xへの投稿回数も減ることになる。
無料枠は1日50回までなので、使い切らないように猫ちゃんと相談して決める。まずは最長の600s(=10分)で設定し、50回までに余裕がありそうなら徐々に減らしていこう。600s → 300s → 200s → ・・・
7.9 最も重要なFTP設定
プレアラームとはアラーム検知前の画像についての設定のこと。猫を検知する前の画像には興味がないので0枚にする。
ポストアラームがアラーム(=動作検知)が発生したときの画像についての設定のこと。
fpsとはframes per secondの略で、1秒当たりの画像枚数のこと。ゲームだと60fpsとかのアレ
このfpsと画像枚数をどうするかが最も悩ましい。
但し、上図のように3000枚まで選べれるのは1fpsまで。2fps,3fpsだと10枚までとなる。
1FPSの3000枚って3000秒だから50分じゃん。なっがいね、どんなニーズがあるんだろ
今回は2fpsの10枚にしてみる。
※初回は1fpsの3枚、無検知時間は10秒にする。理由はテストですぐ確認できるから。テストが終わってから好みの時間に設定しよう。
アクティブモードにしないと上手くいかなかったのだけど、なんかおかしい気がする。まぁもうあまり使わない知識だろうし、詳しくなりたくもないのでちゃんと調べてない。
7.10 テストしよう!
全てが上手くいっていれば動作検知したときにXへGIFを自動投稿するはず。スマホ握りしめてワクワクしながらカメラの前に躍り出る。
7.11 だいたい1発では上手くいかない
最初から上手くいくことは少ない。当たり前だけど、問題を切り分けて考える。
このカメラはシステムログも見れたので、問題解決に役立った。
7.12 スマホからのアクセス
スマホの i-PROMoblie APP だけど、こちらからもカメラにアクセスしてリアルタイム映像が見れる。
しかし今回の記事とは実はあまり関係がない。「Xで見る」が目的だったから。でもまぁせっかくなのでやっておく。
以下で登録できた。ルーターのグローバルIPなのかとか、けっこう色々悩んだぜ...
ただしFTPなどの設定が出来るわけではなさそう。あくまでリアルタイム映像を見るためだけと思った方がいい。
wifi切ったスマホだと見れないんだけどもしかしてwifi経由のスマホのみか...?wifiのことあまり知らないから分かんない!(笑)
ライブ映像も見れた。物理的なカメラの首振りはしないが、カメラ移動も可能
カメラはDHCPなので、電源入り切りするたびにIPアドレス変わっても文句言えない。EasyIpSetupで固定IPに出来たっぽいのは後から気付いたけど、この時はルーターの設定で固定IPにした。このmacアドレスだったらこのIPアドレスにするっていう設定。
8. 実際の運用方法
8.1 安心して家で過ごせれない
こんな仕組みがあると自分が動いてもツイートされることになるので、何か仕組みを考えないと安心して家で過ごせない。
① 毎日手動でカメラのON/OFFをする
② 毎日手動で物理的にカメラを何かで覆う
③ もう少しプログラムで頑張る
なるべく記事とプログラムを簡単にしたかったので省略していますが、③について書きます。
8.2 自動ON/OFF
cronで定周期にプログラムを動かし、次のような仕様にしています。
ON:平日朝9時
OFF:ONされてから9時間後
大体会社に居る時間にONされ、帰る頃にはOFFされることになります。
8.3 LINE Botでも制御する
たまたま遊びでLINEのお天気botを作っていたので、LINEからでも投稿のON/OFFを出来るようにしました。
Xは1日50件までの投稿なので、バックアップ的にLINEへの送信機能も実装しています。GIFではなくただの画像ですが。
このお天気botのソースを公開してもよかったのですが、人に見せるコーディングじゃなかったのと記事が長くなり過ぎたので・・。もし見たいという方が居ればまた別記事にしようと思います。
8.4 でも・・本当に投稿してない!?
Xへ投稿するかどうかはプログラムで判断していることなので、カメラの見た目には何も変わりません。
どうしても緑のランプを消したい人は、SwitchBot スマートプラグを使えば可能です。SwitchBotのAPIを使って、コンセントからの電源供給をストップさせましょう。
9. まとめ
9.1 反省点
そういえばAmazonから来年2月からIPv4は料金高くするってメールが来てたので、いい加減IPv6に向き合わないといけない。あとSFTPにも。
ChatGPTとかWebサイト見て気付いたけど、今の流行りエディタはvimじゃなくてnanoなんだ・・!
9.2 SAMBA用ユーザは必須になのか?
本当はAmazonLinuxじゃなくてRHELを使いたかったけど、今回のSAMBA設定手順だとアクセス権限がないですと言われて仕方なくAmazonLinux2023にした。SAMBA用にユーザ作ればいいんだろうけど、作らずに既存ユーザで済ませたい。SAMBA用ユーザ作れという記述をよく見るし、それが今のスタンダード?知ってたら誰か教えてほしい
9.3 おわりに
出来るだけプログラムやったことない人でも分かるように意識して記事を書きましたが、やっぱりちょっと難しい気はします。ただ少しでも誰かの役に立てばうれしいです。
ご意見ご指摘ありましたらお願いします。
もしやってみたよって人がいれば教えてください!とてもうれしいです!!