Jelajahi Sumber

加入專案檔案。

shayne_lo 1 bulan lalu
induk
melakukan
380b7d0eed

+ 25 - 0
Simano.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35431.28
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simano", "Simano\Simano.csproj", "{848B0E1D-DA1C-4DF4-B125-749366C032A9}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{848B0E1D-DA1C-4DF4-B125-749366C032A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{848B0E1D-DA1C-4DF4-B125-749366C032A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{848B0E1D-DA1C-4DF4-B125-749366C032A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{848B0E1D-DA1C-4DF4-B125-749366C032A9}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {984E23D5-73C6-4085-8606-773FDF919115}
+	EndGlobalSection
+EndGlobal

+ 14 - 0
Simano/App.config

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
+    </startup>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>

+ 9 - 0
Simano/App.xaml

@@ -0,0 +1,9 @@
+<Application x:Class="Simano.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:Simano"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+         
+    </Application.Resources>
+</Application>

+ 38 - 0
Simano/App.xaml.cs

@@ -0,0 +1,38 @@
+using NLog;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Simano
+{
+    /// <summary>
+    /// App.xaml 的互動邏輯
+    /// </summary>
+    public partial class App : Application
+    {
+        public App() :base()
+        {
+            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+            InitNlog();
+        }
+
+        private void InitNlog()
+        {
+            var config = new NLog.Config.LoggingConfiguration();
+
+            // Targets where to log to: File
+            var logfile = new NLog.Targets.FileTarget("logfile") { FileName = "log.txt" , MaxArchiveFiles = 10 ,ArchiveEvery = NLog.Targets.FileArchivePeriod.Day };
+
+            // Rules for mapping loggers to targets
+            config.AddRule(LogLevel.Trace, LogLevel.Fatal, logfile);
+
+            // Apply config           
+            NLog.LogManager.Configuration = config;
+        }
+    }
+}

+ 81 - 0
Simano/AppSettingConfig.cs

@@ -0,0 +1,81 @@
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json;
+using Simano.Model;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Simano
+{
+    public static class AppSettingConfig
+    {
+        private static AppSettingConfigModel _Instance;
+        public static AppSettingConfigModel Instance
+        {
+            get
+            {
+                if (_Instance == null)
+                {
+                    Reload();
+                }
+                return _Instance;
+            }
+            private set => _Instance = value;
+        }
+        public static void Save()
+        {
+            File.WriteAllText(
+                settingFilPath,
+                JsonConvert.SerializeObject(
+                    Instance,
+                    Formatting.Indented,
+                    new StringEnumConverter()
+                    ));
+        }
+
+        public static void Reload()
+        {
+            //Instance = new AppSettingConfig();
+
+            if (!File.Exists(settingFilPath))
+            {
+                LoadDefaultSetting();
+            }
+            else
+            {
+                LoadSettingFromDefaultFile();
+            }
+        }
+
+        private static string settingFileName = "config.ini";
+        private static string settingFilPath => Path.GetFullPath(settingFileName);
+
+        private static void LoadDefaultSetting()
+        {
+            _Instance = new AppSettingConfigModel()
+            {
+                PortName = string.Empty,
+                FirmwarePath = string.Empty,
+            };
+        }
+        private static void LoadSettingFromDefaultFile()
+        {
+            try
+            {
+                LoadDefaultSetting();
+
+                var configStr = File.ReadAllText(settingFilPath);
+                var config = Newtonsoft.Json.JsonConvert.DeserializeObject<AppSettingConfigModel>(configStr);
+
+                _Instance = config;
+            }
+            catch
+            {
+
+            }
+        }
+    }
+}

+ 13 - 0
Simano/MainWindow.xaml

@@ -0,0 +1,13 @@
+<Window x:Class="Simano.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Simano" xmlns:usercontrols="clr-namespace:Simano.UserControls"
+        mc:Ignorable="d"
+        Title="Shimano" Height="450" Width="800"
+        MaxWidth="1220">
+    <Grid>
+        <usercontrols:MainUserUI/>
+    </Grid>
+</Window>

+ 38 - 0
Simano/MainWindow.xaml.cs

@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Simano
+{
+    /// <summary>
+    /// MainWindow.xaml 的互動邏輯
+    /// </summary>
+    public partial class MainWindow : Window
+    {
+        public MainWindow()
+        {
+            InitializeComponent();
+
+            Title += "_V" + Assembly.GetExecutingAssembly().GetName().Version;
+        }
+
+        protected override void OnClosed(EventArgs e)
+        {
+            base.OnClosed(e);
+
+            AppSettingConfig.Save();
+        }
+    }
+}

+ 14 - 0
Simano/Model/AppSettingConfigModel.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Simano.Model
+{
+    public class AppSettingConfigModel
+    {
+        public string PortName { get; set; }
+        public string FirmwarePath { get; set; }
+    }
+}

+ 17 - 0
Simano/Model/UiStatus.cs

@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Simano.Model
+{
+    public enum UiStatus
+    {
+        undefined,
+        Disconnected,
+        Connecting,
+        Connected,
+        Busy
+    }
+}

+ 52 - 0
Simano/Properties/AssemblyInfo.cs

@@ -0,0 +1,52 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// 組件的一般資訊是由下列的屬性集控制。
+// 變更這些屬性的值即可修改組件的相關
+// 資訊。
+[assembly: AssemblyTitle("Simano")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Simano")]
+[assembly: AssemblyCopyright("Copyright ©  2025")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 將 ComVisible 設為 false 可對 COM 元件隱藏
+// 組件中的類型。若必須從 COM 存取此組件中的類型,
+// 的類型,請在該類型上將 ComVisible 屬性設定為 true。
+[assembly: ComVisible(false)]
+
+//若要開始建置可當地語系化的應用程式,請在
+//.csproj 檔案中的 <UICulture>CultureYouAreCodingWith</UICulture>
+//在 <PropertyGroup> 中。例如,如果原始程式檔使用美式英文, 
+//請將 <UICulture> 設為 en-US。然後取消註解下列
+//NeutralResourceLanguage 屬性。在下一行中更新 "en-US",
+//以符合專案檔中的 UICulture 設定。
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //主題特定資源字典的位置
+                                     //(在頁面中找不到時使用,
+                                     // 或應用程式資源字典中找不到資源時)
+    ResourceDictionaryLocation.SourceAssembly //泛型資源字典的位置
+                                              //(在頁面中找不到時使用,
+                                              // 或是應用程式或任何主題特定資源字典中找不到資源時)
+)]
+
+
+// 組件的版本資訊由下列四個值所組成:
+//
+//      主要版本
+//      次要版本
+//      組建編號
+//      修訂
+//
+[assembly: AssemblyVersion("0.0.1.0")]
+[assembly: AssemblyFileVersion("0.0.1.0")]

+ 71 - 0
Simano/Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     這段程式碼是由工具產生的。
+//     執行階段版本:4.0.30319.42000
+//
+//     變更這個檔案可能會導致不正確的行為,而且如果已重新產生
+//     程式碼,則會遺失變更。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Simano.Properties
+{
+
+
+    /// <summary>
+    ///   用於查詢當地語系化字串等的強類型資源類別
+    /// </summary>
+    // 這個類別是自動產生的,是利用 StronglyTypedResourceBuilder
+    // 類別透過 ResGen 或 Visual Studio 這類工具產生。
+    // 若要加入或移除成員,請編輯您的 .ResX 檔,然後重新執行 ResGen
+    // (利用 /str 選項),或重建您的 VS 專案。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   傳回這個類別使用的快取的 ResourceManager 執行個體。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Simano.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   覆寫目前執行緒的 CurrentUICulture 屬性,對象是所有
+        ///   使用這個強類型資源類別的資源查閱。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
Simano/Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 30 - 0
Simano/Properties/Settings.Designer.cs

@@ -0,0 +1,30 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Simano.Properties
+{
+
+
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
+    {
+
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+        public static Settings Default
+        {
+            get
+            {
+                return defaultInstance;
+            }
+        }
+    }
+}

+ 7 - 0
Simano/Properties/Settings.settings

@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+  <Profiles>
+    <Profile Name="(Default)" />
+  </Profiles>
+  <Settings />
+</SettingsFile>

+ 146 - 0
Simano/Service/CubeProgrammerAPI/CubeProgrammerAPIAdapter.cs

@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Interop;
+using System.Windows.Media;
+
+namespace Simano.Service.Adapter
+{
+    public unsafe class CubeProgrammerAPIAdapter
+    {
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern int setLoadersPath([MarshalAs(UnmanagedType.LPWStr)] string path);
+
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern int setDisplayCallbacks(displayCallBacks usartList);
+
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern int getUsartList(usartConnectParameters** usartList);
+
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern int connectUsartBootloader(usartConnectParameters param);
+
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern int downloadFile([MarshalAs(UnmanagedType.LPWStr)] string filePath, UInt32 address, UInt32 skipErase, UInt32 verify, [MarshalAs(UnmanagedType.LPWStr)] string binPath);
+
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern void disconnect();
+
+        [DllImport("CubeProgrammer_API.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
+        public static extern int execute(UInt32 address);
+
+        public static event EventHandler<string> OnLogReceived;
+
+        private static displayCallBacks callBack;
+
+        public static void Init()
+        {
+            setLoadersPath("./.");
+            callBack = default;
+            callBack.initProgressBar = InitP;
+            callBack.logMessage = logMessageDelegate;
+            callBack.loadBar = loadBar;
+            setDisplayCallbacks(callBack);
+        }
+
+        public static List<string> GetUsartList()
+        {
+            var usartList = GetRawUsartList();
+            return usartList.Select(uartParam =>
+            {
+                byte[] arr = new byte[100];
+                Marshal.Copy((IntPtr)uartParam.portName, arr, 0, 100);
+                var portName = Encoding.ASCII.GetString(arr).Trim('\0');
+                return portName;
+            }).ToList();
+        }
+
+        public static int openPort(string name, int? baudrate = null)
+        {
+            var usartList = GetRawUsartList();
+            usartConnectParameters uart = default;
+            bool foundUart = false;
+            foreach (var uartParam in usartList)
+            {
+                byte[] arr = new byte[100];
+                Marshal.Copy((IntPtr)uartParam.portName, arr, 0, 100);
+                var portName = Encoding.ASCII.GetString(arr).Trim('\0');
+                if (portName == name)
+                {
+                    foundUart = true;
+                    uart = uartParam;
+                    break;
+                }
+            }
+            if (!foundUart)
+            {
+                return -123;
+            }
+            if (baudrate != null)
+            {
+                uart.baudrate = (uint)baudrate;
+            }
+            var openResult = connectUsartBootloader(uart);
+            return openResult;
+        }
+
+        public static void ClosePort()
+        {
+            disconnect();
+        }
+
+        public static bool Run(uint address)
+        {
+            var result =  execute(address);
+            return result == 0;
+        }
+
+        private static List<usartConnectParameters> GetRawUsartList()
+        {
+            usartConnectParameters* uartParam;
+            int getUsartListFlag = getUsartList(&uartParam);
+            var toReturn = new List<usartConnectParameters>();
+
+            if (getUsartListFlag == 0)
+            {
+                return toReturn;
+            }
+
+            for (int i = 0; i < getUsartListFlag; i++)
+            {
+                toReturn.Add(uartParam[i]);
+            }
+            return toReturn;
+        }
+
+        private static void InitP()
+        {
+
+        }
+
+        private static void logMessageDelegate(int msgType, string strp)
+        {
+            //var msg = Encoding.ASCII.GetString(str).Trim('\0');
+            //OnLogReceived?.Invoke(null, msg);
+            //OnLogReceived?.Invoke(null, str);
+            OnLogReceived?.Invoke(null, strp);
+
+            //byte[] arr = new byte[100];
+            //Marshal.Copy(strp, arr, 0, 100);
+            //var msg = Encoding.ASCII.GetString(arr).Trim('\0');
+            //OnLogReceived?.Invoke(null, msg);
+
+            //var msg = Encoding.ASCII.GetString(strp).Trim('\0');
+            //OnLogReceived?.Invoke(null, msg);
+        }
+
+        private static void loadBar(int x, int n)
+        {
+
+        }
+    }
+}

+ 62 - 0
Simano/Service/CubeProgrammerAPI/CubeProgrammerAPIAdapterModels.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Simano.Service.Adapter
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public unsafe struct usartConnectParameters
+    {
+        //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 100, ArraySubType = UnmanagedType.LPUTF8Str)]
+        public fixed Byte portName[100];             /**< Interface identifier: COM1, COM2, /dev/ttyS0...*/
+        public UInt32 baudrate;         /**< Speed transmission: 115200, 9600... */
+        public usartParity parity;             /**< Parity bit: value in usartParity. */
+        public Byte dataBits;         /**< Data bit: value in {6, 7, 8}. */
+        public Single stopBits;                 /**< Stop bit: value in {1, 1.5, 2}. */
+        public usartFlowControl flowControl;   /**< Flow control: value in usartFlowControl. */
+        public Int32 statusRTS;                  /**< RTS: Value in {0,1}. */
+        public Int32 statusDTR;                  /**< DTR: Value in {0,1}. */
+        public Byte noinitBits;       /**< Set No Init bits: value in {0,1}. */
+        public Byte rdu;                       /**< request a read unprotect: value in {0,1}.*/
+        public Byte tzenreg;                       /**< request a TZEN regression: value in {0,1}.*/
+    }
+
+    public struct displayCallBacks
+    {
+        public initProgressBarDelegate initProgressBar;
+        public logMessageDelegate logMessage;
+        public loadBarDelegate loadBar;
+    }
+
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    public delegate void initProgressBarDelegate();
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    public delegate void logMessageDelegate(int msgType, [MarshalAs(UnmanagedType.LPWStr)] string strp);
+    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+    public delegate void loadBarDelegate(int x, int n);
+
+    public enum usartParity : UInt32
+    {
+        EVEN = 0,               /**< Even parity bit. */
+        ODD = 1,                /**< Odd parity bit. */
+        NONE = 2,               /**< No check parity. */
+    };
+
+    public enum usartFlowControl : UInt32
+    {
+        OFF = 0,                /**< No flow control.  */
+        HARDWARE = 1,           /**< Hardware flow control : RTS/CTS.  */
+        SOFTWARE = 2,           /**< Software flow control : Transmission is started and stopped by sending special characters. */
+    }
+
+    public enum debugResetMode
+    {
+        SOFTWARE_RESET = 0,         /**< Apply a reset by the software. */
+        HARDWARE_RESET = 1,         /**< Apply a reset by the hardware. */
+        CORE_RESET = 2              /**< Apply a reset by the internal core peripheral. */
+    };
+}

+ 51 - 0
Simano/Service/CubeProgrammerAPI/CubeProgrammerAPIWrapper.cs

@@ -0,0 +1,51 @@
+using Simano.Service.Adapter;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Simano.Service.CubeProgrammerAPI
+{
+    public static class CubeProgrammerAPIWrapper
+    {
+        private delegate int downloadFileCaller(string filePath, UInt32 address, UInt32 skipErase, UInt32 verify, string binPath);
+        private delegate int executeCaller(UInt32 address);
+        private delegate int openPortCaller(string name, int? baudrate = null);
+
+        private static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(0);
+
+        public static async Task<bool> OpenPort(string name, int? baudrate = null)
+        {
+            var method = new openPortCaller(CubeProgrammerAPIAdapter.openPort);
+            IAsyncResult result = method.BeginInvoke(name, baudrate, ActionCompleted, null);
+            await semaphoreSlim.WaitAsync();
+            int openResult = method.EndInvoke(result);
+            return openResult == 0;
+        }
+
+        public static async Task<bool> DownloadFile(string flashFilePath, uint address)
+        {
+            var method = new downloadFileCaller(CubeProgrammerAPIAdapter.downloadFile);
+            IAsyncResult result = method.BeginInvoke(flashFilePath, address, 0, 1, "", ActionCompleted, null);
+            await semaphoreSlim.WaitAsync();
+            int downloadResult = method.EndInvoke(result);
+            return downloadResult == 0;
+        }
+
+        public static async Task<bool> Run(uint address)
+        {
+            var method = new executeCaller(CubeProgrammerAPIAdapter.execute);
+            IAsyncResult result = method.BeginInvoke(address, ActionCompleted, null);
+            await semaphoreSlim.WaitAsync();
+            int runResult = method.EndInvoke(result);
+            return runResult == 0;
+        }
+
+        private static void ActionCompleted(IAsyncResult ar)
+        {
+            semaphoreSlim.Release();
+        }
+    }
+}

+ 218 - 0
Simano/Service/SimanoService.cs

@@ -0,0 +1,218 @@
+using Simano.Service.Adapter;
+using Simano.Service.CubeProgrammerAPI;
+using System;
+using System.Collections.Generic;
+using System.IO.Ports;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Simano.Service
+{
+    public class SimanoService : IDisposable
+    {
+        private static SimanoService _Instance;
+        public static SimanoService Instance
+        {
+            get
+            {
+                if (_Instance == null)
+                {
+                    _Instance = new SimanoService();
+                }
+                return _Instance;
+            }
+        }
+
+        private SimanoService() 
+        {
+            CubeProgrammerAPIAdapter.OnLogReceived += CubeProgrammerAPIAdapter_OnLogReceived;
+            CubeProgrammerAPIAdapter.Init();
+        }
+
+        public void Dispose()
+        {
+            CubeProgrammerAPIAdapter.OnLogReceived -= CubeProgrammerAPIAdapter_OnLogReceived;
+        }
+
+        public List<string> GetComportList()
+        {
+            return SerialPort.GetPortNames().ToList();
+            //return Adapter.CubeProgrammerAPIAdapter.GetUsartList();
+        }
+        public event EventHandler<string> OnLogReceived;
+
+        private SerialPort serialPort;
+        private string portName = string.Empty;
+        private byte cmdCNT = 0;
+        private bool isApiMode = true;
+
+        public async Task<bool> Connect(string portName)
+        {
+            try
+            {
+                cmdCNT = 0;
+                serialPort = new SerialPort(portName, baudRate:115200);
+                serialPort.Open();
+                this.portName = portName;
+
+                ClearReadBuffer();
+                var getStatusSuccess = await GetBoardStatus();
+                return getStatusSuccess;
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+        }
+
+        private async Task<bool> GetBoardStatus()
+        {
+            if (serialPort is null ||
+                !serialPort.IsOpen)
+            {
+                return false;
+            }
+
+            var test = serialPort.BytesToRead;
+
+            WriteLog(null, "Detecting Board status...");
+
+            await Task.Delay(2500);
+            var canreceivedData = serialPort.BytesToRead > 0;
+
+            if (canreceivedData)
+            {
+                var readBuffer = new byte[20];
+                var readByteCnt = serialPort.Read(readBuffer, 0, 20);
+
+                try
+                {
+                    var reseivedString = Encoding.UTF8.GetString(readBuffer);
+                    WriteLog(null, reseivedString);
+                    if (reseivedString.Contains("OPEN"))
+                    {
+                        WriteLog(null, "ApiMode");
+                        isApiMode = true;
+                        return true;
+                    }
+                    return false;
+                }
+                catch
+                {
+                    return false;
+                }
+            }
+
+            if (!canreceivedData)
+            {
+                serialPort.Close();
+                var openPortResult = await CubeProgrammerAPIWrapper.OpenPort(portName);
+                if (!openPortResult)
+                    return false;
+                CubeProgrammerAPIAdapter.ClosePort();
+                serialPort.Open();
+                isApiMode = false;
+                WriteLog(null, "BootloaderMode");
+                return true;
+            }
+
+            return false;
+        }
+
+        public bool Disconnect()
+        {
+            try
+            {
+                serialPort?.Close();
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+            return true;
+        }
+
+        public async Task<bool> FlashFirmware(string filePath)
+        {
+            await GetBoardStatus();
+            if (isApiMode)
+            {
+                SendFlashFirmwareCmd();
+                await Task.Delay(3000);
+                ClearReadBuffer();
+            }
+            serialPort.Close();
+            try
+            {
+                var openPortResult = await CubeProgrammerAPIWrapper.OpenPort(portName);
+                if (!openPortResult)
+                    return false;
+                //var flshResult = CubeProgrammerAPIAdapter.DownloadFile(filePath, 0x08008000u);
+                var flshResult = await CubeProgrammerAPIWrapper.DownloadFile(filePath, 0x08008000u);
+                if (!flshResult)
+                    return false;
+                var runResult = await CubeProgrammerAPIWrapper.Run(0x08008000u);
+                if (!runResult)
+                    return false;
+            }
+            finally
+            {
+                CubeProgrammerAPIAdapter.ClosePort();
+                serialPort.Open();
+            }
+            return true;
+        }
+
+        private void CubeProgrammerAPIAdapter_OnLogReceived(object sender, string e)
+        {
+            //Console.WriteLine(e);
+            WriteLog(sender, e);
+        }
+
+        private void WriteLog(object sender, string e)
+        {
+            OnLogReceived?.Invoke(sender, e);
+        }
+
+        private void SendFlashFirmwareCmd()
+        {
+            ClearReadBuffer();
+            var cmd = GetFlashFirmwareCmd();
+            serialPort.Write(cmd, 0, cmd.Length);
+
+            //var readBuffer = new byte[10];
+            //var readByteCnt = serialPort.Read(readBuffer, 0 , 10);
+
+            //try
+            //{
+            //    var reseivedString = Encoding.UTF8.GetString(readBuffer);
+            //    Console.WriteLine(reseivedString);
+            //}
+            //catch
+            //{
+
+            //}
+
+            return;
+        }
+
+        private byte[] GetFlashFirmwareCmd()
+        {
+            byte[] baseCmd = new byte[] { 0x00, 0x00, 0x02, 0xD1, 0x01, 0x26, 0xD4 };
+            baseCmd[1] = cmdCNT;
+            cmdCNT++;
+            return baseCmd;
+        }
+
+        private void ClearReadBuffer()
+        {
+            if (serialPort.BytesToRead > 0)
+            {
+                var dumpBuffer = new byte[serialPort.BytesToRead];
+                serialPort.Read(dumpBuffer, 0, dumpBuffer.Length);
+            }
+        }
+    }
+}

+ 147 - 0
Simano/Simano.csproj

@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{848B0E1D-DA1C-4DF4-B125-749366C032A9}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>Simano</RootNamespace>
+    <AssemblyName>Simano</AssemblyName>
+    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>x64</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Prefer32Bit>false</Prefer32Bit>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+      <HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
+    </Reference>
+    <Reference Include="NLog, Version=5.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.5.3.4\lib\net46\NLog.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Buffers, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Configuration" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.IO.Compression" />
+    <Reference Include="System.Memory, Version=4.0.1.2, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Numerics" />
+    <Reference Include="System.Numerics.Vectors, Version=4.1.4.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Text.Encoding.CodePages, Version=9.0.0.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
+      <HintPath>..\packages\System.Text.Encoding.CodePages.9.0.1\lib\net462\System.Text.Encoding.CodePages.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </ApplicationDefinition>
+    <Compile Include="AppSettingConfig.cs" />
+    <Compile Include="Model\AppSettingConfigModel.cs" />
+    <Compile Include="Model\UiStatus.cs" />
+    <Compile Include="Service\CubeProgrammerAPI\CubeProgrammerAPIAdapter.cs" />
+    <Compile Include="Service\CubeProgrammerAPI\CubeProgrammerAPIAdapterModels.cs" />
+    <Compile Include="Service\CubeProgrammerAPI\CubeProgrammerAPIWrapper.cs" />
+    <Compile Include="Service\SimanoService.cs" />
+    <Compile Include="UserControls\MainUserUI.xaml.cs">
+      <DependentUpon>MainUserUI.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="UserControls\STCubeTestPanel.xaml.cs">
+      <DependentUpon>STCubeTestPanel.xaml</DependentUpon>
+    </Compile>
+    <Page Include="MainWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Page Include="UserControls\MainUserUI.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="UserControls\STCubeTestPanel.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project>

+ 73 - 0
Simano/UserControls/MainUserUI.xaml

@@ -0,0 +1,73 @@
+<UserControl x:Class="Simano.UserControls.MainUserUI"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Simano.UserControls"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="1200">
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="200"/>
+            <ColumnDefinition Width="600"/>
+            <ColumnDefinition Width="400"/>
+        </Grid.ColumnDefinitions>
+        <StackPanel Orientation="Vertical" Grid.Column="0" Margin="10">
+            <Border BorderThickness="1" BorderBrush="Black">
+                <Grid>
+                    <Label x:Name="uxDisconnectedStatusHint" Foreground="Red" FontWeight="Bold" FontSize="24" Height="80"  Content="Disconnected" VerticalAlignment="Center" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
+                    <Label x:Name="uxConnectedStatusHint" Foreground="Green" FontWeight="Bold" FontSize="24" Height="80"  Content="Connected" VerticalAlignment="Center" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
+                    <Label x:Name="uxRunningStatusHint" Foreground="Blue"  FontWeight="Bold" FontSize="24" Height="80"  Content="Running" VerticalAlignment="Center" HorizontalAlignment="Center" VerticalContentAlignment="Center"/>
+                </Grid>
+            </Border>
+            <Rectangle Height="10"/>
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="10"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Button x:Name="uxConnectBtn" Content="Connect"/>
+                <Button x:Name="uxDisconnectBtn" Grid.Column="2" Content="Disconnect"/>
+            </Grid>
+            <Rectangle Height="10"/>
+            <Grid>
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="10"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Label Grid.Column="0" Content="COMPORT" HorizontalAlignment="Center"/>
+                <ComboBox Grid.Column="2" x:Name="uxComportSelector"  />
+            </Grid>
+        </StackPanel>
+        <Grid Grid.Column="1" Margin="10,10,30,10">
+            <Grid.RowDefinitions>
+                <RowDefinition/>
+                <RowDefinition/>
+                <RowDefinition/>
+                <RowDefinition/>
+            </Grid.RowDefinitions>
+            <StackPanel Orientation="Vertical" VerticalAlignment="Center">
+                <Label Content="FW Update" FontWeight="Bold" FontSize="24"  VerticalAlignment="Top" HorizontalAlignment="Left"/>
+                <Grid>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="*"/>
+                        <ColumnDefinition Width="100"/>
+                    </Grid.ColumnDefinitions>
+                    <Button x:Name="uxFirmwareUpdateBtn" Content="Update" Grid.Column="1"/>
+                    <StackPanel Orientation="Horizontal">
+                        <TextBox x:Name="uxFirmwarelocationText" Text="C:\\C071_LED_BUTTON_B0.bin" Width="270" IsReadOnly="True"/>
+                        <Rectangle Width="10"/>
+                        <Button x:Name="uxSelectFirmwareBtn" Content=".." Width="40" />
+                    </StackPanel>
+                </Grid>
+            </StackPanel>
+        </Grid>
+        <FlowDocumentScrollViewer  x:Name="uxConsoleScroller" Grid.Column="2" Background="Black" Foreground="White" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled">
+            <FlowDocument Background="Black">
+                <Paragraph Background="Black" Foreground="White" x:Name="uxConsole"></Paragraph>
+            </FlowDocument>
+        </FlowDocumentScrollViewer>
+    </Grid>
+</UserControl>

+ 259 - 0
Simano/UserControls/MainUserUI.xaml.cs

@@ -0,0 +1,259 @@
+using Microsoft.Win32;
+using Simano.Model;
+using Simano.Service;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+
+namespace Simano.UserControls
+{
+    /// <summary>
+    /// MainUserUI.xaml 的互動邏輯
+    /// </summary>
+    public partial class MainUserUI : UserControl
+    {
+        public MainUserUI()
+        {
+            InitializeComponent();
+            InitUi();
+            Loaded += MainUserUI_Loaded;
+        }
+
+        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
+
+        private UiStatus _Status;
+        private ScrollViewer scrollViewer;
+
+        private UiStatus Status
+        {
+            get => _Status;
+            set => SetStatus(value);
+        }
+
+        private void InitUi()
+        {
+            Status = UiStatus.Disconnected;
+
+            uxConnectBtn.Click += UxConnectBtn_Click;
+            uxDisconnectBtn.Click += UxDisconnectBtn_Click;
+            uxComportSelector.SelectionChanged += UxComportSelector_SelectionChanged;
+            uxFirmwareUpdateBtn.Click += UxFirmwareUpdateBtn_Click;
+            uxSelectFirmwareBtn.Click += UxSelectFirmwareBtn_Click;
+            SimanoService.Instance.OnLogReceived += SimanoService_OnLogReceived;
+
+            var comList = SimanoService.Instance.GetComportList();
+            uxComportSelector.ItemsSource = comList;
+
+            var recordedPortName = AppSettingConfig.Instance.PortName;
+            if (comList.Contains(recordedPortName))
+            {
+                uxComportSelector.SelectedItem = recordedPortName;
+            }
+
+            var recordedFirmwarePath = AppSettingConfig.Instance.FirmwarePath;
+            uxFirmwarelocationText.Text = recordedFirmwarePath;
+        }
+
+        private void MainUserUI_Loaded(object sender, RoutedEventArgs e)
+        {
+            GetScroller();
+        }
+
+        private void SimanoService_OnLogReceived(object sender, string e)
+        {
+            Dispatcher.InvokeAsync(new Action(() =>
+            {
+                //uxConsole.AppendText(e + Environment.NewLine);
+                var view = new Run(e + Environment.NewLine);
+                uxConsole.Inlines.Add(view);
+                scrollViewer?.ScrollToEnd();
+                //uxConsole.Text = e + Environment.NewLine;
+                Logger.Trace(e);
+                Console.WriteLine(e);
+            }));
+        }
+
+        private void UxSelectFirmwareBtn_Click(object sender, RoutedEventArgs e)
+        {
+            OpenFileDialog openFileDialog = new OpenFileDialog();
+            var success = openFileDialog.ShowDialog();
+            if (success != true)
+            {
+                return;
+            }
+            var path = openFileDialog.FileName;
+            path = Path.GetFullPath(path);
+            uxFirmwarelocationText.Text = path;
+            AppSettingConfig.Instance.FirmwarePath = path;
+        }
+
+        private async void UxFirmwareUpdateBtn_Click(object sender, RoutedEventArgs e)
+        {
+            if (Status != UiStatus.Connected)
+            {
+                return;
+            }
+
+            var firmwareFile = uxFirmwarelocationText.Text;
+            firmwareFile = Path.GetFullPath(firmwareFile);
+            if (string.IsNullOrEmpty(firmwareFile) ||
+                !File.Exists(firmwareFile))
+            {
+                MessageBox.Show("Firmware not found!");
+                return;
+            }
+
+            Status = UiStatus.Busy;
+            var result = await SimanoService.Instance.FlashFirmware(firmwareFile);
+            if ( result )
+            {
+                MessageBox.Show("Flash Success");
+            }
+            else
+            {
+                MessageBox.Show("Flash Failed");
+            }
+            Status = UiStatus.Connected;
+        }
+
+        private void UxComportSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            AppSettingConfig.Instance.PortName = uxComportSelector.SelectedItem.ToString();
+        }
+
+        private void UxDisconnectBtn_Click(object sender, RoutedEventArgs e)
+        {
+            uxDisconnectBtn.Click -= UxDisconnectBtn_Click;
+
+            Disconnect();
+
+            uxDisconnectBtn.Click += UxDisconnectBtn_Click;
+        }
+
+        private void UxConnectBtn_Click(object sender, RoutedEventArgs e)
+        {
+            uxConnectBtn.Click -= UxConnectBtn_Click;
+
+            Connect();
+
+            uxConnectBtn.Click += UxConnectBtn_Click;
+        }
+
+        private async void Connect()
+        {
+            if (uxComportSelector.SelectedItem is null)
+            {
+                MessageBox.Show("Comport is not selected");
+                Logger.Trace("Comport is not selected");
+                return;
+            }
+            Status = UiStatus.Connecting;
+            var connectResult = await SimanoService.Instance.Connect(uxComportSelector.SelectedItem as string);
+            if (!connectResult)
+            {
+                Status = UiStatus.Disconnected;
+                MessageBox.Show("Open port failed");
+                Logger.Trace("Open port {portname} failed", uxComportSelector.SelectedItem);
+                return;
+            }
+            Status = UiStatus.Connected;
+            Logger.Trace("Open port {portname}", uxComportSelector.SelectedItem);
+        }
+
+        private void Disconnect()
+        {
+            var disconnectResult = SimanoService.Instance.Disconnect();
+            if (!disconnectResult)
+            {
+                Status = UiStatus.Connected;
+                MessageBox.Show("Close port failed");
+                Logger.Trace("Close port failed");
+                return;
+            }
+            Logger.Trace("Close port");
+            Status = UiStatus.Disconnected;
+        }
+
+        private void GetScroller()
+        {
+            DependencyObject obj = uxConsoleScroller;
+
+            Queue<DependencyObject> visuals = new Queue<DependencyObject> { };
+            do
+            {
+                for (int index = 0; index < VisualTreeHelper.GetChildrenCount(obj); index ++)
+                {
+                    visuals.Enqueue(VisualTreeHelper.GetChild(obj as Visual, index));
+                }
+                obj = visuals.Dequeue();
+            }
+            while (!(obj is ScrollViewer));
+
+            this.scrollViewer = obj as ScrollViewer;
+        }
+
+        private void SetStatus(UiStatus value)
+        {
+            _Status = value;
+            switch (value) {
+                case UiStatus.Disconnected:
+                    uxConnectBtn.IsEnabled = true;
+                    uxDisconnectBtn.IsEnabled = false;
+                    uxComportSelector.IsEnabled = true;
+                    uxFirmwareUpdateBtn.IsEnabled = false;
+                    uxSelectFirmwareBtn.IsEnabled = true;
+
+                    uxConnectedStatusHint.Visibility = Visibility.Collapsed;
+                    uxDisconnectedStatusHint.Visibility = Visibility.Visible;
+                    uxRunningStatusHint.Visibility = Visibility.Collapsed;
+                    break;
+                case UiStatus.Connecting:
+                    uxConnectBtn.IsEnabled = false;
+                    uxDisconnectBtn.IsEnabled = false;
+                    uxComportSelector.IsEnabled = false;
+                    uxFirmwareUpdateBtn.IsEnabled = false;
+                    uxSelectFirmwareBtn.IsEnabled = true;
+
+                    uxConnectedStatusHint.Visibility = Visibility.Collapsed;
+                    uxDisconnectedStatusHint.Visibility = Visibility.Visible;
+                    uxRunningStatusHint.Visibility = Visibility.Collapsed;
+                    break;
+                case UiStatus.Connected:
+                    uxConnectBtn.IsEnabled = false;
+                    uxDisconnectBtn.IsEnabled = true;
+                    uxComportSelector.IsEnabled = false;
+                    uxFirmwareUpdateBtn.IsEnabled = true;
+                    uxSelectFirmwareBtn.IsEnabled = true;
+
+                    uxConnectedStatusHint.Visibility = Visibility.Visible;
+                    uxDisconnectedStatusHint.Visibility = Visibility.Collapsed;
+                    uxRunningStatusHint.Visibility = Visibility.Collapsed;
+                    break;
+                case UiStatus.Busy:
+                    uxConnectBtn.IsEnabled = false;
+                    uxDisconnectBtn.IsEnabled = false;
+                    uxComportSelector.IsEnabled = false;
+                    uxFirmwareUpdateBtn.IsEnabled = false;
+                    uxSelectFirmwareBtn.IsEnabled = false;
+
+                    uxConnectedStatusHint.Visibility = Visibility.Collapsed;
+                    uxDisconnectedStatusHint.Visibility = Visibility.Collapsed;
+                    uxRunningStatusHint.Visibility = Visibility.Visible;
+                    break;
+            }
+
+        }
+    }
+}

+ 31 - 0
Simano/UserControls/STCubeTestPanel.xaml

@@ -0,0 +1,31 @@
+<UserControl x:Class="Simano.UserControls.STCubeTestPanel"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Simano.UserControls"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <StackPanel Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Left">
+            <Button Content="Get Com List" Click="GetComListButton_Click"/>
+            <Rectangle Height="10"/>
+            <StackPanel Orientation="Horizontal">
+                <TextBox Width="100" x:Name="uxPortName" Text="COM10"/>
+                <Rectangle Width="10"/>
+                <Button Content="Open Port" Click="OpenPortTestButton_Click"/>
+            </StackPanel>
+            <Rectangle Height="10"/>
+            <Button Content="Disconnect" Click="DisconnectButton_Click"/>
+            <Rectangle Height="10"/>
+            <Button Content="Download test"  Click="DownloadButton_Click"/>
+        </StackPanel>
+        <TextBox x:Name="uxConsole" Grid.Row="1" Background="Black" Foreground="White" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" TextWrapping="Wrap">
+            
+        </TextBox>
+    </Grid>
+</UserControl>

+ 81 - 0
Simano/UserControls/STCubeTestPanel.xaml.cs

@@ -0,0 +1,81 @@
+using NLog;
+using Simano.Service.Adapter;
+using Simano.Service.CubeProgrammerAPI;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+
+namespace Simano.UserControls
+{
+    /// <summary>
+    /// STCubeTestPanel.xaml 的互動邏輯
+    /// </summary>
+    public partial class STCubeTestPanel : UserControl
+    {
+        public STCubeTestPanel()
+        {
+            InitializeComponent();
+
+            Init();
+        }
+        private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
+
+        private void Init()
+        {
+            CubeProgrammerAPIAdapter.OnLogReceived += CubeProgrammerAPIAdapter_OnLogReceived;
+            CubeProgrammerAPIAdapter.Init();
+        }
+
+        private void CubeProgrammerAPIAdapter_OnLogReceived(object sender, string e)
+        {
+            Logger.Trace(e);
+            uxConsole.AppendText(e + Environment.NewLine);
+        }
+
+        private void GetComListButton_Click(object sender, RoutedEventArgs e)
+        {
+            //var downloadResult = CubeProgrammerAPIAdapter.DownloadFile("", 0x08000000);
+
+            var list = CubeProgrammerAPIAdapter.GetUsartList();
+            foreach (var comName in list)
+            {
+                uxConsole.AppendText(comName + Environment.NewLine);
+            }
+        }
+
+        private async void OpenPortTestButton_Click(object sender, RoutedEventArgs e)
+        {
+            var openPortResult = await CubeProgrammerAPIWrapper.OpenPort(uxPortName.Text);
+            if (openPortResult)
+            {
+                uxConsole.AppendText($"Open port failed({openPortResult})" + Environment.NewLine);
+                return;
+            }
+            uxConsole.AppendText("Open port Success" + Environment.NewLine);
+        }
+
+        private void DownloadButton_Click(object sender, RoutedEventArgs e)
+        {
+            var path = Path.GetFullPath("./C071_LED_BUTTON_B0.bin");
+            var address = 0x08008000u;
+            var result = CubeProgrammerAPIWrapper.DownloadFile(path, address);
+            uxConsole.AppendText($"DownloadFile Result :{result}" + Environment.NewLine);
+        }
+
+        private void DisconnectButton_Click(object sender, RoutedEventArgs e)
+        {
+            CubeProgrammerAPIAdapter.ClosePort();
+        }
+    }
+}

+ 10 - 0
Simano/packages.config

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
+  <package id="NLog" version="5.3.4" targetFramework="net48" />
+  <package id="System.Buffers" version="4.5.1" targetFramework="net48" />
+  <package id="System.Memory" version="4.5.5" targetFramework="net48" />
+  <package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net48" />
+  <package id="System.Runtime.CompilerServices.Unsafe" version="6.0.0" targetFramework="net48" />
+  <package id="System.Text.Encoding.CodePages" version="9.0.1" targetFramework="net48" />
+</packages>