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; // 스레드 안전 송신 패킷 큐 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()