243 lines
18 KiB
Python
243 lines
18 KiB
Python
import os
|
|
import docx
|
|
from docx import Document
|
|
from docx.shared import Inches, Pt, RGBColor
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
from docx.enum.table import WD_TABLE_ALIGNMENT
|
|
from docx_builder_base import (
|
|
add_header_banner, add_heading_1, add_heading_2, add_heading_3,
|
|
add_body_p, add_bullet_item, add_callout, add_code_block, format_table
|
|
)
|
|
|
|
def create_development_manual():
|
|
doc = Document()
|
|
|
|
# Page Margins
|
|
for sec in doc.sections:
|
|
sec.top_margin = Inches(0.8)
|
|
sec.bottom_margin = Inches(0.8)
|
|
sec.left_margin = Inches(0.9)
|
|
sec.right_margin = Inches(0.9)
|
|
|
|
# Title & Metadata
|
|
add_header_banner(
|
|
doc,
|
|
title="스마트팜 HMI 시스템 개발 매뉴얼",
|
|
subtitle="시스템 아키텍처, 소스코드 구조, MQTT 비동기 프로토콜, 룰/관수 엔진 및 빌드 가이드",
|
|
doc_type="시스템 설계 및 개발자 매뉴얼",
|
|
version="v1.1",
|
|
date_str="2026년 8월"
|
|
)
|
|
|
|
# =========================================================================
|
|
# 제1장 개발 환경 및 기술 스택
|
|
# =========================================================================
|
|
add_heading_1(doc, "제1장 개발 환경 및 기술 스택")
|
|
|
|
add_heading_2(doc, "1.1 개발 도구 및 프레임워크")
|
|
add_body_p(doc, "본 시스템은 크로스 플랫폼 네이티브 GUI 컴파일을 위해 Embarcadero Delphi 및 FireMonkey(FMX) 프레임워크를 기반으로 개발되었습니다.")
|
|
|
|
tech_tbl = doc.add_table(rows=1, cols=3)
|
|
format_table(
|
|
tech_tbl,
|
|
col_widths=[1.8, 2.2, 2.5],
|
|
headers=["구분", "선정 기술 / 도구", "버전 및 세부 사양"],
|
|
data=[
|
|
["통합 개발 환경 (IDE)", "Embarcadero RAD Studio / Delphi", "Delphi 11.3 Alexandria / Delphi 12 Athens 이상"],
|
|
["UI 프레임워크", "FireMonkey (FMX)", "GPU 하드웨어 가속 기반 멀티 디바이스 GUI"],
|
|
["통신 컴포넌트", "Indy 10 (Internet Direct)", "IdTCPClient, IdIOHandler, IdGlobal 기반 MQTT 통신"],
|
|
["데이터 직렬화", "System.JSON, System.IniFiles", "JSON 메시지 송수신 및 INI 로컬 설정 영속화"],
|
|
["OS 파일/경로 처리", "System.IOUtils", "TPath, TFile, TDirectory 기반 크로스플랫폼 파일 I/O"],
|
|
["비디오 스트리밍", "MPV / RTSP 라이브러리", "VCL RTSP 뷰어 및 FMX 미디어 컨트롤"]
|
|
]
|
|
)
|
|
|
|
add_heading_2(doc, "1.2 타겟 플랫폼 컴파일 구성")
|
|
add_body_p(doc, "Delphi FMX의 단일 코드베이스(Single Codebase) 아키텍처를 통해 하나의 소스코드로 다음 타겟 플랫폼 바이너리를 직접 빌드합니다:")
|
|
add_bullet_item(doc, " Windows 32-bit (Win32) 및 Windows 64-bit (Win64) x86/x64 네이티브 실행 파일", bold_prefix="• Windows:")
|
|
add_bullet_item(doc, " Android 32-bit (armeabi-v7a) 및 64-bit (arm64-v8a) APK / AAB 패키지", bold_prefix="• Android:")
|
|
add_bullet_item(doc, " macOS 64-bit Intel 및 Apple Silicon (ARM64) 유니버설 앱", bold_prefix="• macOS:")
|
|
add_bullet_item(doc, " Linux 64-bit (Ubuntu/Debian 등) 서버 및 임베디드 런타임", bold_prefix="• Linux:")
|
|
|
|
# =========================================================================
|
|
# 제2장 프로젝트 구조 및 소스 파일 구성
|
|
# =========================================================================
|
|
add_heading_1(doc, "제2장 프로젝트 구조 및 소스 파일 구성")
|
|
|
|
add_heading_2(doc, "2.1 소스 디렉터리 구성 트리")
|
|
add_code_block(doc,
|
|
"""SmartFarmHMI_SOURCE/
|
|
├── SmartFarmHMI_FMX/ # 메인 FMX 멀티플랫폼 프로젝트
|
|
│ ├── SmartFarmHMI.dpr # 프로그램 진입점 (메인 프로젝트 파일)
|
|
│ ├── SmartFarmHMI.dproj # RAD Studio 프로젝트 설정 및 빌드 옵션
|
|
│ ├── UMain.pas / UMain.fmx # 메인 폼, UI 레이아웃, 대시보드, 전체 이벤트 제어
|
|
│ ├── UMQTTClient.pas # 커스텀 경량 비동기 MQTT v3.1.1 클라이언트 유닛
|
|
│ ├── UAutoControl.pas # 자동 운전 및 관수 제어 자료구조/타입 정의
|
|
│ ├── UAutoControl_Impl.inc # TfrmMain 자동 운전 룰 엔진 구현 인클루드 파일
|
|
│ ├── UIrrigation_Impl.inc # TfrmMain 순차 관수 스케줄러 구현 인클루드 파일
|
|
│ ├── AndroidManifest.template.xml# 안드로이드 권한 및 메타데이터 템플릿
|
|
│ └── Artwork/ # 앱 아이콘 및 스플래시 이미지 리소스
|
|
├── SmartFarmHMI_VCL_RTSP_/ # Windows 전용 고성능 VCL RTSP/CCTV 뷰어
|
|
│ ├── SmartFarmHMI_VCL_RTSP.dpr # RTSP 독립 플레이어 프로젝트
|
|
│ ├── UMainRTSP.pas / .dfm # RTSP 스트리밍 폼
|
|
│ └── MPVClient.pas / MPV*.pas # LibMPV 기반 하드웨어 가속 비디오 렌더러
|
|
└── docs/ # 운영 및 개발 매뉴얼 문서 폴더"""
|
|
)
|
|
|
|
add_heading_2(doc, "2.2 주요 소스 파일 역할 및 의존 관계")
|
|
src_tbl = doc.add_table(rows=1, cols=3)
|
|
format_table(
|
|
src_tbl,
|
|
col_widths=[1.8, 1.8, 2.9],
|
|
headers=["소스 파일명", "소속 모듈", "핵심 구현 내용 및 책임"],
|
|
data=[
|
|
["UMain.pas / .fmx", "메인 컨트롤러", "UI 뷰 라이프사이클, 위젯 동적 생성, 타이머 루프, 설정 저장, 언어 전환"],
|
|
["UMQTTClient.pas", "통신 계층", "소켓 레벨 MQTT 패킷 인코딩/디코딩, 백그라운드 수신 스레드, 송신 큐, Keep-Alive"],
|
|
["UAutoControl.pas", "데이터 모델", "자동 룰 및 관수 스케줄의 레코드, Enum, 비교 연산자 정의"],
|
|
["UAutoControl_Impl.inc", "룰 엔진", "센서 임계값 비교, 다중 조건(AND/OR) 평가, 스케줄 On/Off 반복 제어 구현"],
|
|
["UIrrigation_Impl.inc", "관수 엔진", "구역별 순차 관수 FSM, 쿨다운 타이머, 유량계 펄스 적산 계산 구현"]
|
|
]
|
|
)
|
|
|
|
# =========================================================================
|
|
# 제3장 핵심 아키텍처 및 모듈 상세 분석
|
|
# =========================================================================
|
|
add_heading_1(doc, "제3장 핵심 아키텍처 및 모듈 상세 분석")
|
|
|
|
add_heading_2(doc, "3.1 메인 UI 및 위젯 동적 생성 시스템 (UMain.pas)")
|
|
add_body_p(doc, "메인 폼은 고정된 정적 컴포넌트 배치가 아닌, 노드 설정 배열(FNodeConfigs)에 따라 실행 시 동적으로 위젯(TSensorWidget, TControlWidget)을 생성하여 FlowLayout에 자동 배치하는 반응형 구조를 가집니다.")
|
|
|
|
add_bullet_item(doc, " FormCreate 시점에 INI 파일에서 노드 설정 및 통신 설정을 로드하고, InUse=True인 노드에 대해 AddSensor() 또는 AddControl()을 호출하여 위젯을 인스턴스화합니다.", bold_prefix="위젯 동적 빌드:")
|
|
add_bullet_item(doc, " 폼 크기 변경 이벤트(FormResize) 발생 시 디바이스 해상도에 맞춰 그리드 열 수와 위젯 크기를 재계산하여 PC, 태블릿, 모바일 화면에 최적 대응합니다.", bold_prefix="반응형 레이아웃:")
|
|
add_bullet_item(doc, " SwitchScreen(Index) 메서드를 통해 Dashboard(0), NodeSettings(1), CCTVSettings(2), CCTVViewer(3), AutoControl(4), Logs(5), Irrigation(6) 간 화면을 부드럽게 전환합니다.", bold_prefix="화면 관리자:")
|
|
|
|
add_heading_2(doc, "3.2 경량 비동기 MQTT v3.1.1 클라이언트 (UMQTTClient.pas)")
|
|
add_body_p(doc, "외부 무거운 라이브러리 종속성 없이 Indy 10 IdTCPClient 위에 순수 바이너리 패킷 수준으로 자체 구현된 고성능 비동기 MQTT 클라이언트입니다.")
|
|
|
|
add_code_block(doc,
|
|
"""// UMQTTClient.pas 핵심 클래스 구조
|
|
type
|
|
TMQTTMessageEvent = procedure(const ATopic, APayload: string) of object;
|
|
TMQTTStatusEvent = procedure(AConnected: Boolean) of object;
|
|
|
|
TMQTTClient = class
|
|
private
|
|
FTCPClient : TIdTCPClient;
|
|
FRecvThread : TMQTTRecvThread; // 백그라운드 수신 전용 스레드
|
|
FSendQueue : TList<TIdBytes>; // 스레드 안전 송신 패킷 큐
|
|
FSendLock : TCriticalSection; // 큐 동기화 락
|
|
FSubscribeTopics : TStringList; // 등록된 구독 토픽 목록
|
|
FKeepAlive : Word; // PING 주기 (기본 60초)
|
|
...
|
|
public
|
|
procedure Connect;
|
|
procedure Disconnect;
|
|
procedure Subscribe(const ATopic: string);
|
|
procedure Publish(const ATopic, APayload: string);
|
|
end;"""
|
|
)
|
|
|
|
add_body_p(doc, "핵심 통신 메커니즘:")
|
|
add_bullet_item(doc, " MQTT 수신 루프는 TMQTTRecvThread 백그라운드 스레드에서 무한 루프로 구동되며, 패킷 수신 시 TThread.Queue(nil, ...)를 통해 UI 메인 스레드에 비동기로 안전하게 전달(FireMessage)합니다.", bold_prefix="1. 비동기 수신 스레드:")
|
|
add_bullet_item(doc, " 모든 발행(Publish) 및 구독(Subscribe) 패킷은 즉시 소켓에 쓰지 않고 FSendQueue에 적재된 후 FlushSendQueue를 통해 스레드 락(TCriticalSection) 하에서 순차 전송되어 소켓 충돌을 원천 차단합니다.", bold_prefix="2. 송신 큐 동기화:")
|
|
add_bullet_item(doc, " KeepAlive 주기의 절반(최소 10초)마다 자동으로 PINGREQ 패킷을 전송하고 PINGRESP 응답을 확인하여 세션을 유지합니다.", bold_prefix="3. 하트비트(Keep-Alive):")
|
|
add_bullet_item(doc, " 소켓 예외 발생 시 자동으로 연결 해제 처리 후 재접속 루프를 반복 수행합니다.", bold_prefix="4. 자동 재연결:")
|
|
|
|
add_heading_2(doc, "3.3 자동 운전 룰 엔진 (UAutoControl_Impl.inc)")
|
|
add_body_p(doc, "자동 운전 엔진은 TimerAuto(1초 주기)에 의해 실행되며, 사용자가 정의한 TAutoRule 목록을 순회 평가하여 디지털 출력(DO)을 자동 제어합니다.")
|
|
|
|
add_bullet_item(doc, " 현재 시각이 StartHH:StartMM ~ EndHH:EndMM 범위에 포함되는지 검사합니다. 야간(자정 넘김) 조건도 완벽하게 지원합니다.", bold_prefix="1. 시간 범위 평가:")
|
|
add_bullet_item(doc, " 등록된 조건 배열(Conditions)을 순회하며 acoGT(>), acoLT(<), acoEQ(=) 수치 비교 및 acoON/acoOFF 접점 비교를 수행하고, CondMode(acmAND / acmOR)에 따라 최종 참/거짓을 판정합니다.", bold_prefix="2. 다중 조건식 평가 (EvalCondition):")
|
|
add_bullet_item(doc, " UseSchedule=True인 경우, 조건 충족 상태에서 WorkMinutes(가동) 및 RestMinutes(휴식) 타이머 틱을 계산하여 주기적 On/Off 반복 제어를 실행합니다.", bold_prefix="3. 작동/휴식 반복 타이머:")
|
|
add_bullet_item(doc, " WasActive 상태 플래그를 두어 상태가 실제로 변경될 때만 MQTT PublishControl()을 호출하므로 불필요한 네트워크 트래픽을 방지합니다.", bold_prefix="4. 중복 전송 방지:")
|
|
|
|
add_heading_2(doc, "3.4 순차 관수 스케줄러 엔진 (UIrrigation_Impl.inc)")
|
|
add_body_p(doc, "순차 관수 엔진은 여러 관수 구역을 지정된 조건과 순서대로 제어하는 상태 머신(Finite State Machine)으로 동작합니다.")
|
|
|
|
add_bullet_item(doc, " 1) 대기(Idle) -> 2) 트리거 시각 도달 -> 3) 구역 진입 & 시작조건 검사 -> 4) DO 밸브 개방(관수 시작) -> 5) 정지조건 달성 -> 6) 쿨다운 대기 -> 7) 종료조건/타임아웃 판정 -> 8) 다음 구역 이동 -> 9) 전 구역 완료 후 대기 복귀", bold_prefix="FSM 상태 전이 흐름:")
|
|
add_bullet_item(doc, " DI 유량계 펄스 입력 수신 시 이전 기준값(BaseValue)과의 차이를 계산하고, PerPulse 계수를 곱하여 실시간 유량(L 또는 Kg)을 산출하여 정지/종료 조건을 평가합니다.", bold_prefix="유량 적산 계산 알고리즘:")
|
|
add_bullet_item(doc, " TimeoutMin 설정 시 해당 시간 초과 시 다음 구역으로 강제 스킵하여 밸브 고착으로 인한 침수 사고를 예방합니다.", bold_prefix="안전 방어 로직:")
|
|
|
|
add_heading_2(doc, "3.5 MQTT 통신 프로토콜 상세 명세")
|
|
add_body_p(doc, "스마트팜 HMI와 게이트웨이 간 교환되는 토픽 및 JSON 메시지 규격은 다음과 같습니다:")
|
|
|
|
proto_tbl = doc.add_table(rows=1, cols=4)
|
|
format_table(
|
|
proto_tbl,
|
|
col_widths=[1.5, 1.8, 1.8, 1.4],
|
|
headers=["구분", "토픽 규격", "페이로드 포맷 예시", "설명"],
|
|
data=[
|
|
["센서 데이터 수신", "{HEAD}PUB/{TAIL}{GW_ID}", "{\"TM\":\"24.5|23.8\",\"HM\":\"65.2\"}", "게이트웨이 -> HMI 센서 측정값 보고"],
|
|
["DO 상태 수신", "{HEAD}PUB/{TAIL}{GW_ID}", "{\"DO\":\"1|0|0|1|0...\"}", "게이트웨이 -> HMI 릴레이 출력 상태 보고"],
|
|
["DI 접점 수신", "{HEAD}PUB/{TAIL}{GW_ID}", "{\"DI\":\"0|1|0|0...\"}", "게이트웨이 -> HMI 접점/펄스 상태 보고"],
|
|
["DO 장비 제어 송신", "{HEAD}SUB/{TAIL}{GW_ID}", "{\"DO_01\":\"1\"}", "HMI -> 게이트웨이 DO 1번 ON 명령 송신"]
|
|
]
|
|
)
|
|
|
|
add_heading_3(doc, "파이프(|) 구분자 데이터 파싱 함수 (ParsePipeValue)")
|
|
add_body_p(doc, "센서값 및 DI/DO 배열은 파이프(|)로 구분된 문자열로 전송되며, UMain.pas의 ParsePipeValue 함수가 지정된 인덱스의 값을 안전하게 실수(Double)로 추출합니다.")
|
|
|
|
add_heading_2(doc, "3.6 분리형 로깅 시스템 구현 (AddSystemLog)")
|
|
add_body_p(doc, "모든 로그는 AddSystemLog 메서드를 통해 4가지 카테고리(Sensor, Control, System, Error)로 자동 분류되어 디렉터리 및 파일별로 안전하게 분리 저장됩니다.")
|
|
|
|
add_code_block(doc,
|
|
"""procedure TfrmMain.AddSystemLog(const AMessage: string; const ALogType: string = 'System');
|
|
var
|
|
LogDir, LogFile: string;
|
|
LogLine: string;
|
|
begin
|
|
// 1. 플랫폼 독립적 홈 디렉터리 하위 전용 폴더 설정
|
|
LogDir := System.IOUtils.TPath.Combine(System.IOUtils.TPath.GetHomePath, 'SmartFarmHMI_Logs');
|
|
LogDir := System.IOUtils.TPath.Combine(LogDir, ALogType);
|
|
if not DirectoryExists(LogDir) then
|
|
ForceDirectories(LogDir);
|
|
|
|
// 2. 카테고리명_YYYYMMDD.log 파일명 생성
|
|
LogFile := System.IOUtils.TPath.Combine(LogDir, ALogType + '_' + FormatDateTime('yyyymmdd', Now) + '.log');
|
|
|
|
// 3. 타임스탬프 결합
|
|
LogLine := '[' + FormatDateTime('yyyy-mm-dd hh:nn:ss', Now) + '] ' + AMessage;
|
|
|
|
// 4. 스레드 세이프 UTF-8 추가 기록
|
|
try
|
|
System.IOUtils.TFile.AppendAllText(LogFile, LogLine + sLineBreak, TEncoding.UTF8);
|
|
except
|
|
end;
|
|
end;"""
|
|
)
|
|
|
|
# =========================================================================
|
|
# 제4장 빌드, 디버깅 및 배포 가이드
|
|
# =========================================================================
|
|
add_heading_1(doc, "제4장 빌드, 디버깅 및 배포 가이드")
|
|
|
|
add_heading_2(doc, "4.1 Windows 배포 (Win32 / Win64)")
|
|
add_bullet_item(doc, " RAD Studio Project Manager에서 Target Platforms -> 32-bit Windows 또는 64-bit Windows 선택", bold_prefix="1) 타겟 플랫폼 선택:")
|
|
add_bullet_item(doc, " Build Configurations를 'Release'로 변경하여 디버그 심볼 제거 및 최적화 활성화", bold_prefix="2) 릴리즈 모드 빌드:")
|
|
add_bullet_item(doc, " Project -> Build SmartFarmHMI 실행 -> Release 폴더에 생성된 단일 exe 배포", bold_prefix="3) 산출물 패키징:")
|
|
|
|
add_heading_2(doc, "4.2 Android 배포 (ARM64 / ARM32)")
|
|
add_bullet_item(doc, " AndroidManifest.template.xml에 INTERNET, ACCESS_NETWORK_STATE, READ/WRITE_EXTERNAL_STORAGE 권한 확인", bold_prefix="1) 매니페스트 권한 확인:")
|
|
add_bullet_item(doc, " Project Options -> Provisioning에서 배포용 키스토어(Keystore) 인증서 서명 설정", bold_prefix="2) 배포 서명 키 설정:")
|
|
add_bullet_item(doc, " Target Platforms -> Android 64-bit 선택 후 Project -> Deploy 실행하여 최종 APK/AAB 생성", bold_prefix="3) 패키징:")
|
|
|
|
# =========================================================================
|
|
# 제5장 기능 확장 및 유지보수 가이드
|
|
# =========================================================================
|
|
add_heading_1(doc, "제5장 기능 확장 및 유지보수 가이드")
|
|
|
|
add_heading_2(doc, "5.1 신규 센서 및 노드 타입 추가 절차")
|
|
add_bullet_item(doc, " UMain.pas 상단 상수 정의부(Init_TM, Init_HM 등)에 신규 노드 타입 수량 상수(예: Init_SOLAR = 2)를 추가합니다.", bold_prefix="Step 1: 상수 선언:")
|
|
add_bullet_item(doc, " InitNodeConfigs 메서드 내의 SetLength 배열 크기 계산 및 AddNodes('SOLAR', '일사량', 'Solar Radiation', Init_SOLAR) 호출 코드를 추가합니다.", bold_prefix="Step 2: 노드 초기화 등록:")
|
|
add_bullet_item(doc, " GetGroupName() 함수에 신규 노드 타입의 한/영 그룹 라벨 매핑 코드를 추가합니다.", bold_prefix="Step 3: 그룹 라벨 매핑:")
|
|
add_bullet_item(doc, " OnMQTTMessage의 센서값 파싱 루프에서 신규 노드 타입에 대한 스케일링 계수를 반영합니다.", bold_prefix="Step 4: 데이터 파싱:")
|
|
|
|
add_heading_2(doc, "5.2 커스텀 UI 위젯 및 테마 색상 수정")
|
|
add_body_p(doc, "위젯 배경색, 프로그레스 바 색상, 폰트 규격은 UMain.pas의 AddSensor 및 AddControl 메서드 내부 TRectangle.Fill.Color 설정을 통해 중앙 제어됩니다. 표준 테마 컬러 팔레트를 수정하여 다크 모드 또는 고유 브랜딩 테마를 쉽게 적용할 수 있습니다.")
|
|
|
|
doc.save(r"c:\Users\MyName\Desktop\SmartFarmHMI_SOURCE\docs\SmartFarmHMI_개발매뉴얼.docx")
|
|
print("Development manual generated successfully.")
|
|
|
|
if __name__ == "__main__":
|
|
create_development_manual()
|