Skip to content

Commit

Permalink
Merge pull request #4 from KatLab-MiyazakiUniv/ticket-KLI-165
Browse files Browse the repository at this point in the history
close #KLI-165 Webアプリによる走行ログ可視化
  • Loading branch information
YKhm20020 authored Sep 8, 2024
2 parents ba2ee31 + dc55482 commit 5170f1c
Show file tree
Hide file tree
Showing 7 changed files with 415 additions and 181 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
**/__pycache__/
.coverage
coverage*
coverage*
*.jpeg
*.csv
*.json
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ help:
@echo " $$ make check_style"
@echo "カバレッジレポートの表示"
@echo " $$ make coverage"
@echo "サーバの立ち上げ"
@echo " $$ make server"
@echo "実行ログディレクトリ内の全てのファイルを削除する"
@echo " $$ make clean"

run:
poetry run python src
Expand All @@ -27,3 +31,9 @@ coverage:
poetry run coverage run -m pytest
poetry run coverage report

server:
poetry run python -m src.server.flask_server

clean:
rm -rf src/server/run_log_csv/*
rm -rf src/server/run_log_json/*
361 changes: 202 additions & 159 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ opencv-python = "^4.8.0.74"
requests = "^2.31.0"
flask = "^3.0.3"
pillow = "^10.4.0"
flask-cors = "^4.0.1"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
Expand Down
68 changes: 68 additions & 0 deletions src/csv_to_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
CSVファイルをJSONファイルに変換するクラス.
@author Keiya121
"""

import csv
import json
import os
from typing import List


class CSVToJSONConverter:
"""CSVファイルをJSONファイルに変換するクラス."""

def __init__(self, csv_file_path: str) -> None:
"""コンストラクタ.
Args:
csv_file_path (str): CSVファイルのパス
"""
self.csv_file_path = csv_file_path
self.json_file_path = self._get_json_file_path()

def convert(self) -> None:
"""CSVファイルを読み込み、JSONファイルに変換する."""
data = self._read_csv()
self._write_json(data)

def _read_csv(self) -> List[dict]:
"""CSVファイルを読み込み、辞書のリストを返す.
Return:
run_log_data (List[dict]): 走行ログデータ
"""
run_log_data = []
with open(self.csv_file_path, mode='r', encoding='utf-8') as csv_file:
reader = csv.DictReader(csv_file, fieldnames=[
'brightness', 'rightPWM', 'leftPWM', 'R', 'G', 'B'])
for row in reader:
run_log_data.append(row)
return run_log_data

def _write_json(self, run_log_data: List[dict]) -> None:
"""データをJSONファイルに書き込む.
Args:
run_log_data (List[dict]): 走行ログデータ
"""
json_data = {'runLog': run_log_data}

# JSONファイルの保存先フォルダーを確認し、存在しない場合は作成
os.makedirs(os.path.dirname(self.json_file_path), exist_ok=True)

with open(self.json_file_path, mode='w',
encoding='utf-8') as json_file:
json.dump(json_data, json_file, ensure_ascii=False, indent=4)

def _get_json_file_path(self) -> str:
"""JSONファイルのパスを作成する.
Return:
json_file_path (str): jsonファイルのパス
"""
base, _ = os.path.splitext(os.path.basename(self.csv_file_path))
json_file_path = os.path.join(
'src', 'server', 'run_log_json', base + '.json')
return json_file_path
17 changes: 9 additions & 8 deletions src/server/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# flaskサーバ
走行体から画像ファイルを取得するflaskサーバプログラムです
server()は、"http://"サーバIPアドレス":8000/"にアクセスされたときに実行されます
走行体や走行ログ確認用Webアプリと通信を行うflaskサーバプログラム
server()は、"http://"サーバIPアドレス":8000/"にアクセスされたときに実行される

## サーバの立て方

etrobocon2024-camera-system/ディレクトリ内で以下のコマンドを実行する。$から前は含まない。
```
<~etrobocon2024-camera-system>$ poetry run python3 src/server/flask_server.py
<~etrobocon2024-camera-system>$ make server
```
上のコマンドで実行出来ない場合は,次のコマンドを実行する

## データ送信
画像ファイルを送信する
```
<~etrobocon2024-camera-system>$ poetry run python src/server/flask_server.py
$ curl -X POST -F "file=@"画像ファイルのパス"" http://サーバIPアドレス:8000/images
```

## データ送信
ファイルを送信する
実行ログを送信する
```
$ curl -X POST -F "file=@"画像ファイルのパス"" http://"サーバIPアドレス":8000/upload
$ curl -X POST -F "file=@"ログファイルのパス"" http://サーバIPアドレス:8000/run-log
```
134 changes: 121 additions & 13 deletions src/server/flask_server.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
"""
走行体から画像ファイルを受信するWebサーバー.
走行体と通信するWebサーバー.
@author Keiya121 CHIHAYATAKU
@author Keiya121 CHIHAYATAKU KakinokiKanta
"""

import os
import socket
import platform
from flask_cors import CORS
from ..csv_to_json import CSVToJSONConverter
from ..official_interface import OfficialInterface

from flask import Flask, request, jsonify
from flask import Flask, request, jsonify, send_file

app = Flask(__name__)
CORS(app)

UPLOAD_FOLDER = 'datafiles'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

# '/upload'へのPOSTリクエストに対する操作
# サーバ起動確認用


@app.route('/upload', methods=['POST'])
def getImageFile() -> jsonify:
"""走行体から、画像ファイルを取得するための関数."""
@app.route('/', methods=['GET'])
def health_check() -> jsonify:
"""サーバ起動確認のための関数.
Return:
jsonify (string, int):
レスポンスメッセージ,ステータスコード
"""
return jsonify({"message": "I'm healty!"}), 200


# '/images'へのPOSTリクエストに対する操作


@app.route('/images', methods=['POST'])
def get_image() -> jsonify:
"""走行体から、画像ファイルを取得するための関数.
Return:
jsonify (string, int): レスポンスメッセージ,ステータスコード
"""
# curlコマンドのエラーハンドリング
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
Expand All @@ -30,12 +52,98 @@ def getImageFile() -> jsonify:
if file.filename == '':
return jsonify({"error": "No selected file"}), 400

fileName = file.filename
# src/server/datafilesに、受信したファイルを保存する。
filePath = os.path.join(UPLOAD_FOLDER, fileName)
file.save(filePath)
return jsonify({"message": "File uploaded successfully",
"filePath": filePath}), 200
file_name = file.filename

upload_folder = os.path.join(os.path.dirname(__file__), 'image_data')
os.makedirs(upload_folder, exist_ok=True)

# src/server/image_dataに、受信したファイルを保存する。
file_path = os.path.join(upload_folder, file_name)
file.save(file_path)

# TODO: 現在は、1枚目のフィグ画像、プラレール画像の場合に競技システムへアップロードしている
if file_name == 'Fig_1.jpeg' or file_name == 'Pla.jpeg':
OfficialInterface.upload_snap(file_path)

return jsonify({"message": "File uploaded successfully"}), 200

# '/run-log'へのPOSTリクエストに対する操作


@app.route('/run-log', methods=['POST'])
def get_run_log() -> jsonify:
"""走行体から、実行ログのcsvファイルを取得するための関数.
Return:
jsonify (string, int): レスポンスメッセージ,ステータスコード
"""
# curlコマンドのエラーハンドリング
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400

file = request.files['file']

if file.filename == '':
return jsonify({"error": "No selected file"}), 400

file_name = file.filename

upload_folder = os.path.join(os.path.dirname(__file__), 'run_log_csv')
os.makedirs(upload_folder, exist_ok=True)

# src/server/run_log_csvに、受信したファイルを保存する。
file_path = os.path.join(upload_folder, file_name)
file.save(file_path)

# CSVファイルをJSONに変換する
converter = CSVToJSONConverter(file_path)
converter.convert()

return jsonify({"message": "File uploaded successfully"}), 200

# '/run-log'へのGETリクエストに対する操作


@app.route('/run-log', methods=['GET'])
def send_run_log() -> jsonify:
"""Webアプリに実行ログのjsonファイルを送信するための関数.
Return:
jsonify (string, int): レスポンスメッセージ,ステータスコード
"""
# jsonファイルを指定するための変数
latest = request.args.get('latest')

# リクエストにクエリパラメータが存在しない場合
if latest is None:
return jsonify({"error": "Query parameter 'latest' is required"}), 400

# クエリパラメータが整数かどうかの判定
try:
latest = int(latest)
except ValueError:
return jsonify({"error":
"Query parameter 'latest' must be an integer"}), 400

# jsonファイルが保存されているディレクトリを指定
storage_folder = os.path.join(os.path.dirname(__file__), 'run_log_json')

# jsonファイルのリストを取得
files = os.listdir(storage_folder)

# jsonファイルが存在するかをチェック
if not files:
return jsonify({"error": "No files available"}), 404

# クエリパラメータの指定したファイルが存在しない場合
if latest == 0 or latest > len(files):
return jsonify({"error": "'latest' is out of range"}), 404

# 最後のファイルを送信
file_name = files[-latest]
file_path = os.path.join(storage_folder, file_name)

return send_file(file_path, as_attachment=True), 200


# ポート番号の設定
Expand Down

0 comments on commit 5170f1c

Please sign in to comment.