From aeb4b40768164ebf38bc3cd64b17c06681356d68 Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期六, 04 一月 2025 15:53:05 +0800
Subject: [PATCH] 1.增加日志窗口和模块;

---
 SourceCode/Bond/Servo/Servo.vcxproj         |    9 
 SourceCode/Bond/Servo/resource.h            |    0 
 SourceCode/Bond/Servo/LogEdit.h             |   21 
 SourceCode/Bond/Servo/Servo.cpp             |    1 
 SourceCode/Bond/Servo/Configuration.cpp     |  105 ++++
 SourceCode/Bond/Servo/LogEdit.cpp           |   87 +++
 SourceCode/Bond/Servo/Model.cpp             |  246 +++++++++++
 SourceCode/Bond/Servo/ServoDlg.cpp          |   69 +++
 SourceCode/Bond/Servo/Configuration.h       |   31 +
 SourceCode/Bond/Servo/Servo.vcxproj.filters |   24 +
 SourceCode/Bond/Servo/Log.cpp               |  214 +++++++++
 SourceCode/Bond/Servo/LogDlg.cpp            |  314 ++++++++++++++
 SourceCode/Bond/Servo/Model.h               |   27 +
 SourceCode/Bond/Servo/Servo.rc              |    0 
 SourceCode/Bond/Servo/LogDlg.h              |   61 ++
 SourceCode/Bond/Servo/Log.h                 |   61 ++
 SourceCode/Bond/Servo/Common.h              |    1 
 SourceCode/Bond/Servo/ServoDlg.h            |    6 
 18 files changed, 1,277 insertions(+), 0 deletions(-)

diff --git a/SourceCode/Bond/Servo/Common.h b/SourceCode/Bond/Servo/Common.h
index 3fbaa5e..394b577 100644
--- a/SourceCode/Bond/Servo/Common.h
+++ b/SourceCode/Bond/Servo/Common.h
@@ -12,6 +12,7 @@
 
 /* 颜色 */
 #define APPDLG_BACKGROUND_COLOR			RGB(255, 255, 255)
+#define LOGDLG_BACKGROUND_COLOR			RGB(255, 255, 255)
 
 
 /* LOG BTN */
diff --git a/SourceCode/Bond/Servo/Configuration.cpp b/SourceCode/Bond/Servo/Configuration.cpp
new file mode 100644
index 0000000..b8529b6
--- /dev/null
+++ b/SourceCode/Bond/Servo/Configuration.cpp
@@ -0,0 +1,105 @@
+#include "stdafx.h"
+#include "Configuration.h"
+
+
+CConfiguration::CConfiguration()
+{
+}
+
+CConfiguration::CConfiguration(const char* pszFilepath)
+{
+	m_strFilepath = pszFilepath;
+}
+
+CConfiguration::~CConfiguration()
+{
+}
+
+void CConfiguration::setFilepath(const char* pszFilepath)
+{
+	m_strFilepath = pszFilepath;
+}
+
+void CConfiguration::getUnitId(CString& strUnitId)
+{
+	char szTemp[256];
+	GetPrivateProfileString("App", _T("UnitId"), _T(""), szTemp, 256, m_strFilepath);
+	strUnitId = szTemp;
+}
+
+void CConfiguration::getBond1(CString& strIp, UINT& port, UINT& doorCount)
+{
+	char szTemp[256];
+
+	// 读IP和端口
+	GetPrivateProfileString("Bond1", _T("IP"), _T(""), szTemp, 256, m_strFilepath);
+	strIp = szTemp;
+	port = GetPrivateProfileInt("Bond1", _T("Port"), 0, m_strFilepath);
+	doorCount = GetPrivateProfileInt("Bond1", _T("DoorCount"), 0, m_strFilepath);
+}
+
+int CConfiguration::getLogcatLevel()
+{
+	return GetPrivateProfileInt(_T("Logcat"), _T("Level"), 0, m_strFilepath);
+}
+
+void CConfiguration::setLogcatLevel(int level)
+{
+	WritePrivateProfileString(_T("Logcat"), _T("Level"),
+		std::to_string(level).c_str(), m_strFilepath);
+}
+
+int CConfiguration::setLogcatIncludeText(CString& strInclude)
+{
+	WritePrivateProfileString(_T("Logcat"), _T("IncludeText"),
+		strInclude, m_strFilepath);
+	return 0;
+}
+
+int CConfiguration::getLogcatIncludeText(CString& strInclude)
+{
+	char szTemp[256];
+	GetPrivateProfileString("Logcat", _T("IncludeText"), _T(""), szTemp, 256, m_strFilepath);
+	strInclude = szTemp;
+	return 0;
+}
+
+void CConfiguration::setLogcatIncludeRegex(BOOL bRegex)
+{
+	WritePrivateProfileString(_T("Logcat"), _T("IncludeRegex"),
+		bRegex ? "1" : "0", m_strFilepath);
+}
+
+BOOL CConfiguration::isLogcatIncludeRegex()
+{
+	return GetPrivateProfileInt(_T("Logcat"), _T("IncludeRegex"), 0, m_strFilepath);
+}
+
+int CConfiguration::getCustomLogcatIncludeTexts(std::vector<std::string>& texts)
+{
+	char szSection[256], szTemp[256];
+
+	for (int i = 0; i < 10; i++) {
+		sprintf_s(szSection, 256, "CustomInclude%d", i + 1);
+		GetPrivateProfileString("Logcat", szSection, _T(""),
+			szTemp, 256, m_strFilepath);
+		std::string strInclude(szTemp);
+		if (!strInclude.empty()) {
+			texts.push_back(strInclude);
+		}
+	}
+
+	return (int)texts.size();	
+}
+
+int CConfiguration::getP2RemoteEqReconnectInterval()
+{
+	return GetPrivateProfileInt(_T("P2"), _T("RemoteEqReconnectInterval"), 20, m_strFilepath);
+}
+
+void CConfiguration::setP2RemoteEqReconnectInterval(int second)
+{
+	WritePrivateProfileString(_T("P2"), _T("RemoteEqReconnectInterval"),
+		std::to_string(second).c_str(), m_strFilepath);
+}
+
diff --git a/SourceCode/Bond/Servo/Configuration.h b/SourceCode/Bond/Servo/Configuration.h
new file mode 100644
index 0000000..95d00e2
--- /dev/null
+++ b/SourceCode/Bond/Servo/Configuration.h
@@ -0,0 +1,31 @@
+#pragma once
+#include <vector>
+#include <string>
+
+class CConfiguration
+{
+public:
+	CConfiguration();
+	CConfiguration(const char* pszFilepath);
+	~CConfiguration();
+
+public:
+	void setFilepath(const char* pszFilepath);
+	void getUnitId(CString& strUnitId);
+	void getBond1(CString& strIp, UINT& port, UINT& doorCount);
+	int getLogcatLevel();
+	void setLogcatLevel(int level);
+	int setLogcatIncludeText(CString& strInclude);
+	int getLogcatIncludeText(CString& strInclude);
+	void setLogcatIncludeRegex(BOOL bRegex);
+	BOOL isLogcatIncludeRegex();
+	int getCustomLogcatIncludeTexts(std::vector<std::string>& texts);
+
+public:
+	void setP2RemoteEqReconnectInterval(int second);
+	int getP2RemoteEqReconnectInterval();
+
+private:
+	CString m_strFilepath;
+};
+
diff --git a/SourceCode/Bond/Servo/Log.cpp b/SourceCode/Bond/Servo/Log.cpp
new file mode 100644
index 0000000..8f2ce20
--- /dev/null
+++ b/SourceCode/Bond/Servo/Log.cpp
@@ -0,0 +1,214 @@
+#include "stdafx.h"
+#include "Log.h"
+
+
+static const char* pszLevel[] = {" [Debug] ", " [Info] ", " [Warn] ", " [Error] "};
+
+CLog::CLog()
+{
+	m_nLevel = 0;
+	m_nOutputTarget = OT_TRACE;
+	m_bAutoAppendTime = TRUE;
+	m_strEquipmentId = _T("Unknown");
+	m_nDay = 0;
+	m_funOnLog = nullptr;
+	InitializeCriticalSection(&m_criticalSection);
+}
+
+
+CLog::~CLog()
+{
+	DeleteCriticalSection(&m_criticalSection);
+}
+
+CLog *CLog::GetLog(void)
+{
+	static CLog* pLog = NULL;
+	if (pLog == NULL) {
+		static CLog log;
+		pLog = &log;
+	}
+
+	return pLog;
+}
+
+void CLog::SetOnLogCallback(ONLOG funOnLog)
+{
+	m_funOnLog = funOnLog;
+}
+
+void CLog::SetOutputTarget(int flag)
+{
+	m_nOutputTarget = flag;
+}
+
+void CLog::SetEquipmentId(const char* pszEquipmentId)
+{
+	m_strEquipmentId = pszEquipmentId;
+}
+
+void CLog::SetAutoAppendTimeString(BOOL bAutoAppendTime)
+{
+	m_bAutoAppendTime = bAutoAppendTime;
+}
+
+void CLog::Batch()
+{
+	if (m_file.m_hFile != CFile::hFileNull) {
+		m_file.Close();
+	}
+}
+
+BOOL CLog::BatchAndNew(int& nDay)
+{
+	Batch();
+	if ( (m_nOutputTarget & OT_FILE) && m_file.m_hFile == CFile::hFileNull) {
+		CString strFilepath;
+		BOOL bRet = m_file.Open(MakeFilepathD(strFilepath, nDay), CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::shareDenyWrite);
+		if (bRet) {
+			m_file.SeekToEnd();
+		}
+		return bRet;
+	}
+
+	return FALSE;
+}
+
+#define BUFFERSIZE		1024*10
+void CLog::LogFormat(int nLevel, const char* pszTag, char* szMessage, ...)
+{
+	// 检查日期是否有变化,有变化则结批
+	Lock();
+	_SYSTEMTIME sysTime;
+	GetLocalTime(&sysTime);
+	if(m_nDay != sysTime.wDay) {
+		int nDay = 0;
+		if (BatchAndNew(nDay)) {
+			m_nDay = nDay;
+		}
+	}
+	Unlock();
+
+	if (nLevel < m_nLevel) {
+		return;
+	}
+
+	char szFullMessage[BUFFERSIZE];
+	char szFormatMessage[BUFFERSIZE];
+
+	// format message
+	va_list ap;
+	va_start(ap, szMessage);
+	_vsnprintf_s(szFormatMessage, BUFFERSIZE, szMessage, ap);
+	va_end(ap);
+
+	if (m_bAutoAppendTime) {
+		CString strTime;
+		strcpy_s(szFullMessage, BUFFERSIZE, (LPTSTR)(LPCTSTR)GetCurTime(strTime));
+	}
+	strcat_s(szFullMessage, BUFFERSIZE, pszLevel[nLevel]);
+	strcat_s(szFullMessage, szFormatMessage);
+	strcat_s(szFullMessage, BUFFERSIZE, "\n");
+
+	if (m_nOutputTarget & OT_FILE) {
+		Lock();
+		if (m_file.m_hFile != CFile::hFileNull) {
+			m_file.WriteString(szFullMessage);
+		}
+		Unlock();
+	}
+	if (m_nOutputTarget & OT_ODSTRING) {
+		OutputDebugStringA(szFullMessage);
+	}
+	else if(m_nOutputTarget & OT_TRACE) {
+		TRACE(szFormatMessage);
+	}
+
+	if (m_funOnLog != nullptr) {
+		m_funOnLog(nLevel, szFullMessage);
+	}
+}
+
+void CLog::Log(int nLevel, const char* pszTag, const char* szMessage)
+{
+	// 检查日期是否有变化,有变化则结批
+	Lock();
+	_SYSTEMTIME sysTime;
+	GetLocalTime(&sysTime);
+	if (m_nDay != sysTime.wDay) {
+		int nDay = 0;
+		if (BatchAndNew(nDay)) {
+			m_nDay = nDay;
+		}
+	}
+	Unlock();
+
+	if (nLevel < m_nLevel) {
+		return;
+	}
+
+	CString strMsg;
+	if (m_bAutoAppendTime) {
+		CString strTime;
+		GetCurTime(strTime);
+		strMsg.Append(strTime);
+	}
+	strMsg.Append(pszTag);
+	strMsg.Append(szMessage);
+	strMsg.Append("\n");
+
+	if (m_nOutputTarget & OT_FILE) {
+		Lock();
+		if (m_file.m_hFile != CFile::hFileNull) {
+			m_file.WriteString(strMsg);
+		}
+		Unlock();
+	}
+	if (m_nOutputTarget & OT_ODSTRING) {
+		OutputDebugStringA(strMsg);
+	}
+	else if (m_nOutputTarget & OT_TRACE) {
+		TRACE(strMsg);
+	}
+
+	if (m_funOnLog != nullptr) {
+		m_funOnLog(nLevel, strMsg);
+	}
+}
+
+CString& CLog::GetCurTime(CString& strTime)
+{
+	_SYSTEMTIME sysTime;
+	GetLocalTime(&sysTime);
+	strTime.Format(_T("%d/%02d/%02d %02d:%02d:%02d.%03d"), sysTime.wYear, sysTime.wMonth, sysTime.wDay,
+		sysTime.wHour, sysTime.wMinute, sysTime.wSecond, sysTime.wMilliseconds);
+	return strTime;
+}
+
+CString& CLog::MakeFilepath(CString& strFilepath)
+{
+	_SYSTEMTIME sysTime;
+	GetLocalTime(&sysTime);
+	strFilepath.Format(_T("%s\\Log(%s)_%d_%02d_%02d.log"), (LPTSTR)(LPCTSTR)m_strLogsDir,
+		(LPTSTR)(LPCTSTR)m_strEquipmentId,
+		sysTime.wYear, sysTime.wMonth, sysTime.wDay);
+
+	return strFilepath;
+}
+
+CString& CLog::MakeFilepathD(CString& strFilepath, int& day)
+{
+	_SYSTEMTIME sysTime;
+	GetLocalTime(&sysTime);
+	strFilepath.Format(_T("%s\\Log(%s)_%d_%02d_%02d.log"), (LPTSTR)(LPCTSTR)m_strLogsDir,
+		(LPTSTR)(LPCTSTR)m_strEquipmentId,
+		sysTime.wYear, sysTime.wMonth, sysTime.wDay);
+	day = sysTime.wDay;
+
+	return strFilepath;
+}
+
+void CLog::SetLogsDir(CString strDir)
+{
+	m_strLogsDir = strDir;
+}
diff --git a/SourceCode/Bond/Servo/Log.h b/SourceCode/Bond/Servo/Log.h
new file mode 100644
index 0000000..ee28e15
--- /dev/null
+++ b/SourceCode/Bond/Servo/Log.h
@@ -0,0 +1,61 @@
+#pragma once
+#include <functional>
+
+
+#define LEVEL_DEBUG		0
+#define LEVEL_INFO		1
+#define LEVEL_WARN		2
+#define LEVEL_ERROR		3
+
+
+#define LOGD(msg, ...)		CLog::GetLog()->LogFormat(LEVEL_DEBUG, "", msg, __VA_ARGS__)
+#define LOGI(msg, ...)		CLog::GetLog()->LogFormat(LEVEL_INFO, "", msg, __VA_ARGS__)
+#define LOGW(msg, ...)		CLog::GetLog()->LogFormat(LEVEL_WARN, "", msg, __VA_ARGS__)
+#define LOGE(msg, ...)		CLog::GetLog()->LogFormat(LEVEL_ERROR, "", msg, __VA_ARGS__)
+
+
+#define OT_FILE			0x01
+#define OT_ODSTRING		0x02
+#define OT_TRACE		0x04
+#define LOGBATHCH()				CLog::GetLog()->Batch()
+#define LOGNEW()				CLog::GetLog()->BatchAndNew()
+
+typedef std::function<void(int level, const char* pszMessage)> ONLOG;
+
+class CLog
+{
+public:
+	CLog();
+	~CLog();
+
+public:
+	void SetOnLogCallback(ONLOG funOnLog);
+	static CLog *GetLog(void);
+	void SetOutputTarget(int flag);
+	void SetEquipmentId(const char* pszEquipmentId);
+	static CString& GetCurTime(CString& strTime);
+	CString& MakeFilepath(CString& strFilepath);
+	CString& MakeFilepathD(CString& strFilepath, int& day);
+	void LogFormat(int nLevel, const char* pszTag, char* szMessage, ...);
+	void Log(int nLevel, const char* pszTag, const char* szMessage);
+	void SetAutoAppendTimeString(BOOL bAutoAppendTime);
+	void SetLogsDir(CString strDir);
+	void Batch();
+	BOOL BatchAndNew(int& nDay);
+
+private:
+	inline void Lock() { EnterCriticalSection(&m_criticalSection); }
+	inline void Unlock() { LeaveCriticalSection(&m_criticalSection); }
+
+private:
+	ONLOG m_funOnLog;
+	int m_nOutputTarget;
+	int m_nLevel;
+	BOOL m_bAutoAppendTime;
+	CString m_strLogsDir;
+	CString m_strEquipmentId;
+	CStdioFile m_file;
+	int m_nDay;						// 按日保存一条记录,比较此数字,以决定是否结批并创建新文件
+	CRITICAL_SECTION m_criticalSection;
+};
+
diff --git a/SourceCode/Bond/Servo/LogDlg.cpp b/SourceCode/Bond/Servo/LogDlg.cpp
new file mode 100644
index 0000000..ce0617e
--- /dev/null
+++ b/SourceCode/Bond/Servo/LogDlg.cpp
@@ -0,0 +1,314 @@
+// LogDlg.cpp : 实现文件
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "LogDlg.h"
+#include "afxdialogex.h"
+#include "Common.h"
+#include <regex>
+
+
+// CLogDlg 对话框
+
+IMPLEMENT_DYNAMIC(CLogDlg, CDialogEx)
+
+CLogDlg::CLogDlg(CWnd* pParent /*=NULL*/)
+	: CDialogEx(IDD_DIALOG_LOG, pParent)
+{
+	m_crBkgnd = LOGDLG_BACKGROUND_COLOR;
+	m_hbrBkgnd = nullptr;
+	m_pObserver = nullptr;
+	m_nLevel = 0;
+	m_strIncludeText = _T("");
+	m_bIncludeRegex = FALSE;
+}
+
+CLogDlg::~CLogDlg()
+{
+}
+
+void CLogDlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_BUTTON_LEVEL, m_btnLevel);
+	DDX_Control(pDX, IDC_BUTTON_INCLUDE, m_btnInclude);
+	DDX_Control(pDX, IDC_EDIT_LOG, m_logEdit);
+}
+
+
+BEGIN_MESSAGE_MAP(CLogDlg, CDialogEx)
+	ON_WM_CTLCOLOR()
+	ON_WM_SIZE()
+	ON_WM_DESTROY()
+	ON_WM_CLOSE()
+	ON_NOTIFY(BLBUTTON_MENU_ITEM_CLICKED, IDC_BUTTON_LEVEL, &CLogDlg::OnButtonLevelMenuClicked)
+	ON_NOTIFY(BLBUTTON_MENU_ITEM_CLICKED, IDC_BUTTON_INCLUDE, &CLogDlg::OnButtonIncludeMenuClicked)
+	ON_EN_CHANGE(IDC_EDIT_INCLUDE, &CLogDlg::OnEnChangeEditInclude)
+	ON_BN_CLICKED(IDC_CHECK_REGEX, &CLogDlg::OnBnClickedCheckRegex)
+END_MESSAGE_MAP()
+
+
+// CLogDlg 消息处理程序
+
+
+void CLogDlg::InitRxWindow()
+{
+	/* code */
+	// 订阅数据
+	IRxWindows* pRxWindows = RX_GetRxWindows();
+	pRxWindows->enableLog(5);
+	if (m_pObserver == NULL) {
+		m_pObserver = pRxWindows->allocObserver([&](IAny* pAny) -> void {
+			// onNext
+			pAny->addRef();
+			int code = pAny->getCode();
+			if (RX_CODE_LOG == code && ::IsWindow(m_hWnd)) {
+				const char* pszLogMsg;
+				int level;
+				if (pAny->getStringValue("text", pszLogMsg)
+					&& pAny->getIntValue("exCode", level)) {
+					if (level >= m_nLevel) {
+						CString strText = pszLogMsg;
+						BOOL bInclude = TRUE;
+						if (!m_strIncludeText.IsEmpty()) {
+							if (!m_bIncludeRegex) {
+								bInclude = (strText.Find(m_strIncludeText) >= 0);
+							}
+							else {
+								bInclude = std::regex_search((LPTSTR)(LPCTSTR)strText,
+									std::regex((LPTSTR)(LPCTSTR)m_strIncludeText));
+							}
+						}
+
+
+						if (bInclude) {
+							strText.Replace("\n", "\r\n");
+							AppendLog(level, (LPTSTR)(LPCTSTR)strText);
+						}
+					}
+				}
+			}
+
+			pAny->release();
+		}, [&]() -> void {
+			// onComplete
+		}, [&](IThrowable* pThrowable) -> void {
+			// onErrorm
+			pThrowable->printf();
+		});
+		theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())
+			->subscribe(m_pObserver);
+	}
+}
+
+BOOL CLogDlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+
+	// 缓存
+	m_nLevel = theApp.m_model.m_configuration.getLogcatLevel();
+	theApp.m_model.m_configuration.getLogcatIncludeText(m_strIncludeText);
+	m_bIncludeRegex = theApp.m_model.m_configuration.isLogcatIncludeRegex();
+	theApp.m_model.m_configuration.getCustomLogcatIncludeTexts(m_customIncludeTexts);
+
+
+	// Level
+	HMENU hMenu = LoadMenu(AfxGetInstanceHandle(), MAKEINTRESOURCEA(IDR_MENU_LOGCAT_LEVEL));
+	m_btnLevel.SetMenu(hMenu);
+	m_btnLevel.SetCurrentMenuItem(m_nLevel);
+	m_btnLevel.SetBkgndColor(BS_NORMAL, RGB(241, 242, 244));
+	m_btnLevel.SetBkgndColor(BS_HOVER, RGB(220, 223, 228));
+	m_btnLevel.SetBkgndColor(BS_PRESS, RGB(179, 185, 196));
+	m_btnLevel.SetFrameColor(BS_NORMAL, RGB(133, 144, 162));
+	m_btnLevel.SetFrameColor(BS_HOVER, RGB(56, 139, 255));
+	m_btnLevel.SetFrameColor(BS_PRESS, RGB(56, 139, 255));
+	m_btnLevel.SetFrameColor(BS_FOCUS, RGB(56, 139, 255));
+
+
+	// 包含字符串
+	CString strIcon1;
+	strIcon1.Format(_T("%s\\Res\\logcat_include.ico"), theApp.m_strAppDir);
+	HICON hIcon = (HICON)::LoadImage(AfxGetInstanceHandle(),
+		strIcon1, IMAGE_ICON, 24, 24,
+		LR_LOADFROMFILE | LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE);
+	m_btnInclude.SetIcon(hIcon, hIcon, 24);
+
+	{
+		HMENU hMenu = LoadMenu(AfxGetInstanceHandle(), MAKEINTRESOURCEA(IDR_MENU_INCLUDE));
+		HMENU hSubMenu = GetSubMenu(hMenu, 0);
+		DeleteMenu(hSubMenu, 0, MF_BYPOSITION);
+		int i = 0;
+		for (auto& item : m_customIncludeTexts) {
+			i++;
+			InsertMenu(hSubMenu, 0, MF_BYPOSITION, 0x1998 + i, item.c_str());
+			m_btnInclude.SetMenu(hMenu);
+		}
+	}
+
+
+	SetDlgItemText(IDC_EDIT_INCLUDE, m_strIncludeText);
+	CButton* pCheckBox = (CButton*)GetDlgItem(IDC_CHECK_REGEX);
+	pCheckBox->SetCheck(m_bIncludeRegex ? BST_CHECKED : BST_UNCHECKED);
+
+
+	// 内容
+	m_logEdit.SetMaxLineCount(20);
+	m_logEdit.SetLimitText(-1);
+
+
+	InitRxWindow();
+
+
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // 异常: OCX 属性页应返回 FALSE
+}
+
+void CLogDlg::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+	if (GetDlgItem(IDC_EDIT_LOG) == nullptr) return;
+
+	int x, y, y2, temp;
+	CRect rcClient, rcItem;
+	CWnd* pItem;
+	GetClientRect(&rcClient);
+
+	y = 0;
+	x = 12;
+	pItem = GetDlgItem(IDC_BUTTON_LEVEL);
+	pItem->GetWindowRect(&rcItem);
+	ScreenToClient(&rcItem);
+	pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
+	x += rcItem.Width();
+	x += 18;
+	y2 = rcItem.bottom;
+
+	pItem = GetDlgItem(IDC_BUTTON_INCLUDE);
+	pItem->GetWindowRect(&rcItem);
+	pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
+	x += rcItem.Width();
+	x += 2;
+
+	pItem = GetDlgItem(IDC_EDIT_INCLUDE);
+	pItem->GetWindowRect(&rcItem);
+	pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
+	x += rcItem.Width();
+	x += 12;
+	temp = rcItem.Height();
+
+	pItem = GetDlgItem(IDC_CHECK_REGEX);
+	pItem->GetWindowRect(&rcItem);
+	pItem->MoveWindow(x, y + (temp - rcItem.Height()) / 2, rcItem.Width(), rcItem.Height());
+
+	x = 12;
+	pItem = GetDlgItem(IDC_EDIT_LOG);
+	pItem->MoveWindow(x, y2, rcClient.Width() - 24, rcClient.Height() - 5 - y2);
+}
+
+void CLogDlg::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+
+	{
+		HMENU hMenu = m_btnLevel.GetMenu();
+		if (hMenu != NULL) {
+			::DestroyMenu(hMenu);
+		}
+	}
+
+	{
+		HMENU hMenu = m_btnInclude.GetMenu();
+		if (hMenu != NULL) {
+			::DestroyMenu(hMenu);
+		}
+	}
+
+	if (m_hbrBkgnd != nullptr) {
+		::DeleteObject(m_hbrBkgnd);
+	}
+
+	if (m_pObserver != NULL) {
+		m_pObserver->unsubscribe();
+		m_pObserver = NULL;
+	}
+}
+
+HBRUSH CLogDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	if (nCtlColor == CTLCOLOR_STATIC) {
+		pDC->SetBkColor(m_crBkgnd);
+	}
+
+	if (m_hbrBkgnd == nullptr) {
+		m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
+	}
+
+	return m_hbrBkgnd;
+}
+
+void CLogDlg::OnClose()
+{
+	ShowWindow(SW_HIDE);
+	GetParent()->PostMessage(ID_MSG_LOGDLG_HIDE, 0, 0);
+}
+
+BOOL CLogDlg::PreTranslateMessage(MSG* pMsg)
+{
+	if (pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE) {
+		return TRUE;
+	}
+
+	return CDialogEx::PreTranslateMessage(pMsg);
+}
+
+void CLogDlg::AppendLog(int level, const char* pszText)
+{
+	if (!::IsWindow(m_logEdit.m_hWnd)) {
+		return;
+	}
+	m_logEdit.AppendText(pszText);
+}
+
+void CLogDlg::OnButtonLevelMenuClicked(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	BLBUTTON_NMHDR* pblbNmhdr = reinterpret_cast<BLBUTTON_NMHDR*>(pNMHDR);
+	m_nLevel = (int)pblbNmhdr->dwData;
+	theApp.m_model.m_configuration.setLogcatLevel(m_nLevel);
+	m_btnLevel.SetCurrentMenuItem(m_nLevel);
+
+	*pResult = 0;
+}
+
+void CLogDlg::OnButtonIncludeMenuClicked(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	BLBUTTON_NMHDR* pblbNmhdr = reinterpret_cast<BLBUTTON_NMHDR*>(pNMHDR);
+	int position = (int)pblbNmhdr->dwData;
+	std::string& strInclude = m_customIncludeTexts.at(position);
+	SetDlgItemText(IDC_EDIT_INCLUDE, strInclude.c_str());
+	CButton* pCheckBox = (CButton*)GetDlgItem(IDC_CHECK_REGEX);
+	m_bIncludeRegex = FALSE;
+	pCheckBox->SetCheck(BST_UNCHECKED);
+
+	theApp.m_model.m_configuration.setLogcatIncludeRegex(m_bIncludeRegex);
+	theApp.m_model.m_configuration.setLogcatIncludeText(m_strIncludeText);
+
+	*pResult = 0;
+}
+
+void CLogDlg::OnEnChangeEditInclude()
+{
+	GetDlgItemText(IDC_EDIT_INCLUDE, m_strIncludeText);
+	theApp.m_model.m_configuration.setLogcatIncludeText(m_strIncludeText);
+}
+
+void CLogDlg::OnBnClickedCheckRegex()
+{
+	CButton* pCheckBox = (CButton*)GetDlgItem(IDC_CHECK_REGEX);
+	m_bIncludeRegex = pCheckBox->GetCheck();
+	theApp.m_model.m_configuration.setLogcatIncludeRegex(m_bIncludeRegex);
+}
diff --git a/SourceCode/Bond/Servo/LogDlg.h b/SourceCode/Bond/Servo/LogDlg.h
new file mode 100644
index 0000000..457fcf7
--- /dev/null
+++ b/SourceCode/Bond/Servo/LogDlg.h
@@ -0,0 +1,61 @@
+#pragma once
+#include "LogEdit.h"
+#include "BlButton.h"
+#include <vector>
+#include <string>
+
+
+#define ID_MSG_LOGDLG_HIDE		WM_USER + 1023
+
+// CLogDlg 对话框
+
+class CLogDlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CLogDlg)
+
+public:
+	CLogDlg(CWnd* pParent = NULL);   // 标准构造函数
+	virtual ~CLogDlg();
+
+
+private:
+	void InitRxWindow();
+	void AppendLog(int level, const char* pszText);
+
+
+private:
+	COLORREF m_crBkgnd;
+	HBRUSH m_hbrBkgnd;
+	IObserver* m_pObserver;
+	int m_nLevel;
+	CString m_strIncludeText;
+	BOOL m_bIncludeRegex;
+	std::vector<std::string> m_customIncludeTexts;
+
+private:
+	CBlButton m_btnLevel;
+	CBlButton m_btnInclude;
+	CLogEdit m_logEdit;
+
+
+// 对话框数据
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_LOG };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnDestroy();
+	afx_msg void OnClose();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	virtual BOOL PreTranslateMessage(MSG* pMsg);
+	afx_msg void OnButtonLevelMenuClicked(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg void OnButtonIncludeMenuClicked(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg void OnEnChangeEditInclude();
+	afx_msg void OnBnClickedCheckRegex();
+};
diff --git a/SourceCode/Bond/Servo/LogEdit.cpp b/SourceCode/Bond/Servo/LogEdit.cpp
new file mode 100644
index 0000000..0e9fd35
--- /dev/null
+++ b/SourceCode/Bond/Servo/LogEdit.cpp
@@ -0,0 +1,87 @@
+#include "stdafx.h"
+#include "LogEdit.h"
+
+
+#define MENU_ITEM_SEL_ALL		0x666
+#define MENU_ITEM_COPY			0x667
+#define MENU_ITEM_CLEAR			0x668
+
+CLogEdit::CLogEdit()
+{
+	m_nMaxLineCount = 0xffff;
+}
+
+
+CLogEdit::~CLogEdit()
+{
+}
+
+BEGIN_MESSAGE_MAP(CLogEdit, CEdit)
+	ON_WM_CONTEXTMENU()
+END_MESSAGE_MAP()
+
+void CLogEdit::SetMaxLineCount(int line)
+{
+	m_nMaxLineCount = line;
+}
+
+void CLogEdit::OnContextMenu(CWnd* pWnd, CPoint point)
+{
+	HMENU hMenu = CreatePopupMenu();
+	InsertMenu(hMenu, 0, MF_BYPOSITION, MENU_ITEM_SEL_ALL, "全选");
+	InsertMenu(hMenu, 1, MF_BYPOSITION, MENU_ITEM_COPY, "复制");
+	InsertMenu(hMenu, 2, MF_BYPOSITION | MF_SEPARATOR, NULL, NULL);	
+	InsertMenu(hMenu, 3, MF_BYPOSITION, MENU_ITEM_CLEAR, "全部清除");
+	int cmd = ::TrackPopupMenu(hMenu,
+		TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
+		point.x, point.y + 2, 0, m_hWnd, NULL);
+	DestroyMenu(hMenu);
+
+	if (cmd == MENU_ITEM_SEL_ALL) {
+		SetFocus();
+		this->SetSel(0, -1);
+	}
+	else if (cmd == MENU_ITEM_COPY) {
+		this->Copy();
+	}
+	else if (cmd == MENU_ITEM_CLEAR) {
+		SetWindowText(_T(""));
+	}
+}
+
+void CLogEdit::AppendText(const char* pszText)
+{
+	// 获取选择范围以便恢复
+	int nStart, nEnd;
+	GetSel(nStart, nEnd);
+
+
+	// 超过指定行数则删除最前面的行
+	int nLineCount = GetLineCount();
+	while (nLineCount > m_nMaxLineCount) {
+		int nLin1End = LineIndex(1);
+		nStart -= nLin1End;
+		if (nStart < 0) nStart = 0;
+		nEnd -= nLin1End;
+		if (nEnd < 0) nEnd = 0;
+
+		SetSel(0, nLin1End);
+		ReplaceSel(_T(""));
+		nLineCount = GetLineCount();
+	}
+
+
+	// 追加到最后
+	int length = GetWindowTextLength();
+	SetSel(length, length);
+	ReplaceSel(pszText);
+	PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
+
+
+	// 恢复
+	if (nStart == 0 && nEnd == 0) {
+		nStart = GetWindowTextLength();
+		nEnd = nStart;
+	}
+	SetSel(nStart, nEnd);
+}
diff --git a/SourceCode/Bond/Servo/LogEdit.h b/SourceCode/Bond/Servo/LogEdit.h
new file mode 100644
index 0000000..cdccb5c
--- /dev/null
+++ b/SourceCode/Bond/Servo/LogEdit.h
@@ -0,0 +1,21 @@
+#pragma once
+#include "afxwin.h"
+class CLogEdit :
+	public CEdit
+{
+public:
+	CLogEdit();
+	~CLogEdit();
+
+public:
+	void SetMaxLineCount(int line);
+	void AppendText(const char* pszText);
+
+
+private:
+	int m_nMaxLineCount;
+
+	DECLARE_MESSAGE_MAP()
+	afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
+};
+
diff --git a/SourceCode/Bond/Servo/Model.cpp b/SourceCode/Bond/Servo/Model.cpp
index d80aee1..4526155 100644
--- a/SourceCode/Bond/Servo/Model.cpp
+++ b/SourceCode/Bond/Servo/Model.cpp
@@ -1,21 +1,267 @@
 #include "stdafx.h"
 #include "Model.h"
+#include "Log.h"
+#include "Common.h"
 
 
 CModel::CModel()
 {
+	m_pObservableEmitter = nullptr;
+	m_pObservable = nullptr;
 }
 
 CModel::~CModel()
 {
 }
 
+IObservable* CModel::getObservable()
+{
+	return m_pObservable;
+}
+
+void CModel::setWorkDir(const char* pszWorkDir)
+{
+	m_strWorkDir = pszWorkDir;
+}
+
 int CModel::init()
 {
+	CString strIniFile;
+	CString strUnitId;
+	strIniFile.Format(_T("%s\\ServoConfiguration.ini"), (LPTSTR)(LPCTSTR)m_strWorkDir);
+	m_configuration.setFilepath((LPTSTR)(LPCTSTR)strIniFile);
+	m_configuration.getUnitId(strUnitId);
+
+
+	// Log
+	CString strLogDir;
+	strLogDir.Format(_T("%s\\Log"), (LPTSTR)(LPCTSTR)m_strWorkDir);
+	::CreateDirectory(strLogDir, NULL);
+	CLog::GetLog()->SetOnLogCallback([&](int level, const char* pszMessage) -> void {
+		notifyTextAndInt(RX_CODE_LOG, pszMessage, level);
+	});
+	CLog::GetLog()->SetAutoAppendTimeString(TRUE);
+	CLog::GetLog()->SetOutputTarget(OT_FILE);
+	CLog::GetLog()->SetLogsDir(strLogDir);
+	CLog::GetLog()->SetEquipmentId((LPTSTR)(LPCTSTR)strUnitId);
+	LOGI("\r\n\r\n~~~ Prog Start! ~~~");
+
+
+	m_pObservable = RX_AllocaObservable([&](IObservableEmitter* e) -> void {
+		m_pObservableEmitter = e;			// 保存发射器
+	});
+
+
 	return 0;
 }
 
 int CModel::term()
 {
+	CLog::GetLog()->SetOnLogCallback(nullptr);
+	return 0;
+}
+
+int CModel::notify(int code)
+{
+	/* code */
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 1;
+}
+
+int CModel::notifyPtr(int code, void* ptr/* = NULL*/)
+{
+	/* code */
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setPtrValue("ptr", ptr);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 1;
+}
+
+int CModel::notifyObj(int code, IRxObject* pObj)
+{
+	/* code */
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setObject("obj", pObj);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 1;
+}
+
+int CModel::notifyObjAndPtr(int code, IRxObject* pObj, void* ptr)
+{
+	/* code */
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setObject("obj", pObj);
+			pAny->setPtrValue("ptr", ptr);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 1;
+}
+
+int CModel::notifyInt(int code, int exCode)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setIntValue("exCode", exCode);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyInt2(int code, int exCode, int exCode2)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setIntValue("exCode", exCode);
+			pAny->setIntValue("exCode2", exCode2);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyDouble(int code, double dValue)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setDoubleValue("value", dValue);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyObjAndInt(int code, IRxObject* pObj1, IRxObject* pObj2, int exCode)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			if (pObj1 != nullptr) pAny->setObject("obj", pObj1);
+			if (pObj2 != nullptr) pAny->setObject("obj2", pObj2);
+			pAny->setIntValue("exCode", exCode);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyText(int code, const char* pszText)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setStringValue("text", pszText);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyTextAndInt(int code, const char* pszText, int exCode)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setStringValue("text", pszText);
+			pAny->setIntValue("exCode", exCode);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyPtrAndInt(int code, void* ptr1, void* ptr2, int exCode)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setPtrValue("ptr", ptr1);
+			pAny->setPtrValue("ptr1", ptr1);
+			pAny->setPtrValue("ptr2", ptr2);
+			pAny->setIntValue("exCode", exCode);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
+	return 0;
+}
+
+int CModel::notifyMesMsg(int code, int stream, int function, const char* pszText)
+{
+	if (m_pObservableEmitter != NULL) {
+		IAny* pAny = RX_AllocaAny();
+		if (pAny != NULL) {
+			pAny->addRef();
+			pAny->setCode(code);
+			pAny->setIntValue("stream", stream);
+			pAny->setIntValue("function", function);
+			pAny->setStringValue("text", pszText);
+			m_pObservableEmitter->onNext(pAny);
+			pAny->release();
+		}
+	}
+
 	return 0;
 }
diff --git a/SourceCode/Bond/Servo/Model.h b/SourceCode/Bond/Servo/Model.h
index acba736..b6b954f 100644
--- a/SourceCode/Bond/Servo/Model.h
+++ b/SourceCode/Bond/Servo/Model.h
@@ -1,4 +1,6 @@
 #pragma once
+#include "Configuration.h"
+
 class CModel
 {
 public:
@@ -6,7 +8,32 @@
 	~CModel();
 
 public:
+	IObservable* getObservable();
+	void setWorkDir(const char* pszWorkDir);
 	int init();
 	int term();
+
+public:
+	int notify(int code);
+	int notifyPtr(int code, void* ptr = NULL);
+	int notifyObj(int code, IRxObject* pObj);
+	int notifyObjAndPtr(int code, IRxObject* pObj, void* ptr);
+	int notifyObjAndInt(int code, IRxObject* pObj1, IRxObject* pObj2, int exCode);
+	int notifyInt(int code, int exCode);
+	int notifyInt2(int code, int exCode, int exCode2);
+	int notifyDouble(int code, double dValue);
+	int notifyText(int code, const char* pszText);
+	int notifyPtrAndInt(int code, void* ptr1, void* ptr2, int exCode);
+	int notifyTextAndInt(int code, const char* pszText, int exCode);
+	int notifyMesMsg(int code, int stream, int function, const char* pszText);
+
+public:
+	CConfiguration m_configuration;
+
+private:
+	IObservable* m_pObservable;
+	IObservableEmitter* m_pObservableEmitter;
+	CString m_strWorkDir;
+	CString m_strDataDir;
 };
 
diff --git a/SourceCode/Bond/Servo/Servo.cpp b/SourceCode/Bond/Servo/Servo.cpp
index 4e005c6..9f7e3fe 100644
--- a/SourceCode/Bond/Servo/Servo.cpp
+++ b/SourceCode/Bond/Servo/Servo.cpp
@@ -81,6 +81,7 @@
 	_tsplitpath_s(sAppFilename, sDrive, sDir, sFilename, sExt);
 	m_strAppDir = CString(sDrive) + CString(sDir);
 	m_strAppFile = CString(sFilename);
+	m_model.setWorkDir((LPTSTR)(LPCTSTR)m_strAppDir);
 
 
 	// 注册控件
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index 7685750..3fdbd84 100644
--- a/SourceCode/Bond/Servo/Servo.rc
+++ b/SourceCode/Bond/Servo/Servo.rc
Binary files differ
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj b/SourceCode/Bond/Servo/Servo.vcxproj
index 41fedff..6fc02ae 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/SourceCode/Bond/Servo/Servo.vcxproj
@@ -119,6 +119,7 @@
     <Link>
       <SubSystem>Windows</SubSystem>
       <GenerateDebugInformation>true</GenerateDebugInformation>
+      <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
     </Link>
     <Midl>
       <MkTypLibCompatible>false</MkTypLibCompatible>
@@ -191,6 +192,10 @@
   <ItemGroup>
     <ClInclude Include="BlButton.h" />
     <ClInclude Include="Common.h" />
+    <ClInclude Include="Configuration.h" />
+    <ClInclude Include="Log.h" />
+    <ClInclude Include="LogDlg.h" />
+    <ClInclude Include="LogEdit.h" />
     <ClInclude Include="Model.h" />
     <ClInclude Include="Resource.h" />
     <ClInclude Include="Servo.h" />
@@ -201,6 +206,10 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="BlButton.cpp" />
+    <ClCompile Include="Configuration.cpp" />
+    <ClCompile Include="Log.cpp" />
+    <ClCompile Include="LogDlg.cpp" />
+    <ClCompile Include="LogEdit.cpp" />
     <ClCompile Include="Model.cpp" />
     <ClCompile Include="Servo.cpp" />
     <ClCompile Include="ServoDlg.cpp" />
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index 11dc3e6..da9d2bb 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -45,6 +45,18 @@
     <ClInclude Include="Common.h">
       <Filter>澶存枃浠�</Filter>
     </ClInclude>
+    <ClInclude Include="LogDlg.h">
+      <Filter>澶存枃浠�</Filter>
+    </ClInclude>
+    <ClInclude Include="LogEdit.h">
+      <Filter>澶存枃浠�</Filter>
+    </ClInclude>
+    <ClInclude Include="Configuration.h">
+      <Filter>澶存枃浠�</Filter>
+    </ClInclude>
+    <ClInclude Include="Log.h">
+      <Filter>澶存枃浠�</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Servo.cpp">
@@ -65,6 +77,18 @@
     <ClCompile Include="BlButton.cpp">
       <Filter>婧愭枃浠�</Filter>
     </ClCompile>
+    <ClCompile Include="LogDlg.cpp">
+      <Filter>婧愭枃浠�</Filter>
+    </ClCompile>
+    <ClCompile Include="LogEdit.cpp">
+      <Filter>婧愭枃浠�</Filter>
+    </ClCompile>
+    <ClCompile Include="Configuration.cpp">
+      <Filter>婧愭枃浠�</Filter>
+    </ClCompile>
+    <ClCompile Include="Log.cpp">
+      <Filter>婧愭枃浠�</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Servo.rc">
diff --git a/SourceCode/Bond/Servo/ServoDlg.cpp b/SourceCode/Bond/Servo/ServoDlg.cpp
index a4f5ae8..b59a03d 100644
--- a/SourceCode/Bond/Servo/ServoDlg.cpp
+++ b/SourceCode/Bond/Servo/ServoDlg.cpp
@@ -7,6 +7,7 @@
 #include "ServoDlg.h"
 #include "afxdialogex.h"
 #include "Common.h"
+#include "Log.h"
 
 
 #ifdef _DEBUG
@@ -73,6 +74,7 @@
 	m_crBkgnd = APPDLG_BACKGROUND_COLOR;
 	m_hbrBkgnd = nullptr;
 	m_bShowLogWnd = FALSE;
+	m_pLogDlg = nullptr;
 }
 
 void CServoDlg::DoDataExchange(CDataExchange* pDX)
@@ -91,6 +93,10 @@
 	ON_WM_DESTROY()
 	ON_BN_CLICKED(IDC_BUTTON_LOG, &CServoDlg::OnBnClickedButtonLog)
 	ON_WM_SIZE()
+	ON_WM_CLOSE()
+	ON_MESSAGE(ID_MSG_LOGDLG_HIDE, &CServoDlg::OnLogDlgHide)
+	ON_WM_MOVING()
+	ON_WM_MOVE()
 END_MESSAGE_MAP()
 
 
@@ -206,6 +212,18 @@
 	m_pGraph->SetBoxText(INDICATE_MEASUREMENT, "13", "Measurement");
 
 
+	// 调整初始窗口位置
+	CRect rcWnd;
+	GetWindowRect(&rcWnd);
+	int width = GetSystemMetrics(SM_CXSCREEN);
+	int height = GetSystemMetrics(SM_CYSCREEN);
+	MoveWindow((width - rcWnd.Width()) / 2, 0, rcWnd.Width(), rcWnd.Height(), TRUE);
+
+
+	// model init
+	theApp.m_model.init();
+
+
 	UpdateLogBtn();
 	Resize();
 	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
@@ -295,6 +313,12 @@
 {
 	CDialogEx::OnDestroy();
 
+	if (m_pLogDlg != nullptr) {
+		m_pLogDlg->DestroyWindow();
+		delete m_pLogDlg;
+		m_pLogDlg = nullptr;
+	}
+
 	if (m_hbrBkgnd != nullptr) {
 		::DeleteObject(m_hbrBkgnd);
 	}
@@ -303,6 +327,18 @@
 void CServoDlg::OnBnClickedButtonLog()
 {
 	m_bShowLogWnd = !m_bShowLogWnd;
+	if (m_pLogDlg == nullptr) {
+		m_pLogDlg = new CLogDlg();
+		m_pLogDlg->Create(IDD_DIALOG_LOG, this);
+
+		CRect rcWnd;
+		GetWindowRect(&rcWnd);
+		m_pLogDlg->MoveWindow(rcWnd.left, rcWnd.bottom - 8, rcWnd.Width(), 200);
+	}
+	ASSERT(m_pLogDlg);
+	m_pLogDlg->ShowWindow(m_bShowLogWnd ? SW_SHOW : SW_HIDE);
+	LOGE("OnBnClickedButtonLog");
+
 	UpdateLogBtn();
 }
 
@@ -314,6 +350,7 @@
 	m_btnLog.SetBkgndColor(BS_NORMAL, m_bShowLogWnd ? BTN_LOG_BKGND_PRESS : BTN_LOG_BKGND_NORMAL);
 	m_btnLog.SetBkgndColor(BS_HOVER, BTN_LOG_BKGND_HOVER);
 	m_btnLog.SetBkgndColor(BS_PRESS, BTN_LOG_BKGND_PRESS);
+	m_btnLog.Invalidate();
 }
 
 void CServoDlg::OnSize(UINT nType, int cx, int cy)
@@ -342,3 +379,35 @@
 	pItem->GetClientRect(&rcItem);
 	pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
 }
+
+void CServoDlg::OnClose()
+{
+	// TODO: 在此添加消息处理程序代码和/或调用默认值
+
+	CDialogEx::OnClose();
+}
+
+LRESULT CServoDlg::OnLogDlgHide(WPARAM wParam, LPARAM lParam)
+{
+	m_bShowLogWnd = FALSE;
+	UpdateLogBtn();
+	LOGE("OnLogDlgHide");
+
+	return 0;
+}
+
+void CServoDlg::OnMoving(UINT fwSide, LPRECT pRect)
+{
+	CDialogEx::OnMoving(fwSide, pRect);
+}
+
+void CServoDlg::OnMove(int x, int y)
+{
+	if (m_pLogDlg != nullptr && !m_pLogDlg->IsZoomed()) {
+		CRect rcWnd;
+		GetWindowRect(&rcWnd);
+		m_pLogDlg->MoveWindow(rcWnd.left, rcWnd.bottom - 8, rcWnd.Width(), 200);
+	}
+
+	CDialogEx::OnMove(x, y);
+}
diff --git a/SourceCode/Bond/Servo/ServoDlg.h b/SourceCode/Bond/Servo/ServoDlg.h
index 30bea0c..df41e4a 100644
--- a/SourceCode/Bond/Servo/ServoDlg.h
+++ b/SourceCode/Bond/Servo/ServoDlg.h
@@ -5,6 +5,7 @@
 #pragma once
 #include "ServoGraph.h"
 #include "BlButton.h"
+#include "LogDlg.h"
 
 
 // CServoDlg 对话框
@@ -31,6 +32,7 @@
 
 private:
 	BOOL m_bShowLogWnd;
+	CLogDlg* m_pLogDlg;
 
 // 实现
 protected:
@@ -54,4 +56,8 @@
 	afx_msg void OnDestroy();
 	afx_msg void OnBnClickedButtonLog();
 	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnClose();
+	afx_msg LRESULT OnLogDlgHide(WPARAM wParam, LPARAM lParam);
+	afx_msg void OnMoving(UINT fwSide, LPRECT pRect);
+	afx_msg void OnMove(int x, int y);
 };
diff --git a/SourceCode/Bond/Servo/resource.h b/SourceCode/Bond/Servo/resource.h
index c8d683b..0431ed5 100644
--- a/SourceCode/Bond/Servo/resource.h
+++ b/SourceCode/Bond/Servo/resource.h
Binary files differ

--
Gitblit v1.9.3