Compare commits

...

2 Commits

Author SHA1 Message Date
backuppc
efab3d042c ... 2026-01-08 17:37:04 +09:00
backuppc
b84f8c7d2d ,.. 2026-01-08 17:36:34 +09:00
6 changed files with 1217 additions and 954 deletions

View File

@@ -45,9 +45,13 @@ namespace AGVEmulator
{ {
if (checkBox1.Checked) if (checkBox1.Checked)
this.trackBar1.Invoke(new Action(() => this.trackBar1.Invoke(new Action(() =>
{
if (this.trackBar1.Value > 0)
{ {
this.trackBar1.Value -= 1; this.trackBar1.Value -= 1;
trackBar1_Scroll(null, null); trackBar1_Scroll(null, null);
}
})); }));
e.CurA = (int)BMS_CurA; e.CurA = (int)BMS_CurA;

1030
NewMap.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,948 +0,0 @@
"""
AGV 종합 분석 리포트 생성 스크립트
- BMS 배터리 데이터 분석 (0x03: 배터리 상태, 0x04: 셀 전압)
- 상차작업완료 집계
- 충전상태전환 이벤트 분석
- 셀 전압 불균형 분석
- 시간대별 종합 리포트 및 엑셀 차트 생성
"""
import re
from datetime import datetime, timedelta
from collections import defaultdict
import pandas as pd
from openpyxl import load_workbook
from openpyxl.chart import LineChart, Reference, BarChart, AreaChart
from openpyxl.styles import Font, Alignment, PatternFill
import os
import glob
print("=" * 80)
print("AGV 종합 분석 리포트 생성 (셀 전압 분석 포함)")
print("=" * 80)
# ============================================================================
# 1. BMS 배터리 데이터 파싱 (0x03: 배터리 상태)
# ============================================================================
def parse_bms_packet(hex_string):
"""BMS 배터리 상태 패킷 파싱 (0x03)"""
try:
bytes_data = [int(x, 16) for x in hex_string.split()]
if len(bytes_data) < 34 or bytes_data[0] != 0xDD or bytes_data[-1] != 0x77:
return None
if bytes_data[1] != 0x03: # 배터리 상태 정보만
return None
volt_raw = (bytes_data[4] << 8) | bytes_data[5]
voltage = volt_raw / 100.0
cur_amp = (bytes_data[8] << 8) | bytes_data[9]
max_amp = (bytes_data[10] << 8) | bytes_data[11]
level_direct = bytes_data[23]
temp1_raw = (bytes_data[27] << 8) | bytes_data[28]
temp1 = (temp1_raw - 2731) / 10.0
return {
'voltage': voltage,
'current_amp': cur_amp,
'max_amp': max_amp,
'level': level_direct,
'temp': temp1
}
except:
return None
def read_bms_log(file_path):
"""BMS 로그 파일 읽기 (배터리 상태)"""
encodings = ['utf-8', 'cp949', 'euc-kr']
for encoding in encodings:
try:
with open(file_path, 'r', encoding=encoding) as f:
lines = f.readlines()
break
except:
continue
else:
return []
pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\w+\s+BMS:(.*?)(?=\n|$)'
battery_data = []
for line in lines:
match = re.search(pattern, line)
if match:
timestamp_str = match.group(1)
packet_hex = match.group(2).strip()
parsed = parse_bms_packet(packet_hex)
if parsed:
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
battery_data.append({
'timestamp': timestamp,
**parsed
})
return battery_data
# ============================================================================
# 1-2. BMS 셀 전압 데이터 파싱 (0x04: 셀 전압)
# ============================================================================
def parse_cell_voltage_packet(hex_string):
"""BMS 셀 전압 패킷 파싱 (0x04)"""
try:
bytes_data = [int(x, 16) for x in hex_string.split()]
if len(bytes_data) < 23 or bytes_data[0] != 0xDD or bytes_data[-1] != 0x77:
return None
if bytes_data[1] != 0x04: # 셀 전압 정보만
return None
# 8개 셀 전압 추출
voltages = []
for i in range(8):
v_raw = (bytes_data[4 + i*2] << 8) | bytes_data[5 + i*2]
voltages.append(v_raw / 1000.0)
return {
'cell1': voltages[0],
'cell2': voltages[1],
'cell3': voltages[2],
'cell4': voltages[3],
'cell5': voltages[4],
'cell6': voltages[5],
'cell7': voltages[6],
'cell8': voltages[7],
'max_voltage': max(voltages),
'min_voltage': min(voltages),
'voltage_diff': max(voltages) - min(voltages),
'avg_voltage': sum(voltages) / len(voltages)
}
except:
return None
def read_cell_voltage_log(file_path):
"""셀 전압 로그 파일 읽기"""
encodings = ['utf-8', 'cp949', 'euc-kr']
for encoding in encodings:
try:
with open(file_path, 'r', encoding=encoding) as f:
lines = f.readlines()
break
except:
continue
else:
return []
pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\w+\s+BMS:(.*?)(?=\n|$)'
cell_data = []
for line in lines:
match = re.search(pattern, line)
if match:
timestamp_str = match.group(1)
packet_hex = match.group(2).strip()
parsed = parse_cell_voltage_packet(packet_hex)
if parsed:
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
cell_data.append({
'timestamp': timestamp,
**parsed
})
return cell_data
# ============================================================================
# 2. 상차작업완료 카운트
# ============================================================================
def read_loading_complete(file_path):
"""상차작업완료 메시지 추출"""
encodings = ['utf-8', 'cp949', 'euc-kr']
for encoding in encodings:
try:
with open(file_path, 'r', encoding=encoding) as f:
content = f.read()
break
except:
continue
else:
return []
pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*상차작업완료\(([^)]+)\)'
matches = re.findall(pattern, content)
results = []
for timestamp_str, location in matches:
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
results.append({
'timestamp': timestamp,
'location': location
})
return results
# ============================================================================
# 3. 충전상태전환 이벤트
# ============================================================================
def read_charge_status(file_path):
"""충전상태전환 메시지 추출"""
encodings = ['utf-8', 'cp949', 'euc-kr']
for encoding in encodings:
try:
with open(file_path, 'r', encoding=encoding) as f:
lines = f.readlines()
break
except:
continue
else:
return []
pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*충전상태전환\s+(True|False)'
results = []
for line in lines:
match = re.search(pattern, line)
if match:
timestamp_str = match.group(1)
status = match.group(2)
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
results.append({
'timestamp': timestamp,
'status': status == 'True'
})
return results
# ============================================================================
# 4. 데이터 읽기
# ============================================================================
print("\n데이터 로딩 중...")
# 현재 실행 폴더 기준 (서브폴더 포함)
base_path = os.getcwd()
print(f" 분석 폴더: {base_path} (서브폴더 포함)")
# 파일 패턴으로 자동 검색 (재귀 검색)
# BMS 파일들 찾기 (*_bms.txt)
bms_files = sorted(glob.glob(os.path.join(base_path, "**", "*_bms.txt"), recursive=True))
print(f" BMS 파일: {len(bms_files)}개 발견")
for f in bms_files:
rel_path = os.path.relpath(f, base_path)
print(f" - {rel_path}")
# 운용기록 로그 파일들 찾기 (202*.txt, 단 _bms.txt 제외)
log_files = sorted([f for f in glob.glob(os.path.join(base_path, "**", "202*.txt"), recursive=True)
if not f.endswith("_bms.txt")])
print(f" 운용기록 파일: {len(log_files)}개 발견")
for f in log_files:
rel_path = os.path.relpath(f, base_path)
print(f" - {rel_path}")
# BMS 배터리 상태 데이터 (0x03)
all_battery = []
for bms_file in bms_files:
data = read_bms_log(bms_file)
all_battery.extend(data)
rel_path = os.path.relpath(bms_file, base_path)
print(f" {rel_path}: {len(data)}개 배터리 데이터")
all_battery.sort(key=lambda x: x['timestamp'])
print(f" 배터리 데이터 총합: {len(all_battery)}")
# BMS 셀 전압 데이터 (0x04)
all_cells = []
for bms_file in bms_files:
data = read_cell_voltage_log(bms_file)
all_cells.extend(data)
rel_path = os.path.relpath(bms_file, base_path)
print(f" {rel_path}: {len(data)}개 셀 전압 데이터")
all_cells.sort(key=lambda x: x['timestamp'])
print(f" 셀 전압 데이터 총합: {len(all_cells)}")
# 상차작업완료 데이터
all_loading = []
for log_file in log_files:
data = read_loading_complete(log_file)
all_loading.extend(data)
rel_path = os.path.relpath(log_file, base_path)
print(f" {rel_path}: {len(data)}건 작업완료")
all_loading.sort(key=lambda x: x['timestamp'])
print(f" 상차작업완료 총합: {len(all_loading)}")
# 충전상태전환 데이터
all_charge = []
for log_file in log_files:
data = read_charge_status(log_file)
all_charge.extend(data)
rel_path = os.path.relpath(log_file, base_path)
print(f" {rel_path}: {len(data)}건 충전이벤트")
all_charge.sort(key=lambda x: x['timestamp'])
print(f" 충전상태전환 총합: {len(all_charge)}")
# ============================================================================
# 4-2. 셀 불균형 분석
# ============================================================================
print("\n셀 불균형 분석 중...")
if all_cells:
# 불균형 기준: 0.1V 이상 차이
critical_imbalance = [c for c in all_cells if c['voltage_diff'] > 0.1]
warning_imbalance = [c for c in all_cells if 0.05 < c['voltage_diff'] <= 0.1]
print(f" 심각한 불균형 (>0.1V): {len(critical_imbalance)}")
print(f" 경고 수준 불균형 (0.05~0.1V): {len(warning_imbalance)}")
# 일별 셀 전압 분석
from collections import defaultdict
daily_cells = defaultdict(list)
for c in all_cells:
date_key = c['timestamp'].strftime('%Y-%m-%d')
daily_cells[date_key].append(c)
for date_key in sorted(daily_cells.keys()):
day_data = daily_cells[date_key]
print(f"\n[{date_key} 셀 전압 분석]")
print(f" 측정 건수: {len(day_data)}")
max_diff = max(c['voltage_diff'] for c in day_data)
avg_diff = sum(c['voltage_diff'] for c in day_data) / len(day_data)
print(f" 최대 불균형: {max_diff:.3f}V")
print(f" 평균 불균형: {avg_diff:.3f}V")
# 최대 불균형 시점 찾기
max_imbalance = max(day_data, key=lambda x: x['voltage_diff'])
print(f" 최대 불균형 시점: {max_imbalance['timestamp'].strftime('%H:%M:%S')}")
print(f" 셀 전압: C1={max_imbalance['cell1']:.3f}V, C2={max_imbalance['cell2']:.3f}V, "
f"C3={max_imbalance['cell3']:.3f}V, C4={max_imbalance['cell4']:.3f}V")
print(f" C5={max_imbalance['cell5']:.3f}V, C6={max_imbalance['cell6']:.3f}V, "
f"C7={max_imbalance['cell7']:.3f}V, C8={max_imbalance['cell8']:.3f}V")
print(f" 전압 차이: {max_imbalance['voltage_diff']:.3f}V "
f"(최고 {max_imbalance['max_voltage']:.3f}V - 최저 {max_imbalance['min_voltage']:.3f}V)")
# 해당 일의 심각한 불균형 건수
day_critical = [c for c in day_data if c['voltage_diff'] > 0.1]
day_warning = [c for c in day_data if 0.05 < c['voltage_diff'] <= 0.1]
print(f" 심각한 불균형: {len(day_critical)}건, 경고 수준: {len(day_warning)}")
# ============================================================================
# 5. 시간대별 집계 (1시간 단위)
# ============================================================================
print("\n시간대별 데이터 집계 중...")
# 시작/종료 시간 결정
if all_battery:
start_time = all_battery[0]['timestamp']
end_time = all_battery[-1]['timestamp']
else:
start_time = datetime(2025, 11, 5, 0, 0, 0)
end_time = datetime(2025, 11, 6, 23, 59, 59)
# 1시간 단위 시간 슬롯 생성
time_slots = []
current = start_time.replace(minute=0, second=0, microsecond=0)
while current <= end_time:
time_slots.append(current)
current += timedelta(hours=1)
# 각 시간대별 데이터 집계
timeline_data = []
for slot in time_slots:
slot_end = slot + timedelta(hours=1)
# 배터리 데이터 평균
slot_battery = [b for b in all_battery if slot <= b['timestamp'] < slot_end]
if slot_battery:
avg_voltage = sum(b['voltage'] for b in slot_battery) / len(slot_battery)
avg_amp = sum(b['current_amp'] for b in slot_battery) / len(slot_battery)
avg_level = sum(b['level'] for b in slot_battery) / len(slot_battery)
avg_temp = sum(b['temp'] for b in slot_battery) / len(slot_battery)
battery_count = len(slot_battery)
else:
avg_voltage = avg_amp = avg_level = avg_temp = battery_count = 0
# 작업 완료 카운트
slot_loading = [l for l in all_loading if slot <= l['timestamp'] < slot_end]
loading_count = len(slot_loading)
# 작업 위치별 카운트
location_counts = defaultdict(int)
for l in slot_loading:
location_counts[l['location']] += 1
# 충전 상태 - 현재 시간이 충전 구간에 포함되는지 확인
is_charging = False
for c in all_charge:
if c['timestamp'] <= slot:
is_charging = c['status']
elif c['timestamp'] > slot_end:
break
# 슬롯 내 충전상태전환 이벤트 확인
slot_charge = [c for c in all_charge if slot <= c['timestamp'] < slot_end]
if slot_charge:
is_charging = slot_charge[-1]['status']
charging = "충전중" if is_charging else "-"
charging_indicator = 100 if is_charging else 0
timeline_data.append({
'시간대': slot.strftime('%Y-%m-%d %H:%M'),
'평균전압(V)': round(avg_voltage, 2) if avg_voltage > 0 else '',
'평균용량(mAh)': int(avg_amp) if avg_amp > 0 else '',
'평균잔량(%)': int(avg_level) if avg_level > 0 else '',
'평균온도(°C)': round(avg_temp, 1) if avg_temp > 0 else '',
'작업완료건수': loading_count,
'F1': location_counts.get('F1', 0),
'F2': location_counts.get('F2', 0),
'F3': location_counts.get('F3', 0),
'F4': location_counts.get('F4', 0),
'F5': location_counts.get('F5', 0),
'F6': location_counts.get('F6', 0),
'충전상태': charging,
'충전구간': charging_indicator,
'배터리측정수': battery_count
})
print(f" 시간대별 데이터: {len(timeline_data)}개 슬롯")
# ============================================================================
# 5-2. 일자별 집계
# ============================================================================
print("\n일자별 데이터 집계 중...")
# 일자별 작업 완료 건수 집계
daily_summary = defaultdict(lambda: {
'date': '',
'total_work': 0,
'F1': 0, 'F2': 0, 'F3': 0, 'F4': 0, 'F5': 0, 'F6': 0,
'HOME': 0,
'charge_count': 0,
'battery_count': 0,
'avg_battery_level': 0
})
# 작업 완료 집계
for work in all_loading:
date_key = work['timestamp'].strftime('%Y-%m-%d')
daily_summary[date_key]['date'] = date_key
daily_summary[date_key]['total_work'] += 1
daily_summary[date_key][work['location']] += 1
# 충전 이벤트 집계
for charge in all_charge:
date_key = charge['timestamp'].strftime('%Y-%m-%d')
daily_summary[date_key]['charge_count'] += 1
# 배터리 데이터 집계
battery_by_day = defaultdict(list)
for bat in all_battery:
date_key = bat['timestamp'].strftime('%Y-%m-%d')
battery_by_day[date_key].append(bat['level'])
for date_key, levels in battery_by_day.items():
daily_summary[date_key]['battery_count'] = len(levels)
daily_summary[date_key]['avg_battery_level'] = sum(levels) / len(levels)
# DataFrame 생성
daily_data = []
for date_key in sorted(daily_summary.keys()):
data = daily_summary[date_key]
daily_data.append({
'일자': data['date'],
'총작업건수': data['total_work'],
'F1': data['F1'],
'F2': data['F2'],
'F3': data['F3'],
'F4': data['F4'],
'F5': data['F5'],
'F6': data['F6'],
'충전이벤트': data['charge_count']
})
print(f" 일자별 데이터: {len(daily_data)}")
# ============================================================================
# 5-3. 일자별 교대조(Shift)별 집계
# ============================================================================
print("\n교대조별 데이터 집계 중...")
def get_shift(timestamp):
"""시간대별 교대조 분류"""
hour = timestamp.hour
if 6 <= hour < 14:
return 'Day'
elif 14 <= hour < 22:
return 'Swing'
else: # 22:00~06:00
return 'Night'
# 일자별 교대조별 작업 집계
shift_summary = defaultdict(lambda: {'date': '', 'Day': 0, 'Swing': 0, 'Night': 0})
for work in all_loading:
date_key = work['timestamp'].strftime('%Y-%m-%d')
shift = get_shift(work['timestamp'])
shift_summary[date_key]['date'] = date_key
shift_summary[date_key][shift] += 1
# DataFrame 생성
shift_data = []
for date_key in sorted(shift_summary.keys()):
data = shift_summary[date_key]
total = data['Day'] + data['Swing'] + data['Night']
avg = round(total / 3, 1) if total > 0 else 0
shift_data.append({
'일자': data['date'],
'day': data['Day'],
'swing': data['Swing'],
'night': data['Night'],
'합계': total,
'평균': avg
})
print(f" 교대조별 데이터: {len(shift_data)}")
# ============================================================================
# 6. 엑셀 리포트 생성
# ============================================================================
print("\n엑셀 리포트 생성 중...")
# 출력 파일명 동적 생성 (시작일~종료일)
if all_battery:
start_date = all_battery[0]['timestamp'].strftime('%Y%m%d')
end_date = all_battery[-1]['timestamp'].strftime('%Y%m%d')
output_filename = f"agv_log_report_{start_date}~{end_date}.xlsx"
else:
output_filename = "agv_log_report.xlsx"
output_file = os.path.join(base_path, output_filename)
print(f" 출력 파일: {output_filename}")
# DataFrame 생성
df_timeline = pd.DataFrame(timeline_data)
# 배터리 상세 데이터
df_battery = pd.DataFrame([{
'시간': b['timestamp'].strftime('%Y-%m-%d %H:%M:%S'),
'전압(V)': round(b['voltage'], 2),
'남은용량(mAh)': b['current_amp'],
'총용량(mAh)': b['max_amp'],
'잔량(%)': b['level'],
'온도(°C)': round(b['temp'], 1)
} for b in all_battery])
# 작업 상세 데이터
df_loading = pd.DataFrame([{
'시간': l['timestamp'].strftime('%Y-%m-%d %H:%M:%S'),
'위치': l['location']
} for l in all_loading])
# 충전 이벤트 데이터
df_charge = pd.DataFrame([{
'시간': c['timestamp'].strftime('%Y-%m-%d %H:%M:%S'),
'충전상태': '시작' if c['status'] else '종료'
} for c in all_charge])
# 셀 전압 상세 데이터
df_cells = pd.DataFrame([{
'시간': c['timestamp'].strftime('%Y-%m-%d %H:%M:%S'),
'Cell1(V)': round(c['cell1'], 3),
'Cell2(V)': round(c['cell2'], 3),
'Cell3(V)': round(c['cell3'], 3),
'Cell4(V)': round(c['cell4'], 3),
'Cell5(V)': round(c['cell5'], 3),
'Cell6(V)': round(c['cell6'], 3),
'Cell7(V)': round(c['cell7'], 3),
'Cell8(V)': round(c['cell8'], 3),
'최고전압(V)': round(c['max_voltage'], 3),
'최저전압(V)': round(c['min_voltage'], 3),
'전압차(V)': round(c['voltage_diff'], 3),
'평균전압(V)': round(c['avg_voltage'], 3)
} for c in all_cells]) if all_cells else pd.DataFrame()
# 일자별 요약 DataFrame
df_daily = pd.DataFrame(daily_data)
df_shift = pd.DataFrame(shift_data)
# 엑셀 저장
with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
df_daily.to_excel(writer, sheet_name='일자별작업요약', index=False)
# 교대조별 데이터를 같은 시트에 추가 (일자별 데이터 아래 3행 띄우고)
df_shift.to_excel(writer, sheet_name='일자별작업요약', startrow=len(df_daily)+3, index=False)
df_timeline.to_excel(writer, sheet_name='시간대별종합', index=False)
df_battery.to_excel(writer, sheet_name='배터리상세', index=False)
df_loading.to_excel(writer, sheet_name='작업상세', index=False)
df_charge.to_excel(writer, sheet_name='충전이벤트', index=False)
if not df_cells.empty:
df_cells.to_excel(writer, sheet_name='셀전압상세', index=False)
# ============================================================================
# 7. 차트 추가
# ============================================================================
print("차트 생성 중...")
wb = load_workbook(output_file)
# ============================================================================
# 7-1. 일자별작업요약 시트 스타일 및 차트
# ============================================================================
ws_daily = wb['일자별작업요약']
# 헤더 스타일
header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
header_font = Font(color="FFFFFF", bold=True)
for cell in ws_daily[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center')
# 열 너비 조정
ws_daily.column_dimensions['A'].width = 12
for col in ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I']:
ws_daily.column_dimensions[col].width = 12
# 일자별 작업 건수 차트
chart_daily_work = BarChart()
chart_daily_work.type = "col"
chart_daily_work.title = "일자별 작업 완료 건수"
chart_daily_work.y_axis.title = '작업 건수'
chart_daily_work.x_axis.title = '일자'
chart_daily_work.height = 12
chart_daily_work.width = 20
# 총작업건수 데이터
data = Reference(ws_daily, min_col=2, min_row=1, max_row=len(daily_data)+1)
cats = Reference(ws_daily, min_col=1, min_row=2, max_row=len(daily_data)+1)
chart_daily_work.add_data(data, titles_from_data=True)
chart_daily_work.set_categories(cats)
ws_daily.add_chart(chart_daily_work, "N2")
# 일자별 위치별 작업 건수 차트 (누적 막대)
chart_daily_location = BarChart()
chart_daily_location.type = "col"
chart_daily_location.grouping = "stacked"
chart_daily_location.title = "일자별 위치별 작업 건수 (누적)"
chart_daily_location.y_axis.title = '작업 건수'
chart_daily_location.x_axis.title = '일자'
chart_daily_location.height = 12
chart_daily_location.width = 20
# F1~F6 데이터 (3~8열)
data = Reference(ws_daily, min_col=3, max_col=8, min_row=1, max_row=len(daily_data)+1)
cats = Reference(ws_daily, min_col=1, min_row=2, max_row=len(daily_data)+1)
chart_daily_location.add_data(data, titles_from_data=True)
chart_daily_location.set_categories(cats)
ws_daily.add_chart(chart_daily_location, "N22")
# 교대조별 데이터 영역 스타일 및 차트
shift_start_row = len(daily_data) + 4 # 일자별 데이터 + 빈 행 + 헤더
# 교대조별 헤더 스타일
for col_idx in range(1, 7): # A~F 열 (일자, day, swing, night, 합계, 평균)
cell = ws_daily.cell(row=shift_start_row, column=col_idx)
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
cell.alignment = Alignment(horizontal='center')
# 교대조별 작업 건수 차트 (혼합 차트: 막대 + 선)
chart_shift = BarChart()
chart_shift.type = "col"
chart_shift.grouping = "clustered"
chart_shift.title = "일자별 교대조별 작업 건수"
chart_shift.y_axis.title = '작업 건수'
chart_shift.x_axis.title = '일자'
chart_shift.height = 12
chart_shift.width = 20
# Day, Swing, Night 데이터 (막대 차트)
data = Reference(ws_daily, min_col=2, max_col=4, min_row=shift_start_row, max_row=shift_start_row+len(shift_data))
cats = Reference(ws_daily, min_col=1, min_row=shift_start_row+1, max_row=shift_start_row+len(shift_data))
chart_shift.add_data(data, titles_from_data=True)
chart_shift.set_categories(cats)
# 데이터 레이블 추가 (숫자만 표시)
from openpyxl.chart.label import DataLabelList
for series in chart_shift.series:
series.dLbls = DataLabelList()
series.dLbls.showVal = True
series.dLbls.showCatName = False
series.dLbls.showSerName = False
series.dLbls.showPercent = False
series.dLbls.showLeaderLines = False
# 평균 데이터 (선 차트)
line_chart = LineChart()
line_data = Reference(ws_daily, min_col=6, min_row=shift_start_row, max_row=shift_start_row+len(shift_data))
line_chart.add_data(line_data, titles_from_data=True)
line_chart.set_categories(cats)
# 선 차트에 데이터 레이블 추가
for series in line_chart.series:
series.dLbls = DataLabelList()
series.dLbls.showVal = True
series.dLbls.showCatName = False
series.dLbls.showSerName = False
series.dLbls.showPercent = False
series.dLbls.showLeaderLines = False
# 혼합 차트 조합
chart_shift += line_chart
ws_daily.add_chart(chart_shift, "N42")
# ============================================================================
# 7-2. 시간대별종합 시트 스타일
# ============================================================================
ws = wb['시간대별종합']
# 헤더 스타일
for cell in ws[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal='center')
# 열 너비 조정
ws.column_dimensions['A'].width = 18
ws.column_dimensions['B'].width = 12
ws.column_dimensions['C'].width = 14
ws.column_dimensions['D'].width = 12
ws.column_dimensions['E'].width = 12
ws.column_dimensions['F'].width = 12
ws.column_dimensions['L'].width = 12
# 차트 1: 배터리 잔량 추이
chart1 = LineChart()
chart1.title = "배터리 잔량 추이"
chart1.style = 10
chart1.y_axis.title = '배터리 잔량 (%)'
chart1.x_axis.title = '시간대'
chart1.height = 10
chart1.width = 20
data = Reference(ws, min_col=4, min_row=1, max_row=len(timeline_data)+1)
cats = Reference(ws, min_col=1, min_row=2, max_row=len(timeline_data)+1)
chart1.add_data(data, titles_from_data=True)
chart1.set_categories(cats)
ws.add_chart(chart1, "N2")
# 차트 2: 작업 건수 추이
chart2 = BarChart()
chart2.type = "col"
chart2.title = "시간대별 작업 완료 건수"
chart2.y_axis.title = '작업 건수'
chart2.x_axis.title = '시간대'
chart2.height = 10
chart2.width = 20
data = Reference(ws, min_col=6, min_row=1, max_row=len(timeline_data)+1)
cats = Reference(ws, min_col=1, min_row=2, max_row=len(timeline_data)+1)
chart2.add_data(data, titles_from_data=True)
chart2.set_categories(cats)
ws.add_chart(chart2, "N22")
# 차트 3: 전압 추이
chart3 = LineChart()
chart3.title = "배터리 전압 추이"
chart3.style = 12
chart3.y_axis.title = '전압 (V)'
chart3.x_axis.title = '시간대'
chart3.height = 10
chart3.width = 20
data = Reference(ws, min_col=2, min_row=1, max_row=len(timeline_data)+1)
cats = Reference(ws, min_col=1, min_row=2, max_row=len(timeline_data)+1)
chart3.add_data(data, titles_from_data=True)
chart3.set_categories(cats)
ws.add_chart(chart3, "N42")
# 차트 4: 종합 혼합 차트 (배터리 잔량 + 전압 + 작업횟수 + 충전구간)
chart4_area = AreaChart()
chart4_area.title = "시간대별 종합 현황 (배터리/전압/작업/충전)"
chart4_area.style = 27
chart4_area.y_axis.title = '배터리 잔량 (%) / 전압 (V × 10)'
chart4_area.x_axis.title = '시간대'
chart4_area.height = 15
chart4_area.width = 30
# 충전 구간 배경 (면적 차트)
charging_data = Reference(ws, min_col=13, min_row=1, max_row=len(timeline_data)+1)
cats = Reference(ws, min_col=1, min_row=2, max_row=len(timeline_data)+1)
chart4_area.add_data(charging_data, titles_from_data=True)
chart4_area.set_categories(cats)
# 배터리 잔량 선 추가
battery_data = Reference(ws, min_col=4, min_row=1, max_row=len(timeline_data)+1)
chart4_area.add_data(battery_data, titles_from_data=True)
# 전압 데이터 추가
voltage_data = Reference(ws, min_col=2, min_row=1, max_row=len(timeline_data)+1)
chart4_area.add_data(voltage_data, titles_from_data=True)
# 보조 차트: 작업횟수 (오른쪽 Y축 바 차트)
chart4_bar = BarChart()
chart4_bar.type = "col"
chart4_bar.grouping = "standard"
work_data = Reference(ws, min_col=6, min_row=1, max_row=len(timeline_data)+1)
chart4_bar.add_data(work_data, titles_from_data=True)
chart4_bar.set_categories(cats)
# 복합 차트 조합
chart4_area.y_axis.crossAx = 500
chart4_bar.y_axis.axId = 500
chart4_bar.y_axis.title = "작업 횟수"
chart4_area += chart4_bar
ws.add_chart(chart4_area, "A72")
# ============================================================================
# 8. 셀 전압 시트 스타일 및 차트
# ============================================================================
if all_cells and '셀전압상세' in wb.sheetnames:
print("셀 전압 차트 생성 중...")
ws_cells = wb['셀전압상세']
# 헤더 스타일
for c_idx, col_name in enumerate(df_cells.columns, start=1):
cell = ws_cells.cell(row=1, column=c_idx)
cell.font = Font(bold=True, color="FFFFFF")
cell.fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
cell.alignment = Alignment(horizontal='center')
# 열 너비 조정
ws_cells.column_dimensions['A'].width = 20
for col in ['B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M']:
ws_cells.column_dimensions[col].width = 12
# 셀 전압 차이가 큰 행 강조 표시
red_fill = PatternFill(start_color="FFCCCC", end_color="FFCCCC", fill_type="solid")
yellow_fill = PatternFill(start_color="FFFFCC", end_color="FFFFCC", fill_type="solid")
for row_idx in range(2, len(df_cells) + 2):
voltage_diff = ws_cells.cell(row=row_idx, column=12).value
if voltage_diff and voltage_diff > 0.1:
for col_idx in range(1, 14):
ws_cells.cell(row=row_idx, column=col_idx).fill = red_fill
elif voltage_diff and voltage_diff > 0.05:
for col_idx in range(1, 14):
ws_cells.cell(row=row_idx, column=col_idx).fill = yellow_fill
# 차트 5: 셀별 전압 추이
chart5 = LineChart()
chart5.title = "셀별 전압 추이"
chart5.style = 10
chart5.y_axis.title = '전압 (V)'
chart5.x_axis.title = '시간대'
chart5.height = 15
chart5.width = 30
# 각 셀 데이터 추가
for col_idx in range(2, 10): # Cell1~Cell8
data = Reference(ws_cells, min_col=col_idx, min_row=1, max_row=len(df_cells)+1)
chart5.add_data(data, titles_from_data=True)
cats = Reference(ws_cells, min_col=1, min_row=2, max_row=len(df_cells)+1)
chart5.set_categories(cats)
ws_cells.add_chart(chart5, "O2")
# 차트 6: 전압 불균형 추이
chart6 = LineChart()
chart6.title = "셀 전압 불균형 추이 (최고-최저)"
chart6.style = 12
chart6.y_axis.title = '전압 차이 (V)'
chart6.x_axis.title = '시간대'
chart6.height = 12
chart6.width = 30
data = Reference(ws_cells, min_col=12, min_row=1, max_row=len(df_cells)+1)
chart6.add_data(data, titles_from_data=True)
chart6.set_categories(cats)
ws_cells.add_chart(chart6, "O32")
wb.save(output_file)
print(f"\n리포트 생성 완료: {output_file}")
# ============================================================================
# 9. 요약 통계
# ============================================================================
print("\n" + "=" * 80)
print("종합 분석 요약")
print("=" * 80)
print(f"\n[분석 기간]")
print(f" {start_time.strftime('%Y-%m-%d %H:%M')} ~ {end_time.strftime('%Y-%m-%d %H:%M')}")
print(f"\n[배터리 통계]")
if all_battery:
print(f" 최소 잔량: {min(b['level'] for b in all_battery)}%")
print(f" 최대 잔량: {max(b['level'] for b in all_battery)}%")
print(f" 평균 잔량: {sum(b['level'] for b in all_battery) / len(all_battery):.1f}%")
print(f" 최저 전압: {min(b['voltage'] for b in all_battery):.2f}V")
print(f" 최고 전압: {max(b['voltage'] for b in all_battery):.2f}V")
print(f"\n[작업 통계]")
print(f" 총 작업 완료: {len(all_loading)}")
location_stats = defaultdict(int)
for l in all_loading:
location_stats[l['location']] += 1
for loc in sorted(location_stats.keys()):
print(f" {loc}: {location_stats[loc]}")
print(f"\n[충전 통계]")
charge_on_count = sum(1 for c in all_charge if c['status'])
charge_off_count = sum(1 for c in all_charge if not c['status'])
print(f" 충전 시작: {charge_on_count}")
print(f" 충전 종료: {charge_off_count}")
# 셀 전압 통계
if all_cells:
print(f"\n[셀 전압 통계]")
print(f" 최대 불균형: {max(c['voltage_diff'] for c in all_cells):.3f}V")
print(f" 평균 불균형: {sum(c['voltage_diff'] for c in all_cells) / len(all_cells):.3f}V")
print(f" 심각한 불균형 건수 (>0.1V): {len(critical_imbalance)}")
print(f" 경고 수준 건수 (0.05~0.1V): {len(warning_imbalance)}")
print(f"\n[개별 셀 전압 범위]")
for i in range(1, 9):
cell_key = f'cell{i}'
cell_voltages = [c[cell_key] for c in all_cells]
print(f" Cell {i}: {min(cell_voltages):.3f}V ~ {max(cell_voltages):.3f}V "
f"(평균 {sum(cell_voltages)/len(cell_voltages):.3f}V)")
# 가장 심각한 불균형 TOP 5
print(f"\n[불균형 심각도 TOP 5]")
top5_imbalance = sorted(all_cells, key=lambda x: x['voltage_diff'], reverse=True)[:5]
for i, c in enumerate(top5_imbalance, 1):
print(f" {i}. {c['timestamp'].strftime('%Y-%m-%d %H:%M:%S')} - "
f"불균형: {c['voltage_diff']:.3f}V "
f"(최고 {c['max_voltage']:.3f}V - 최저 {c['min_voltage']:.3f}V)")
print("\n" + "=" * 80)
print("분석 완료!")
print("=" * 80)

0
경로계산백업.md Normal file
View File

158
경로수동계산.md Normal file
View File

@@ -0,0 +1,158 @@
AGV는 아래 태그사이에 존재한다면 해당 라인으로 간주하고
각 라인별 목적지에 대한 경우의 수를 미리 계산 해본다
*1~10 : unloader line
*17~9 : charger 1
*11~6 : cleanner
6~2 : buffer14 line
*8~13 : Loader Line
충전기 #1 : 전진도킹
충전기 #2 : 후진도킹
로더,언로더,클리너,버퍼 : 후진도킹
AGV는 모니터반대방향에 리프트가 설치되어있고. 모니터쪽으로 이동하는 방향이 F방향이다.
충전소1(Chg#1)을 제외하고 나머지 포인트는 모두 리프트쪽으로 도킹을 해야한다(후면)
AGV에는 마그넷센서가달려있고 이 센서를 이용하여 좌/우/직진을 수행한다 L/R/S 로 명명함
Unloader line (모니터:우 -> F)
chg#1 : 0001(FS) 0016(FS) 0012(FS) 0010(FR) 0009(FS) 0015 [MARKSTOP]
chg#2 : 0001(FS) 0016(FS) 0012(FS) 0010(FR) 0009(BR) 0007(BS) 0013(BL) 0019 [MARKSTOP]
loader : 0001(FS) 0016(FS) 0012(FS) 0010(FR) 0009(BR) 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
unloader : 0012(FS) 0016(FS) 0001 [MARKSTOP]
cleanner : 0001(FS) 0016(FS) 0012(FS) 0010(FR) 0009(BR) 0007(BS) 0006(BR) 0011 [MARKSTOP]
buffer14 : 0001(FS) 0016(FS) 0012(FS) 0010(FR) 0009(BR) 0007(BS) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) 0034 [MARKSTOP]
Unloader line (모니터:좌 <- F)
chg#1 : 0001(BS) 0016(BS) 0012(BS) 0010(BL) 0007(FR) 0009(FS) 0015 [MARKSTOP]
chg#2 : 0001(BS) 0016(BS) 0012(BS) 0010(BL) 0007(BS) 0013(BL) 0019 [MARKSTOP]
loader : 0001(BS) 0016(BS) 0012(BS) 0010(BL) 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
unloader : 0001(BS) 0016(BS) 0012(BS) 0010(BL) 0009(FL) 0007(BR) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
cleanner : 0001(BS) 0016(BS) 0012(BS) 0010(BL) 0007(BS) 0006(BR) 0011 [MARKSTOP]
buffer14 : 0001(BS) 0016(BS) 0012(BS) 0010(BL) 0007(BS) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
Charger #1 Line (모니터:우 ->F)
Chg#1: 0017(FS) 0009(FR) 0007(BR) 0010(FR) 0009(FS) 0015 [MARKSTOP]
Chg#2 : 0017(FS) 0009(FR) 0010(BR) 0007(BS) 0013(BL) 0019 [MARKSTOP]
Loader: 0017(FS) 0009(FR) 0010(BR) 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
Unloader: 0017(FS) 0009(FR) 0007(BS) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner: 0017(FS) 0009(FR) 0010(BR) 0007(BS) 0006(BR) 0011 [MARKSTOP]
Buffer14: 0017(FS) 0009(FR) 0010(BR) 0007(BS) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
Charger #1 Line (모니터:좌 <-F )
Chg#1: 0017(BS) 0009(BL) 0015 [MARKSTOP]
Chg#2: 0017(BS) 0009(BS) 0007(FS) 0013(BL) 0019 [MARKSTOP]
Loader: 0017(BS) 0009(BS) 0007(FS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader: 0017(BS) 0009(BL) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner: 0017(BS) 0009(BS) 0007(FR) 0006(BR) 0011 [MARKSTOP]
Buffer: 0017(BS) 0009(BS) 0007(FR) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) 0034 ~ 0031 [MARKSTOP]
Cleanner Line (모니터:우 ->F)
Chg#1: 0011(FS) 0006(FL) 0007(FL) 0009(FL) 0015 [MARKSTOP]
Chg#2: 0011(FS) 0006(FL) 0007(BR) 0013(BL) 0019 [MARKSTOP]
Loader: 0011(FS) 0006(FL) 0007(BR) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader: 0011(FS) 0006(FR) 0013(BL) 0007(BS) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner: 0006 0011 [MARKSTOP]
Buffer: 0011(FS) 0006(FL) 0007(BR) 0006(FR) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
Cleanner Line (모니터:좌 <- F)
Chg#1 0011(BS) 0006(BR) 0007(BL) 0010(BL) 0009(FL) 0015 [MARKSTOP]
Chg#2 0011(BS) 0006(BR) 0013(BS) 0019 [MARKSTOP]
Loader: 0011(BS) 0006(BR) 0013(BS) 0019(BR) 0008 [MARKSTOP]
UnLoader: 0011(BS) 0006(BR) 0007(BS) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0011(BS) 0006(BR) 0007(FL) 0013(BR) 0006(BR) 0011 [MARKSTOP]
Buffer 0011(BS) 0006(BR) 0007(FL) 0013(BR) 0006(FR) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
Buffer Line (모니터:우 -> F) - 교차로 : 0006
Chg#1 0002(FS) 0004(FL) 0003(FL) 0005(FR) 0006(FS) 0007(FL) 0009(FL) 0015 [MARKSTOP]
Chg#2 0002(FS) 0004(FL) 0003(FL) 0005(FR) 0006(FS) 0007(BR) 0013(BL) 0019 [MARKSTOP]
Loader 0002(FS) 0004(FL) 0003(FL) 0005(FR) 0006(FS) 0007(BR) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader 0002(FS) 0004(FL) 0003(FL) 0005(FR) 0006(FR) 0013(BL) 0007(BS) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0002(FS) 0004(FL) 0003(FL) 0005(FR) 0006(FR) 0013(BL) 0006(BR) 0011 [MARKSTOP]
Buffer 0005(B) 0003 0004 0002 0034 ~ 0031 [MARKSTOP]
Buffer Line(모니터:좌 <- F) - 교차로 : 0006
Chg#1 0002(BS) 0004(BL) 0003(BL) 0005(BR) 0006(BR) 0013(FL) 0007(FL) 0009(FL) 0015 [MARKSTOP]
Chg#2 0002(BS) 0004(BL) 0003(BL) 0005(BR) 0006(BR) 0013(BS) 0019 [MARKSTOP]
Loader 0002(BS) 0004(BL) 0003(BL) 0005(BR) 0006(BR) 0013(BS) 0019(BR) 0008 [MARKSTOP]
UnLoader 0002(BS) 0004(BL) 0003(BL) 0005(BR) 0006(BR) 0007(BS) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0002(BS) 0004(BL) 0003(BL) 0005(BR) 0006(BR) 0013(FL) 0007(BR) 0006(BR) 0011 [MARKSTOP]
Buffer 0002(BS) 0004(BL) 0003(BL) 0005(BR) 0006(BR) 0013(FL) 0007(BR) 0006(FR) 0005(BL) 0003(BS) ~ 0034 [MARKSTOP]
10 노드 시작 (모니터:우 -> F)
Chg#1 0010(FS) 0009(FL) 0015 [MARKSTOP]
Chg#2 0010(FR) 0009(BR) 0007(BS) 0013(BL) 0019 [MARKSTOP]
Loader 0010(FR) 0009(BR) 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader 0010(BS) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0010(FR) 0009(BR) 0007(BS) 0006(BR) 0011 [MARKSTOP]
Buffer 0010(FR) 0009(BR) 0007(BS) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
10 노드 시작 (모니터:좌 <- F)
Chg#1 0010(BL) 0007(FR) 0009(FS) 0015 [MARKSTOP]
Chg#2 0010(BL) 0007(BS) 0013(BL) 0019 [MARKSTOP]
Loader 0010(BL) 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader 0010(BL) 0009(FL) 0007(BS) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0010(BL) 0007(BS) 0006(BR) 0011 [MARKSTOP]
Buffer 0010(BL) 0007(BS) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
7 노드 시작 (모니터:우 -> F)
Chg#1 0007(BR) 0010(FR) 0009(FS) 0015 [MARKSTOP]
Chg#2 0007(BS) 0013(BL) 0019 [MARKSTOP]
Loader 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader 0007(BR) 0009(FL) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0007(BS) 0013(BL) 0006(BR) 0011 [MARKSTOP]
Buffer 0007(BS) 0013(BL) 0006(FR) 0005(BL) 0003(BS) 0004(BR) 0002(FS) [MARKSTOP]
7 노드 시작 (모니터:좌 <- F)
Chg#1 0007(FR) 0009(FL) 0015 [MARKSTOP]
Chg#2 0007(BS) 0013(BL) 0019 [MARKSTOP]
Loader 0007(BS) 0013(BL) 0019(BR) 0008 [MARKSTOP]
UnLoader 0007(FR) 0009(BR) 0010(BL) 0012(BL) 0016(BS) 0001 [MARKSTOP]
Cleanner 0007(BS) 0006(BR) 0011 [MARKSTOP]
Buffer 0007(BS) 0006(BL) 0005(BL) 0003(BS) 0004(BR) 0002(FS) 0034 [MARKSTOP]
Buffer Internal Move 목표버퍼가 우측에 있는 경우 (모니터:우 -> F) : 도킹방향은 맞음
Buf1->Buf4 0031(FS) 0032(FS) 0033(FS) 0034(FS) : 목표 노드까지는 전진방향으로 이동(전진은 우측으로 이동함)
이동완료 후 R방향(반대)으로 MARSTOP 신호를 통해서 멈춤
Buffer Internal Move 목표버퍼가 좌측에 있는 경우 (모니터:우 -> F) : 도킹방향은 맞음
Buf4->Buf1 목표 노드까지는 후진방향으로 이동(후진은 좌측으로 이동함)
이동완료 후 MARKSTOP 신호를 통해서 멈춤
* NOTE: 정지 정밀도를 높이기 위해 항상 목표지점의 우측에서 좌측방향으로 진입하며 멈추도록 설계됨. (센서 히스테리시스 고려)
Buffer Internal Move (모니터:좌 <- F) : 도킹방향 맞지않음 (방향전환 필요하니 6에서 전환 필요)
~ 0006(BS) 0005(BR) 0013(FS) 0007(BR) 0006(BL) 0005(BS) ~ 목표노드까지 이동한 후 MARKSTOP
#노드범위 [0008,0019,0013]
Loader Line (모니터:우 -> F) : 전진(F)은 0008 -> 좌측/하단 방향. 따라서 19번(우측/상단)으로 가려면 후진(B)해야 함.
Chg#1 0008(BS) 0019(BS) 0013(BS) 0007(BS) 0010(FR) 0009(FS) 0015 [MARKSTOP]
Chg#2 0008(BS) 0019(BS) 0013(BS) 0007(FR) 0006(BR) 0013(BS) 0019 [MARKSTOP]
Loader 0008(BS) 0019(BS) 0013(BS) 0007(FR) 0006(BR) 0013(BS) 0019(BS) 0008 [MARKSTOP]
UnLoader 0008(BS) 0019(BS) 0013(BS) 0007(BS) 0010(BS) 0012(BS) 0016(BS) 0001 [MARKSTOP]
Cleanner 0008(BS) 0019(BS) 0013(BL) 0006(BS) 0011 [MARKSTOP]
Buffer 0008(BS) 0019(BS) 0013(BL) 0006(BL) 0005(BL) 0003(BS) 0004(BS) 0002(FS) 0034 [MARKSTOP]
Loader Line (모니터:좌 <- F) : 전진(F)은 0008 -> 우측/상단(19번) 방향. 따라서 19번으로 가려면 전진(F)해야 함.
Chg#1 0008(FS) 0019(FS) 0013(FS) 0007(FL) 0009(FS) 0015 [MARKSTOP]
Chg#2 0008(FS) 0019(FS) BS(반대방향으로) [MARKSTOP]
Loader 0013(BS) 0019(BS) 0008(BS) [MARKSTOP]
UnLoader 0008(FS) 0019(FS) 0013(FS) 0007(FL) 0009(BL) 0010(BS) 0012(BS) 0016(BS) 0001 [MARKSTOP]
Cleanner 0008(FS) 0019(FS) 0013(FS) 0007(BR) 0006(BS) 0011 [MARKSTOP]
Buffer 0008(FS) 0019(FS) 0013(FS) 0007(BR) 0006(BL) 0005(BL) 0003(BS) 0004(BS) 0002(BS) 0034 [MARKSTOP]
============ 각 노드별 모든 경우의 수를 표시한다 ========================================================
도킹위치에따른 방향 : 0001(B),0015(F),0011(B),0019(B),0008(B),0034(B),0033(B),0032(B),0031(B)
====================================================================================================
공용그룹이동 (해당 목표까지의 중요 사이의 경로)
[모니터방향:좌/상] -
[G1312ML] 0013(FS) 0007(FL) 0009(BL) 0010(BS) 0012(BS)
[G1913ML] 0019(FS) 0013(FS) 0007(FL) 0009(FS)
[G1309ML] 0013(FS) 0007(FL) 0009(FS)
[G0110ML] 0001(BS) 0016(BS) 0012(BS) 0010(BL)
[G0601ML] 0006(BR) 0007(BS) 0010(BS) 0012(BS) 0016(BS) 0001(BS)
[G0519ML] 0005(BR) 0006(BR) 0007(BS) 0013(BL) 0019(BS)
[G1213ML] 0012(FS) 0010(FR) 0009(BR) 0007(BS) 0013(BL)
[모니터방향:우/하]
[G1312MR] 0013(BS) 0007(BS) 0010(BS) 0012(BS)
[G1309MR] 0013(BS) 0007(BS) 0010(FR) 0009(FS)
[G1610MR] 0001(BS) 0016(BS) 0012(BS) 0010(BL)
====================================================================================================
모든 경우의 수 데이터 나열 값 순서 모니터방향(ML,MR) | 시작노드 | 목표노드 | 경로
====================================================================================================

19
길목재계산.md Normal file
View File

@@ -0,0 +1,19 @@
모든 길목(10,9,6,13) 에 대한 정/역 방향에 대한 로직을 정의하고, 나머지 모든위치에서는 해당 길목으로 이동을 한다.
목적지에 따라서 길목을 지정한다
0001 10 역방향 : 0010(BS) 0012(BS) 0016(BS) 0001(BS), 정방향(회전필요) : [ 0010(BR) 0009(FR) 0007(BS) 0010(BS) : 방향전환 완료] - 동일
0015 09 정방향 : 0009(FS), 역방향(회전필요) : [ 0009(FL) 0010(BS) 0007(FL) 0009(FS) ] - 동일
0011 06 역방향 : 0006(BS) 0011(BS), 정방향(회전필요) : [ 0006(BL) 0007(FS) 00013(BL) 0006(BS) ] - 동일
버퍼 06 역방향 : 0006(BL) 0005(BS) ~ 나머지 0031까지는 BS상태로 계속 이동, 정방향(회전필요) : [ 0006(BL) 0007(FS) 0013(BL) 0006(BL) ] - 동일
0008 13 역방향 : 0013(BS) 0019(BS) 0008(BS), 정방향(회전필요) : [ 0013(BL) 0006(FL) 0007(BS) 0013(BS) ] - 동일
나머지모든 경유는 각 경유지로 해당 방향으로 그대로 길을 찾는다
목적지를 보고 목적지의 경유지까지 경로로를 계산한다 (A*)
동일노드가 목적지 일때에는
도킹방향이 맞고 마크센서가 ON 되어있는 경우에만 완료처리를 한다.
도킹방향이 맞고 마크센서가 OFF되어있다면 반대방향으로 한번 이동하고, 다시 역방향 이동하면서 MARK STOP 처리한다.
도킹방향이 맞지 않는 경우 일반 노드 검색처럼 경유지 처리한다
버퍼에서 버퍼로 이동할때에만 추가 코드를 적용한다