├── .gitattributes ├── .gitignore ├── Arduino-LiveSerial.sln ├── Arduino-LiveSerial ├── App.config ├── App.xaml ├── App.xaml.cs ├── Arduino-LiveSerial.csproj ├── Domain │ ├── ArduinoSerialPort.cs │ ├── SerialData.cs │ ├── SerialDataReciever.cs │ ├── SerialDataSerie.cs │ └── SerialMessage.cs ├── Images │ ├── live_serial.ico │ └── live_serial.png ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Utils │ ├── EnumerableExtensions.cs │ └── StringExtensions.cs ├── ViewModels │ ├── MainViewModel.cs │ └── SerieVisualizerViewModel.cs ├── Views │ ├── Behaviors │ │ └── AutoScrollBehavior.cs │ ├── Converters │ │ ├── ColorToBrushConverter.cs │ │ └── InverseBooleanConverter.cs │ ├── Dialogs │ │ ├── AboutDialog.xaml │ │ ├── AboutDialog.xaml.cs │ │ ├── ConfirmDialog.xaml │ │ ├── ConfirmDialog.xaml.cs │ │ ├── MessageDialog.xaml │ │ ├── MessageDialog.xaml.cs │ │ └── PredefinedDialogs.cs │ ├── MainView.xaml │ ├── MainView.xaml.cs │ ├── RightDrawer.xaml │ ├── RightDrawer.xaml.cs │ ├── RightDrawerPartial.xaml │ ├── RightDrawerPartial.xaml.cs │ ├── ViewSerieWindow.xaml │ └── ViewSerieWindow.xaml.cs ├── live_serial.ico └── packages.config ├── ArduinoDemo.ino ├── README.md └── Screenshots ├── MainScreen.png ├── Options.png ├── ViewSeriesScreen.png └── excel-export.png /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /Arduino-LiveSerial.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Arduino-LiveSerial", "Arduino-LiveSerial\Arduino-LiveSerial.csproj", "{44BC6091-42F8-4CEC-89FD-DE885564B07B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {44BC6091-42F8-4CEC-89FD-DE885564B07B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {44BC6091-42F8-4CEC-89FD-DE885564B07B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {44BC6091-42F8-4CEC-89FD-DE885564B07B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {44BC6091-42F8-4CEC-89FD-DE885564B07B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {C13C4706-0786-457D-8B99-C36308264F4B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | : 27 | 28 | 29 | @ 30 | 31 | 32 | 1000 33 | 34 | 35 | COM1 36 | 37 | 38 | 115200 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using MaterialDesignThemes.Wpf; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | 10 | namespace Arduino_LiveSerial 11 | { 12 | /// 13 | /// Interaction logic for App.xaml 14 | /// 15 | public partial class App : Application 16 | { 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Arduino-LiveSerial.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {44BC6091-42F8-4CEC-89FD-DE885564B07B} 8 | WinExe 9 | Arduino_LiveSerial 10 | Arduino-LiveSerial 11 | v4.6.1 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | live_serial.ico 39 | 40 | 41 | 42 | ..\packages\ControlzEx.3.0.2.4\lib\net45\ControlzEx.dll 43 | 44 | 45 | ..\packages\Csv.1.0.38\lib\net40\Csv.dll 46 | 47 | 48 | ..\packages\DynamicData.6.7.1.2534\lib\net46\DynamicData.dll 49 | 50 | 51 | ..\packages\EPPlus.4.5.3\lib\net40\EPPlus.dll 52 | 53 | 54 | ..\packages\MahApps.Metro.1.6.0\lib\net45\MahApps.Metro.dll 55 | 56 | 57 | ..\packages\MaterialDesignColors.1.1.1\lib\net45\MaterialDesignColors.dll 58 | 59 | 60 | ..\packages\MaterialDesignExtensions.2.4.0\lib\net45\MaterialDesignExtensions.dll 61 | 62 | 63 | ..\packages\MaterialDesignThemes.MahApps.0.0.12\lib\net45\MaterialDesignThemes.MahApps.dll 64 | 65 | 66 | ..\packages\MaterialDesignThemes.2.5.0.1205\lib\net45\MaterialDesignThemes.Wpf.dll 67 | 68 | 69 | ..\packages\Microsoft.Win32.Registry.4.3.0\lib\net46\Microsoft.Win32.Registry.dll 70 | 71 | 72 | ..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll 73 | 74 | 75 | ..\packages\OxyPlot.Core.1.0.0\lib\net45\OxyPlot.dll 76 | 77 | 78 | ..\packages\OxyPlot.Wpf.1.0.0\lib\net45\OxyPlot.Wpf.dll 79 | 80 | 81 | ..\packages\ReactiveUI.9.5.1\lib\net461\ReactiveUI.dll 82 | 83 | 84 | ..\packages\ReactiveUI.WPF.9.5.1\lib\net461\ReactiveUI.WPF.dll 85 | 86 | 87 | ..\packages\SerialPortStream.2.2.0\lib\net45\RJCP.SerialPortStream.dll 88 | 89 | 90 | ..\packages\Splat.5.1.4\lib\net461\Splat.dll 91 | 92 | 93 | 94 | ..\packages\System.Collections.Specialized.4.3.0\lib\net46\System.Collections.Specialized.dll 95 | 96 | 97 | 98 | 99 | ..\packages\System.Diagnostics.FileVersionInfo.4.3.0\lib\net46\System.Diagnostics.FileVersionInfo.dll 100 | 101 | 102 | ..\packages\System.Diagnostics.TraceSource.4.3.0\lib\net46\System.Diagnostics.TraceSource.dll 103 | 104 | 105 | 106 | ..\packages\System.Drawing.Primitives.4.3.0\lib\net45\System.Drawing.Primitives.dll 107 | 108 | 109 | ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll 110 | 111 | 112 | ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll 113 | 114 | 115 | ..\packages\System.Reactive.4.0.0\lib\net46\System.Reactive.dll 116 | 117 | 118 | ..\packages\System.Reactive.Core.4.0.0\lib\net46\System.Reactive.Core.dll 119 | 120 | 121 | ..\packages\System.Reactive.Experimental.4.0.0\lib\net46\System.Reactive.Experimental.dll 122 | 123 | 124 | ..\packages\System.Reactive.Interfaces.4.0.0\lib\net46\System.Reactive.Interfaces.dll 125 | 126 | 127 | ..\packages\System.Reactive.Linq.4.0.0\lib\net46\System.Reactive.Linq.dll 128 | 129 | 130 | ..\packages\System.Reactive.PlatformServices.4.0.0\lib\net46\System.Reactive.PlatformServices.dll 131 | 132 | 133 | ..\packages\System.Reactive.Providers.4.0.0\lib\net46\System.Reactive.Providers.dll 134 | 135 | 136 | ..\packages\System.Reactive.Runtime.Remoting.4.0.0\lib\net46\System.Reactive.Runtime.Remoting.dll 137 | 138 | 139 | ..\packages\System.Reactive.Windows.Forms.4.0.0\lib\net46\System.Reactive.Windows.Forms.dll 140 | 141 | 142 | ..\packages\System.Reactive.Windows.Threading.4.0.0\lib\net46\System.Reactive.Windows.Threading.dll 143 | 144 | 145 | ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll 146 | 147 | 148 | 149 | ..\packages\System.Threading.Overlapped.4.3.0\lib\net46\System.Threading.Overlapped.dll 150 | 151 | 152 | ..\packages\System.Threading.Thread.4.3.0\lib\net46\System.Threading.Thread.dll 153 | 154 | 155 | ..\packages\System.Threading.ThreadPool.4.3.0\lib\net46\System.Threading.ThreadPool.dll 156 | 157 | 158 | ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll 159 | 160 | 161 | 162 | 163 | ..\packages\ControlzEx.3.0.2.4\lib\net45\System.Windows.Interactivity.dll 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 4.0 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | MSBuild:Compile 181 | Designer 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | AboutDialog.xaml 197 | 198 | 199 | ConfirmDialog.xaml 200 | 201 | 202 | MessageDialog.xaml 203 | 204 | 205 | 206 | MainView.xaml 207 | 208 | 209 | RightDrawer.xaml 210 | 211 | 212 | ViewSerieWindow.xaml 213 | 214 | 215 | MSBuild:Compile 216 | Designer 217 | 218 | 219 | App.xaml 220 | Code 221 | 222 | 223 | MainWindow.xaml 224 | Code 225 | 226 | 227 | Designer 228 | MSBuild:Compile 229 | 230 | 231 | MSBuild:Compile 232 | Designer 233 | 234 | 235 | MSBuild:Compile 236 | Designer 237 | 238 | 239 | Designer 240 | MSBuild:Compile 241 | 242 | 243 | Designer 244 | MSBuild:Compile 245 | 246 | 247 | Designer 248 | MSBuild:Compile 249 | 250 | 251 | 252 | 253 | Code 254 | 255 | 256 | True 257 | True 258 | Resources.resx 259 | 260 | 261 | True 262 | Settings.settings 263 | True 264 | 265 | 266 | ResXFileCodeGenerator 267 | Resources.Designer.cs 268 | 269 | 270 | 271 | SettingsSingleFileGenerator 272 | Settings.Designer.cs 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Domain/ArduinoSerialPort.cs: -------------------------------------------------------------------------------- 1 | using Arduino_LiveSerial.Utils; 2 | using RJCP.IO.Ports; 3 | using System; 4 | using System.IO.Ports; 5 | using System.Linq; 6 | using System.Threading; 7 | 8 | namespace Arduino_LiveSerial 9 | { 10 | public class ArduinoSerialPort 11 | { 12 | private const char NEW_LINE_CHAR = '\n'; 13 | 14 | private SerialPortStream _arduinoPort = new SerialPortStream(); 15 | 16 | public string LastLine { get; private set; } = ""; 17 | private string _lastLineBuffer { get; set; } = ""; 18 | 19 | public bool IsReady { get; private set; } 20 | 21 | public bool IsOpen; 22 | 23 | public event EventHandler DataArrived; 24 | public event EventHandler LineArrived; 25 | 26 | public void Open(string port, int baud) 27 | { 28 | IsOpen = true; 29 | _arduinoPort.PortName = port; 30 | _arduinoPort.BaudRate = baud; 31 | _arduinoPort.DtrEnable = true; 32 | _arduinoPort.ReadTimeout = 1; 33 | _arduinoPort.WriteTimeout = 1; 34 | _arduinoPort.Open(); 35 | _arduinoPort.DiscardInBuffer(); 36 | _arduinoPort.DiscardOutBuffer(); 37 | ClearData(); 38 | _arduinoPort.DataReceived += DataRecieved; 39 | } 40 | 41 | 42 | public void Close() 43 | { 44 | if (!IsOpen) return; 45 | try 46 | { 47 | _arduinoPort.Flush(); 48 | _arduinoPort.DataReceived -= DataRecieved; 49 | _arduinoPort.Close(); 50 | IsOpen = false; 51 | } 52 | catch (Exception) 53 | { 54 | //do nothing 55 | } 56 | 57 | } 58 | 59 | private void DataRecieved(object sender, RJCP.IO.Ports.SerialDataReceivedEventArgs e) 60 | { 61 | var incomingData = _arduinoPort.ReadExisting(); 62 | _lastLineBuffer += incomingData; 63 | 64 | while (_lastLineBuffer.Contains(NEW_LINE_CHAR)) 65 | { 66 | LastLine = _lastLineBuffer.GetBefore(NEW_LINE_CHAR); 67 | _lastLineBuffer = _lastLineBuffer.GetAfter(NEW_LINE_CHAR); 68 | LineArrived?.Invoke(this, new EventArgs()); 69 | } 70 | 71 | DataArrived?.Invoke(this, new EventArgs()); 72 | } 73 | 74 | public void ClearData() 75 | { 76 | LastLine = ""; 77 | _lastLineBuffer = ""; 78 | } 79 | 80 | public void SendData(string data) 81 | { 82 | _arduinoPort.WriteLine(data); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Arduino-LiveSerial/Domain/SerialData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Arduino_LiveSerial.Domain 9 | { 10 | public class SerialData 11 | { 12 | public SerialDataSerie Serie; 13 | 14 | public string Key { get; set; } 15 | 16 | public double Value { get; set; } 17 | 18 | public uint? Millis { get; set; } 19 | 20 | public DateTime Time { get; set; } = System.DateTime.Now; 21 | 22 | public static SerialData CreateFromLine(string line, char valueSeparator, char millisSeparator) 23 | { 24 | if(line.Contains(valueSeparator)) 25 | { 26 | if (!line.Contains(millisSeparator)) 27 | { 28 | return CreateSerialData(line, valueSeparator); 29 | } 30 | else 31 | { 32 | return CreateSerialData(line, valueSeparator, millisSeparator); 33 | } 34 | } 35 | 36 | return null; 37 | } 38 | 39 | private static SerialData CreateSerialData(string line, char valueSeparator) 40 | { 41 | var key = line.Split(valueSeparator)[0]; 42 | var valueToken = line.Split(valueSeparator)[1]; 43 | var validValue = double.TryParse(valueToken, NumberStyles.Any, CultureInfo.InvariantCulture, out double value); 44 | 45 | if (validValue) return new SerialData() 46 | { 47 | Key = key, 48 | Value = value 49 | }; 50 | 51 | return null; 52 | } 53 | 54 | private static SerialData CreateSerialData(string line, char valueSeparator, char millisSeparator) 55 | { 56 | var key = line.Split(valueSeparator)[0]; 57 | var restToken = line.Split(valueSeparator)[1]; 58 | var valueToken = restToken.Split(millisSeparator)[0]; 59 | var millisToken = restToken.Split(millisSeparator)[1]; 60 | 61 | var validValue = double.TryParse(valueToken, NumberStyles.Any, CultureInfo.InvariantCulture, out double value); 62 | var validMillis = uint.TryParse(millisToken, NumberStyles.Integer, CultureInfo.InvariantCulture, out uint millis); 63 | 64 | if (validValue) return new SerialData() 65 | { 66 | Key = key, 67 | Value = value, 68 | Millis = millis 69 | }; 70 | 71 | return null; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Domain/SerialDataReciever.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using OxyPlot; 8 | using OxyPlot.Axes; 9 | using OxyPlot.Series; 10 | using ReactiveUI; 11 | 12 | namespace Arduino_LiveSerial.Domain 13 | { 14 | public class SerialDataReciever 15 | { 16 | 17 | private ArduinoSerialPort _arduinoSerialPort = new ArduinoSerialPort(); 18 | public List DataSeries { get; set; } = new List(); 19 | 20 | public Dictionary DictSeries { get; set; } = new Dictionary(); 21 | 22 | public char ValueSeparator { get; set; } 23 | public char MillisSeparator { get; set; } 24 | public string LineRecieved { get; set; } 25 | public bool IsConnected { get; set; } 26 | 27 | public void OpenPort(string port, int baudRate) 28 | { 29 | _arduinoSerialPort.Open(port, baudRate); 30 | _arduinoSerialPort.LineArrived += DataArrived; 31 | IsConnected = true; 32 | } 33 | 34 | public void ClosePort() 35 | { 36 | _arduinoSerialPort.LineArrived -= DataArrived; 37 | _arduinoSerialPort.Close(); 38 | IsConnected = false; 39 | } 40 | 41 | private void DataArrived(object sender, EventArgs e) 42 | { 43 | LineRecieved = _arduinoSerialPort.LastLine; 44 | ProccessNewLine(); 45 | } 46 | 47 | private void ProccessNewLine() 48 | { 49 | if (LineRecieved.Contains(ValueSeparator)) ProcessSerialData(); 50 | else ProcessSerialMessage(); 51 | } 52 | 53 | private void ProcessSerialData() 54 | { 55 | var newData = SerialData.CreateFromLine(LineRecieved, ValueSeparator, MillisSeparator); 56 | AddSerialData(newData); 57 | } 58 | 59 | private void ProcessSerialMessage() 60 | { 61 | var message = new SerialMessage() { Content = LineRecieved }; 62 | MessageBus.Current.SendMessage(message, "NewMessage"); 63 | } 64 | 65 | public void SendData(string data) 66 | { 67 | _arduinoSerialPort.SendData(data); 68 | } 69 | 70 | private void AddSerialData(Domain.SerialData data) 71 | { 72 | if (data == null) return; 73 | 74 | if (!DictSeries.ContainsKey(data.Key)) 75 | AddSeries(data.Key); 76 | 77 | AddPoint(data); 78 | } 79 | 80 | private void AddSeries(string key) 81 | { 82 | var dataSerie = new SerialDataSerie() { Name = key }; 83 | DictSeries.Add(key, dataSerie); 84 | DataSeries.Add(dataSerie); 85 | 86 | MessageBus.Current.SendMessage(dataSerie, "NewDataSerie"); 87 | } 88 | 89 | private void AddPoint(Domain.SerialData data) 90 | { 91 | var dataSerie = DictSeries[data.Key]; 92 | data.Serie = dataSerie; 93 | 94 | if (data.Millis != null) 95 | { 96 | var firstData = dataSerie.Data.FirstOrDefault(x => x.Millis != null); 97 | if (firstData != null) 98 | data.Time = firstData.Time.AddMilliseconds((double)data.Millis - (double)firstData.Millis); 99 | } 100 | 101 | dataSerie.AddData(data); 102 | MessageBus.Current.SendMessage(data, "NewData"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Domain/SerialDataSerie.cs: -------------------------------------------------------------------------------- 1 | using Arduino_LiveSerial.View.Dialogs; 2 | using Arduino_LiveSerial.ViewModels; 3 | using Arduino_LiveSerial.Views; 4 | using OxyPlot.Series; 5 | using ReactiveUI; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Reactive; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using System.Windows; 13 | using System.Windows.Media; 14 | 15 | namespace Arduino_LiveSerial.Domain 16 | { 17 | public class SerialDataSerie : ReactiveObject 18 | { 19 | public ReactiveCommand OpenSerieCommand { get; set; } 20 | 21 | public SerialDataSerie() 22 | { 23 | peakSearch = new PeakSearch(this); 24 | OpenSerieCommand = ReactiveCommand.Create(OpenSerie); 25 | } 26 | 27 | private void OpenSerie() 28 | { 29 | var context = new SerieVisualizerViewModel() { Data = this.Data.ToArray(), SerieName = Name}; 30 | var viewSerieWindow = new ViewSerieWindow() 31 | { 32 | Title = $"Serie: {Name}", 33 | DataContext = context, 34 | }; 35 | 36 | viewSerieWindow.Show(); 37 | } 38 | 39 | public string Name { get; set; } 40 | 41 | public string Designation { get; set; } 42 | 43 | public List Data { get; set; } = new List(); 44 | 45 | 46 | private bool _visible = true; 47 | public bool IsVisible 48 | { 49 | get => _visible; 50 | set 51 | { 52 | this.RaiseAndSetIfChanged(ref _visible, value); 53 | this.ChartSerie.IsVisible = value; 54 | } 55 | } 56 | 57 | private int _count; 58 | public int Count 59 | { 60 | get => _count; 61 | set => this.RaiseAndSetIfChanged(ref _count, value); 62 | } 63 | 64 | private double _value; 65 | public double Value 66 | { 67 | get => _value; 68 | set => this.RaiseAndSetIfChanged(ref _value, value); 69 | } 70 | 71 | 72 | public LineSeries ChartSerie { get; set; } 73 | 74 | private DateTime _minT; 75 | public DateTime MinT 76 | { 77 | get => _minT; 78 | set => this.RaiseAndSetIfChanged(ref _minT, value); 79 | } 80 | 81 | private DateTime _maxT; 82 | public DateTime MaxT 83 | { 84 | get => _maxT; 85 | set => this.RaiseAndSetIfChanged(ref _maxT, value); 86 | } 87 | 88 | 89 | private double _min; 90 | public double Min 91 | { 92 | get => _min; 93 | set => this.RaiseAndSetIfChanged(ref _min, value); 94 | } 95 | 96 | private double _max; 97 | public double Max 98 | { 99 | get => _max; 100 | set => this.RaiseAndSetIfChanged(ref _max, value); 101 | } 102 | 103 | private double _peakMax; 104 | public double PeakMax 105 | { 106 | get => _peakMax; 107 | set => this.RaiseAndSetIfChanged(ref _peakMax, value); 108 | } 109 | 110 | 111 | private DateTime _peakT; 112 | public DateTime PeakT 113 | { 114 | get => _peakT; 115 | set => this.RaiseAndSetIfChanged(ref _peakT, value); 116 | } 117 | 118 | private double _frequency; 119 | public double Frequency 120 | { 121 | get => _frequency; 122 | set => this.RaiseAndSetIfChanged(ref _frequency, value); 123 | } 124 | 125 | private double _range; 126 | public double Range 127 | { 128 | get => _range; 129 | set => this.RaiseAndSetIfChanged(ref _range, value); 130 | } 131 | 132 | private double _sum; 133 | public double Sum 134 | { 135 | get => _sum; 136 | set => this.RaiseAndSetIfChanged(ref _sum, value); 137 | } 138 | 139 | private double _mean; 140 | public double Mean 141 | { 142 | get => _mean; 143 | set => this.RaiseAndSetIfChanged(ref _mean, value); 144 | } 145 | 146 | private double _delta; 147 | public double Delta 148 | { 149 | get => _delta; 150 | set => this.RaiseAndSetIfChanged(ref _delta, value); 151 | } 152 | 153 | 154 | private TimeSpan _deltaT; 155 | public TimeSpan DeltaT 156 | { 157 | get => _deltaT; 158 | set => this.RaiseAndSetIfChanged(ref _deltaT, value); 159 | } 160 | 161 | private TimeSpan _interval; 162 | public TimeSpan Interval 163 | { 164 | get => _interval; 165 | set => this.RaiseAndSetIfChanged(ref _interval, value); 166 | } 167 | 168 | private double _slope; 169 | public double Slope 170 | { 171 | get => _slope; 172 | set => this.RaiseAndSetIfChanged(ref _slope, value); 173 | } 174 | 175 | private PeakSearch peakSearch; 176 | 177 | public void AddData(SerialData item) 178 | { 179 | Count++; 180 | Value = item.Value; 181 | Min = Math.Min(item.Value, Min); 182 | Max = Math.Max(item.Value, Max); 183 | MinT = item.Time < MinT ? item.Time : MinT; 184 | MaxT = item.Time > MaxT ? item.Time : MaxT; 185 | Range = Max - Min; 186 | Sum += item.Value; 187 | Mean = Sum / Count; 188 | 189 | SerialData prev = Data.LastOrDefault(); 190 | if (prev != null) 191 | { 192 | 193 | Delta = item.Value - prev.Value; 194 | DeltaT = item.Time - prev.Time; 195 | Slope = Delta / DeltaT.TotalSeconds; 196 | } 197 | else 198 | { 199 | MinT = item.Time; 200 | Min = item.Value; 201 | } 202 | peakSearch.ProcessItems(prev, item); 203 | Data.Add(item); 204 | } 205 | 206 | public class PeakSearch 207 | { 208 | public SerialDataSerie serie; 209 | public SerialData start; 210 | 211 | //public bool IsGoingDown = false; 212 | public bool IsGoingUp = false; 213 | 214 | public PeakSearch(SerialDataSerie serie) 215 | { 216 | this.serie = serie; 217 | } 218 | 219 | public void ProcessItems(SerialData prev, SerialData current) 220 | { 221 | if (prev == null) return; 222 | if (prev.Value > current.Value) 223 | { 224 | if (IsGoingUp && start != null) 225 | { 226 | TimeSpan ts = current.Time.Subtract(start.Time); 227 | DateTime middleTime = start.Time.AddMinutes(ts.TotalMinutes / 2); 228 | 229 | if (serie.PeakT.Ticks > 0) 230 | { 231 | serie.Interval = middleTime - serie.PeakT; 232 | serie.Frequency = 1 / serie.Interval.TotalSeconds; 233 | } 234 | 235 | serie.PeakT = middleTime; 236 | serie.PeakMax = prev.Value; 237 | } 238 | IsGoingUp = false; 239 | } 240 | if (prev.Value < current.Value) 241 | { 242 | start = current; 243 | IsGoingUp = true; 244 | } 245 | 246 | } 247 | } 248 | } 249 | } -------------------------------------------------------------------------------- /Arduino-LiveSerial/Domain/SerialMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Arduino_LiveSerial.Domain 8 | { 9 | public class SerialMessage 10 | { 11 | public DateTime Time { get; set; } = DateTime.Now; 12 | 13 | public string Content { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Images/live_serial.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Arduino-LiveSerial/Images/live_serial.ico -------------------------------------------------------------------------------- /Arduino-LiveSerial/Images/live_serial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Arduino-LiveSerial/Images/live_serial.png -------------------------------------------------------------------------------- /Arduino-LiveSerial/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | using Arduino_LiveSerial.ViewModels; 16 | using MahApps.Metro.Controls; 17 | using MaterialDesignThemes.Wpf; 18 | 19 | namespace Arduino_LiveSerial 20 | { 21 | /// 22 | /// Interaction logic for MainWindow.xaml 23 | /// 24 | public partial class MainWindow : MetroWindow 25 | { 26 | public MainWindow() 27 | { 28 | InitializeComponent(); 29 | 30 | DataContext = new MainViewModel(); 31 | } 32 | } 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("Arduino-LiveSerial")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("www.LuisLlamas.es")] 14 | [assembly: AssemblyProduct("Arduino-LiveSerial")] 15 | [assembly: AssemblyCopyright("Copyright ©2019")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Arduino_LiveSerial.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Arduino_LiveSerial.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Arduino_LiveSerial.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute(":")] 29 | public string ValueSeparator { 30 | get { 31 | return ((string)(this["ValueSeparator"])); 32 | } 33 | set { 34 | this["ValueSeparator"] = value; 35 | } 36 | } 37 | 38 | [global::System.Configuration.UserScopedSettingAttribute()] 39 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 40 | [global::System.Configuration.DefaultSettingValueAttribute("@")] 41 | public string MillisSeparator { 42 | get { 43 | return ((string)(this["MillisSeparator"])); 44 | } 45 | set { 46 | this["MillisSeparator"] = value; 47 | } 48 | } 49 | 50 | [global::System.Configuration.UserScopedSettingAttribute()] 51 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 52 | [global::System.Configuration.DefaultSettingValueAttribute("1000")] 53 | public int DataWindow { 54 | get { 55 | return ((int)(this["DataWindow"])); 56 | } 57 | set { 58 | this["DataWindow"] = value; 59 | } 60 | } 61 | 62 | [global::System.Configuration.UserScopedSettingAttribute()] 63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 64 | [global::System.Configuration.DefaultSettingValueAttribute("COM1")] 65 | public string Port { 66 | get { 67 | return ((string)(this["Port"])); 68 | } 69 | set { 70 | this["Port"] = value; 71 | } 72 | } 73 | 74 | [global::System.Configuration.UserScopedSettingAttribute()] 75 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 76 | [global::System.Configuration.DefaultSettingValueAttribute("115200")] 77 | public int BaudRate { 78 | get { 79 | return ((int)(this["BaudRate"])); 80 | } 81 | set { 82 | this["BaudRate"] = value; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | : 7 | 8 | 9 | @ 10 | 11 | 12 | 1000 13 | 14 | 15 | COM1 16 | 17 | 18 | 115200 19 | 20 | 21 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Utils/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Arduino_LiveSerial.Utils 8 | { 9 | static class EnumerableExtensions 10 | { 11 | public static void ForEach(this IEnumerable values, Action action) 12 | { 13 | foreach (T value in values) 14 | action(value); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Utils/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Arduino_LiveSerial.Utils 8 | { 9 | static class StringExtensions 10 | { 11 | public static string GetBefore(this string value, char x) 12 | { 13 | int xPos = value.IndexOf(x); 14 | return xPos == -1 ? string.Empty : value.Substring(0, xPos); 15 | } 16 | 17 | public static string GetAfter(this string value, char x) 18 | { 19 | int xPos = value.IndexOf(x); 20 | 21 | if (xPos == -1) return string.Empty; 22 | 23 | int startIndex = xPos + 1; 24 | return startIndex >= value.Length ? string.Empty : value.Substring(startIndex); 25 | } 26 | 27 | 28 | public static string GetUntilOrEmpty(this string text, char stopAt) 29 | { 30 | if (!string.IsNullOrWhiteSpace(text)) 31 | { 32 | int charLocation = text.IndexOf(stopAt); 33 | 34 | if (charLocation > 0) 35 | { 36 | return text.Substring(0, charLocation); 37 | } 38 | } 39 | 40 | return string.Empty; 41 | } 42 | 43 | public static string GetFromOrEmpty(this string text, char stopAt) 44 | { 45 | if (!string.IsNullOrWhiteSpace(text)) 46 | { 47 | int charLocation = text.IndexOf(stopAt); 48 | 49 | if (charLocation < text.Length - 1) 50 | { 51 | return text.Substring(charLocation, text.Length-1); 52 | } 53 | } 54 | 55 | return string.Empty; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using Arduino_LiveSerial.Domain; 2 | using Arduino_LiveSerial.Utils; 3 | using Arduino_LiveSerial.View.Dialogs.Predefined; 4 | using MaterialDesignThemes.Wpf; 5 | using OxyPlot; 6 | using OxyPlot.Axes; 7 | using OxyPlot.Series; 8 | using ReactiveUI; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Collections.ObjectModel; 12 | using System.ComponentModel; 13 | using System.Diagnostics; 14 | using System.Globalization; 15 | using System.IO.Ports; 16 | using System.Linq; 17 | using System.Reactive; 18 | using System.Reactive.Linq; 19 | using System.Text; 20 | using System.Threading.Tasks; 21 | using System.Windows; 22 | 23 | namespace Arduino_LiveSerial.ViewModels 24 | { 25 | public class MainViewModel : ReactiveObject 26 | { 27 | #region Constructor 28 | public MainViewModel() : base() 29 | { 30 | _isPortSelected = this 31 | .WhenAnyValue(x => x.SelectedSerialPort) 32 | .Select(SelectedSerialPort => !string.IsNullOrEmpty(SelectedSerialPort)) 33 | .ToProperty(this, x => x.IsPortSelected); 34 | 35 | _dataWindow = this.WhenAnyValue(x => x.DataWindowUI) 36 | .Throttle(TimeSpan.FromMilliseconds(500)) 37 | .DistinctUntilChanged() 38 | .ObserveOn(RxApp.MainThreadScheduler) 39 | .ToProperty(this, x => x.DataWindow); 40 | 41 | this.WhenAnyValue(x => x.ValueSeparator) 42 | .Throttle(TimeSpan.FromMilliseconds(500)) 43 | .DistinctUntilChanged() 44 | .Where(sep => !string.IsNullOrWhiteSpace(sep)) 45 | .Select(x => x.First()) 46 | .ObserveOn(RxApp.MainThreadScheduler) 47 | .Subscribe((x) => SerialDataReciever.ValueSeparator = x); 48 | 49 | this.WhenAnyValue(x => x.MillisSeparator) 50 | .Throttle(TimeSpan.FromMilliseconds(500)) 51 | .DistinctUntilChanged() 52 | .Where(sep => !string.IsNullOrWhiteSpace(sep)) 53 | .Select(x => x.First()) 54 | .ObserveOn(RxApp.MainThreadScheduler) 55 | .Subscribe((x) => SerialDataReciever.MillisSeparator = x); 56 | 57 | SuscribeMessageBus(); 58 | WireCommands(); 59 | InitData(); 60 | } 61 | 62 | private void SuscribeMessageBus() 63 | { 64 | MessageBus.Current.Listen("NewDataSerie") 65 | .Subscribe(x => DataSerieRecieved(x)); 66 | 67 | MessageBus.Current.Listen("NewData") 68 | .Subscribe(x => DataRecieved(x)); 69 | 70 | MessageBus.Current.Listen("NewMessage") 71 | .Subscribe(x => MessageRecieved(x)); 72 | } 73 | 74 | private void InitData() 75 | { 76 | ValueSeparator = Properties.Settings.Default["ValueSeparator"].ToString(); 77 | MillisSeparator = Properties.Settings.Default["MillisSeparator"].ToString(); 78 | DataWindowUI = (int)Properties.Settings.Default["DataWindow"]; 79 | SelectedSerialPort = Properties.Settings.Default["Port"].ToString(); 80 | SelectedBaudRate = (int)Properties.Settings.Default["BaudRate"]; 81 | 82 | LoadPorts(); 83 | BaudRates = new int[] { 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 }; 84 | 85 | PlotModel.Axes.Add(new DateTimeAxis()); 86 | } 87 | #endregion 88 | 89 | 90 | #region Commands 91 | private void WireCommands() 92 | { 93 | ShowAboutCommand = ReactiveCommand.Create(ShowAbout); 94 | OpenGithubCommand = ReactiveCommand.Create(OpenGithub); 95 | ClosingCommand = ReactiveCommand.Create(OnClosing); 96 | LoadPortsCommand = ReactiveCommand.Create(LoadPorts); 97 | ChangeConnectionCommand = ReactiveCommand.Create(ChangeConnection, this.WhenAnyValue(x => x.IsPortSelected)); 98 | 99 | SendCommand = ReactiveCommand.Create(Send, this.WhenAnyValue( 100 | x => x.IsConnected, x => x.SendText, 101 | (isConnected, sendText) => 102 | isConnected && !string.IsNullOrEmpty(sendText))); 103 | } 104 | 105 | 106 | 107 | public ReactiveCommand ShowAboutCommand { get; set; } 108 | public ReactiveCommand OpenGithubCommand { get; set; } 109 | public ReactiveCommand ClosingCommand { get; set; } 110 | public ReactiveCommand LoadPortsCommand { get; set; } 111 | public ReactiveCommand ChangeConnectionCommand { get; set; } 112 | public ReactiveCommand SendCommand { get; set; } 113 | #endregion 114 | 115 | 116 | #region Properties 117 | 118 | public ObservableCollection SerialPorts { get; set; } = new ObservableCollection(); 119 | 120 | 121 | private string _valueSeparator; 122 | public string ValueSeparator 123 | { 124 | get => _valueSeparator; 125 | set => this.RaiseAndSetIfChanged(ref _valueSeparator, value); 126 | } 127 | 128 | 129 | private string _MillisSeparator; 130 | public string MillisSeparator 131 | { 132 | get => _MillisSeparator; 133 | set => this.RaiseAndSetIfChanged(ref _MillisSeparator, value); 134 | } 135 | 136 | 137 | private readonly ObservableAsPropertyHelper _dataWindow; 138 | public int DataWindow => _dataWindow.Value; 139 | 140 | private int _dataWindowUI; 141 | public int DataWindowUI 142 | { 143 | get => _dataWindowUI; 144 | set => this.RaiseAndSetIfChanged(ref _dataWindowUI, value); 145 | } 146 | 147 | public ObservableCollection SendedMessages { get; set; } = new ObservableCollection(); 148 | 149 | public ObservableCollection RecievedMessages { get; set; } = new ObservableCollection(); 150 | 151 | public int[] BaudRates { get; set; } 152 | 153 | 154 | private string _selectedSerialPort; 155 | public string SelectedSerialPort 156 | { 157 | get => _selectedSerialPort; 158 | set => this.RaiseAndSetIfChanged(ref _selectedSerialPort, value); 159 | } 160 | 161 | 162 | private int _selectedBaudRate = 115200; 163 | public int SelectedBaudRate 164 | { 165 | get => _selectedBaudRate; 166 | set => this.RaiseAndSetIfChanged(ref _selectedBaudRate, value); 167 | } 168 | 169 | 170 | private readonly ObservableAsPropertyHelper _isPortSelected; 171 | public bool IsPortSelected => _isPortSelected.Value; 172 | 173 | private bool _canConnect; 174 | public bool CanConnect 175 | { 176 | get => _canConnect; 177 | set => this.RaiseAndSetIfChanged(ref _canConnect, value); 178 | } 179 | 180 | private bool _isConnected; 181 | public bool IsConnected 182 | { 183 | get => _isConnected; 184 | set => this.RaiseAndSetIfChanged(ref _isConnected, value); 185 | } 186 | 187 | 188 | private string _sendText; 189 | public string SendText 190 | { 191 | get => _sendText; 192 | set => this.RaiseAndSetIfChanged(ref _sendText, value); 193 | } 194 | 195 | public SerialDataReciever SerialDataReciever { get; set; } = new SerialDataReciever(); 196 | public ObservableCollection DataSeries { get; set; } = new ObservableCollection(); 197 | 198 | private PlotModel _plotModel = new PlotModel(); 199 | public PlotModel PlotModel 200 | { 201 | get => _plotModel; 202 | set => this.RaiseAndSetIfChanged(ref _plotModel, value); 203 | } 204 | #endregion 205 | 206 | 207 | #region Methods 208 | private void ShowAbout() 209 | { 210 | var dialog = new Views.Dialogs.AboutDialog(); 211 | DialogHost.Show(dialog, "RootDialog", (s, e)=> { }, (s, e) => { }); 212 | } 213 | 214 | private void OpenGithub() 215 | { 216 | try 217 | { 218 | Process.Start(new ProcessStartInfo("https://github.com/luisllamasbinaburo")); 219 | } 220 | catch (Exception) 221 | { 222 | // do nothing 223 | } 224 | 225 | } 226 | 227 | private void OnClosing() 228 | { 229 | ClosePort(); 230 | 231 | Properties.Settings.Default["ValueSeparator"] = ValueSeparator; 232 | Properties.Settings.Default["MillisSeparator"] = MillisSeparator; 233 | Properties.Settings.Default["DataWindow"] = DataWindowUI; 234 | Properties.Settings.Default["Port"] = SelectedSerialPort; 235 | Properties.Settings.Default["BaudRate"] = SelectedBaudRate; 236 | Properties.Settings.Default.Save(); 237 | } 238 | 239 | private void LoadPorts() 240 | { 241 | SerialPorts.Clear(); 242 | SerialPort.GetPortNames().ForEach(x => SerialPorts.Add(x)); 243 | if (!SerialPorts.Contains(SelectedSerialPort)) SelectedSerialPort = SerialPorts.FirstOrDefault(); 244 | } 245 | 246 | private Unit ChangeConnection(bool changeToConnected) 247 | { 248 | if (changeToConnected) OpenPort(); 249 | else ClosePort(); 250 | return new Unit(); 251 | } 252 | 253 | private void OpenPort() 254 | { 255 | try 256 | { 257 | SerialDataReciever.OpenPort(SelectedSerialPort, SelectedBaudRate); 258 | IsConnected = true; 259 | } 260 | catch (Exception) 261 | { 262 | PredefinedDialogs.MessageDialog(this, "Error opening port", (s, e) => { }, (s, e) => { }); 263 | IsConnected = false; 264 | } 265 | } 266 | 267 | private void ClosePort() 268 | { 269 | try 270 | { 271 | SerialDataReciever.ClosePort(); 272 | IsConnected = false; 273 | } 274 | catch (Exception) 275 | { 276 | PredefinedDialogs.MessageDialog(this, "Error closing port", (s, e) => { }, (s, e) => { }); 277 | IsConnected = false; 278 | } 279 | } 280 | 281 | private void Send() 282 | { 283 | try 284 | { 285 | SerialDataReciever.SendData(SendText); 286 | SendedMessages.Add(new SerialMessage { Content = SendText }); 287 | SendText = ""; 288 | } 289 | catch (Exception) 290 | { 291 | PredefinedDialogs.MessageDialog(this, "Error sending", (s, e) => { }, (s, e) => { }); 292 | IsConnected = false; 293 | } 294 | } 295 | 296 | private void DataSerieRecieved(SerialDataSerie dataSerie) 297 | { 298 | Application.Current.Dispatcher.Invoke(() => DataSeries.Add(dataSerie)); 299 | 300 | var line = new LineSeries 301 | { 302 | Title = dataSerie.Name, 303 | TrackerFormatString = "{0}\n{1}: {2:HH.mm.ss.ffff}\n{3}: {4:0.###}" 304 | }; 305 | dataSerie.ChartSerie = line; 306 | 307 | PlotModel.Series.Add(line); 308 | } 309 | 310 | private void DataRecieved(Domain.SerialData data) 311 | { 312 | data.Serie.ChartSerie.Points.Add(DateTimeAxis.CreateDataPoint(data.Time, data.Value)); 313 | if (DataWindow > 0) 314 | { 315 | while (data.Serie.ChartSerie.Points.Count > DataWindow) 316 | { 317 | data.Serie.ChartSerie.Points.RemoveAt(0); 318 | } 319 | } 320 | 321 | PlotModel.InvalidatePlot(true); 322 | } 323 | 324 | private void MessageRecieved(SerialMessage x) 325 | { 326 | Application.Current.Dispatcher.Invoke(() => RecievedMessages.Add(x)); 327 | } 328 | 329 | private void Refresh() 330 | { 331 | PlotModel.Series.Clear(); 332 | 333 | var serie = DataSeries.First(); 334 | DataSerieRecieved(serie); 335 | PlotModel.InvalidatePlot(true); 336 | foreach (var data in serie.Data) 337 | { 338 | DataRecieved(data); 339 | PlotModel.InvalidatePlot(true); 340 | } 341 | PlotModel.InvalidatePlot(true); 342 | PlotModel.InvalidatePlot(true); 343 | } 344 | #endregion 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/ViewModels/SerieVisualizerViewModel.cs: -------------------------------------------------------------------------------- 1 | using Arduino_LiveSerial.View.Dialogs.Predefined; 2 | using Newtonsoft.Json; 3 | using OfficeOpenXml; 4 | using ReactiveUI; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reactive; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace Arduino_LiveSerial.ViewModels 14 | { 15 | public class SerieVisualizerViewModel 16 | { 17 | #region Constructor 18 | public SerieVisualizerViewModel() 19 | { 20 | ExportToCsvCommand = ReactiveCommand.Create(ExportToCsv); 21 | ExportToExcelCommand = ReactiveCommand.Create(ExportToExcel); 22 | ExportToJsonCommand = ReactiveCommand.Create(ExportToJson); 23 | } 24 | #endregion 25 | 26 | 27 | #region Commands 28 | public ReactiveCommand ExportToCsvCommand { get; set; } 29 | public ReactiveCommand ExportToExcelCommand { get; set; } 30 | public ReactiveCommand ExportToJsonCommand { get; set; } 31 | #endregion 32 | 33 | 34 | #region Properties 35 | public Domain.SerialData[] Data { get; set; } 36 | 37 | public string SerieName { get; set; } 38 | #endregion 39 | 40 | 41 | #region Methods 42 | private void ExportToExcel() 43 | { 44 | try 45 | { 46 | var path = getPath("Excel files (*.xlsx)|*.xlsx", ".xlsx"); 47 | if (path != null) 48 | { 49 | using (ExcelPackage xlPackage = new ExcelPackage(new FileInfo(path))) 50 | { 51 | var sheet = xlPackage.Workbook.Worksheets.Add("Data"); 52 | var range = sheet.Cells[1, 1].LoadFromCollection(Data, true, OfficeOpenXml.Table.TableStyles.Light9); 53 | 54 | // formating 55 | var tbl = sheet.Tables[0]; 56 | var dateStyle = xlPackage.Workbook.Styles.CreateNamedStyle("TableDate"); 57 | dateStyle.Style.Numberformat.Format = "hh:mm:ss.000"; 58 | tbl.Columns[3].DataCellStyleName = "TableDate"; 59 | sheet.Cells[sheet.Dimension.Address].AutoFitColumns(); 60 | 61 | var chart = sheet.Drawings.AddChart("Chart", OfficeOpenXml.Drawing.Chart.eChartType.XYScatterLinesNoMarkers); 62 | chart.SetPosition(20, 300); 63 | chart.SetSize(500, 400); 64 | 65 | var ser = chart.Series.Add(range.Offset(1, 1, range.End.Row - 1, 1), range.Offset(1, 3, range.End.Row - 1, 1)); 66 | ser.Header = SerieName; 67 | chart.Style = OfficeOpenXml.Drawing.Chart.eChartStyle.Style7; 68 | 69 | xlPackage.Save(); 70 | 71 | PredefinedDialogs.MessageDialog(this, "Done!", (s, e) => { }, (s, e) => { }, "ViewSerieDialog"); 72 | } 73 | } 74 | } 75 | catch (Exception) 76 | { 77 | PredefinedDialogs.MessageDialog(this, "Error", (s, e) => { }, (s, e) => { }, "ViewSerieDialog"); 78 | } 79 | } 80 | 81 | private void ExportToCsv() 82 | { 83 | try 84 | { 85 | var path = getPath("csv files (*.csv)|*.csv", ".csv"); 86 | if (path != null) 87 | { 88 | using (StreamWriter outputFile = new StreamWriter(path)) 89 | { 90 | 91 | var writer = new StringWriter(); 92 | var textData = Data.Select(x => new[] { x.Key.ToString(), 93 | x.Value.ToString(), 94 | x.Millis.ToString(), 95 | x.Time.ToString(),}); 96 | 97 | Csv.CsvWriter.Write(writer, new string[] { "Name", "Value", "Millis", "Time" }, textData, ','); 98 | outputFile.WriteLine(writer); 99 | 100 | PredefinedDialogs.MessageDialog(this, "Done!", (s, e) => { }, (s, e) => { }, "ViewSerieDialog"); 101 | } 102 | } 103 | } 104 | catch (Exception) 105 | { 106 | PredefinedDialogs.MessageDialog(this, "Error", (s, e) => { }, (s, e) => { }, "ViewSerieDialog"); 107 | } 108 | 109 | } 110 | 111 | private void ExportToJson() 112 | { 113 | try 114 | { 115 | var path = getPath("json files (*.json)|*.json", ".json"); 116 | if (path != null) 117 | { 118 | string json = JsonConvert.SerializeObject(Data); 119 | System.IO.File.WriteAllText(path, json); 120 | 121 | PredefinedDialogs.MessageDialog(this, "Done!", (s, e) => { }, (s, e) => { }, "ViewSerieDialog"); 122 | } 123 | } 124 | catch (Exception) 125 | { 126 | PredefinedDialogs.MessageDialog(this, "Error", (s, e) => { }, (s, e) => { }, "ViewSerieDialog"); 127 | } 128 | } 129 | 130 | private string getPath(string filter, string extension) 131 | { 132 | Microsoft.Win32.SaveFileDialog savedialog = new Microsoft.Win32.SaveFileDialog(); 133 | savedialog.FileName = SerieName; 134 | savedialog.Filter = filter; 135 | savedialog.FilterIndex = 2; 136 | savedialog.RestoreDirectory = true; 137 | 138 | if (savedialog.ShowDialog() == true) 139 | { 140 | var path = savedialog.FileName; 141 | System.IO.Path.ChangeExtension(path, extension); 142 | if (File.Exists(path)) File.Delete(path); 143 | return path; 144 | } 145 | 146 | return null; 147 | } 148 | #endregion 149 | } 150 | } -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Behaviors/AutoScrollBehavior.cs: -------------------------------------------------------------------------------- 1 | using Arduino_LiveSerial.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Controls; 11 | using System.Windows.Interactivity; 12 | using System.Windows.Media; 13 | using static Arduino_LiveSerial.ViewModels.MainViewModel; 14 | 15 | namespace Arduino_LiveSerial.Behaviors 16 | { 17 | public class ScrollIntoViewBehavior : Behavior 18 | { 19 | private DataGrid Grid 20 | { 21 | get 22 | { 23 | return AssociatedObject; 24 | } 25 | } 26 | 27 | protected override void OnAttached() 28 | { 29 | base.OnAttached(); 30 | 31 | Grid.Loaded += Grid_Loaded; ; 32 | } 33 | 34 | private void Grid_Loaded(object sender, RoutedEventArgs e) 35 | { 36 | var ItemSource = Grid.ItemsSource as ObservableCollection; 37 | ItemSource.CollectionChanged += ItemSource_CollectionChanged; 38 | } 39 | 40 | private void ItemSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) 41 | { 42 | if (Grid.Items.Count > 0) 43 | Grid.ScrollIntoView(Grid.Items[Grid.Items.Count - 1]); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Converters/ColorToBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Data; 7 | using System.Windows.Media; 8 | 9 | namespace Arduino_LiveSerial.View.Converters 10 | { 11 | public class ColorToSolidColorBrushValueConverter : IValueConverter 12 | { 13 | 14 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 15 | { 16 | if (null == value) 17 | { 18 | return null; 19 | } 20 | if (value is Color) 21 | { 22 | Color color = (Color)value; 23 | return new SolidColorBrush(color); 24 | } 25 | 26 | Type type = value.GetType(); 27 | throw new InvalidOperationException("Unsupported type [" + type.Name + "]"); 28 | } 29 | 30 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Converters/InverseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Data; 3 | 4 | 5 | namespace Arduino_LiveSerial.View.Converters 6 | { 7 | [ValueConversion(typeof(object), typeof(bool))] 8 | public class InverseBooleanConverter : IValueConverter 9 | { 10 | // 11 | 12 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 13 | { 14 | if (!(value is bool)) throw new InvalidOperationException("The target must be a boolean"); 15 | return !(bool)value; 16 | } 17 | 18 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 19 | { 20 | if (!(value is bool)) throw new InvalidOperationException("The target must be a boolean"); 21 | return !(bool)value; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/AboutDialog.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Open Source 31 | This project is completely open source. If you like it and want to say thanks you could hit the GitHub Star button, tweet or post about it. 33 | 34 | 41 | 42 | 43 | 44 | Instructions 45 | Simply send data throw Serial port in format ID:VALUE or ID:VALUE@MILLIS 46 | Or use these macros to make process even easier! 47 | 48 | 49 | ID:VALUE 50 | 51 | #define LIVESERIAL(id, value) { Serial.print(id); Serial.print(':'); Serial.println(value); } 52 | 53 | ID:VALUE@MILLIS 54 | 55 | #define LIVESERIAL_MILLIS(id, value) { unsigned long ms = millis(); Serial.print(id); Serial.print(':'); Serial.print(value); Serial.print('@'); Serial.println(ms); } 56 | 57 | 58 | 59 | 60 | 61 | 62 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/AboutDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace Arduino_LiveSerial.Views.Dialogs 17 | { 18 | /// 19 | /// Interaction logic for AboutDialog.xaml 20 | /// 21 | public partial class AboutDialog : UserControl 22 | { 23 | public AboutDialog() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/ConfirmDialog.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/ConfirmDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | 4 | namespace Arduino_LiveSerial.View.Dialogs.Predefined 5 | { 6 | /// 7 | /// Interaction logic for MessageDialog.xaml 8 | /// 9 | public partial class ConfirmDialog : UserControl 10 | { 11 | public ConfirmDialog() 12 | { 13 | InitializeComponent(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/MessageDialog.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/MessageDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | 4 | namespace Arduino_LiveSerial.View.Dialogs.Predefined 5 | { 6 | /// 7 | /// Interaction logic for MessageDialog.xaml 8 | /// 9 | public partial class MessageDialog : UserControl 10 | { 11 | public MessageDialog() 12 | { 13 | InitializeComponent(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/Dialogs/PredefinedDialogs.cs: -------------------------------------------------------------------------------- 1 | using MaterialDesignThemes.Wpf; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Arduino_LiveSerial.View.Dialogs.Predefined 9 | { 10 | static class PredefinedDialogs 11 | { 12 | public static async Task MessageDialog(object datacontext, string message, DialogOpenedEventHandler doe, DialogClosingEventHandler dce, string rootDialog = "RootDialog") 13 | { 14 | var dialog = new MessageDialog 15 | { 16 | DataContext = datacontext, 17 | Message = {Text = message} 18 | }; 19 | 20 | return await DialogHost.Show(dialog, rootDialog, doe, dce); 21 | } 22 | 23 | public static async Task MessageDialog(object datacontext, string message, string buttonText, DialogOpenedEventHandler doe, DialogClosingEventHandler dce, string rootDialog = "RootDialog") 24 | { 25 | var dialog = new MessageDialog 26 | { 27 | DataContext = datacontext, 28 | Message = { Text = message }, 29 | Button = { Content = buttonText } 30 | }; 31 | 32 | return await DialogHost.Show(dialog, rootDialog, doe, dce); 33 | } 34 | 35 | public static async Task TwoButtonsDialog(object datacontext, string message, string yesText, string noText, DialogOpenedEventHandler doe, DialogClosingEventHandler dce, string rootDialog = "RootDialog") 36 | { 37 | var dialog = new ConfirmDialog 38 | { 39 | DataContext = datacontext, 40 | Message = {Text = message}, 41 | YesButton = {Content = yesText}, 42 | NoButton = {Content = noText} 43 | }; 44 | 45 | return await DialogHost.Show(dialog, rootDialog, doe, dce); 46 | } 47 | 48 | public static async Task AcceptCancelDialog(object datacontext, string message, DialogOpenedEventHandler doe, DialogClosingEventHandler dce) 49 | { 50 | return await TwoButtonsDialog(datacontext, message, "ACCEPT", "CANCEL", doe, dce); 51 | } 52 | 53 | public static async Task AcceptYesNoDialog(object datacontext, string message, DialogOpenedEventHandler doe, DialogClosingEventHandler dce) 54 | { 55 | return await TwoButtonsDialog(datacontext, message, "YES", "NO", doe, dce); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/MainView.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 112 | 113 | 114 | 115 | 116 | 117 | 119 | 120 | 121 | 123 | 124 | 126 | 127 | 129 | 130 | 132 | 133 | 135 | 136 | 138 | 139 | 141 | 142 | 144 | 145 | 147 | 148 | 150 | 151 | 153 | 154 | 156 | 157 | 159 | 160 | 162 | 163 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/MainView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace Arduino_LiveSerial.Views 17 | { 18 | /// 19 | /// Interaction logic for MainView.xaml 20 | /// 21 | public partial class MainView : UserControl 22 | { 23 | public MainView() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/RightDrawer.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 82 | 83 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/RightDrawer.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace Arduino_LiveSerial.Views 17 | { 18 | /// 19 | /// Interaction logic for RightDrawer.xaml 20 | /// 21 | public partial class RightDrawer : UserControl 22 | { 23 | public RightDrawer() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/RightDrawerPartial.xaml: -------------------------------------------------------------------------------- 1 |  10 | 11 | 12 | 13 | 37 | 38 | 41 | 42 | 45 | 46 | 47 | 48 | 55 | 56 | 57 | 58 | 60 | 61 | 63 | 64 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/Views/ViewSerieWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using MahApps.Metro.Controls; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Shapes; 15 | 16 | namespace Arduino_LiveSerial.Views 17 | { 18 | /// 19 | /// Interaction logic for ViewSerieWindow.xaml 20 | /// 21 | public partial class ViewSerieWindow : MetroWindow 22 | { 23 | public ViewSerieWindow() 24 | { 25 | InitializeComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Arduino-LiveSerial/live_serial.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Arduino-LiveSerial/live_serial.ico -------------------------------------------------------------------------------- /Arduino-LiveSerial/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ArduinoDemo.ino: -------------------------------------------------------------------------------- 1 | #define LIVESERIAL(id, value) { Serial.print(id); Serial.print(':'); Serial.println(value); } 2 | #define LIVESERIAL_MILLIS(id, value) { unsigned long ms = millis(); Serial.print(id); Serial.print(':'); Serial.print(value); Serial.print('@'); Serial.println(ms); } 3 | 4 | void setup() { 5 | Serial.begin(115200); 6 | } 7 | 8 | void loop() { 9 | while(Serial.available()) 10 | { 11 | Serial.print((char)Serial.read()); 12 | } 13 | 14 | LIVESERIAL("NORMAL", 2.0*cos(millis()/500.0)); 15 | LIVESERIAL_MILLIS("WITH_MILLIS", 2.0*cos(ms/500.0)); 16 | delay(100); 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino LiveSerial 2 | Con frecuencia he necesitado en proyectos **obtener una serie de datos enviados por puerto serie**, por ejemplo, desde un microprocesador como Arduino, ESP8266 o ESP32. Sin embargo, no he encontrado un software, gratuito o comercial, que cumpliera lo que necesitaba. 3 | 4 | Así surge LiveSerial, un programa que **permite graficar y realizar estadísticas** de los datos obtenidos por puerto serie en tiempo real, y exportar los datos obtenidos a diversos formatos. 5 | 6 | Otra ventaja de LiveSerial es que almacena el instante en el que es recibido los valores. Así, las gráficas tienen un eje X temporal (en lugar de la mayoría de valores que muestran los valores "todo seguidos"). 7 | 8 | LiveSerial está programado en C# + WPF, y está disponible en Windows 7-10. Como sabemos, Windows no es un sistema de tiempo real, y LiveSerial tiene una velocidad máxima de muestreo reducida. No está pensando para hacer, por ejemplo, un osciloscopio. La tasa de refresco que podéis obtener depende de vuestro ordenador, pero esperar un máximo de 20-50 muestras por segundo. 9 | 10 | ## Usando LiveSerial 11 | La idea principal de LiveSerial es que sea **mínimamente intrusivo** con el código del microprocesador. No es necesario ninguna librería, ni ningún código complejo. 12 | 13 | Tenemos dos modos de funcionamiento: 14 | 15 | ### Modo normal 16 | Para comunicarnos con LiveSerial únicamente tenemos que enviarle por puerto serie un comando en formato 17 | 18 | >ID:value 19 | 20 | Siendo: 21 | * ID el nombre de la serie. 22 | * Value el valor numérico a almacenar. 23 | 24 | Por ejemplo, 25 | 26 | > Temp1:257.8 27 | 28 | Cuando LiveSerial recibe un ID nuevo, crea una nueva serie, las añade a las anteriores, y comienza su registro. 29 | 30 | ### Modo síncrono 31 | En el modo "norma " la fecha y hora del punto es el instante en el que es recibido por el ordenador. Sin embargo, como decimos, Windows no es un sistema de tiempo real por lo que, dependiendo de la carga del SO, puede variaciones de milisegundos desde el envió. 32 | 33 | Un ejemplo es que probéis a maximizar o cambiar el tamaño de LiveSerial, o ejecutar otro programa mientas está recibiendo datos. Veréis que la gráfica se deforma, por el retraso que el SO provoca para atender los datos recibidos por puerto serie. 34 | 35 | En muchos casos el modo "normal" es suficiente, pero en ciertos casos en los que se requiere mayor precisión es posible enviar la marca temporal desde el microprocesador con el comando. 36 | 37 | > ID:value@millis 38 | 39 | Siendo, 40 | 41 | * ID el nombre de la serie de datos. 42 | * Value, el valor numérico a almacenar. 43 | * Millis, los milisegundos enviados desde el procesador. 44 | 45 | LiveSerial recoge los datos y los ajusta, atendiendo a las marcas de tiempo enviados por el microprocesador, para que la escala temporal se ajuste con precisión. 46 | 47 | ### Mensajes 48 | Los datos recibidos por puerto serie que no contienen el identificador de serie (por defecto ':') son interpretados como "mensajes". Estos se muestran en su propia ventana. 49 | 50 | Así mismo es posible enviar mensajes al microprocesador. Estos mensajes son registrados y se muestran en su propia ventana. 51 | 52 | ### Macros 53 | Si bien no es necesario emplear ninguna librería para usar LiveSerial en el procesador, se facilitan las siguientes macros para hacer más sencillo su uso 54 | ```c++ 55 | // modo normal 56 | #define LIVESERIAL(id, value) { Serial.print(id); Serial.print(':'); Serial.println(value); } 57 | 58 | // modo sincrono 59 | #define LIVESERIAL_MILLIS(id, value) { unsigned long ms = millis(); Serial.print(id); Serial.print(':'); Serial.print(value); Serial.print('@'); Serial.println(ms); } 60 | ``` 61 | Estas macros están disponibles a modo de recordatorio en LiveSerial pulsando el botón "?". 62 | 63 | 64 | ## Interface de usuario 65 | ![Screehshot1](/Screenshots/MainScreen.png) 66 | 67 | ### Conectar 68 | Para conectar LiveSerial emplear el botón deslizante de la parte superior de la ventana. 69 | Si no aparece ningún puerto seleccionado, abrir la pestaña de opciones para recargar los puertos serie. 70 | 71 | ### Area de gráficas 72 | Aquí se muestran en una gráfica los N últimos valores recibidos (el número de elementos mostrados se controla desde las opciones, para evitar saturar el programa). 73 | 74 | ### Area de mensajes 75 | Desde aquí podemos enviar mensajes por puerto serie, así como ver los mensajes enviados y recibidos. 76 | 77 | ### Area de estadísticas 78 | A medida que se reciben datos de la serie, se calculan en tiempo real sus estadísticas. Estas estadísticas son: 79 | * Name, nombre de la serie 80 | * Count, número total de elementos recibidos 81 | * Value, último valor recibido 82 | * ΔValue, incremento respecto al valor anterior 83 | * Δt, incremento temporal respecto al anterior 84 | * Sum, suma total 85 | * Slope, pendiente de los últimos elementos 86 | * Min, valor mínimo recibido 87 | * Max, valor máximo recibodo 88 | * Range, diferencia entre Max y Min 89 | * MinT, tiempo del primer dato recibido 90 | * MaxT, tiempo del último dato recibido 91 | * Peak, valor del último pico registrado 92 | * PeakT, tiempo del último pico registrado 93 | * Interval, diferencia de tiempo entre dos últimos picos 94 | * Frequency, frecuencia basada en los últimos dos picos 95 | 96 | ### Ver datos y exportar datos 97 | ![Screehshot3](/Screenshots/ViewSeriesScreen.png) 98 | Podemos visualizar todos los datos recibidos, haciendo click en el icono de la serie. Se abrirá una nueva ventana que muestra los datos de la serie. 99 | 100 | Desde esta ventana podemos elegir exportar a diversos formatos, CSV, JSON, y Excel. En el caso de Excel, la hoja exportada contiene los datos y una gráfica de los valores. 101 | 102 | ![Screehshot4](/Screenshots/excel-export.png) 103 | 104 | ### Opciones 105 | ![Screehshot2](/Screenshots/Options.png) 106 | Pulsando el botón de opciones accedemos a la ventana para modificarlas. Las opciones se guardan de una ejecución a otra del programa, por usuario. 107 | 108 | En primer lugar, tenemos el puerto serie y velocidad de transmisión que queremos emplear. 109 | 110 | Por otro lado, tenemos el número de elementos (por serie) a graficar. A medida que la gráfica aumenta de número de puntos la carga de procesado aumenta, especialmente si tenemos muchas series. Por ello, sólo se grafican los N últimos elementos recibidos. 111 | 112 | Por ejemplo, en caso de una única serie valores de 1000-1000 son frecuentes, pero si tenemos varias series puede que tengamos que bajarlo a 100-500, o sobrecargaremos el programa. 113 | 114 | Finalmente, podemos cambiar los separadores por defecto ':' y '@' por cualquier otro carácter. 115 | 116 | ## Ejemplo de uso 117 | Un ejemplo de uso de LiveSerial con Arduino sería el siguiente: 118 | 119 | ```c++ 120 | #define LIVESERIAL(id, value) { Serial.print(id); Serial.print(':'); Serial.println(value); } 121 | #define LIVESERIAL_MILLIS(id, value) { unsigned long ms = millis(); Serial.print(id); Serial.print(':'); Serial.print(value); Serial.print('@'); Serial.println(ms); } 122 | 123 | void setup() { 124 | Serial.begin(115200); 125 | } 126 | 127 | void loop() { 128 | while(Serial.available()) 129 | { 130 | Serial.print((char)Serial.read()); 131 | } 132 | 133 | LIVESERIAL("NORMAL", 2.0*cos(millis()/500.0)); 134 | LIVESERIAL_MILLIS("WITH_MILLIS", 2.0*cos(ms/500.0)); 135 | delay(100); 136 | } 137 | ``` 138 | 139 | Este ejemplo grafica dos funciones seno, uno con el modo "normal" y otro con el modo "síncrono". 140 | 141 | Además, el programa emite todo lo que recibe. De esta forma podéis probar a enviar mensajes, recibirlos, incluso probar a enviar nuevas series en tiempo real (enviando un ID:value) 142 | 143 | 144 | 145 | ## TO-DOs: 146 | - [ ] Triggers: Acciones que se realizan cuando el valor de una serie cumple una condición. 147 | - [ ] Scripts: Archivos de comandos que se ejecutan de forma secuencia mediante LiveSerial. 148 | 149 | ## Dependencias: 150 | * LiveSerial usa las siguientes librerías: 151 | * RJCP: Puerto serie mejorado para .Net 152 | * Oxyplot: Gráficas en .NET 153 | * MahApps: Estilo visual aplicaciones XAML. 154 | * Material design XAML: Estilo visual "Material design" para XAML. 155 | * ReactiveUI: Framework reactive MVVM para WPF. 156 | * NewtsonJSON: Trabajos con ficheros JSON. 157 | * EEPlus: Trabajo con ficheros EXCEL. 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /Screenshots/MainScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Screenshots/MainScreen.png -------------------------------------------------------------------------------- /Screenshots/Options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Screenshots/Options.png -------------------------------------------------------------------------------- /Screenshots/ViewSeriesScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Screenshots/ViewSeriesScreen.png -------------------------------------------------------------------------------- /Screenshots/excel-export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luisllamasbinaburo/Arduino-LiveSerial/a5cf3bd7f9c39bce2b7a9d4a7693022370c0e823/Screenshots/excel-export.png --------------------------------------------------------------------------------