LINEで通知できるようにする
在庫管理をできるようにしたら
足りないものを知らせる機能が必要
LINEで買い物リストとして昼ぐらいに送信すれば
帰りに購入して帰ることができる
過去記事を参考に
LINE Messasging API でメッセージ送信
を参考にリンクをしたら
LINE Business ID
になってしまうので
再度調べることにする
LINE: LINE Notifyを用いてWindowsのcurlコマンドからメッセージを投稿する
を参考に再度設定をしていく
スマホのLINEアプリで
トーク > トークルームの作成 > グループ
友達は誰も選択せずに次へ
次に友達をグループに追加があるけど
デフォルトで友達をグループに自動で追加がチェックされているので
チェックを外す
グループ名は買い物リスト
とした
次にLINE Notifyにログインする
https://notify-bot.line.me/ja/
ログインの時にQRコードからログインできるので
スマホのLINEアプリでQRコードを読み取る
もしくはスマホのカメラアプリでQRコードを読み取ると
LINEアプリでQRコードを読み取るように出るので
そのまま実行していくと認証画面になり
PCの画面に表示された数字をスマホで打ち込めば
ログインできる
ログインできたらトークンを発行する
マイページ > アクセストークンの発行
トークンを発行する
をクリックし
トークン名を入力し
通知を送信するトークルームを選択
今回は両方とも
買い物リストを選択
これでトークンが発行される
次にスマホで
買い物リストの
トークルームを開き
設定 > 招待 で
LINE notifyを選択し招待する
ここへはcurlコマンドでメッセージを送信できる
https://notify-bot.line.me/doc/ja/
のサンプルは
curl -X POST -H 'Authorization: Bearer <access_token>' -F 'message=foobar' \ https://notify-api.line.me/api/notify {"status":200,"message":"ok"} curl -v -X POST -H 'Authorization: Bearer invalidtoken' -F 'message=foobar' \ https://notify-api.line.me/api/notify {"status":401,"message":"Invalid access token"}
https://qiita.com/frozencatpisces/items/679d66ab1d617b7a40cb#1-投稿先トークルームの作成
では
curl -X POST -H "Authorization: Bearer 発行したトークン" -F "message=foobar" https://notify-api.line.me/api/notify
これをリストにする場合は複数行必要なので
LINE Notify で curl で改行する
https://blog.framinal.life/entry/2023/06/14/151933
を参考に
URLエンコーディングされた改行文字(%0A) に変換して送ることでできそう
message="こんにちは\n元気ですか?\n\n" # Convert newline characters to URL-encoded form message_encoded=$(echo -e $message | awk '{printf "%s%%0A", $0}') curl -X POST -H "Authorization: Bearer XXXXXX" --data-binary "message=$message_encoded" https://notify-api.line.me/api/notify
ちょっと古い情報で3年前のだと
LINE NotifyからのLINE通知を改行する方法【Python/LINE Notify(API)】
だと
Lineで通知する文章を改行したい場合、「\n」を入れると改行できるらしい
send_contents = f'\n今日は\n{strftime}({day_of_the_week[weekday]})です。'
また
send_contents = '\n今日は\n' + str(strftime) + '(' + str(day_of_the_week[weekday]) + ')です。'
というようにしてもOK
“Line Notify”を利用してPythonでLineに通知を送る
では
requesta
を使っている
pip install requests
テキストだけなら
import requests url = 'https://notify-api.line.me/api/notify' token = '発行したトークン' message = '通知したいメッセージ' headers = {'Authorization': f"Bearer {token}"} params = {'message': message} requests.post(url, headers=headers, params=params)
テキストと画像付きなら
import requests url = 'https://notify-api.line.me/api/notify' token = '発行したトークン' message = '通知したいテキスト' image_file_path = 'イメージファイルパス' headers = {'Authorization': f"Bearer {token}"} params = {'message': message} files = {'imageFile': open(image_file_path, 'rb')} res = requests.post(url, headers=headers, params=params, files=files)
テキストの改行なら
message = 'ここで改行\n改行後のテキスト'
PythonでLINE Notifyを使ってみよう
によれば
・メッセージを改行したい場合は「\n」を挿入
・メッセージは最大1000文字まで
import requests def notify(message): url = 'https://notify-api.line.me/api/notify' token = '発行されたトークン' headers = {'Authorization': 'Bearer ' + token} message = message params = {'message': message} requests.post(url, headers=headers, params=params) if __name__ == '__main__': notify('テスト')
がサンプル
こちらもrequestを使用
[Python]LINEで天気を自動通知させてみた[初心者]
によれば
Webから天気情報を取得してLINEで自動通知をしてみました。定期実行までやります
では
Cronで定期実行
実行するコードは
import datetime import urllib.request as req import requests from bs4 import BeautifulSoup import re #LINE notifyの設定を行う url = "https://notify-api.line.me/api/notify" access_token = '○○○○○' headers = {'Authorization': 'Bearer ' + access_token} #天気サイトから欲しい情報を取得する url2 = "https://tenki.jp/forecast/3/17/4610/14100/" #欲しい情報があるURLを指定 res = requests.get(url2) #上記URL情報を取得する soup = BeautifulSoup(res.content, 'html.parser') #取得した情報をhtmlで解析する # 以下各種情報を取得 ddd = soup.find(class_="left-style") telop = soup.find("p", class_="weather-telop").string highlists = soup.find("dd",class_="high-temp temp") lowlists = soup.find("dd",class_="low-temp temp") ttt = soup.find(class_="rain-probability") row=[] for t in ttt: row.append(t) # message変数に通知したい文を代入する 改行したい場合は "\n" とダブルクォテーションで囲う message="\n" + ddd.text + "\n" + telop + "\n" + "最高 " + highlists.text + "\n" + "最低 " + lowlists.text + "\n"+ "---------" + "\n" +row[1].text +"\n" + "~6 : " + row[3].text + "\n" + "~12 : " + row[5].text +"\n" + "~18 : " + row[7].text +"\n" + "~24 : " + row[9].text +"\n" +"今日も元気に٩( 'ω' )و " payload = {'message': message} r = requests.post(url, headers=headers, params=payload,)
というように requestを使っている
あと
1人のユーザーにつき、1時間に通知できる回数は1000回まで
という縛りがあるが
買い物リストは1日1回程度だと思うし
カメラ画像からとして考えても3箇所程度だと思うので問題なし
とりあえずchatgptで調べた結果
Curl でも requestでも問題はなさそう
コードメンテを考えるとrequestsの方が良さそう
ということで
vim line_order.py
でファイルを作成し
vim config.json
で設定ファイルを作成
{ "token": "発行したトークン", "image_file_path": "イメージファイルパス" }
import requests import json # 設定ファイルを読み込む関数 def load_config(file_path): with open(file_path, 'r') as file: return json.load(file) # 設定ファイルを読み込む config = load_config('config.json') # 設定ファイルからトークンとファイルパスを取得 token = config['token'] image_file_path = config['image_file_path'] url = 'https://notify-api.line.me/api/notify' message = '通知したいテキスト' headers = {'Authorization': f"Bearer {token}"} params = {'message': message} files = {'imageFile': open(image_file_path, 'rb')} # LINE Notify APIにリクエストを送信 res = requests.post(url, headers=headers, params=params, files=files) # レスポンスを出力 print(res.status_code) print(res.text)
と
line_order.py
の内容を書き換えても
Traceback (most recent call last): File "/Users/snowpool/aw10s/inventory/line_order.py", line 21, in <module> files = {'imageFile': open(image_file_path, 'rb')} IsADirectoryError: [Errno 21] Is a directory: 'image/'
となる
とりあえず画像を指定する
{ "token": "発行したトークン", "image_file_path": "runs/detect/predict7/Baskulin1.jpg" }
とすれば成功
改良点としては
メッセージの文章を
検出結果のラベルを変換した文字列にすること
検出結果は
runs/detect/
の中にどんどん新しい番号が付けられて増えていくため
動的にパスを取得するスクリプトにすること
osモジュールを使用して、指定されたディレクトリ内のサブディレクトリをリストアップし、その中で最新の番号を持つディレクトリを特定できる
1. os.listdir(base_path)を使用して、指定されたディレクトリ内の全てのファイルとディレクトリのリストを取得します。
2. リスト内の要素がディレクトリであるかどうかを確認するためにos.path.isdir()を使用します。
3. predictプレフィックスを削除して数値に変換し、max()関数を使用して最大の数値を持つディレクトリを特定します。
4. os.path.join(base_path, latest_dir)を使用して、フルパスを生成します。
import os def get_latest_directory(base_path): # 指定されたディレクトリ内の全てのサブディレクトリを取得 subdirs = [d for d in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, d))] # サブディレクトリ名を数値に変換し、ソートして最新のディレクトリを特定 latest_dir = max(subdirs, key=lambda x: int(x.replace('predict', ''))) return os.path.join(base_path, latest_dir) # 使用例 base_path = 'runs/detect' latest_dir = get_latest_directory(base_path) print(f"Latest directory: {latest_dir}")
実行すると
File "/Users/snowpool/aw10s/inventory/utils.py", line 14, in <module> latest_dir = get_latest_directory(base_path) File "/Users/snowpool/aw10s/inventory/utils.py", line 8, in get_latest_directory latest_dir = max(subdirs, key=lambda x: int(x.replace('predict', ''))) File "/Users/snowpool/aw10s/inventory/utils.py", line 8, in <lambda> latest_dir = max(subdirs, key=lambda x: int(x.replace('predict', ''))) ValueError: invalid literal for int() with base 10: ''
となる
原因は
ValueError: invalid literal for int() with base 10: ''
というエラーは
int()関数が空文字列を処理しようとしたときに発生
これは、predictという文字列をreplaceで削除した結果が
空文字列になる場合に起こる
例えば、predictという名前のディレクトリがある場合など
この問題を解決するために
ディレクトリ名がpredictのプレフィックスを持っているかどうかをチェックし
それ以外のディレクトリ名を無視するようにする
また、predictの後の文字列が数字であることを確認するために
追加のチェックを行う
対処として
1. predictで始まり、その後に数字が続くディレクトリのみを対象とするようにフィルタリングしています。
2. 有効なディレクトリが存在しない場合に適切なエラーメッセージを出力します。
import os def get_latest_directory(base_path): # 指定されたディレクトリ内の全てのサブディレクトリを取得 subdirs = [d for d in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, d))] # サブディレクトリ名が 'predict' で始まり、その後に数字が続くものをフィルタリング predict_dirs = [d for d in subdirs if d.startswith('predict') and d[7:].isdigit()] if not predict_dirs: raise ValueError("No valid 'predict' directories found") # サブディレクトリ名を数値に変換し、ソートして最新のディレクトリを特定 latest_dir = max(predict_dirs, key=lambda x: int(x[7:])) return os.path.join(base_path, latest_dir) # 使用例 base_path = 'runs/detect' latest_dir = get_latest_directory(base_path) print(f"Latest directory: {latest_dir}")
これで実行すると
Latest directory: runs/detect/predict7
となった
次は画像ファイルパスの取得
import os def get_latest_directory(base_path): # 指定されたディレクトリ内の全てのサブディレクトリを取得 subdirs = [d for d in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, d))] # サブディレクトリ名が 'predict' で始まり、その後に数字が続くものをフィルタリング predict_dirs = [d for d in subdirs if d.startswith('predict') and d[7:].isdigit()] if not predict_dirs: raise ValueError("No valid 'predict' directories found") # サブディレクトリ名を数値に変換し、ソートして最新のディレクトリを特定 latest_dir = max(predict_dirs, key=lambda x: int(x[7:])) return os.path.join(base_path, latest_dir) def get_image_files(directory): # 指定されたディレクトリ内の全ての画像ファイルのパスを取得 image_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.bmp') image_files = [os.path.join(directory, file) for file in os.listdir(directory) if file.lower().endswith(image_extensions)] return image_files # 使用例 base_path = 'runs/detect' latest_dir = get_latest_directory(base_path) image_files = get_image_files(latest_dir) print(f"Latest directory: {latest_dir}") print("Image files:") for image_file in image_files: print(image_file)
とすることで画像ファイルのパスが取得できた
Latest directory: runs/detect/predict7 Image files: runs/detect/predict7/Baskulin1.jpg
次にこれをモジュールにして
LINEの画像パスにして送信テストする
import os import json def load_config(file_path): with open(file_path, 'r') as file: return json.load(file) def get_latest_directory(base_path): # 指定されたディレクトリ内の全てのサブディレクトリを取得 subdirs = [d for d in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, d))] # サブディレクトリ名が 'predict' で始まり、その後に数字が続くものをフィルタリング predict_dirs = [d for d in subdirs if d.startswith('predict') and d[7:].isdigit()] if not predict_dirs: raise ValueError("No valid 'predict' directories found") # サブディレクトリ名を数値に変換し、ソートして最新のディレクトリを特定 latest_dir = max(predict_dirs, key=lambda x: int(x[7:])) return os.path.join(base_path, latest_dir) def get_image_files(directory): # 指定されたディレクトリ内の全ての画像ファイルのパスを取得 image_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.bmp') image_files = [os.path.join(directory, file) for file in os.listdir(directory) if file.lower().endswith(image_extensions)] return image_files
として
config.jsonの中の
"image_file_path": "runs/detect/predict7/Baskulin1.jpg"
という指定を
"image_file_path": "runs/detect"
に変更
line_order.pyの中身を
import requests import os from utils import load_config, get_latest_directory, get_image_files # 設定ファイルを読み込む config = load_config('config.json') # 設定ファイルからトークンとディレクトリパスを取得 token = config['token'] base_path = config['image_file_path'] # 最新のpredictディレクトリを取得 latest_dir = get_latest_directory(base_path) image_files = get_image_files(latest_dir) url = 'https://notify-api.line.me/api/notify' message = '通知したいテキスト' headers = {'Authorization': f"Bearer {token}"} params = {'message': message} # 最新のpredictディレクトリ内の全ての画像ファイルに対してLINE Notify APIにリクエストを送信 for image_file_path in image_files: files = {'imageFile': open(image_file_path, 'rb')} # LINE Notify APIにリクエストを送信 res = requests.post(url, headers=headers, params=params, files=files) # レスポンスを出力 print(f"File: {image_file_path}") print(res.status_code) print(res.text)
として保存
これで
python line_order.py
を実行すれば画像付きでLINEで送信してくれる
試しに再度新しいyolov8での推論をして
できたディレクトリを対象にするか実験する
しかし
python count_inventory_terminal.py data_bak/Baskulin1.jpg 0: 640x512 1 baskulin, 93.9ms Speed: 2.7ms preprocess, 93.9ms inference, 2.9ms postprocess per image at shape (1, 3, 640, 512) バスクリン: 1個
の後に
python line_order.py
を実行しても
File: runs/detect/predict7/Baskulin1.jpg 200 {"status":200,"message":"ok"}
となる
ls runs/detect の結果も predict predict3 predict5 predict7 predict2 predict4 predict6
となる
どうやらyoloのコマンドで実行した時だけ
runs/predict以下に保存されるらしい
つまりカウントした後に画像を保存するプログラムを追加しないとだめ
あと、カウントした時に残り1以下になった時に
ラベルを書き出すプログラムが必要