first commit

This commit is contained in:
2026-08-04 14:30:00 +09:00
commit 4176f04a61
25 changed files with 5294 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
__pycache__/
*.py[cod]
.venv/
venv/
.env

BIN
SDK/jakaAPI.dll Normal file

Binary file not shown.

BIN
SDK/jakaAPI.exp Normal file

Binary file not shown.

BIN
SDK/jakaAPI.lib Normal file

Binary file not shown.

BIN
SDK/jkrc.exp Normal file

Binary file not shown.

BIN
SDK/jkrc.lib Normal file

Binary file not shown.

BIN
SDK/jkrc.pyd Normal file

Binary file not shown.

3257
api_routes.py Normal file

File diff suppressed because it is too large Load Diff

113
app.py Normal file
View File

@@ -0,0 +1,113 @@
from flask import Flask, render_template, request, jsonify
import sys
import os
from api_routes import api_routes
import webview
from waitress import serve
if getattr(sys, 'frozen', False):
template_folder = os.path.join(sys._MEIPASS, 'templates')
app = Flask(__name__, template_folder=template_folder, static_url_path='/static')
else:
app = Flask(__name__, static_url_path='/static')
#app = Flask(__name__, static_url_path='/static')
app.register_blueprint(api_routes) # 분리된 API 라우트 등록
@app.route('/')
def index():
return render_template('layer.html')
@app.route('/progress')
def progess():
return render_template('progress.html')
@app.route('/oneLineProgress')
def oneLineProgess():
return render_template('oneLineProgress.html')
# 종료 함수
def shutdown_waitress_server():
print("Shutting down the server...")
os._exit(0)
def destroy(window):
print('Destroying window..')
window.destroy()
print('Destroyed!')
@app.route('/shutdown', methods=['POST'])
def shutdown():
destroy(test)
shutdown_waitress_server()
return 'Server shutting down'
port=5000
# if __name__ == "__main__":
# app.run(port=port, debug=True)
test = webview.create_window('Palletizing', app)
if __name__ == "__main__":
webview.start()
serve(app, host="0.0.0.0", port=port)
# from flask import Flask, render_template, request
# import sys
# import os
# from api_routes import api_routes
# import webview
# from waitress import serve
# import threading
# # Flask 애플리케이션 설정
# if getattr(sys, 'frozen', False):
# template_folder = os.path.join(sys._MEIPASS, 'templates')
# app = Flask(__name__, template_folder=template_folder, static_url_path='/static')
# else:
# app = Flask(__name__, static_url_path='/static')
# app.register_blueprint(api_routes)
# @app.route('/')
# def index():
# return render_template('layer.html')
# @app.route('/progress')
# def progress():
# return render_template('progress.html')
# @app.route('/oneLineProgress')
# def oneLineProgress():
# return render_template('oneLineProgress.html')
# # 종료 함수
# def shutdown_server():
# print("Shutting down the server...")
# os._exit(0)
# # 종료 라우트 설정
# @app.route('/shutdown', methods=['POST'])
# def shutdown():
# print("Shutdown request received.")
# destroy(test) # Webview 윈도우 종료
# shutdown_server() # Waitress 서버와 프로그램 종료
# return 'Server shutting down...'
# # Webview 윈도우 종료 함수
# def destroy(window):
# print('Destroying window..')
# window.destroy()
# print('Destroyed!')
# # 서버 실행
# port = 5000
# test = webview.create_window('Palletizing', app)
# if __name__ == "__main__":
# # Webview와 Waitress 서버를 별도 스레드로 실행
# server_thread = threading.Thread(target=lambda: serve(app, host="0.0.0.0", port=port))
# server_thread.start()
# webview.start()

133
socket_test.py Normal file
View File

@@ -0,0 +1,133 @@
# import socket
# import time
# def handle_client(client_socket, client_address):
# print(f"Connected by {client_address}")
# with client_socket:
# while True:
# try:
# recv_data = client_socket.recv(1024).decode()
# if not recv_data:
# print(f"Connection closed by {client_address}")
# break
# print(f"Received from {client_address}: {recv_data}")
# if recv_data == 'get <socket_num>':
# send_data = '<socket_num><2>'
# client_socket.send(send_data.encode())
# if recv_data == 'get <socket_str>':
# send_data = '<socket_str><"b">'
# client_socket.send(send_data.encode())
# if recv_data == 'get <socket_arr>':
# send_data = '<socket_arr><[1,1,1,1,1,1]>'
# client_socket.send(send_data.encode())
# if recv_data == 'get #real#6#':
# send_data = '[2,2,2,2,2,2]'
# client_socket.send(send_data.encode())
# if recv_data == 'palletType':
# send_data = '<palletType><1>'
# client_socket.send(send_data.encode())
# except ConnectionResetError as e:
# print(f"Connection closed by {client_address}")
# break
# def start_server(host='0.0.0.0', port=12345):
# server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# server_socket.bind((host, port))
# server_socket.listen()
# server_socket.settimeout(1)
# print(f"Server listening on {host}:{port}")
# try:
# while True:
# try:
# client_socket, client_address = server_socket.accept()
# handle_client(client_socket, client_address)
# except socket.timeout:
# continue
# except KeyboardInterrupt:
# print("Server shutting down...")
# finally:
# server_socket.close()
# if __name__ == "__main__":
# start_server()
import socket
import time
def handle_client(client_socket, client_address):
print(f"Connected by {client_address}")
with client_socket:
while True:
try:
recv_data = client_socket.recv(1024).decode()
if not recv_data:
print(f"Connection closed by {client_address}")
return False # 클라이언트 종료 신호 반환
print(f"Received from {client_address}: {recv_data}")
if recv_data == 'get <socket_num>':
send_data = '<socket_num><2>'
client_socket.send(send_data.encode())
if recv_data == 'get <socket_str>':
send_data = '<socket_str><"b">'
client_socket.send(send_data.encode())
if recv_data == 'get <socket_arr>':
send_data = '<socket_arr><[1,1,1,1,1,1]>'
client_socket.send(send_data.encode())
if recv_data == 'get #real#6#':
send_data = '[2,2,2,2,2,2]'
client_socket.send(send_data.encode())
if recv_data == 'palletType':
send_data = '<palletType><1>'
client_socket.send(send_data.encode())
except ConnectionResetError:
print(f"Connection closed by {client_address}")
return False # 클라이언트 종료 신호 반환
return True # 클라이언트 정상 처리 완료 신호 반환
def start_server(host='0.0.0.0', port=12345):
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((host, port))
server_socket.listen()
server_socket.settimeout(1)
print(f"Server listening on {host}:{port}")
try:
while True:
try:
client_socket, client_address = server_socket.accept()
connection_active = handle_client(client_socket, client_address)
if not connection_active:
print("Client disconnected. Shutting down server...")
break # 루프 종료
except socket.timeout:
continue
except KeyboardInterrupt:
print("Server shutting down...")
finally:
server_socket.close()
print("Server socket closed.")
if __name__ == "__main__":
start_server()

258
static/css/layer.css Normal file
View File

@@ -0,0 +1,258 @@
.container {
margin: 20px;
border: solid 1px black;
}
main {
display: grid;
grid-template-columns: 1fr 1.5fr 1fr;
}
.leftContainer {
display: flex;
flex-direction: column;
align-items: center;
border: solid 1px black;
}
.leftbuttonBox {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.leftContainer button {
margin-top: 20px;
width: 200px;
height: 72px;
}
.leftContainer button.selected {
color: rgb(54, 54, 54);
background-color: rgb(61, 230, 69);
}
.middleContainer {
display: grid;
grid-template-rows: auto;
border: solid 1px black;
}
.middleBoxContainer {
margin-bottom: 20px;
}
.buttonBox {
display: flex;
flex-direction: column;
margin-top: 30px;
}
.buttonBox button {
padding: 15px 20px;
font-size: 32px;
}
.buttonWithImage {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.buttonWithImage img {
width: 140px;
height: 140px;
margin-right: 10px;
margin-left: 17px;
}
.buttonBox button.selected {
color: rgb(54, 54, 54);
background-color: rgb(61, 230, 69);
}
.rightContainer {
display: grid;
grid-template-rows: 60px 60px 238px 60px 60px;
border: solid 1px black;
}
button {
margin: 0 20px;
margin-bottom: 10px;
padding: 10px 20px;
font-size: 32px;
font-weight: bold;
color: rgb(54, 54, 54);
background-color: #3396ff;
border: solid 2px black;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #0056b3;
}
#sendPaletteInfo {
margin: 0;
width: 100%;
height: 72px;
margin-bottom: 20px;
background-color: #3396ff;
}
#startRobotButton {
margin: 0;
width: 100%;
height: 72px;
margin-bottom: 55px;
margin-top: 10px;
padding: 10px 15px;
background-color: #3396ff;
}
#startRobotButton:disabled {
background-color: gray;
color: white;
cursor: not-allowed;
}
#woodenFlatPallet:disabled {
background-color: gray;
color: white;
cursor: not-allowed;
}
#exitAppButton {
margin: 0;
width: 100%;
color: rgb(54, 54, 54);
background-color: rgb(252, 101, 101);
padding: 10px;
cursor: pointer;
font-size: 22px;
}
#resetButton {
margin: 0;
width: 100%;
color: rgb(54, 54, 54);
background-color: rgb(252, 169, 101);
padding: 10px;
cursor: pointer;
font-size: 22px;
}
#bookHeight {
width: 100px;
height: 40px;
margin-top: 10px;
padding: 10px;
font-size: 19px;
border: 1px solid #ccc;
border-radius: 5px;
}
#palletHeight {
width: 180px;
height: 40px;
margin-top: 10px;
padding: 10px;
font-size: 19px;
border: 1px solid #ccc;
border-radius: 5px;
}
.inputContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.inputBox {
display: flex;
}
p {
display: flex;
justify-content: center;
align-items: center;
width: 60px;
height: 40px;
margin-top: 10px;
padding: 10px;
font-size: 19px;
border: 1px solid #ccc;
border-radius: 5px;
}
#bookHeight::placeholder,
#palletHeight::placeholder {
color: #999;
}
#bookHeightTitle,
#palletHeightTitle {
margin-top: 20px;
font-size: 19px;
font-weight: 600;
}
html, body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
justify-content: center;
align-items: center;
position: relative;
overflow: hidden;
}
#alert {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.3);
position: absolute;
justify-content: center;
display: none;
}
#alertBox{
width: 80%;
height: 50%;
background-color: white;
display: flex;
align-self: center;
border-radius: 10px;
border:2px solid black;
justify-content: center;
align-items: center;
flex-direction: column;
}
#loader {
border: 16px solid #f3f3f3; /* Light gray */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 100px;
height: 100px;
animation: spin 1s linear infinite;
margin: 10px auto; /* Center the loader */
}
#alert_msg {
font-size: 32px;
font-weight: bold;
color: rgb(54, 54, 54);
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@@ -0,0 +1,249 @@
.container {
margin: 20px;
border: solid 1px black;
}
main {
display: grid;
grid-template-columns: 2fr 1.5fr;
}
.leftContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: solid 1px black;
min-height: 90vh;
}
.gridContainer {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
width: 100%;
max-width: 380px;
margin: 0 auto;
margin-top: 0px;
}
.gridItem {
width: 180px;
height: 120px;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
cursor: pointer;
font-size: 45px;
font-weight: 600;
}
.gridItem.selected {
background-color: #34b800;
color: #fff;
}
.gridItem.disabled {
pointer-events: none;
opacity: 0.5; /* 비활성화 상태를 시각적으로 나타내기 위해 투명도를 줄입니다 */
}
.buttonContainer {
margin-top: 10px;
display: flex;
flex-direction: column;
align-items: center;
}
.buttonContainer button {
width: 330px;
height: 72px;
margin-top: 10px;
}
.buttonContainer button:disabled {
background-color: gray;
color: white;
cursor: not-allowed;
}
.rightContainer {
border: solid 1px black;
display: grid;
grid-template-rows: repeat(4, 60px);
}
#finishAction {
margin: 0;
width: 100%;
height: 70px;
}
#terminateAction {
margin: 0;
width: 100%;
height: 70px;
background-color: #ff3c00;
}
#currentLayerIndexDisplay {
font-weight: 600;
font-size: 23px;
}
button {
margin: 0 20px;
margin-bottom: 10px;
padding: 10px 20px;
font-size: 37px;
font-weight: 700;
color: rgb(54, 54, 54);
background-color: #3396ff;
border: solid 20x black;
cursor: pointer;
transition: background-color 0.3s ease;
}
#exitAppButton {
width: 100%;
margin: 0;
height: 60px;
border-radius: 0px;
color: rgb(54, 54, 54);
background-color: rgb(252, 101, 101);
font-size: 22px;
border: solid 1px black;
}
.buttonBox {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.buttonBox button {
margin: 0;
height: 60px;
width: 50%;
border-radius: 0px;
font-size: 22px;
border: solid 1px black;
}
#stackerStartButton,
#upDownOnButton,
#leftRightOnButton {
color: rgb(54, 54, 54);
background-color: rgb(61, 230, 69);
}
#stackerStopButton,
#upDownOffButton,
#leftRightOffButton {
color: rgb(54, 54, 54);
background-color: rgb(252, 169, 101);
}
.inputContainer {
display: flex;
flex-direction: column;
justify-content: end;
align-items: center;
}
.inputBox {
display: flex;
}
p {
display: flex;
justify-content: center;
align-items: center;
width: 60px;
height: 35px;
margin-top: 10px;
padding: 10px;
font-size: 19px;
border: 1px solid #ccc;
border-radius: 5px;
}
span {
font-size: 21px;
font-weight: 600;
}
#bookHeight {
width: 100px;
height: 35px;
margin-top: 10px;
padding: 10px;
font-size: 19px;
border: 1px solid #ccc;
border-radius: 5px;
}
#bookHeight::placeholder {
color: #999;
}
#bookHeightTitle {
margin-top: 10px;
font-size: 19px;
font-weight: 600;
}
/* 팝업을 화면 중앙에 표시하기 위한 스타일 */
#bookCountPopup {
display: none; /* 기본적으로 보이지 않음 */
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* 창을 중앙에 위치하게 함 */
width: 300px;
padding: 20px;
background-color: white;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
border-radius: 8px;
z-index: 1001;
text-align: center;
}
/* 배경을 어둡게 처리 */
#popupOverlay {
display: none; /* 기본적으로 보이지 않음 */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
/* 제출 버튼 스타일 */
#submitBookCount {
margin-top: 15px;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#submitBookCount:hover {
background-color: #45a049;
}
/* 입력 필드 스타일 */
#bookCountInput {
margin-top: 10px;
padding: 8px;
width: 80%;
border: 1px solid #ccc;
border-radius: 5px;
}

109
static/css/progress.css Normal file
View File

@@ -0,0 +1,109 @@
.container {
margin: 0;
margin: 20px;
border: solid 1px black;
}
main {
display: grid;
grid-template-columns: 2fr 1fr;
}
.leftContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 90vh;
border: solid 1px black;
}
.rightContainer {
border: solid 1px black;
}
.msg {
margin-top: 70px;
font-size: 45px;
font-weight: 700;
}
#finishAction {
margin-top: 20px;
width: 343px;
height: 74px;
}
#terminateAction {
margin-top: 25px;
width: 343px;
height: 74px;
background-color: rgb(252, 101, 101);
}
button {
margin: 0 20px;
margin-bottom: 10px;
padding: 10px 20px;
font-size: 38px;
font-weight: 700;
color: rgb(54, 54, 54);
background-color: #3396ff;
border: solid 2px black;
cursor: pointer;
transition: background-color 0.3s ease;
}
p {
font-size: 30px;
font-weight: 600;
}
span {
font-size: 30px;
font-weight: 600;
}
.rightContainer {
display: grid;
grid-template-rows: repeat(4, 60px);
}
.buttonBox {
display: flex;
height: 70px;
}
#exitAppButton {
width: 100%;
margin: 0;
height: 60px;
border-radius: 0px;
color: rgb(54, 54, 54);
background-color: rgb(252, 101, 101);
font-size: 22px;
border: solid 1px black;
}
.buttonBox button {
margin: 0;
height: 60px;
width: 16vw;
border-radius: 0px;
font-size: 22px;
border: solid 1px black;
}
#stackerStartButton,
#upDownOnButton,
#leftRightOnButton {
color: rgb(54, 54, 54);
background-color: rgb(61, 230, 69);
}
#stackerStopButton,
#upDownOffButton,
#leftRightOffButton {
color: rgb(54, 54, 54);
background-color: rgb(252, 169, 101);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

275
static/js/layer.js Normal file
View File

@@ -0,0 +1,275 @@
const bookCenterPosition_Wheel = [
{ x: 450.5, y: 105 },
{ x: 450.5, y: 320 },
{ x: 148.5, y: 105 },
{ x: 148.5, y: 320 },
];
const bookCenterPosition_Flat = [
{ x: 750, y: 148.5 },
{ x: 535, y: 148.5 },
{ x: 320, y: 148.5 },
{ x: 105, y: 148.5 },
{ x: 750, y: 450.5 },
{ x: 535, y: 450.5 },
{ x: 320, y: 450.5 },
{ x: 105, y: 450.5 },
];
const woodenWheelPallet = {
palletWidth: 620,
palletLength: 442,
palletHeight: 653,
bookCenterPosition_Wheel,
};
const ironWheelPallet = {
palletWidth: 620,
palletLength: 442,
palletHeight: 633,
bookCenterPosition_Wheel,
};
const woodenFlatPallet = {
palletWidth: 878,
palletLength: 638,
palletHeight: 148,
bookCenterPosition_Flat,
};
const pallets = {
woodenWheelPallet,
ironWheelPallet,
woodenFlatPallet,
};
document.addEventListener('DOMContentLoaded', function () {
const sendPaletteInfoButton = document.getElementById('sendPaletteInfo');
const startRobotMove = document.getElementById('startRobotButton');
const resetButton = document.getElementById('resetButton');
const exitAppButton = document.getElementById('exitAppButton');
const palletHeightInput = document.getElementById('palletHeight');
const palletHeightTitle = document.getElementById('palletHeightTitle');
const stackingButtons = document.querySelectorAll('.leftbuttonBox button');
let stackingSelected = false;
const palletButtons = document.querySelectorAll('.buttonBox button');
let selectedPalletId = null;
const woodenFlatPalletButton = document.getElementById('woodenFlatPallet');
let isStartclicked = false;
function customAlert(msg) {
const alert = document.getElementById('alert');
const text = document.getElementById('alert_msg');
if (msg) {
alert.style.display = 'flex';
text.innerText = msg;
} else {
alert.style.display = 'none';
text.innerText = '';
}
}
function logAction(message) {
fetch('/writelog', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: message,
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to write log.');
}
return response.text();
})
.then((data) => {
console.log(`Log result: ${data}`);
})
.catch((error) => console.error('Error writing log:', error));
}
stackingButtons.forEach((button) => {
button.addEventListener('click', () => {
stackingButtons.forEach((btn) => btn.classList.remove('selected'));
button.classList.add('selected');
stackingSelected = true;
stackingButtons.forEach((btn) => btn.classList.remove('disabled'));
// pallet 버튼의 선택 상태를 해제
palletButtons.forEach((btn) => {
btn.classList.remove('selected');
btn.disabled = false; // pallet 버튼을 활성화 상태로 되돌림
});
selectedPalletId = null;
sendPaletteInfoButton.disabled = true;
if (button.id === 'oneLineButton') {
woodenFlatPalletButton.classList.add('disabled');
woodenFlatPalletButton.disabled = true;
palletHeightInput.style.display = 'none';
palletHeightTitle.style.display = 'none';
} else if (button.id === 'normalButton') {
woodenFlatPalletButton.classList.remove('disabled');
woodenFlatPalletButton.disabled = false;
}
logAction(`FE : Stacking button ${button.id} clicked - layer.js`);
});
});
palletButtons.forEach((button) => {
button.addEventListener('click', () => {
if (!stackingSelected) {
alert('⛔ 쌓기 버튼을 먼저 선택해주세요!');
return;
}
palletButtons.forEach((btn) => btn.classList.remove('selected'));
button.classList.add('selected');
selectedPalletId = button.id;
sendPaletteInfoButton.disabled = false;
if (selectedPalletId === 'woodenFlatPallet') {
palletHeightInput.style.display = 'block';
palletHeightTitle.style.display = 'block';
} else {
palletHeightInput.style.display = 'none';
palletHeightTitle.style.display = 'none';
}
logAction(`FE : Pallet button ${button.id} clicked - layer.js`);
});
});
sendPaletteInfoButton.addEventListener('click', function () {
let palletHeight = pallets[selectedPalletId].palletHeight; // 기본 높이를 사용
if (selectedPalletId === 'woodenFlatPallet') {
palletHeight = parseFloat(palletHeightInput.value);
if (isNaN(palletHeight) || palletHeight <= 0) {
alert('⛔ 유효한 팔레트 높이를 입력해주세요!');
return;
}
}
if (selectedPalletId) {
const selectedPallet = pallets[selectedPalletId];
selectedPallet.palletHeight = palletHeight; // update the pallet height
fetch('/update_pallet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(selectedPallet),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send palette info.');
}
return response.text();
})
.then((data) => {
if (data == 'Success') {
startRobotMove.disabled = false;
sendPaletteInfoButton.style.backgroundColor = '#34b800';
isStartclicked = false;
logAction('FE : update_pallet api sent successfully - layer.js');
}
})
.catch((error) => console.error('Error sending palette info:', error));
} else {
alert('⛔ 팔레트를 선택해주세요!');
}
});
startRobotMove.addEventListener('click', function () {
logAction('FE : startRobotMove button clicked - layer.js');
if (selectedPalletId) {
let confirmResult = confirm('로봇 동작을 시작하시겠습니까?');
if (confirmResult && isStartclicked == false) {
isStartclicked = true;
const selectedPallet = pallets[selectedPalletId];
const selectedStackingButton = document.querySelector(
'.leftbuttonBox .selected'
);
let btnId = '';
if (selectedStackingButton.id === 'normalButton') {
btnId = 'normalButton';
window.location.href = '/progress';
} else if (selectedStackingButton.id === 'oneLineButton') {
btnId = 'oneLineButton';
window.location.href = '/oneLineProgress';
}
const data = { selectedPallet, btnId, selectedPalletId };
customAlert('로봇 동작을 시작합니다...');
fetch('/robot_start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
// 지금 현재 상황 (2024.10.10 문제점 : .then아래로 안내려옴 문제 해결 필요 시급)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send palette info.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : robot_start function(server) finish successfully - layer.js'
);
})
.catch((error) =>
console.error('Error sending palette info:', error)
);
}
} else {
alert('⛔ 팔레트를 선택해주세요!');
}
});
resetButton.addEventListener('click', function () {
fetch('/reset_data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'Reset button clicked' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send reset data.');
}
return response.text();
})
.then((data) => {
if (data == 'Success') {
logAction('FE : reset_data api sent successfully - layer.js');
}
})
.catch((error) => console.error('Error sending reset data:', error));
});
exitAppButton.addEventListener('click', function () {
fetch('/shutdown', {
method: 'POST',
})
.then((response) => response.text())
.then((data) => {
console.log(data);
logAction('FE : shutdown api sent successfully - layer.js');
})
.catch((error) => console.error('Error shutting down:', error));
});
});

View File

@@ -0,0 +1,418 @@
document.addEventListener('DOMContentLoaded', function () {
const terminateAction = document.getElementById('terminateAction');
const finishAction = document.getElementById('finishAction');
const gridItems = document.querySelectorAll('.gridItem');
const runButton = document.getElementById('run');
const stopButton = document.getElementById('stop');
const exitAppButton = document.getElementById('exitAppButton');
const stackerStartButton = document.getElementById('stackerStartButton');
const stackerStopButton = document.getElementById('stackerStopButton');
const upDownOnButton = document.getElementById('upDownOnButton');
const upDownOffButton = document.getElementById('upDownOffButton');
const leftRightOnButton = document.getElementById('leftRightOnButton');
const leftRightOffButton = document.getElementById('leftRightOffButton');
let selectedGridId = null; // 선택된 그리드 ID
let intervalId = null; // setInterval ID
let isRunning = false; // 한줄쌓기 실행 상태
// 초기 상태 설정
stopButton.disabled = true;
runButton.disabled = true;
function logAction(message) {
fetch('/writelog', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: message,
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to write log.');
}
return response.text();
})
.then((data) => {
console.log(`Log result: ${data}`);
})
.catch((error) => console.error('Error writing log:', error));
}
// JSON 파일에서 데이터 로드
function fetchJsonData() {
fetch('/static/json/oneLine.json')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
updateGridStatus(data);
})
.catch((error) => console.error('Error loading JSON:', error));
}
// 초기 데이터 로드
fetchJsonData();
// 그리드 상태 업데이트 함수
function updateGridStatus(data) {
const grids = ['grid1', 'grid2', 'grid3', 'grid4'];
// 그리드 비활성화 초기화
gridItems.forEach((item) => item.classList.remove('disabled'));
grids.forEach((grid) => {
if (data[grid].currentLayerHeight > 720) {
document.getElementById(grid).classList.add('disabled');
}
});
if (data.grid4.currentLayerHeight > data.grid2.currentLayerHeight) {
document.getElementById('grid2').classList.add('disabled');
}
if (data.grid3.currentLayerHeight > data.grid1.currentLayerHeight) {
document.getElementById('grid1').classList.add('disabled');
}
// 선택된 그리드의 currentLayerIndex 출력
if (selectedGridId) {
const selectedGridData = data[selectedGridId];
document.getElementById(
'currentLayerIndexDisplay'
).textContent = `현재 쌓고 있는 단 : ${
selectedGridData.currentLayerIndex + 1
}`;
}
// 한줄쌓기 실행 중인 경우 그리드 비활성화 유지
if (isRunning) {
toggleGridItems(true);
}
}
// 그리드 아이템 클릭 핸들러
function handleGridItemClick(event) {
const item = event.currentTarget;
if (item.classList.contains('disabled')) {
return; // 클릭 이벤트 무시
}
gridItems.forEach((btn) => btn.classList.remove('selected'));
item.classList.add('selected');
selectedGridId = item.id;
checkRunButtonState();
}
gridItems.forEach((item) => {
item.addEventListener('click', handleGridItemClick);
});
// 한줄쌓기 실행 버튼 클릭 핸들러
runButton.addEventListener('click', function () {
runButton.disabled = true;
stopButton.disabled = false;
isRunning = true;
toggleGridItems(true);
intervalId = setInterval(fetchJsonData, 5000);
fetch('/one_line_run', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'one_line_run',
gridId: selectedGridId, // 선택된 그리드 아이템의 ID 전송
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('one_line_run failed.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : one_line_run api sent successfully - oneLineProgress.js'
);
})
.catch((error) =>
console.error('Error sending one_line_run action:', error)
);
});
// 한줄쌓기 멈춤 버튼 클릭 핸들러
stopButton.addEventListener('click', function () {
runButton.disabled = false;
stopButton.disabled = true;
isRunning = false;
toggleGridItems(false);
clearGridSelection();
// JSON 데이터 주기적으로 가져오기 중지
clearInterval(intervalId);
// 멈춤 버튼 클릭 시 JSON 데이터 다시 로드
fetchJsonData();
fetch('/one_line_stop', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'one_line_stop' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('one_line_stop failed.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : one_line_stop api sent successfully - oneLineProgress.js'
);
})
.catch((error) =>
console.error('Error sending one_line_stop action:', error)
);
});
// 한줄쌓기 실행 버튼 활성화 여부 확인 함수
function checkRunButtonState() {
const anyGridSelected = Array.from(gridItems).some((item) =>
item.classList.contains('selected')
);
runButton.disabled = !anyGridSelected;
}
// 그리드 아이템 활성화/비활성화 함수
function toggleGridItems(disable) {
gridItems.forEach((item) => item.classList.toggle('disabled', disable));
}
// 그리드 아이템 선택 해제 함수
function clearGridSelection() {
gridItems.forEach((item) => item.classList.remove('selected'));
checkRunButtonState(); // 선택 해제 후 버튼 상태 재확인
}
finishAction.addEventListener('click', function () {
finishAction.disabled = true;
fetch('/robot_immediately_take_book', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
immediately_take_book: 'robot_immediately_take_book',
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('immediately_take_book failed.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : robot_immediately_take_book api sent successfully - oneLineProgress.js'
);
})
.catch((error) =>
console.error('Error Sending robot finish action:', error)
)
.finally(() => {
setTimeout(() => {
finishAction.disabled = false;
}, 10000);
});
});
terminateAction.addEventListener('click', function () {
fetch('/robot_finish_action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'robot_finish_action' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send robot finish action.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : terminateAction api sent successfully - oneLineProgress.js'
);
window.location.href = '/';
})
.catch((error) =>
console.error('Error sending robot finish action:', error)
);
});
exitAppButton.addEventListener('click', function () {
fetch('/shutdown', {
method: 'POST',
})
.then((response) => response.text())
.then((data) => {
console.log(data);
logAction('FE : shutdown api sent successfully - oneLineProgress.js');
})
.catch((error) => console.error('Error shutting down:', error));
});
stackerStartButton.addEventListener('click', function () {
fetch('/stacker_start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'stacker_start' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send stacker start.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : stacker_start api sent successfully - oneLineProgress.js'
);
})
.catch((error) => console.error('Error sending stacker start:', error));
});
stackerStopButton.addEventListener('click', function () {
fetch('/stacker_stop', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'stacker_stop' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send stacker stop.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : stacker_stop api sent successfully - oneLineProgress.js'
);
})
.catch((error) => console.error('Error sending stacker stop:', error));
});
upDownOnButton.addEventListener('click', function () {
fetch('/updown_on', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'updown_on' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send updown on');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : updown_on api sent successfully - oneLineProgress.js');
})
.catch((error) => console.error('Error sending updown on:', error));
});
upDownOffButton.addEventListener('click', function () {
fetch('/updown_off', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'updown_off' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send updown off');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : updown_off api sent successfully - oneLineProgress.js');
})
.catch((error) => console.error('Error sending updown off:', error));
});
leftRightOnButton.addEventListener('click', function () {
fetch('/leftright_on', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'leftright_on' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send leftright on');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : leftright_on api sent successfully - oneLineProgress.js'
);
})
.catch((error) => console.error('Error sending leftright on:', error));
});
leftRightOffButton.addEventListener('click', function () {
fetch('/leftright_off', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'leftright_off' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send leftright off');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : leftright_off api sent successfully - oneLineProgress.js'
);
})
.catch((error) => console.error('Error sending leftright off:', error));
});
});

255
static/js/progress.js Normal file
View File

@@ -0,0 +1,255 @@
document.addEventListener('DOMContentLoaded', function () {
const terminateAction = document.getElementById('terminateAction');
const finishAction = document.getElementById('finishAction');
const exitAppButton = document.getElementById('exitAppButton');
const stackerStartButton = document.getElementById('stackerStartButton');
const stackerStopButton = document.getElementById('stackerStopButton');
const upDownOnButton = document.getElementById('upDownOnButton');
const upDownOffButton = document.getElementById('upDownOffButton');
const leftRightOnButton = document.getElementById('leftRightOnButton');
const leftRightOffButton = document.getElementById('leftRightOffButton');
function logAction(message) {
fetch('/writelog', {
method: 'POST',
headers: {
'Content-Type': 'text/plain',
},
body: message,
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to write log.');
}
return response.text();
})
.then((data) => {
console.log(`Log result: ${data}`);
})
.catch((error) => console.error('Error writing log:', error));
}
// 인덱스 표시 업데이트 함수
function updateIndexDisplay() {
fetch('/static/json/cache.json')
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => {
document.getElementById('currentDataIndex').textContent =
data.currentDataIndex + 1;
document.getElementById('currentLayerIndex').textContent =
data.currentLayerIndex + 1;
console.log(data.currentDataIndex, data.currentLayerIndex);
})
.catch((error) => console.error('Error loading cache.json:', error));
}
// 페이지 로드 시 초기 인덱스 표시 업데이트
updateIndexDisplay();
// 일정 시간마다 인덱스 표시 업데이트
intervalId = setInterval(updateIndexDisplay, 5000); // 5초마다 업데이트
finishAction.addEventListener('click', function () {
finishAction.disabled = true;
fetch('/robot_immediately_take_book', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
immediately_take_book: 'robot_immediately_take_book',
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('immediately_take_book failed.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction(
'FE : robot_immediately_take_book api sent successfully - progress.js'
);
})
.catch((error) =>
console.error('Error Sending robot finish action:', error)
).finally(() => {
setTimeout(() => {
finishAction.disabled = false;
}, 10000);
})
});
terminateAction.addEventListener('click', function () {
fetch('/robot_finish_action', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'robot_finish_action' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send robot finish action.');
}
return response.text();
})
.then((data) => {
console.log(data);
clearInterval(intervalId);
logAction(
'FE : robot_finish_action api sent successfully - progress.js'
);
window.location.href = '/';
})
.catch((error) =>
console.error('Error sending robot finish action:', error)
);
});
exitAppButton.addEventListener('click', function () {
fetch('/shutdown', {
method: 'POST',
})
.then((response) => response.text())
.then((data) => {
console.log(data);
logAction('FE : shutdown api sent successfully - progress.js');
})
.catch((error) => console.error('Error shutting down:', error));
});
stackerStartButton.addEventListener('click', function () {
fetch('/stacker_start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'stacker_start' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send stacker start.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : stacker_start api sent successfully - progress.js');
})
.catch((error) => console.error('Error sending stacker start:', error));
});
stackerStopButton.addEventListener('click', function () {
fetch('/stacker_stop', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'stacker_stop' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send stacker stop.');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : stacker_stop api sent successfully - progress.js');
})
.catch((error) => console.error('Error sending stacker stop:', error));
});
upDownOnButton.addEventListener('click', function () {
fetch('/updown_on', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'updown_on' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send updown on');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : updown_on api sent successfully - progress.js');
})
.catch((error) => console.error('Error sending updown on:', error));
});
upDownOffButton.addEventListener('click', function () {
fetch('/updown_off', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'updown_off' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send updown off');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : updown_off api sent successfully - progress.js');
})
.catch((error) => console.error('Error sending updown off:', error));
});
leftRightOnButton.addEventListener('click', function () {
fetch('/leftright_on', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'leftright_on' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send leftright on');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : leftright_on api sent successfully - progress.js');
})
.catch((error) => console.error('Error sending leftright on:', error));
});
leftRightOffButton.addEventListener('click', function () {
fetch('/leftright_off', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'leftright_off' }),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to send leftright off');
}
return response.text();
})
.then((data) => {
console.log(data);
logAction('FE : leftright_off api sent successfully - progress.js');
})
.catch((error) => console.error('Error sending leftright off:', error));
});
});

5
static/json/cache.json Normal file
View File

@@ -0,0 +1,5 @@
{
"currentDataIndex": 0,
"currentLayerIndex": 0,
"currentLayerHeight": 0
}

3
static/json/height.json Normal file
View File

@@ -0,0 +1,3 @@
{
"measuredHeight": 0
}

22
static/json/oneLine.json Normal file
View File

@@ -0,0 +1,22 @@
{
"grid1": {
"currentDataIndex": 0,
"currentLayerIndex": 0,
"currentLayerHeight": 0
},
"grid2": {
"currentDataIndex": 1,
"currentLayerIndex": 0,
"currentLayerHeight": 0
},
"grid3": {
"currentDataIndex": 2,
"currentLayerIndex": 0,
"currentLayerHeight": 0
},
"grid4": {
"currentDataIndex": 3,
"currentLayerIndex": 0,
"currentLayerHeight": 0
}
}

86
templates/layer.html Normal file
View File

@@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>레이어 관리</title>
<link rel="stylesheet" href="../static/css/layer.css" />
</head>
<body>
<div id="alert">
<div id="alertBox">
<div id="loader"></div>
<span id="alert_msg"></span>
</div>
</div>
<div class="container">
<main>
<div class="leftContainer">
<div class="leftbuttonBox">
<button id="normalButton">일반 쌓기</button>
<button id="oneLineButton">한줄 쌓기</button>
<div class="inputContainer">
<div id="palletHeightTitle" style="display: none">
납작 팔레트 높이
</div>
<input
type="number"
id="palletHeight"
placeholder="mm"
style="display: none"
/>
<div id="palletHeightTitle" style="display: none">
팔레트 높이
</div>
<input
type="number"
id="palletHeight"
placeholder="mm"
style="display: none"
/>
</div>
</div>
</div>
<div class="middleContainer">
<div class="middleBoxContainer">
<div class="buttonBox">
<div class="buttonWithImage">
<img
src="../static/img/wooden_wheel_pallet.png"
alt="나무 바퀴 팔레트"
/>
<button id="woodenWheelPallet">나무 바퀴</button>
</div>
<div class="buttonWithImage">
<img
src="../static/img/iron_wheel_pallet.png"
alt="철제 바퀴 팔레트"
/>
<button id="ironWheelPallet">철제 바퀴</button>
</div>
<div class="buttonWithImage">
<img
src="../static/img/wooden_flat_pallet.png"
alt="나무 납작 팔레트"
/>
<button id="woodenFlatPallet">나무 납작</button>
</div>
</div>
</div>
</div>
<div class="rightContainer">
<button id="exitAppButton">앱 종료</button>
<button id="resetButton">데이터 초기화</button>
<div></div>
<button id="sendPaletteInfo">데이터 저장</button>
<button id="startRobotButton" disabled>로봇 동작 시작</button>
</div>
</main>
</div>
<script src="../static/js/layer.js"></script>
</body>
</html>

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>로봇 동작 중</title>
<link rel="stylesheet" href="../static/css/oneLineProgress.css" />
</head>
<body>
<div class="container">
<main>
<div class="leftContainer">
<div class="gridContainer">
<div class="gridItem" id="grid2">2</div>
<div class="gridItem" id="grid4">4</div>
<div class="gridItem" id="grid1">1</div>
<div class="gridItem" id="grid3">3</div>
</div>
<div class="buttonContainer">
<div id="currentLayerIndexDisplay">현재 쌓고 있는 단 :</div>
<button id="run">한줄 쌓기 실행</button>
<button id="stop">한줄 쌓기 멈춤</button>
</div>
</div>
<div class="rightContainer">
<div class="buttonBox">
<button id="exitAppButton">앱 종료</button>
</div>
<div class="buttonBox">
<button id="stackerStartButton">스태커 시작</button>
<button id="stackerStopButton">스태커 정지</button>
</div>
<div class="buttonBox">
<button id="upDownOnButton">올림</button>
<button id="upDownOffButton">내림</button>
</div>
<div class="buttonBox">
<button id="leftRightOnButton">닫힘</button>
<button id="leftRightOffButton">열림</button>
</div>
<div class="inputContainer">
<button id="finishAction">바로 책 집어가기</button>
<button id="terminateAction">로봇 동작 종료</button>
</div>
</div>
</main>
</div>
</body>
<script src="../static/js/oneLineProgress.js"></script>
</html>

51
templates/progress.html Normal file
View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>로봇 동작 중</title>
<link rel="stylesheet" href="../static/css/progress.css" />
</head>
<body>
<div class="container">
<main>
<div class="leftContainer">
<div class="msg">로봇이 움직이는 중입니다.</div>
<div id="indexDisplay">
<p>
현재 <span id="currentLayerIndex">0</span>
<span id="currentDataIndex">0</span>위치
</p>
<!-- <P>책 높이 : <span id="currentSensorHeight">0</span></P> -->
</div>
<button id="finishAction">바로 책 집어가기</button>
<button id="terminateAction">로봇 동작 종료</button>
</div>
<div class="rightContainer">
<div class="buttonBox">
<button id="exitAppButton">앱 종료</button>
</div>
<div class="buttonBox">
<button id="stackerStartButton">스태커 시작</button>
<button id="stackerStopButton">스태커 정지</button>
</div>
<div class="buttonBox">
<button id="upDownOnButton">올림</button>
<button id="upDownOffButton">내림</button>
</div>
<div class="buttonBox">
<button id="leftRightOnButton">닫힘</button>
<button id="leftRightOffButton">열림</button>
</div>
</div>
</main>
</div>
</body>
<script src="../static/js/progress.js"></script>
</html>