first commit
This commit is contained in:
commit
f6edfe2c79
|
@ -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://<s3_bucket>/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://<s3_bucket>/path/text.txt"
|
||||||
|
},
|
||||||
|
"timestamp": 12312311...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 279 KiB |
Binary file not shown.
After Width: | Height: | Size: 357 KiB |
Binary file not shown.
After Width: | Height: | Size: 314 KiB |
Binary file not shown.
After Width: | Height: | Size: 303 KiB |
Binary file not shown.
After Width: | Height: | Size: 318 KiB |
|
@ -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)에 저장될 저장소 이릅니다.
|
|
@ -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: <AioRpcError of RPC that terminated with:
|
||||||
|
status = StatusCode.UNKNOWN
|
||||||
|
details = "Exception calling application: name 'runAction' is not defined"
|
||||||
|
debug_error_string = "UNKNOWN:Error received from peer ipv6:%5B::1%5D:50051 {created_time:"2024-12-03T10:02:40.260169108+09:00", grpc_status:2, grpc_message:"Exception calling application: name \'runAction\' is not defined"}"
|
||||||
|
>
|
||||||
|
|
||||||
|
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: <AioRpcError of RPC that terminated with:
|
||||||
|
status = StatusCode.UNKNOWN
|
||||||
|
details = "Exception calling application: name 'runAction' is not defined"
|
||||||
|
debug_error_string = "UNKNOWN:Error received from peer ipv6:%5B::1%5D:50051 {created_time:"2024-12-03T10:02:40.260169108+09:00", grpc_status:2, grpc_message:"Exception calling application: name \'runAction\' is not defined"}"
|
||||||
|
>
|
||||||
|
|
||||||
|
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!
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue