commit f6edfe2c79d1cf2915a6daf938305b39542d61d9 Author: sujune Date: Thu Dec 5 07:42:21 2024 +0900 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..554ee67 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# 이 코드 템플릿은 SDT Cloud 환경에서 s3에 파일을 업로드하는 MQTT 메세지를 발행하는 코드입니다. + +# 패키지 설치 +- 코드는 sdtclouds3, sdtcloudpubsub 패키지를 사용합니다. 아래 명령어로 패키지를 다운로드 해야합니다. +```bash +$ pip install sdtclouds3 sdtcloudpubsub +``` + +# 환경 셋팅 +- 코드를 실행하기 전에, 장비에 sdtcloud 로그인 작업을 수행해야 합니다. +```bash +sudo bwc-cli login +``` + +# 코드 작성 +## s3 코드 작성 +- 코드는 runAction 함수에서 동작하고자 하는 기능을 작성합니다. +- uploadFile 변수는 s3에 업로드할 파일의 위치입니다. 반드시 파일의 위치와 파일명을 함께 작성해야 합니다. +```bash +uploadFile = "filepath/text.txt" +``` +- 업로드가 완료되면 파일의 URL 값이 반환됩니다. +```bash +result = sdtcloudClient.uploadData(uploadFile) +print(result) +---------- + https:///path/text.txt +``` +- 만약 업로드하는 파일이 PNG 파일이며, URL를 클릭했을 때 이미지가 바로 보이도록 설정하고 싶다면 다음과 같이 수정합니다. +```bash +result = sdtcloudClient.uploadData(uploadFile, {“ContentType”: “image/png”}) +``` +- 이외에 여러 옵션이 있으므로 [참고 페이지](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-uploading-files.html)를 클릭하세요. + + +## mqtt 코드 작성 +- 다음 변수로 메세지를 발행하는 코드를 작성하면... +```bash +msg = { + "message": "Hello World", + "uploadFile": result +} +``` +- 실제로 발행되는 메세지은 다음과 같습니다. +```bash +msg = { + "data": { + "message": "Hello World", + "uploadFile": "https:///path/text.txt" + }, + "timestamp": 12312311... +} + +``` \ No newline at end of file diff --git a/__pycache__/main.cpython-310.pyc b/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000..ba62603 Binary files /dev/null and b/__pycache__/main.cpython-310.pyc differ diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc new file mode 100644 index 0000000..dd5efd4 Binary files /dev/null and b/__pycache__/main.cpython-311.pyc differ diff --git a/data/label_0.jpg b/data/label_0.jpg new file mode 100644 index 0000000..dd34311 Binary files /dev/null and b/data/label_0.jpg differ diff --git a/data/label_1.jpg b/data/label_1.jpg new file mode 100644 index 0000000..4ada439 Binary files /dev/null and b/data/label_1.jpg differ diff --git a/data/test_0.jpg b/data/test_0.jpg new file mode 100644 index 0000000..cfdf425 Binary files /dev/null and b/data/test_0.jpg differ diff --git a/data/test_1.jpg b/data/test_1.jpg new file mode 100644 index 0000000..9631a2d Binary files /dev/null and b/data/test_1.jpg differ diff --git a/data/test_2.jpg b/data/test_2.jpg new file mode 100644 index 0000000..83cc091 Binary files /dev/null and b/data/test_2.jpg differ diff --git a/framework.yaml b/framework.yaml new file mode 100644 index 0000000..f2593b1 --- /dev/null +++ b/framework.yaml @@ -0,0 +1,12 @@ +version: bwc/v2 # bwc 버전 정보입니다. +spec: + appName: request-app # 앱의 이름입니다. + runFile: main.py # 앱의 실행 파일입니다. + env: + bin: python3 # 앱을 실행할 바이너라 파일 종류입니다.(장비에 따라 다르므로 확인 후 정의해야 합니다.) + virtualEnv: request-app-env3 # 사용할 가상환경 이름입니다. + package: requirement.txt # 설치할 Python 패키지 정보 파일입니다.(기본 값은 requirement.txt 입니다.) + runtime: python3.11.4 +stackbase: + tagName: v1.0.1 # Stackbase(gitea)에 릴리즈 태그명 입니다. + repoName: request-app # Stackbase(gitea)에 저장될 저장소 이릅니다. diff --git a/logs/log_request.log b/logs/log_request.log new file mode 100644 index 0000000..60d0e90 --- /dev/null +++ b/logs/log_request.log @@ -0,0 +1,81 @@ +2024-07-04 17:25:24,746 - root - INFO - Successfully requested! +2024-12-02 20:03:09,055 - root - INFO - Successfully requested! +2024-12-02 20:03:09,055 - root - INFO - Successfully requested! +2024-12-02 20:03:22,874 - root - INFO - Successfully requested! +2024-12-02 20:03:22,874 - root - INFO - Successfully requested! +2024-12-02 20:03:56,206 - root - INFO - Successfully requested! +2024-12-02 20:03:56,206 - root - INFO - Successfully requested! +2024-12-02 20:04:12,046 - root - INFO - Successfully requested! +2024-12-02 20:04:12,046 - root - INFO - Successfully requested! +2024-12-02 20:04:18,298 - root - INFO - Successfully requested! +2024-12-02 20:04:18,298 - root - INFO - Successfully requested! +2024-12-02 20:04:18,430 - root - INFO - Successfully requested! +2024-12-02 20:04:18,430 - root - INFO - Successfully requested! +2024-12-02 20:04:18,575 - root - INFO - Successfully requested! +2024-12-02 20:04:18,575 - root - INFO - Successfully requested! +2024-12-02 20:04:18,730 - root - INFO - Successfully requested! +2024-12-02 20:04:18,730 - root - INFO - Successfully requested! +2024-12-02 20:04:18,879 - root - INFO - Successfully requested! +2024-12-02 20:04:18,879 - root - INFO - Successfully requested! +2024-12-02 20:04:19,040 - root - INFO - Successfully requested! +2024-12-02 20:04:19,040 - root - INFO - Successfully requested! +2024-12-02 20:04:19,156 - root - INFO - Successfully requested! +2024-12-02 20:04:19,156 - root - INFO - Successfully requested! +2024-12-02 20:04:19,285 - root - INFO - Successfully requested! +2024-12-02 20:04:19,285 - root - INFO - Successfully requested! +2024-12-02 20:04:19,447 - root - INFO - Successfully requested! +2024-12-02 20:04:19,447 - root - INFO - Successfully requested! +2024-12-02 20:04:19,580 - root - INFO - Successfully requested! +2024-12-02 20:04:19,580 - root - INFO - Successfully requested! +2024-12-02 20:04:19,734 - root - INFO - Successfully requested! +2024-12-02 20:04:19,734 - root - INFO - Successfully requested! +2024-12-02 20:04:19,885 - root - INFO - Successfully requested! +2024-12-02 20:04:19,885 - root - INFO - Successfully requested! +2024-12-02 20:04:20,032 - root - INFO - Successfully requested! +2024-12-02 20:04:20,032 - root - INFO - Successfully requested! +2024-12-02 20:04:20,161 - root - INFO - Successfully requested! +2024-12-02 20:04:20,161 - root - INFO - Successfully requested! +2024-12-02 20:04:20,303 - root - INFO - Successfully requested! +2024-12-02 20:04:20,303 - root - INFO - Successfully requested! +2024-12-02 20:04:20,448 - root - INFO - Successfully requested! +2024-12-02 20:04:20,448 - root - INFO - Successfully requested! +2024-12-03 10:02:40,261 - root - ERROR - Traceback (most recent call last): + File "/home/sdt-dev1/Workspace/kimdy/request-app/main.py", line 72, in upload_image_endpoint + response = await run(request.image_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/sdt-dev1/Workspace/kimdy/request-app/main.py", line 60, in run + response = await upload_image(stub, image_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/sdt-dev1/Workspace/kimdy/request-app/main.py", line 52, in upload_image + response = await stub.UploadImage(pb2.ImageRequest(filename=filename, image_data=image_data)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/etc/sdt/venv/request-app-env3/lib/python3.11/site-packages/grpc/aio/_call.py", line 290, in __await__ + raise _create_rpc_error(self._cython_call._initial_metadata, +grpc.aio._call.AioRpcError: + +2024-12-03 10:02:40,261 - root - ERROR - Traceback (most recent call last): + File "/home/sdt-dev1/Workspace/kimdy/request-app/main.py", line 72, in upload_image_endpoint + response = await run(request.image_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/sdt-dev1/Workspace/kimdy/request-app/main.py", line 60, in run + response = await upload_image(stub, image_path) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/home/sdt-dev1/Workspace/kimdy/request-app/main.py", line 52, in upload_image + response = await stub.UploadImage(pb2.ImageRequest(filename=filename, image_data=image_data)) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + File "/etc/sdt/venv/request-app-env3/lib/python3.11/site-packages/grpc/aio/_call.py", line 290, in __await__ + raise _create_rpc_error(self._cython_call._initial_metadata, +grpc.aio._call.AioRpcError: + +2024-12-03 10:05:07,070 - root - INFO - Successfully requested! +2024-12-03 10:05:07,070 - root - INFO - Successfully requested! +2024-12-03 10:06:08,238 - root - INFO - Successfully requested! +2024-12-03 10:06:08,238 - root - INFO - Successfully requested! diff --git a/logs/test.txt b/logs/test.txt new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..eba93eb --- /dev/null +++ b/main.py @@ -0,0 +1,95 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +import asyncio +import uvicorn + +import uuid +import time +import sdtcloudpubsub +import logging.handlers +import glob +import traceback +# Communication use grpc +import grpc +import grpc.aio +import utils.image_pb2 as pb2 +import utils.image_pb2_grpc as pb2_grpc + +############################################### +# Logger Setting # +############################################### +logger = logging.getLogger() +logger.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + +log_fileHandler = logging.handlers.RotatingFileHandler( + filename=f"./logs/log_request.log", + maxBytes=1024000, + backupCount=3, + mode='a') + +log_fileHandler.setFormatter(formatter) +logger.addHandler(log_fileHandler) + +############################################### +# MQTT Setting # +############################################### +sdtcloud = sdtcloudpubsub.sdtcloudpubsub() +sdtcloud.setClient(f"device-app-{uuid.uuid1()}") # parameter is client ID(string) + +############################################### +# FastAPI instance # +############################################### +app = FastAPI() + +############################################### +# grpc client # +############################################### + +async def upload_image(stub, filename): + with open(filename, 'rb') as f: + image_data = f.read() + response = await stub.UploadImage(pb2.ImageRequest(filename=filename, image_data=image_data)) + print(response.message) + print(f"Inference result: {response.inference_result}") + return response + +async def run(image_path): + async with grpc.aio.insecure_channel('localhost:50051') as channel: + stub = pb2_grpc.ImageServiceStub(channel) + response = await upload_image(stub, image_path) + return response + + +class ImagePathRequest(BaseModel): + image_path: str = "./data/test_1.jpg" + +# FastAPI endpoint +@app.post("/upload_image/") +async def upload_image_endpoint(request: ImagePathRequest): + start_time = time.time() + try: + response = await run(request.image_path) + logger.info('Successfully requested!') + end_time = time.time() + data = { + "processingTime": end_time - start_time + } + + print(f"#### Topic: {sdtcloud.topic}") + print(f"#### DATA: {data}") + sdtcloud.pubReqMessage(data) + + return { + "message": response.message, + "inference_result": response.inference_result + } + except Exception as e: + logger.error(traceback.format_exc()) + raise HTTPException(status_code=500, detail="Internal Server Error") + +if __name__ == '__main__': + uvicorn.run("main:app", host="0.0.0.0", port=8888, reload=True) + + + diff --git a/requirement.txt b/requirement.txt new file mode 100644 index 0000000..8e964e6 --- /dev/null +++ b/requirement.txt @@ -0,0 +1,8 @@ +# Write package's name that need your app. +grpcio==1.56.2 +protobuf==4.25.0 +fastapi +uvicorn +awscrt +awsiotsdk +pyyaml diff --git a/utils/__pycache__/image_pb2.cpython-310.pyc b/utils/__pycache__/image_pb2.cpython-310.pyc new file mode 100644 index 0000000..0c3079b Binary files /dev/null and b/utils/__pycache__/image_pb2.cpython-310.pyc differ diff --git a/utils/__pycache__/image_pb2.cpython-311.pyc b/utils/__pycache__/image_pb2.cpython-311.pyc new file mode 100644 index 0000000..0e4ef9b Binary files /dev/null and b/utils/__pycache__/image_pb2.cpython-311.pyc differ diff --git a/utils/__pycache__/image_pb2_grpc.cpython-310.pyc b/utils/__pycache__/image_pb2_grpc.cpython-310.pyc new file mode 100644 index 0000000..ba48ca3 Binary files /dev/null and b/utils/__pycache__/image_pb2_grpc.cpython-310.pyc differ diff --git a/utils/__pycache__/image_pb2_grpc.cpython-311.pyc b/utils/__pycache__/image_pb2_grpc.cpython-311.pyc new file mode 100644 index 0000000..d05b0e3 Binary files /dev/null and b/utils/__pycache__/image_pb2_grpc.cpython-311.pyc differ diff --git a/utils/image.proto b/utils/image.proto new file mode 100644 index 0000000..1541e26 --- /dev/null +++ b/utils/image.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package image; + +service ImageService { + rpc UploadImage (ImageRequest) returns (ImageResponse); +} + +message ImageRequest{ + string filename =1; + bytes image_data = 2; +} + +message ImageResponse { + string message = 1; + string inference_result = 2; +} + diff --git a/utils/image_pb2.py b/utils/image_pb2.py new file mode 100644 index 0000000..3fc9faf --- /dev/null +++ b/utils/image_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: image.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bimage.proto\x12\x05image\"4\n\x0cImageRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x12\n\nimage_data\x18\x02 \x01(\x0c\":\n\rImageResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x18\n\x10inference_result\x18\x02 \x01(\t2H\n\x0cImageService\x12\x38\n\x0bUploadImage\x12\x13.image.ImageRequest\x1a\x14.image.ImageResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'image_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_IMAGEREQUEST']._serialized_start=22 + _globals['_IMAGEREQUEST']._serialized_end=74 + _globals['_IMAGERESPONSE']._serialized_start=76 + _globals['_IMAGERESPONSE']._serialized_end=134 + _globals['_IMAGESERVICE']._serialized_start=136 + _globals['_IMAGESERVICE']._serialized_end=208 +# @@protoc_insertion_point(module_scope) diff --git a/utils/image_pb2_grpc.py b/utils/image_pb2_grpc.py new file mode 100644 index 0000000..c9f19b1 --- /dev/null +++ b/utils/image_pb2_grpc.py @@ -0,0 +1,66 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +import utils.image_pb2 as image__pb2 + + +class ImageServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.UploadImage = channel.unary_unary( + '/image.ImageService/UploadImage', + request_serializer=image__pb2.ImageRequest.SerializeToString, + response_deserializer=image__pb2.ImageResponse.FromString, + ) + + +class ImageServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def UploadImage(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ImageServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'UploadImage': grpc.unary_unary_rpc_method_handler( + servicer.UploadImage, + request_deserializer=image__pb2.ImageRequest.FromString, + response_serializer=image__pb2.ImageResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'image.ImageService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class ImageService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def UploadImage(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/image.ImageService/UploadImage', + image__pb2.ImageRequest.SerializeToString, + image__pb2.ImageResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata)