Implemented UI

Added detection of sim running and paused states
Added rudimentary live KML option (needs more styling)
This commit is contained in:
Mark van Renswoude 2020-09-09 21:20:42 +02:00
parent a3cb0eb7ca
commit d6fbff68af
39 changed files with 2325 additions and 563 deletions

View File

@ -1,6 +1,18 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</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-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -55,16 +55,46 @@
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Context, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Context.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Context.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Coordination, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Coordination.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Coordination.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Interop.WaitHandles, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Interop.WaitHandles.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Interop.WaitHandles.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Oop, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Oop.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Oop.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Tasks, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Tasks.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Tasks.dll</HintPath>
</Reference>
<Reference Include="Nito.Cancellation, Version=1.0.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Cancellation.1.0.5\lib\netstandard2.0\Nito.Cancellation.dll</HintPath>
</Reference>
<Reference Include="Nito.Collections.Deque, Version=1.0.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Collections.Deque.1.0.4\lib\netstandard2.0\Nito.Collections.Deque.dll</HintPath>
</Reference>
<Reference Include="Nito.Disposables, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Disposables.2.0.0\lib\netstandard2.0\Nito.Disposables.dll</HintPath>
</Reference>
<Reference Include="SimpleInjector, Version=5.0.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
<HintPath>..\packages\SimpleInjector.5.0.3\lib\net461\SimpleInjector.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
<Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.2\lib\netstandard2.0\System.Threading.Tasks.Extensions.dll</HintPath>
<Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@ -77,6 +107,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Settings.cs" />
<Compile Include="SimConnectLogger.cs" />
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
@ -85,6 +117,11 @@
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Resources\i18n.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>i18n.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
@ -97,6 +134,10 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<EmbeddedResource Include="Resources\i18n.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>i18n.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@ -115,6 +156,10 @@
<Folder Include="SimConnect\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FlightLoggerLib\FlightLoggerLib.csproj">
<Project>{D85BCC97-F653-4286-98D9-073A33A55857}</Project>
<Name>FlightLoggerLib</Name>
</ProjectReference>
<ProjectReference Include="..\SimConnect\SimConnect.csproj">
<Project>{f160bb6a-7620-41e5-a99c-948c208875e4}</Project>
<Name>SimConnect</Name>

View File

@ -18,30 +18,42 @@
this.components = new System.ComponentModel.Container();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.OutputGroupbox = new System.Windows.Forms.GroupBox();
this.OutputKMLPathBrowseButton = new System.Windows.Forms.Button();
this.OutputKMLPathLabel = new System.Windows.Forms.Label();
this.OutputKMLPathTextbox = new System.Windows.Forms.TextBox();
this.OutputCSVPathBrowseButton = new System.Windows.Forms.Button();
this.OutputCSVPathLabel = new System.Windows.Forms.Label();
this.OutputCSVPathTextbox = new System.Windows.Forms.TextBox();
this.OutputKMLCheckbox = new System.Windows.Forms.CheckBox();
this.OutputCSVCheckbox = new System.Windows.Forms.CheckBox();
this.StatusImageList = new System.Windows.Forms.ImageList(this.components);
this.FlightSimulatorStatusIcon = new System.Windows.Forms.PictureBox();
this.FlightSimulatorLabel = new System.Windows.Forms.Label();
this.FlightSimulatorStatusLabel = new System.Windows.Forms.Label();
this.RecordButton = new System.Windows.Forms.Button();
this.RecordingStatusIcon = new System.Windows.Forms.PictureBox();
this.OutputCSVCheckbox = new System.Windows.Forms.CheckBox();
this.OutputKMLCheckbox = new System.Windows.Forms.CheckBox();
this.OutputCSVPathTextbox = new System.Windows.Forms.TextBox();
this.OutputCSVPathLabel = new System.Windows.Forms.Label();
this.OutputCSVPathBrowseButton = new System.Windows.Forms.Button();
this.OutputKMLPathBrowseButton = new System.Windows.Forms.Button();
this.OutputKMLPathLabel = new System.Windows.Forms.Label();
this.OutputKMLPathTextbox = new System.Windows.Forms.TextBox();
this.TriggersGroupbox = new System.Windows.Forms.GroupBox();
this.TriggerWaitForMovementCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerNewLogStationaryCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerNewLogStationaryTimeEdit = new System.Windows.Forms.NumericUpDown();
this.TriggerConnectedCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerNewLogStationaryUnitsLabel = new System.Windows.Forms.Label();
this.TriggerNewLogStationaryTimeEdit = new System.Windows.Forms.NumericUpDown();
this.TriggerNewLogStationaryCheckbox = new System.Windows.Forms.CheckBox();
this.TriggerWaitForMovementCheckbox = new System.Windows.Forms.CheckBox();
this.FolderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog();
this.KMLLiveGroupbox = new System.Windows.Forms.GroupBox();
this.KMLLiveHelpLabel = new System.Windows.Forms.Label();
this.KMLLiveLinkEdit = new System.Windows.Forms.TextBox();
this.KMLLivePortEdit = new System.Windows.Forms.NumericUpDown();
this.KMLLiveLinkLabel = new System.Windows.Forms.Label();
this.KMLLiveCheckbox = new System.Windows.Forms.CheckBox();
this.TryConnectTimer = new System.Windows.Forms.Timer(this.components);
this.ChangesWhileRecordingLabel = new System.Windows.Forms.Label();
this.OutputGroupbox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.FlightSimulatorStatusIcon)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.RecordingStatusIcon)).BeginInit();
this.TriggersGroupbox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).BeginInit();
this.KMLLiveGroupbox.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.KMLLivePortEdit)).BeginInit();
this.SuspendLayout();
//
// OutputGroupbox
@ -59,10 +71,96 @@
this.OutputGroupbox.Location = new System.Drawing.Point(12, 59);
this.OutputGroupbox.Name = "OutputGroupbox";
this.OutputGroupbox.Size = new System.Drawing.Size(482, 136);
this.OutputGroupbox.TabIndex = 0;
this.OutputGroupbox.TabIndex = 1;
this.OutputGroupbox.TabStop = false;
this.OutputGroupbox.Text = " Output ";
//
// OutputKMLPathBrowseButton
//
this.OutputKMLPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathBrowseButton.Enabled = false;
this.OutputKMLPathBrowseButton.Location = new System.Drawing.Point(448, 102);
this.OutputKMLPathBrowseButton.Name = "OutputKMLPathBrowseButton";
this.OutputKMLPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputKMLPathBrowseButton.TabIndex = 7;
this.OutputKMLPathBrowseButton.Text = "...";
this.OutputKMLPathBrowseButton.UseVisualStyleBackColor = true;
this.OutputKMLPathBrowseButton.Click += new System.EventHandler(this.OutputKMLPathBrowseButton_Click);
//
// OutputKMLPathLabel
//
this.OutputKMLPathLabel.AutoSize = true;
this.OutputKMLPathLabel.Location = new System.Drawing.Point(26, 105);
this.OutputKMLPathLabel.Name = "OutputKMLPathLabel";
this.OutputKMLPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputKMLPathLabel.TabIndex = 6;
this.OutputKMLPathLabel.Text = "Path:";
//
// OutputKMLPathTextbox
//
this.OutputKMLPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathTextbox.Enabled = false;
this.OutputKMLPathTextbox.Location = new System.Drawing.Point(71, 102);
this.OutputKMLPathTextbox.Name = "OutputKMLPathTextbox";
this.OutputKMLPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputKMLPathTextbox.TabIndex = 5;
this.OutputKMLPathTextbox.TextChanged += new System.EventHandler(this.Configuration_Changed);
//
// OutputCSVPathBrowseButton
//
this.OutputCSVPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathBrowseButton.Enabled = false;
this.OutputCSVPathBrowseButton.Location = new System.Drawing.Point(448, 46);
this.OutputCSVPathBrowseButton.Name = "OutputCSVPathBrowseButton";
this.OutputCSVPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputCSVPathBrowseButton.TabIndex = 4;
this.OutputCSVPathBrowseButton.Text = "...";
this.OutputCSVPathBrowseButton.UseVisualStyleBackColor = true;
this.OutputCSVPathBrowseButton.Click += new System.EventHandler(this.OutputCSVPathBrowseButton_Click);
//
// OutputCSVPathLabel
//
this.OutputCSVPathLabel.AutoSize = true;
this.OutputCSVPathLabel.Location = new System.Drawing.Point(26, 49);
this.OutputCSVPathLabel.Name = "OutputCSVPathLabel";
this.OutputCSVPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputCSVPathLabel.TabIndex = 3;
this.OutputCSVPathLabel.Text = "Path:";
//
// OutputCSVPathTextbox
//
this.OutputCSVPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathTextbox.Enabled = false;
this.OutputCSVPathTextbox.Location = new System.Drawing.Point(71, 46);
this.OutputCSVPathTextbox.Name = "OutputCSVPathTextbox";
this.OutputCSVPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputCSVPathTextbox.TabIndex = 2;
this.OutputCSVPathTextbox.TextChanged += new System.EventHandler(this.Configuration_Changed);
//
// OutputKMLCheckbox
//
this.OutputKMLCheckbox.AutoSize = true;
this.OutputKMLCheckbox.Location = new System.Drawing.Point(10, 79);
this.OutputKMLCheckbox.Name = "OutputKMLCheckbox";
this.OutputKMLCheckbox.Size = new System.Drawing.Size(119, 17);
this.OutputKMLCheckbox.TabIndex = 1;
this.OutputKMLCheckbox.Text = "Google Earth (KML)";
this.OutputKMLCheckbox.UseVisualStyleBackColor = true;
this.OutputKMLCheckbox.CheckedChanged += new System.EventHandler(this.OutputKMLCheckbox_CheckedChanged);
//
// OutputCSVCheckbox
//
this.OutputCSVCheckbox.AutoSize = true;
this.OutputCSVCheckbox.Location = new System.Drawing.Point(10, 23);
this.OutputCSVCheckbox.Name = "OutputCSVCheckbox";
this.OutputCSVCheckbox.Size = new System.Drawing.Size(141, 17);
this.OutputCSVCheckbox.TabIndex = 0;
this.OutputCSVCheckbox.Text = "Comma-separated (CSV)";
this.OutputCSVCheckbox.UseVisualStyleBackColor = true;
this.OutputCSVCheckbox.CheckedChanged += new System.EventHandler(this.OutputCSVCheckbox_CheckedChanged);
//
// StatusImageList
//
this.StatusImageList.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("StatusImageList.ImageStream")));
@ -106,9 +204,10 @@
this.RecordButton.Location = new System.Drawing.Point(348, 17);
this.RecordButton.Name = "RecordButton";
this.RecordButton.Size = new System.Drawing.Size(108, 23);
this.RecordButton.TabIndex = 4;
this.RecordButton.Text = "&Start recording";
this.RecordButton.TabIndex = 0;
this.RecordButton.Text = "<runtime>";
this.RecordButton.UseVisualStyleBackColor = true;
this.RecordButton.Click += new System.EventHandler(this.RecordButton_Click);
//
// RecordingStatusIcon
//
@ -119,151 +218,180 @@
this.RecordingStatusIcon.TabIndex = 5;
this.RecordingStatusIcon.TabStop = false;
//
// OutputCSVCheckbox
//
this.OutputCSVCheckbox.AutoSize = true;
this.OutputCSVCheckbox.Location = new System.Drawing.Point(10, 23);
this.OutputCSVCheckbox.Name = "OutputCSVCheckbox";
this.OutputCSVCheckbox.Size = new System.Drawing.Size(141, 17);
this.OutputCSVCheckbox.TabIndex = 0;
this.OutputCSVCheckbox.Text = "Comma-separated (CSV)";
this.OutputCSVCheckbox.UseVisualStyleBackColor = true;
//
// OutputKMLCheckbox
//
this.OutputKMLCheckbox.AutoSize = true;
this.OutputKMLCheckbox.Location = new System.Drawing.Point(10, 79);
this.OutputKMLCheckbox.Name = "OutputKMLCheckbox";
this.OutputKMLCheckbox.Size = new System.Drawing.Size(119, 17);
this.OutputKMLCheckbox.TabIndex = 1;
this.OutputKMLCheckbox.Text = "Google Earth (KML)";
this.OutputKMLCheckbox.UseVisualStyleBackColor = true;
//
// OutputCSVPathTextbox
//
this.OutputCSVPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathTextbox.Enabled = false;
this.OutputCSVPathTextbox.Location = new System.Drawing.Point(71, 46);
this.OutputCSVPathTextbox.Name = "OutputCSVPathTextbox";
this.OutputCSVPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputCSVPathTextbox.TabIndex = 2;
//
// OutputCSVPathLabel
//
this.OutputCSVPathLabel.AutoSize = true;
this.OutputCSVPathLabel.Location = new System.Drawing.Point(26, 49);
this.OutputCSVPathLabel.Name = "OutputCSVPathLabel";
this.OutputCSVPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputCSVPathLabel.TabIndex = 3;
this.OutputCSVPathLabel.Text = "Path:";
//
// OutputCSVPathBrowseButton
//
this.OutputCSVPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputCSVPathBrowseButton.Enabled = false;
this.OutputCSVPathBrowseButton.Location = new System.Drawing.Point(448, 46);
this.OutputCSVPathBrowseButton.Name = "OutputCSVPathBrowseButton";
this.OutputCSVPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputCSVPathBrowseButton.TabIndex = 4;
this.OutputCSVPathBrowseButton.Text = "...";
this.OutputCSVPathBrowseButton.UseVisualStyleBackColor = true;
//
// OutputKMLPathBrowseButton
//
this.OutputKMLPathBrowseButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathBrowseButton.Enabled = false;
this.OutputKMLPathBrowseButton.Location = new System.Drawing.Point(448, 102);
this.OutputKMLPathBrowseButton.Name = "OutputKMLPathBrowseButton";
this.OutputKMLPathBrowseButton.Size = new System.Drawing.Size(28, 20);
this.OutputKMLPathBrowseButton.TabIndex = 7;
this.OutputKMLPathBrowseButton.Text = "...";
this.OutputKMLPathBrowseButton.UseVisualStyleBackColor = true;
//
// OutputKMLPathLabel
//
this.OutputKMLPathLabel.AutoSize = true;
this.OutputKMLPathLabel.Location = new System.Drawing.Point(26, 105);
this.OutputKMLPathLabel.Name = "OutputKMLPathLabel";
this.OutputKMLPathLabel.Size = new System.Drawing.Size(32, 13);
this.OutputKMLPathLabel.TabIndex = 6;
this.OutputKMLPathLabel.Text = "Path:";
//
// OutputKMLPathTextbox
//
this.OutputKMLPathTextbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.OutputKMLPathTextbox.Enabled = false;
this.OutputKMLPathTextbox.Location = new System.Drawing.Point(71, 102);
this.OutputKMLPathTextbox.Name = "OutputKMLPathTextbox";
this.OutputKMLPathTextbox.Size = new System.Drawing.Size(373, 20);
this.OutputKMLPathTextbox.TabIndex = 5;
//
// TriggersGroupbox
//
this.TriggersGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.TriggersGroupbox.Controls.Add(this.TriggerConnectedCheckbox);
this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryUnitsLabel);
this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryTimeEdit);
this.TriggersGroupbox.Controls.Add(this.TriggerNewLogStationaryCheckbox);
this.TriggersGroupbox.Controls.Add(this.TriggerWaitForMovementCheckbox);
this.TriggersGroupbox.Location = new System.Drawing.Point(12, 201);
this.TriggersGroupbox.Name = "TriggersGroupbox";
this.TriggersGroupbox.Size = new System.Drawing.Size(482, 99);
this.TriggersGroupbox.TabIndex = 6;
this.TriggersGroupbox.Size = new System.Drawing.Size(482, 124);
this.TriggersGroupbox.TabIndex = 2;
this.TriggersGroupbox.TabStop = false;
this.TriggersGroupbox.Text = " Triggers ";
//
// TriggerConnectedCheckbox
//
this.TriggerConnectedCheckbox.AutoSize = true;
this.TriggerConnectedCheckbox.Location = new System.Drawing.Point(10, 23);
this.TriggerConnectedCheckbox.Name = "TriggerConnectedCheckbox";
this.TriggerConnectedCheckbox.Size = new System.Drawing.Size(324, 17);
this.TriggerConnectedCheckbox.TabIndex = 0;
this.TriggerConnectedCheckbox.Text = " Start recording immediately when connected to Flight Simulator";
this.TriggerConnectedCheckbox.UseVisualStyleBackColor = true;
//
// TriggerNewLogStationaryUnitsLabel
//
this.TriggerNewLogStationaryUnitsLabel.AutoSize = true;
this.TriggerNewLogStationaryUnitsLabel.Location = new System.Drawing.Point(117, 94);
this.TriggerNewLogStationaryUnitsLabel.Name = "TriggerNewLogStationaryUnitsLabel";
this.TriggerNewLogStationaryUnitsLabel.Size = new System.Drawing.Size(47, 13);
this.TriggerNewLogStationaryUnitsLabel.TabIndex = 3;
this.TriggerNewLogStationaryUnitsLabel.Text = "seconds";
//
// TriggerNewLogStationaryTimeEdit
//
this.TriggerNewLogStationaryTimeEdit.Enabled = false;
this.TriggerNewLogStationaryTimeEdit.Location = new System.Drawing.Point(33, 92);
this.TriggerNewLogStationaryTimeEdit.Name = "TriggerNewLogStationaryTimeEdit";
this.TriggerNewLogStationaryTimeEdit.Size = new System.Drawing.Size(78, 20);
this.TriggerNewLogStationaryTimeEdit.TabIndex = 3;
this.TriggerNewLogStationaryTimeEdit.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.TriggerNewLogStationaryTimeEdit.Value = new decimal(new int[] {
30,
0,
0,
0});
this.TriggerNewLogStationaryTimeEdit.ValueChanged += new System.EventHandler(this.Configuration_Changed);
//
// TriggerNewLogStationaryCheckbox
//
this.TriggerNewLogStationaryCheckbox.AutoSize = true;
this.TriggerNewLogStationaryCheckbox.Location = new System.Drawing.Point(10, 69);
this.TriggerNewLogStationaryCheckbox.Name = "TriggerNewLogStationaryCheckbox";
this.TriggerNewLogStationaryCheckbox.Size = new System.Drawing.Size(353, 17);
this.TriggerNewLogStationaryCheckbox.TabIndex = 2;
this.TriggerNewLogStationaryCheckbox.Text = " Start a new log when stationary for at least (excluding when paused):";
this.TriggerNewLogStationaryCheckbox.UseVisualStyleBackColor = true;
this.TriggerNewLogStationaryCheckbox.CheckedChanged += new System.EventHandler(this.TriggerNewLogStationaryCheckbox_CheckedChanged);
//
// TriggerWaitForMovementCheckbox
//
this.TriggerWaitForMovementCheckbox.AutoSize = true;
this.TriggerWaitForMovementCheckbox.Checked = true;
this.TriggerWaitForMovementCheckbox.CheckState = System.Windows.Forms.CheckState.Checked;
this.TriggerWaitForMovementCheckbox.Location = new System.Drawing.Point(10, 23);
this.TriggerWaitForMovementCheckbox.Location = new System.Drawing.Point(10, 46);
this.TriggerWaitForMovementCheckbox.Name = "TriggerWaitForMovementCheckbox";
this.TriggerWaitForMovementCheckbox.Size = new System.Drawing.Size(454, 17);
this.TriggerWaitForMovementCheckbox.TabIndex = 0;
this.TriggerWaitForMovementCheckbox.TabIndex = 1;
this.TriggerWaitForMovementCheckbox.Text = " Wait for movement before logging the starting point (recommended, ignores initia" +
"l teleports)";
this.TriggerWaitForMovementCheckbox.UseVisualStyleBackColor = true;
this.TriggerWaitForMovementCheckbox.CheckedChanged += new System.EventHandler(this.Configuration_Changed);
//
// TriggerNewLogStationaryCheckbox
// KMLLiveGroupbox
//
this.TriggerNewLogStationaryCheckbox.AutoSize = true;
this.TriggerNewLogStationaryCheckbox.Location = new System.Drawing.Point(10, 46);
this.TriggerNewLogStationaryCheckbox.Name = "TriggerNewLogStationaryCheckbox";
this.TriggerNewLogStationaryCheckbox.Size = new System.Drawing.Size(353, 17);
this.TriggerNewLogStationaryCheckbox.TabIndex = 1;
this.TriggerNewLogStationaryCheckbox.Text = " Start a new log when stationary for at least (excluding when paused):";
this.TriggerNewLogStationaryCheckbox.UseVisualStyleBackColor = true;
this.KMLLiveGroupbox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.KMLLiveGroupbox.Controls.Add(this.KMLLiveHelpLabel);
this.KMLLiveGroupbox.Controls.Add(this.KMLLiveLinkEdit);
this.KMLLiveGroupbox.Controls.Add(this.KMLLivePortEdit);
this.KMLLiveGroupbox.Controls.Add(this.KMLLiveLinkLabel);
this.KMLLiveGroupbox.Controls.Add(this.KMLLiveCheckbox);
this.KMLLiveGroupbox.Location = new System.Drawing.Point(12, 331);
this.KMLLiveGroupbox.Name = "KMLLiveGroupbox";
this.KMLLiveGroupbox.Size = new System.Drawing.Size(482, 97);
this.KMLLiveGroupbox.TabIndex = 3;
this.KMLLiveGroupbox.TabStop = false;
this.KMLLiveGroupbox.Text = " Google Earth live location ";
//
// TriggerNewLogStationaryTimeEdit
// KMLLiveHelpLabel
//
this.TriggerNewLogStationaryTimeEdit.Enabled = false;
this.TriggerNewLogStationaryTimeEdit.Location = new System.Drawing.Point(33, 69);
this.TriggerNewLogStationaryTimeEdit.Name = "TriggerNewLogStationaryTimeEdit";
this.TriggerNewLogStationaryTimeEdit.Size = new System.Drawing.Size(78, 20);
this.TriggerNewLogStationaryTimeEdit.TabIndex = 2;
this.TriggerNewLogStationaryTimeEdit.Value = new decimal(new int[] {
30,
this.KMLLiveHelpLabel.AutoSize = true;
this.KMLLiveHelpLabel.ForeColor = System.Drawing.SystemColors.GrayText;
this.KMLLiveHelpLabel.Location = new System.Drawing.Point(124, 72);
this.KMLLiveHelpLabel.Name = "KMLLiveHelpLabel";
this.KMLLiveHelpLabel.Size = new System.Drawing.Size(352, 13);
this.KMLLiveHelpLabel.TabIndex = 5;
this.KMLLiveHelpLabel.Text = "Copy this link and use it in the Add > Network Link option in Google Earth";
//
// KMLLiveLinkEdit
//
this.KMLLiveLinkEdit.BackColor = System.Drawing.SystemColors.ButtonFace;
this.KMLLiveLinkEdit.Location = new System.Drawing.Point(123, 49);
this.KMLLiveLinkEdit.Name = "KMLLiveLinkEdit";
this.KMLLiveLinkEdit.ReadOnly = true;
this.KMLLiveLinkEdit.Size = new System.Drawing.Size(353, 20);
this.KMLLiveLinkEdit.TabIndex = 4;
//
// KMLLivePortEdit
//
this.KMLLivePortEdit.Enabled = false;
this.KMLLivePortEdit.Location = new System.Drawing.Point(123, 22);
this.KMLLivePortEdit.Maximum = new decimal(new int[] {
65535,
0,
0,
0});
this.KMLLivePortEdit.Name = "KMLLivePortEdit";
this.KMLLivePortEdit.Size = new System.Drawing.Size(78, 20);
this.KMLLivePortEdit.TabIndex = 3;
this.KMLLivePortEdit.TextAlign = System.Windows.Forms.HorizontalAlignment.Right;
this.KMLLivePortEdit.Value = new decimal(new int[] {
2020,
0,
0,
0});
this.KMLLivePortEdit.ValueChanged += new System.EventHandler(this.KMLLivePortEdit_ValueChanged);
//
// TriggerNewLogStationaryUnitsLabel
// KMLLiveLinkLabel
//
this.TriggerNewLogStationaryUnitsLabel.AutoSize = true;
this.TriggerNewLogStationaryUnitsLabel.Location = new System.Drawing.Point(117, 71);
this.TriggerNewLogStationaryUnitsLabel.Name = "TriggerNewLogStationaryUnitsLabel";
this.TriggerNewLogStationaryUnitsLabel.Size = new System.Drawing.Size(47, 13);
this.TriggerNewLogStationaryUnitsLabel.TabIndex = 3;
this.TriggerNewLogStationaryUnitsLabel.Text = "seconds";
this.KMLLiveLinkLabel.AutoSize = true;
this.KMLLiveLinkLabel.Location = new System.Drawing.Point(30, 52);
this.KMLLiveLinkLabel.Name = "KMLLiveLinkLabel";
this.KMLLiveLinkLabel.Size = new System.Drawing.Size(30, 13);
this.KMLLiveLinkLabel.TabIndex = 1;
this.KMLLiveLinkLabel.Text = "Link:";
//
// KMLLiveCheckbox
//
this.KMLLiveCheckbox.AutoSize = true;
this.KMLLiveCheckbox.Location = new System.Drawing.Point(10, 23);
this.KMLLiveCheckbox.Name = "KMLLiveCheckbox";
this.KMLLiveCheckbox.Size = new System.Drawing.Size(107, 17);
this.KMLLiveCheckbox.TabIndex = 0;
this.KMLLiveCheckbox.Text = " Enabled on port:";
this.KMLLiveCheckbox.UseVisualStyleBackColor = true;
this.KMLLiveCheckbox.CheckedChanged += new System.EventHandler(this.KMLLiveEnabled_CheckedChanged);
//
// TryConnectTimer
//
this.TryConnectTimer.Interval = 5000;
this.TryConnectTimer.Tick += new System.EventHandler(this.TryConnectTimer_Tick);
//
// ChangesWhileRecordingLabel
//
this.ChangesWhileRecordingLabel.AutoSize = true;
this.ChangesWhileRecordingLabel.ForeColor = System.Drawing.Color.Red;
this.ChangesWhileRecordingLabel.Location = new System.Drawing.Point(191, 43);
this.ChangesWhileRecordingLabel.Name = "ChangesWhileRecordingLabel";
this.ChangesWhileRecordingLabel.Size = new System.Drawing.Size(265, 13);
this.ChangesWhileRecordingLabel.TabIndex = 8;
this.ChangesWhileRecordingLabel.Text = "changes will take effect once the recording is restarted";
this.ChangesWhileRecordingLabel.TextAlign = System.Drawing.ContentAlignment.TopRight;
this.ChangesWhileRecordingLabel.Visible = false;
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(506, 309);
this.ClientSize = new System.Drawing.Size(506, 440);
this.Controls.Add(this.ChangesWhileRecordingLabel);
this.Controls.Add(this.KMLLiveGroupbox);
this.Controls.Add(this.TriggersGroupbox);
this.Controls.Add(this.RecordingStatusIcon);
this.Controls.Add(this.RecordButton);
@ -271,6 +399,8 @@
this.Controls.Add(this.FlightSimulatorLabel);
this.Controls.Add(this.FlightSimulatorStatusIcon);
this.Controls.Add(this.OutputGroupbox);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.MaximizeBox = false;
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "FS Flight Logger";
@ -281,6 +411,9 @@
this.TriggersGroupbox.ResumeLayout(false);
this.TriggersGroupbox.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.TriggerNewLogStationaryTimeEdit)).EndInit();
this.KMLLiveGroupbox.ResumeLayout(false);
this.KMLLiveGroupbox.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.KMLLivePortEdit)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
@ -308,6 +441,16 @@
private System.Windows.Forms.NumericUpDown TriggerNewLogStationaryTimeEdit;
private System.Windows.Forms.CheckBox TriggerNewLogStationaryCheckbox;
private System.Windows.Forms.CheckBox TriggerWaitForMovementCheckbox;
private System.Windows.Forms.FolderBrowserDialog FolderBrowserDialog;
private System.Windows.Forms.GroupBox KMLLiveGroupbox;
private System.Windows.Forms.Label KMLLiveHelpLabel;
private System.Windows.Forms.TextBox KMLLiveLinkEdit;
private System.Windows.Forms.NumericUpDown KMLLivePortEdit;
private System.Windows.Forms.Label KMLLiveLinkLabel;
private System.Windows.Forms.CheckBox KMLLiveCheckbox;
private System.Windows.Forms.Timer TryConnectTimer;
private System.Windows.Forms.Label ChangesWhileRecordingLabel;
private System.Windows.Forms.CheckBox TriggerConnectedCheckbox;
}
}

View File

@ -1,12 +1,39 @@
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using FSFlightLogger.Resources;
using Newtonsoft.Json;
using SimConnect;
namespace FSFlightLogger
{
public partial class MainForm : Form
public partial class MainForm : Form, ISimConnectClientObserver
{
private readonly ISimConnectClientFactory simConnectClientFactory;
private readonly CancellationTokenSource tryConnectCancellationTokenSource = new CancellationTokenSource();
private ISimConnectClient simConnectClient;
private SimConnectLogger logger;
private enum FlightSimulatorStateValue
{
Connecting,
Connected,
Disconnected,
Failed
}
private enum RecordingStateValue
{
Started,
Stopped
}
public MainForm(ISimConnectClientFactory simConnectClientFactory)
@ -15,9 +42,11 @@ namespace FSFlightLogger
InitializeComponent();
SetFlightSimulatorConnected(false);
SetRecording(false);
//var simConnectClient = simConnectClientFactory.TryConnect("FS Flight Logger");
FlightSimulatorState = FlightSimulatorStateValue.Connecting;
RecordingState = RecordingStateValue.Stopped;
LoadSettings();
TryConnect();
}
@ -29,21 +58,333 @@ namespace FSFlightLogger
{
if (disposing)
{
SaveSettings();
components?.Dispose();
simConnectClient?.DisposeAsync();
}
base.Dispose(disposing);
}
private void SetFlightSimulatorConnected(bool connected)
private string GetSettingsFilename()
{
FlightSimulatorStatusIcon.Image = StatusImageList.Images[connected ? "FSConnected" : "FSDisconnected"];
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"FSFlightLogger", @"config.json");
}
private void SetRecording(bool recording)
private void LoadSettings()
{
RecordingStatusIcon.Image = StatusImageList.Images[recording ? "Recording" : "Idle"];
var defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), i18n.DefaultFolderName);
var settings = new Settings
{
CSVPath = defaultPath,
KMLEnabled = true,
KMLPath = defaultPath,
KMLLivePort = 2020,
TriggerWaitForMovement = true
};
var filename = GetSettingsFilename();
if (File.Exists(filename))
using (var streamReader = new StreamReader(filename, Encoding.UTF8))
{
var serializer = new JsonSerializer();
serializer.Populate(streamReader, settings);
}
if (settings.MainFormLeft.HasValue && settings.MainFormTop.HasValue)
{
StartPosition = FormStartPosition.Manual;
Left = settings.MainFormLeft.Value;
Top = settings.MainFormTop.Value;
// Check if the user did not unplug the screen that last contained the form
var formRectangle = new Rectangle(Left, Top, Width, Height);
if (!Screen.AllScreens.Any(s => s.WorkingArea.IntersectsWith(formRectangle)))
StartPosition = FormStartPosition.CenterScreen;
}
OutputCSVCheckbox.Checked = settings.CSVEnabled;
OutputCSVPathTextbox.Text = settings.CSVPath;
OutputKMLCheckbox.Checked = settings.KMLEnabled;
OutputKMLPathTextbox.Text = settings.KMLPath;
KMLLiveCheckbox.Checked = settings.KMLLiveEnabled;
KMLLivePortEdit.Value = settings.KMLLivePort;
TriggerConnectedCheckbox.Checked = settings.TriggerConnected;
TriggerWaitForMovementCheckbox.Checked = settings.TriggerWaitForMovement;
TriggerNewLogStationaryCheckbox.Checked = settings.TriggerNewLogStationaryEnabled;
TriggerNewLogStationaryTimeEdit.Value = settings.TriggerNewLogStationarySeconds;
UpdateKMLLiveLink();
}
private void SaveSettings()
{
var settings = new Settings
{
MainFormTop = Top,
MainFormLeft = Left,
CSVEnabled = OutputCSVCheckbox.Checked,
CSVPath = OutputCSVPathTextbox.Text,
KMLEnabled = OutputKMLCheckbox.Checked,
KMLPath = OutputKMLPathTextbox.Text,
KMLLiveEnabled = KMLLiveCheckbox.Checked,
KMLLivePort = (int)KMLLivePortEdit.Value,
TriggerConnected = TriggerConnectedCheckbox.Checked,
TriggerWaitForMovement = TriggerWaitForMovementCheckbox.Checked,
TriggerNewLogStationaryEnabled = TriggerNewLogStationaryCheckbox.Checked,
TriggerNewLogStationarySeconds = (int)TriggerNewLogStationaryTimeEdit.Value
};
var filename = GetSettingsFilename();
Directory.CreateDirectory(Path.GetDirectoryName(filename));
using (var streamWriter = new StreamWriter(filename, false, Encoding.UTF8))
{
var serializer = new JsonSerializer();
serializer.Serialize(streamWriter, settings);
}
}
private void TryConnect()
{
FlightSimulatorState = FlightSimulatorStateValue.Connecting;
simConnectClientFactory.TryConnect(@"FS Flight Logger")
.ContinueWith((task, state) =>
{
if (task.Result == null)
{
FlightSimulatorState = FlightSimulatorStateValue.Failed;
TryConnectTimer.Enabled = true;
}
else
{
simConnectClient = task.Result;
simConnectClient.AttachObserver(this);
logger = new SimConnectLogger(simConnectClient);
if (TriggerConnectedCheckbox.Checked)
StartRecording();
FlightSimulatorState = FlightSimulatorStateValue.Connected;
}
},
null, tryConnectCancellationTokenSource.Token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
public void OnQuit()
{
if (InvokeRequired)
{
Invoke(new Action(OnQuit));
return;
}
FlightSimulatorState = FlightSimulatorStateValue.Disconnected;
TryConnectTimer.Enabled = true;
}
private FlightSimulatorStateValue internalFlightSimulatorState;
private FlightSimulatorStateValue FlightSimulatorState
{
get => internalFlightSimulatorState;
set
{
if (value == internalFlightSimulatorState)
return;
internalFlightSimulatorState = value;
var isConnected = value == FlightSimulatorStateValue.Connected;
FlightSimulatorStatusIcon.Image = StatusImageList.Images[isConnected ? @"FSConnected" : @"FSDisconnected"];
switch (value)
{
case FlightSimulatorStateValue.Connecting:
FlightSimulatorStatusLabel.Text = i18n.FlightSimulatorConnecting;
break;
case FlightSimulatorStateValue.Connected:
FlightSimulatorStatusLabel.Text = i18n.FlightSimulatorConnected;
break;
case FlightSimulatorStateValue.Disconnected:
FlightSimulatorStatusLabel.Text = string.Format(i18n.FlightSimulatorQuit, TryConnectTimer.Interval / 1000);
break;
case FlightSimulatorStateValue.Failed:
FlightSimulatorStatusLabel.Text = string.Format(i18n.FlightSimulatorFailed, TryConnectTimer.Interval / 1000);
break;
}
CheckCanRecord();
if (!isConnected)
RecordingState = RecordingStateValue.Stopped;
}
}
private RecordingStateValue internalRecordingState;
private RecordingStateValue RecordingState
{
get => internalRecordingState;
set
{
if (value == internalRecordingState)
return;
internalRecordingState = value;
var isRecording = value == RecordingStateValue.Started;
RecordingStatusIcon.Image = StatusImageList.Images[isRecording ? @"Recording" : @"Idle"];
RecordButton.Text = isRecording ? i18n.RecordButtonStop : i18n.RecordButtonStart;
if (!isRecording)
ChangesWhileRecordingLabel.Visible = false;
}
}
private void StartRecording()
{
SaveSettings();
Task.Run(async () =>
{
await logger.Start(new SimConnectLogger.Config
{
CSVOutputPath = OutputCSVCheckbox.Checked ? OutputCSVPathTextbox.Text : null,
KMLOutputPath = OutputKMLCheckbox.Checked ? OutputKMLPathTextbox.Text : null,
KMLLivePort = KMLLiveCheckbox.Checked ? (int) KMLLivePortEdit.Value : (int?) null,
IntervalTime = TimeSpan.FromSeconds(1), // TODO configurable
IntervalDistance = 1, // TODO configurable
WaitForMovement = TriggerWaitForMovementCheckbox.Checked,
NewLogWhenIdleSeconds = TriggerNewLogStationaryCheckbox.Checked
? TimeSpan.FromSeconds((double) TriggerNewLogStationaryTimeEdit.Value)
: (TimeSpan?) null
});
});
RecordingState = RecordingStateValue.Started;
}
private void StopRecording()
{
Task.Run(async () => await logger.Stop());
RecordingState = RecordingStateValue.Stopped;
}
private void UpdateKMLLiveLink()
{
KMLLiveLinkEdit.Text = $@"http://localhost:{KMLLivePortEdit.Value}/live";
}
private void CheckChangesWhileRecording()
{
if (RecordingState == RecordingStateValue.Started)
ChangesWhileRecordingLabel.Visible = true;
}
private void CheckCanRecord()
{
RecordButton.Enabled = FlightSimulatorState == FlightSimulatorStateValue.Connected &&
(OutputCSVCheckbox.Checked || OutputKMLCheckbox.Checked || KMLLiveCheckbox.Checked);
}
private void TryConnectTimer_Tick(object sender, EventArgs e)
{
TryConnectTimer.Enabled = false;
TryConnect();
}
private void OutputCSVCheckbox_CheckedChanged(object sender, EventArgs e)
{
OutputCSVPathTextbox.Enabled = OutputCSVCheckbox.Checked;
OutputCSVPathBrowseButton.Enabled = OutputCSVCheckbox.Checked;
CheckChangesWhileRecording();
CheckCanRecord();
}
private void OutputKMLCheckbox_CheckedChanged(object sender, EventArgs e)
{
OutputKMLPathTextbox.Enabled = OutputKMLCheckbox.Checked;
OutputKMLPathBrowseButton.Enabled = OutputKMLCheckbox.Checked;
CheckChangesWhileRecording();
CheckCanRecord();
}
private void TriggerNewLogStationaryCheckbox_CheckedChanged(object sender, EventArgs e)
{
TriggerNewLogStationaryTimeEdit.Enabled = TriggerNewLogStationaryCheckbox.Checked;
CheckChangesWhileRecording();
}
private void KMLLiveEnabled_CheckedChanged(object sender, EventArgs e)
{
KMLLivePortEdit.Enabled = KMLLiveCheckbox.Checked;
CheckChangesWhileRecording();
CheckCanRecord();
}
private void SelectPath(Control pathTextBox)
{
FolderBrowserDialog.SelectedPath = pathTextBox.Text;
if (FolderBrowserDialog.ShowDialog(this) == DialogResult.OK)
pathTextBox.Text = FolderBrowserDialog.SelectedPath;
}
private void OutputCSVPathBrowseButton_Click(object sender, EventArgs e)
{
SelectPath(OutputCSVPathTextbox);
}
private void OutputKMLPathBrowseButton_Click(object sender, EventArgs e)
{
SelectPath(OutputKMLPathTextbox);
}
private void KMLLivePortEdit_ValueChanged(object sender, EventArgs e)
{
UpdateKMLLiveLink();
}
private void Configuration_Changed(object sender, EventArgs e)
{
CheckChangesWhileRecording();
}
private void RecordButton_Click(object sender, EventArgs e)
{
if (RecordingState == RecordingStateValue.Started)
StopRecording();
else
StartRecording();
}
}
}

View File

@ -124,351 +124,349 @@
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0
ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAACO
UAAAAk1TRnQBSQFMAgEBBAEAAQgBAAEIAQABIAEAASABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAGA
ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAACe
TgAAAk1TRnQBSQFMAgEBBAEAAUwBAAFMAQABIAEAASABAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAGA
AwABQAMAAQEBAAEgBgABgP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A
/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A
/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8AfgADPAFJA3QBlwOGAcIDiwHZA4sB3AOLAdwDjAHaA4YBwwN0
AZgDPQFLAwEBAlQAAjwBOwFJAXMBcgFzAZcBfAF7AYgBwgF1AXQBlQHZAXIBcAGYAdwBcgFwAZgB3AF3
AXYBlQHaAXwBewGIAcMCcgFzAZgCPQE8AUsDAQECMAADAQECAwQEBQEHAwUBBwMFAQcDBQEHAwUBBwMF
AQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMF
AQcDBQEHAwUBBwMFAQcDAwEEAwABARAAAwIBAwMHAQkDCgENAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEO
AwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEO
AwoBDgMKAQ0DBgEIAwEBAigAAw0BEANzAZYDhgHfA4cB/wOEAf8DhAH/A4sB/wOQAf8DkAH/A4sB/wOE
Af8DgwH/A4YB/wOGAeQDdQGbAw8BE0AAAw0BEAJyAXABlgFoAWcBlQHfAUYBRQG0Af8COQG6Af8BMAEx
AcMB/wExATMB0QH/ATUBNwHYAf8BNQE3AdgB/wExATMB0QH/AjABwgH/AjgBuQH/AUUBRAGzAf8BZwFm
AZYB5AJ0AXEBmwMPARMnAAEBAwYBCAMQARUDGAEiAx4BKwMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMe
ASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BLAMeASwDHgEsAx4BKwMX
ASADDgETAwQBBg8AAQEDCwEPAxwBKQErAikBRAEyAi8BVgEyAi8BVwEyAi8BVwEyATABLwFXATMBMAEv
AVcBMwEwAS8BVwEzATABLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFXATMCLwFX
ATMCLwFXATMBMAEvAVcBMwEwAS8BVwEzATABLwFXATMBMAEvAVcBMgIvAVcBMgIvAVcBMgEwAS8BVgEp
AScBKAFAARsCGgEmAwkBDCQAA14BdgOJAekDeQH/A4YB/wOdAf8DqgH/A7UB/wO6Af8DvQH/A70B/wO5
Af8DtAH/A6oB/wOdAf8DhgH/A3cB/wOJAe8DYQF8OAACXwFcAXYCZwGcAekBLgEtAbAB/wEnASgBzwH/
AUIBRQHnAf8BVgFcAfAB/wFlAXAB9wH/AW4BfgH6Af8BdAGGAfsB/wFzAYYB+wH/AW0BfAH6Af8BZAFu
AfcB/wFVAVwB8AH/AUIBRQHnAf8BJwEoAc8B/wIsAa4B/wJlAZwB7wJiAV4BfCAAAwEBAgMQARUDNgFf
A0EBfwNCAYADQgGAA0IBgANCAYADQgGAA0MBgANDAYADQwGAA0MBgANDAYADQwGAA0MBgANDAYADQwGA
A0MBgANDAYADQwGAA0IBgANCAYADQgGAA0IBgANCAYADQQF+AzMBVwMLAQ8DAAEBCAADAgEDAR0CHAEp
AU0BMQErAb4BWwEbAQoB/QFeARsBCAH/AV4BGwEIAf8BXwEcAQgB/wFhARwBCAH/AWEBGwEIAf8BYwEb
AQkB/wFjARsBCQH/AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/
AWMBGwEJAf8BYwEbAQkB/wFjARsBCQH/AWMBGwEJAf8BYQEcAQgB/wFhARsBCAH/AV8BGwEIAf8BXgEb
AQgB/wFeARsBCAH/AVsBHAELAfsBSwE0ATABrQMVAR0DAQECGAADBAEGA3gBpgN1Af8DgAH/A6IB/wOv
Af8DtwH/A78B/wPGAf8DygH/A8wB/wPMAf8DygH/A8UB/wO9Af8DtgH/A68B/wOiAf8DgAH/A3IB/wN9
AbEDDAEPKAADBAEGAnIBeAGmAjEBpAH/Ah4BywH/AUkBTAHqAf8BYgFoAe8B/wFrAXcB9gH/AXYBiQH8
Af8BgwGbAv8BjQGpAv8BkQGtAv8BkAGsAv8BjAGnAv8BgQGYAv8BdAGGAfsB/wFqAXUB9QH/AWEBZwHv
Af8BSQFMAesB/wIfAcwB/wEuAS0BoQH/AnYBfQGxAwwBDxsAAQEDNwFdA0cBgANFAYADRQGAA0YBgANG
/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8AfgADLwFJA08BlwNZAcIDXwHZA2EB3ANhAdwDYgHaA1sBwwNO
AZgDMAFLAwEBAlQAAy8BSQNPAZcDWQHCAl4BYAHZAmEBYgHcAmEBYgHcAmEBYwHaA1sBwwNOAZgDMAFL
AwEBAjAAAwEBAgMEBAUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEH
AwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwUBBwMFAQcDBQEHAwMBBAMAAQEQAAMC
AQMDBwEJAwoBDQMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMK
AQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgEOAwoBDgMKAQ4DCgENAwYBCAMBAQIoAAMMARADTgGW
A2EB3wOHAf8DhAH/A4QB/wOLAf8DkAH/A5AB/wOLAf8DhAH/A4MB/wOGAf8DZAHkA1ABmwMOARNAAAMM
ARADTgGWAlwBYgHfAT4BPQG0Af8CMQG6Af8BKAEpAcMB/wEpASsB0QH/AS0BLwHYAf8BLQEvAdgB/wEp
ASsB0QH/AigBwgH/AjABuQH/AT0BPAGzAf8CYQFnAeQDUAGbAw4BEycAAQEDBgEIAxABFQMYASIDHgEr
Ax8BLAMfASwDHwEsAx8BLAMfASwDHwEsAx8BLAMfASwDHwEsAx8BLAMfASwDHwEsAx8BLAMfASwDHwEs
Ax8BLAMfASwDHwEsAx8BLAMfASwDHgErAxcBIAMOARMDBAEGDwABAQMLAQ8DHQEpAywBRAM1AVYDNQFX
AzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFXAzUBVwM1AVcDNQFX
AzUBVwM1AVcDNQFXAzUBVwM1AVYDKgFAAxsBJgMJAQwkAANCAXYDaQHpA3EB/wOGAf8DnQH/A6oB/wO1
Af8DugH/A70B/wO9Af8DuQH/A7QB/wOqAf8DnQH/A4YB/wNvAf8DawHvA0UBfDgAA0IBdgJiAW4B6QEm
ASUBsAH/AR8BIAHPAf8BOgE9AecB/wFOAVQB8AH/AV0BaAH3Af8BZgF2AfoB/wFsAYYB+wH/AWsBhgH7
Af8BZQF0AfoB/wFcAWYB9wH/AU0BVAHwAf8BOgE9AecB/wEfASABzwH/AiQBrgH/AmIBcwHvA0UBfCAA
AwEBAgMQARUDOQFfA0UBfwNGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGA
A0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRgGAA0YBgANGAYADRQF+AzUBVwMLAQ8DAAEB
CAADAgEDAx0BKQNWAb4BUAEjARIB/QFWARMBAAH/AVYBEwEAAf8BVwEUAQAB/wFZARQBAAH/AVkBEwEA
Af8BWwETAQEB/wFbARMBAQH/AVsBEwEBAf8BWwETAQEB/wFbARMBAQH/AVsBEwEBAf8BWwETAQEB/wFb
ARMBAQH/AVsBEwEBAf8BWwETAQEB/wFbARMBAQH/AVsBEwEBAf8BWQEUAQAB/wFZARMBAAH/AVcBEwEA
Af8BVgETAQAB/wFWARMBAAH/AVsBIQEbAfsDUwGtAxUBHQMBAQIYAAMEAQYDVAGmA20B/wOAAf8DogH/
A68B/wO3Af8DvwH/A8YB/wPKAf8DzAH/A8wB/wPKAf8DxQH/A70B/wO2Af8DrwH/A6IB/wOAAf8DagH/
A1YBsQMLAQ8oAAMEAQYDVAGmAikBpAH/AhYBywH/AUEBRAHqAf8BWgFgAe8B/wFjAW8B9gH/AW4BiQH8
Af8BgwGbAv8BjQGpAv8BkQGtAv8BkAGsAv8BjAGnAv8BgQGYAv8BbAGGAfsB/wFiAW0B9QH/AVkBXwHv
Af8BQQFEAesB/wIXAcwB/wEmASUBoQH/A1YBsQMLAQ8bAAEBAzgBXQNHAYADRgGAA0YBgANGAYADRgGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANGAYADRgGAA0YBgANGAYADRwGAAzEBTgMAAQEIAAMBAQIBVwJVAboBgwEjAQUB/wFuAR0BAwH/
AW8BHgEEAf8BcwEgAQQB/wF2ASABBAH/AYIBIQEFAf8BhAEiAQUB/wGGASIBBQH/AYYBIwEFAf8BhgEj
AQUB/wGGASMBBQH/AYYBIwEFAf8BhgEjAQUB/wGGASMBBQH/AYYBIwEFAf8BhgEjAQUB/wGGASMBBQH/
AYYBIwEFAf8BhQEiAQUB/wGEASIBBQH/AYEBIQEFAf8BdQEhAQQB/wFzAR8BBAH/AW8BHQEEAf8BbgEe
AQMB/wGDASQBBQH/AVACTwGbAwABARQAAxMBGgNgAdcDZgH/A5MB/wOpAf8DsgH/A7kB/wO+Af8DwwH/
A8gB/wPLAf8DygH/A8oB/wPKAf8DxwH/A8EB/wO8Af8DuAH/A7EB/wOoAf8DlAH/A2UB/wNhAd4DFAEc
IAADEwEaAl4BYAHXAQ8BDgGwAf8BLgEvAd0B/wFSAVYB6QH/AV0BZQHxAf8BZgFzAfcB/wFvAYoB+AH/
AYMBmQH6Af8BjQGnAfsB/wGRAa0B/AH/AZABqgH8Af8BkAGrAfwB/wGRAa0B+wH/AYsBpQH6Af8BgAGW
AfgB/wFtAYcB9wH/AWUBcQH2Af8BXAFjAfAB/wFRAVUB6AH/AS8BMAHeAf8BDgENAa8B/wJgAWEB3gMU
ARwUAAMOARMDRgF/A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRQF9
AwQBBggAAxsBJgF/ATQBEAH+AaUBNQEJAf8BqgE3AQoB/wGwATkBCwH/AbYBOwEMAf8BuwE9AQwB/wHA
AUABDQH/AcMBQAENAf8BxAFBAQ0B/wHFAUIBDQH/AcUBQgENAf8BxQFCAQ0B/wHFAUIBDQH/AcUBQgEN
Af8BxQFCAQ0B/wHFAUIBDQH/AcUBQgENAf8BxQFCAQ0B/wHFAUIBDQH/AcQBQQENAf8BwgFAAQ0B/wG/
AUABDQH/AbsBPQEMAf8BtQE7AQwB/wGvATgBCwH/AaoBNwEKAf8BpAE0AQkB/wGAAUEBJAH5AwkBDBQA
A2AB1gNkAf8DmQH/A6cB/wOvAf8DswH/A7cB/wO8Af8DwgH/A8MB/wPEAf8DxQH/A8UB/wPEAf8DwwH/
A8AB/wO7Af8DtgH/A7IB/wOuAf8DpgH/A5kB/wNkAf8DZAHjAwcBCRwAA2AB1gIGAbIB/wE6ATsB3QH/
AVEBVQHmAf8BWgFiAe8B/wFhAW4B7wH/AWkBgwHwAf8BcgGPAfIB/wGEAZsB9QH/AYcBoAH1Af8BiQGj
AfYB/wGKAaUB9gH/AYoBpQH2Af8BiQGjAfUB/wGHAZ8B9QH/AYIBmAHzAf8BcAGNAfEB/wFnAYEB8AH/
AWABbAHvAf8BWQFgAe4B/wFQAVQB5QH/AToBOwHdAf8CBgGyAf8CXwFlAeMDBwEJEAADFwEgA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0YBgANGAYADRQGAA0UBgANHAYADMQFOAwABAQgAAwEBAgFaATcBLgG6AYMBKwENAf8BdgEl
AQsB/wF3ASYBDAH/AXsBKAEMAf8BfgEoAQwB/wGCASkBDQH/AYQBKgENAf8BhgEqAQ0B/wGGASsBDQH/
AYYBKwENAf8BhgErAQ0B/wGGASsBDQH/AYYBKwENAf8BhgErAQ0B/wGGASsBDQH/AYYBKwENAf8BhgEr
AQ0B/wGGASsBDQH/AYUBKgENAf8BhAEqAQ0B/wGBASkBDQH/AX0BKQEMAf8BewEnAQwB/wF3ASUBDAH/
AXYBJgELAf8BgwEsAQ0B/wFRAToBNQGbAwABARQAAxUBGgOIAdcDbgH/A5MB/wOpAf8DsgH/A7kB/wO+
Af8DwwH/A8gB/wPLAf8DygH/A8oB/wPKAf8DxwH/A8EB/wO8Af8DuAH/A7EB/wOoAf8DlAH/A20B/wOE
Ad4DFwEcIAADFQEaAnQBjwHXARcBFgGwAf8BNgE3Ad0B/wFaAV4B6QH/AWUBbQHxAf8BbgF7AfcB/wF3
AYoB+AH/AYMBmQH6Af8BjQGnAfsB/wGRAa0B/AH/AZABqgH8Af8BkAGrAfwB/wGRAa0B+wH/AYsBpQH6
Af8BgAGWAfgB/wF1AYcB9wH/AW0BeQH2Af8BZAFrAfAB/wFZAV0B6AH/ATcBOAHeAf8BFgEVAa8B/wJv
AYwB3gMXARwUAAMOARMDSAF/A0wBgANMAYADTQGAA04BgANPAYADUAGAA1ABgANQAYADUAGAA1ABgANQ
AYADUAGAA1ABgANQAYADUAGAA1ABgANQAYADUAGAA1ABgANQAYADTwGAA08BgANOAYADTQGAA0wBgANM
AYADRwF9AwQBBggAAR0CGwEmAY8BNAEQAf4BpQE9AREB/wGqAT8BEgH/AbABQQETAf8BtgFDARQB/wG7
AUUBFAH/AcABSAEVAf8BwwFIARUB/wHEAUkBFQH/AcUBSgEVAf8BxQFKARUB/wHFAUoBFQH/AcUBSgEV
Af8BxQFKARUB/wHFAUoBFQH/AcUBSgEVAf8BxQFKARUB/wHFAUoBFQH/AcUBSgEVAf8BxAFJARUB/wHC
AUgBFQH/Ab8BSAEVAf8BuwFFARQB/wG1AUMBFAH/Aa8BQAETAf8BqgE/ARIB/wGkATwBEQH/AYgBMgET
AfkDCQEMFAADhwHWA2wB/wOZAf8DpwH/A68B/wOzAf8DtwH/A7wB/wPCAf8DwwH/A8QB/wPFAf8DxQH/
A8QB/wPDAf8DwAH/A7sB/wO2Af8DsgH/A64B/wOmAf8DmQH/A2wB/wOFAeMDBwEJHAACcgGPAdYCDgGy
Af8BQgFDAd0B/wFZAV0B5gH/AWIBagHvAf8BaQF2Ae8B/wFxAYMB8AH/AXoBjwHyAf8BhAGbAfUB/wGH
AaAB9QH/AYkBowH2Af8BigGlAfYB/wGKAaUB9gH/AYkBowH1Af8BhwGfAfUB/wGCAZgB8wH/AXgBjQHx
Af8BbwGBAfAB/wFoAXQB7wH/AWEBaAHuAf8BWAFcAeUB/wFCAUMB3QH/Ag4BsgH/Am0BjgHjAwcBCRAA
AxgBIANLAYADUAGAA1EBgANTAYADVAGAA1UBgANWAYADYgGAA2ABgANXAYADVwGAA1cBgANXAYADVwGA
A1cBgANXAYADVwGAA1cBgANXAYADZQGAA1cBgANVAYADVQGAA1QBgANSAYADUQGAA1ABgANKAYADDgET
CAABLwEqASgBPwGiAT0BEQH/AcMBTAEVAf8BzQFQARYB/wHVAVUBGQH/AdoBWgEeAf8B3AFeASMB/wHd
AWEBKAH/AeoBogGAAf8B5wGVAW4B/wHfAWcBLgH/Ad8BZwEuAf8B3wFnAS4B/wHfAWcBLgH/Ad8BZwEu
Af8B3wFnAS4B/wHfAWcBLgH/Ad8BZwEuAf8B3wFnAS4B/wHfAWcBLgH/Ae0BswGXAf8B3wFqATMB/wHd
AWEBJwH/AdsBXgEiAf8B2QFZAR4B/wHUAVQBGAH/AcwBTwEWAf8BwAFLARUB/wGaATcBEAH/AR0BHAEb
ASYQAANzAZ0DaQH/A5QB/wOjAf8DqgH/A6wB/wOxAf8DtQH/A7kB/wO8Af8DvgH/A78B/wO/Af8DvwH/
A78B/wO9Af8DuwH/A7kB/wO1Af8DsAH/A6wB/wOqAf8DowH/A5YB/wNoAf8DfAGyGAACbQF0AZ0CDwGq
Af8BPgFAAdgB/wFVAVkB4gH/AVwBZAHpAf8BYgFuAegB/wFpAXoB6gH/AXIBhgHsAf8BeQGQAe0B/wF+
AZcB7gH/AYIBnAHvAf8BgwGeAfAB/wGEAZ8B8AH/AYQBngHwAf8BgwGeAfAB/wGBAZsB7wH/AX0BlgHu
Af8BeAGOAe0B/wFxAYQB6wH/AWgBeAHpAf8BYQFsAegB/wFcAWIB6AH/AVQBWAHiAf8CQAHZAf8CDwGp
Af8CdAF9AbIQAAMYASADTAGAA1EBgANTAYADVQGAA1YBgANXAYADWAGAA3EBgANoAYADWQGAA1kBgANZ
AYADWQGAA1kBgANZAYADWQGAA1kBgANZAYADWwGAA3QBgANoAYADWAGAA1cBgANWAYADVQGAA1MBgANR
AYADSgGAAw8BFAgAATABKgEoAT8BqAE/ARIB/wHOAVEBFgH/AdkBVwEaAf8B3AFfASQB/wHeAWYBLQH/
Ad8BawEyAf8B4QFvATgB/wH6AesB5AH/AfEBwQGpAf8B4gFzAT8B/wHiAXMBPwH/AeIBcwE/Af8B4gFz
AT8B/wHiAXMBPwH/AeIBcwE/Af8B4gFzAT8B/wHiAXMBPwH/AeIBcwE/Af8B5AF9AU0B/wH9AfwB+wH/
AfEBwAGpAf8B4QFuATcB/wHfAWoBMQH/Ad0BZQErAf8B3AFeASMB/wHYAVYBGQH/Ac0BUQEWAf8BnAE5
ARAB/wEeARwBGwEnDAADRgFWA2wB/wOIAf8DnwH/A6UB/wOmAf8DqgH/A68B/wOzAf8DtgH/A7gB/wO6
Af8DugH/A7sB/wO7Af8DugH/A7kB/wO4Af8DtgH/A7IB/wOuAf8DqQH/A6UB/wOkAf8DngH/A4oB/wNn
Af8DTgFiEAACRgFFAVYCJAGdAf8BLgEvAc0B/wFQAVMB3AH/AVcBXAHjAf8BWwFlAeIB/wFhAW8B5AH/
AWkBegHmAf8BcAGEAegB/wF1AYwB6gH/AXkBkgHsAf8BewGVAe0B/wF8AZcB7QH/AX0BmAHtAf8BfQGY
Ae0B/wF8AZYB7QH/AXoBlQHtAf8BeAGRAewB/wF0AYoB6gH/AW8BgwHoAf8BZwF4AeYB/wFgAW4B4wH/
AVkBYwHiAf8BVgFbAeMB/wFPAVIB2wH/ATABMQHQAf8BIAEfAZgB/wJOAUwBYgwAAxgBIANNAYADUgGA
A1QBgANWAYADVwGAA1gBgANeAYADdAGAA2kBgANaAYADWgGAA1oBgANaAYADWgGAA1oBgANaAYADWgGA
A1oBgANmAYADdQGAA3QBgANaAYADWAGAA1cBgANWAYADVAGAA1IBgANLAYADDwEUCAABMAEqASgBPwGr
AUEBEgH/AdUBUwEXAf8B2wFbASAB/wHdAWUBKwH/Ad8BawEyAf8B4QFwAToB/wHnAYwBYAH/A/4B/wHz
AcQBrgH/AeQBeQFHAf8B5AF5AUcB/wHkAXkBRwH/AeQBeQFHAf8B5AF5AUcB/wHkAXkBRwH/AeQBeQFH
Af8B5AF5AUcB/wHkAXkBRwH/Ae8BtAGYBf8B/QH7AfoB/wHjAXYBQgH/AeEBbwE5Af8B3wFqATEB/wHd
AWQBKQH/AdsBWwEeAf8B0wFSARcB/wGeATkBEQH/AR8BHAEbAScLAAEBA4YB6QNsAf8DmQH/A6AB/wOg
Af8DpAH/A6kB/wOtAf8DsQH/A7QB/wO2Af8DuAH/A7kB/wO5Af8DuQH/A7gB/wO3Af8DtgH/A7QB/wOw
Af8DrAH/A6gB/wOjAf8DoAH/A58B/wOYAf8DcAH/A4MB8QMBAQILAAEBAmEBnAHpAg0BtAH/AUsBTAHV
Af8BUQFVAd4B/wFUAVsB3AH/AVoBZAHeAf8BYQFuAeIB/wFnAXcB5QH/AW0BgAHoAf8BcQGGAeoB/wF0
AYsB7AH/AXYBjQHtAf8BeAGQAe0B/wF5AZEB7gH/AXgBkQHuAf8BdwGPAe0B/wF2AY0B7AH/AXMBigHr
Af8BcAGFAeoB/wFsAX0B5wH/AWYBdgHkAf8BYAFsAeEB/wFZAWMB3QH/AVQBWwHbAf8BUAFUAd0B/wFK
AUsB1AH/AhEBuAH/AloBmAHxAwEBAggAAxgBIANNAYADUwGAA1UBgANXAYADWAGAA1kBgANoAYADdQGA
A2kBgANbAYADWwGAA1sBgANbAYADWwGAA1sBgANbAYADWwGAA1sBgANvAYADdQGAA28BgANaAYADWQGA
A1gBgANWAYADVQGAA1MBgANLAYADDwEUCAABMAEqASgBPwGvAUIBEwH/AdgBVgEZAf8B3AFgASUB/wHf
AWgBLwH/AeEBbwE4Af8B4wFzAUAB/wHxAb4BpgX/AfMByAGyAf8B5gF9AUwB/wHmAX4BTAH/AeYBfgFM
Af8B5gF+AUwB/wHmAX4BTAH/AeYBfgFMAf8B5gF+AUwB/wHmAX4BTAH/AeYBfgFMAf8B+AHjAdkF/wH4
AeIB2AH/AeQBdwFGAf8B4gFzAT8B/wHhAW4BNwH/Ad4BaAEuAf8B3AFeASMB/wHXAVUBGAH/AaIBOwER
Af8BHwEcARsBJwgAA1oBcwNoAf8DiwH/A5kB/wOcAf8DngH/A6MB/wOnAf8DqwH/A64B/wOwAf8DsgH/
A7MB/wO1Af8DtQH/A7UB/wO1Af8DswH/A7IB/wOwAf8DrQH/A6oB/wOmAf8DogH/A50B/wObAf8DmAH/
A4wB/wNkAf8DZQGGCAABWQFYAVoBcwIYAZ8B/wE3ATgBywH/AUoBTQHWAf8BTwFUAdcB/wFTAVsB2AH/
AVoBYwHbAf8BXwFrAd8B/wFlAXQB4gH/AWkBegHkAf8BbQGBAeYB/wFwAYQB5wH/AXEBhwHoAf8BcwGK
AekB/wF0AYsB6QH/AXQBiwHpAf8BcwGKAekB/wFxAYYB5wH/AW8BhAHnAf8BbAGAAeYB/wFpAXkB4wH/
AWQBcgHhAf8BXgFqAd4B/wFZAWIB2gH/AVMBWgHXAf8BTgFTAdYB/wFJAUwB1QH/ATgBOQHMAf8CFQGb
Af8CYgFkAYYIAAMYASADTgGAA1MBgANWAYADVwGAA10BgANmAYADcgGAA3UBgANqAYADXAGAA1wBgANc
AYADXAGAA1wBgANcAYADXAGAA1wBgANfAYADdAGAA3UBgANqAYADWwGAA1oBgANZAYADVwGAA1UBgANT
AYADTAGAAw8BFAgAATABKgEoAT8BsgFEARMB/wHZAVgBGwH/Ad0BYwEoAf8B3wFrATIB/wHmAYoBXQH/
Ae8BtgGcAf8B+wHxAewF/wH0AcoBtQH/AecBggFPAf8B5wGCAU8B/wHnAYIBTwH/AecBggFPAf8B5wGC
AU8B/wHnAYIBTwH/AecBggFPAf8B5wGCAU8B/wHqAZQBagH/A/4F/wH0AcsBtwH/AeUBewFJAf8B4wF2
AUQB/wHiAXEBPAH/Ad8BagExAf8B3AFiAScB/wHZAVcBGgH/AaUBPQERAf8BHwEcARsBJwgAA4AB3ANp
Af8DkQH/A5YB/wOYAf8DmwH/A58B/wOjAf8DpwH/A6kB/wOrAf8DrQH/A64B/wOvAf8DrwH/A68B/wOv
Af8DrgH/A60B/wOrAf8DqQH/A6YB/wOjAf8DnwH/A5oB/wOYAf8DlgH/A5EB/wNrAf8DgQHxCAACXAGU
AdwCDQGtAf8BQwFFAcwB/wFIAUwB0gH/AUwBUQHRAf8BUQFZAdQB/wFXAWEB1wH/AVwBZwHaAf8BYgFv
AdwB/wFlAXUB3gH/AWgBeQHgAf8BagF9AeEB/wFtAYAB4gH/AW4BgwHjAf8BbgGDAeMB/wFuAYMB4wH/
AW4BgwHiAf8BbAGAAeIB/wFqAXwB4QH/AWgBeQHgAf8BZQF0Ad4B/wFhAW0B3AH/AVsBZwHaAf8BVgFf
AdcB/wFQAVcB0wH/AUwBUQHRAf8BSAFMAdIB/wFDAUQBywH/ARABDwGvAf8CVgGYAfEIAAMYASADTgGA
A1QBgANiAYADbgGAA3QBgAN1AYADdQGAA3UBgANxAYADXwGAA1wBgANcAYADXAGAA1wBgANcAYADXAGA
A1wBgANpAYADdQGAA3UBgANlAYADWwGAA1oBgANZAYADVwGAA1YBgANUAYADTAGAAw8BFAgAATABKgEo
AT8BtQFFARMB/wHaAVoBHQH/AekBoAF9Af8B9wHhAdYB/wH+Av0N/wH6Ae0B5wH/AeoBkgFmAf8B6AGF
AVMB/wHoAYUBUwH/AegBhQFTAf8B6AGFAVMB/wHoAYUBUwH/AegBhQFTAf8B6AGFAVMB/wHzAcUBrwn/
Ae8BsQGTAf8B5gF9AUsB/wHkAXcBRgH/AeIBcgE+Af8B4AFsATMB/wHdAWMBKQH/AdoBWQEcAf8BpwE9
ARIB/wEfARwBGwEnBAADEgEWA3cB/wN5Af8DjwH/A5IB/wOVAf8DmAH/A5sB/wOfAf8DogH/A6UB/wOn
Af8DqAH/A6kB/wOqAf8DqgH/A6oB/wOqAf8DqQH/A6gB/wOmAf8DpAH/A6IB/wOfAf8DmwH/A5gB/wOU
Af8DkgH/A48B/wN8Af8DcQH/AygBMQMSARYBNQE0AaUB/wEkASUBugH/AUEBQwHKAf8BRQFJAcwB/wFJ
AU8BzgH/AU4BVQHQAf8BUgFcAdMB/wFYAWIB1gH/AVwBaAHYAf8BYAFuAdoB/wFjAXIB2wH/AWQBdQHc
Af8BZgF4Ad0B/wFnAXkB3gH/AWcBegHeAf8BZwF5Ad4B/wFnAXkB3gH/AWYBeAHdAf8BZAF0AdwB/wFi
AXIB2wH/AV8BbQHZAf8BWwFnAdgB/wFXAWIB1gH/AVEBWwHTAf8BTQFUAdAB/wFJAU4BzQH/AUUBSQHM
Af8BQQFDAckB/wInAbwB/wIuAZ4B/wMoATEEAAMYASADTgGAA1QBgANmAYADagGAA2sBgANsAYADbQGA
A3QBgAN1AYADcQGAA2ABgANdAYADXQGAA10BgANdAYADXQGAA10BgANxAYADdQGAA3QBgANgAYADWwGA
A1oBgANZAYADVwGAA1YBgANUAYADTAGAAw8BFAgAATABKgEoAT8BtwFEARMB/wHbAVoBHgH/Ae4BtQGa
Af8B8wHLAbgB/wH0AdABvwH/AfYB1QHGAf8B9gHZAcsB/wH+AfwB+wX/AfsB7QHnAf8B6wGZAW8B/wHo
AYcBVQH/AegBhwFVAf8B6AGHAVUB/wHoAYcBVQH/AegBhwFVAf8B6AGHAVUB/wH7Ae4B6AX/A/4B/wHq
AZUBawH/AeYBfQFMAf8B5AF5AUcB/wHiAXMBPwH/AeABbQE0Af8B3QFkASsB/wHaAVoBHQH/AakBPQES
Af8BHwEcARsBJwQAA0kBXANmAf8DgwH/A40B/wOOAf8DkQH/A5UB/wOYAf8DmwH/A54B/wOgAf8DogH/
A6MB/wOkAf8DpQH/A6UB/wOlAf8DpAH/A6QB/wOjAf8DoQH/A6AB/wOdAf8DmgH/A5gB/wOUAf8DkQH/
A44B/wONAf8DhAH/A2EB/wNnAYgCRwFKAVwCGQGbAf8BMgEzAb8B/wFAAUIBxwH/AUIBRwHHAf8BRgFM
AcoB/wFLAVEBzQH/AU8BVwHPAf8BUwFeAdEB/wFYAWMB0wH/AVsBaAHVAf8BXQFsAdYB/wFfAW4B1wH/
AWEBcAHYAf8BYgFyAdgB/wFiAXMB2QH/AWIBcwHZAf8BYQFxAdgB/wFgAXAB2AH/AV4BbgHXAf8BXAFq
AdYB/wFaAWcB1QH/AVcBYgHTAf8BUgFdAdEB/wFOAVYBzwH/AUkBUQHNAf8BRQFKAckB/wFBAUUBxwH/
AT8BQQHHAf8BMwE0Ab8B/wIUAZYB/wJjAWcBiAQAAxgBIANOAYADVAGAA1YBgANYAYADWQGAA1sBgANb
AYADZAGAA3QBgAN1AYADcgGAA2EBgANdAYADXQGAA10BgANdAYADYwGAA3UBgAN1AYADcwGAA1wBgANb
AYADWgGAA1kBgANXAYADVgGAA1QBgANMAYADDwEUCAABMAEqASgBPwG4AUQBEwH/AdsBWgEfAf8B3gFm
ASwB/wHgAW0BNgH/AeMBcwFAAf8B5QF6AUgB/wHmAX4BTAH/Ae4BrAGMAf8C/gH9Bf8B/AHyAe4B/wHr
AZ0BdQH/AegBhwFVAf8B6AGHAVUB/wHoAYcBVQH/AegBhwFVAf8B7QGmAYIJ/wH9AfgB9gH/AecBggFQ
Af8B5gF+AUwB/wHkAXkBRwH/AeIBcwE/Af8B4AFtATQB/wHdAWUBKwH/AdoBWgEdAf8BqgE+ARIB/wEf
ARwBGwEnBAADcwGiA1wB/wOFAf8DigH/A4sB/wOOAf8DkQH/A5QB/wOXAf8DmQH/A5sB/wOdAf8DngH/
A58B/wOgAf8DoAH/A6AB/wOgAf8DnwH/A54B/wOdAf8DmwH/A5gB/wOWAf8DkwH/A5AB/wONAf8DigH/
A4kB/wOFAf8DXgH/A3sBvgJmAXgBogEHAQYBmAH/ATcBOAG9Af8BPQE/AcMB/wE/AUMBwgH/AUMBSAHF
Af8BRwFOAcgB/wFLAVQBygH/AU8BWgHMAf8BUwFeAc0B/wFWAWIBzwH/AVgBZgHQAf8BWgFpAdEB/wFb
AWoB0QH/AVwBbAHSAf8BXQFtAdMB/wFdAWwB0wH/AVwBbAHSAf8BWwFqAdEB/wFZAWgB0QH/AVgBZQHQ
Af8BVQFhAc8B/wFSAV4BzQH/AU8BWQHLAf8BSgFTAckB/wFGAU0BxwH/AUIBRwHEAf8BPQFBAcIB/wE8
AT8BwgH/AjcBvQH/AQkBCAGbAf8CagGCAb4EAAMYASADTgGAA1QBgANWAYADWAGAA1kBgANbAYADWwGA
A1wBgANlAYADdAGAA3UBgANzAYADYgGAA10BgANdAYADXQGAA2sBgAN1AYADdQGAA24BgANcAYADWwGA
A1oBgANZAYADVwGAA1YBgANUAYADTQGAAw8BFAgAATABKgEoAT8BuQFFARMB/wHbAVoBHwH/Ad4BZgEs
Af8B4AFtATYB/wHjAXMBQAH/AeUBegFIAf8B5gF+AUwB/wHnAYIBUAH/Ae8BsQGTAf8D/gX/AfwB9gH0
Af8B7AGkAYAB/wHoAYcBVQH/AegBhwFVAf8B6AGHAVUB/wH1AdEBvwn/AfgB4AHUAf8B5wGCAU8B/wHm
AX4BTAH/AeQBeQFHAf8B4gFzAT8B/wHgAW0BNAH/Ad0BZQErAf8B2gFaAR0B/wGrAT8BEgH/AR8BHAEb
AScEAAN9AckDXwH/A4IB/wOGAf8DhwH/A4oB/wONAf8DjwH/A5EB/wOUAf8DlgH/A5cB/wOYAf8DmgH/
A5oB/wOaAf8DmgH/A5oB/wOaAf8DmAH/A5cB/wOWAf8DkwH/A5EB/wOPAf8DjQH/A4kB/wOGAf8DhQH/
A4IB/wNiAf8DfAHUAmIBiwHJAQgBBwGeAf8CNgG5Af8BOQE8Ab4B/wE7AT4BvgH/AT8BRAHBAf8BQgFJ
AcMB/wFFAU4BxQH/AUkBUwHHAf8BTQFYAckB/wFQAVwBygH/AVIBXwHLAf8BUwFhAcwB/wFVAWMBzQH/
AVYBZQHNAf8BVgFlAc0B/wFWAWUBzQH/AVYBZQHNAf8BVQFjAc0B/wFTAWABywH/AVIBXgHLAf8BTwFb
AcoB/wFMAVgByAH/AUkBUgHGAf8BRQFNAcUB/wFCAUgBwwH/AT4BRAHAAf8BOwE+Ab0B/wE5ATsBvQH/
AjUBuQH/AgsBoQH/Al0BiwHUBAADGAEgA08BgANUAYADVgGAA1gBgANZAYADWwGAA1sBgANcAYADXAGA
A2gBgAN0AYADdQGAA3MBgANkAYADXQGAA2UBgAN0AYADdQGAA3UBgANoAYADXAGAA1sBgANaAYADWQGA
A1cBgANWAYADVAGAA00BgAMPARQIAAEwASoBKAE/AboBRQETAf8B2wFbAR8B/wHeAWYBLAH/AeABbQE2
Af8B4wFzAUAB/wHlAXoBSAH/AeYBfgFMAf8B5wGCAVAB/wHoAYYBVAH/AfIBwAGoAf8D/gX/Af0B+gH4
Af8B7gGqAYgB/wHoAYcBVQH/Ae8BsAGRAf8B/gH9AfwJ/wHzAcIBqgH/AecBggFPAf8B5gF+AUwB/wHk
AXkBRwH/AeIBcwE/Af8B4AFtATQB/wHdAWUBKwH/AdoBWgEdAf8BrAE/ARIB/wEfARwBGwEnBAADdgHH
A2IB/wN+Af8DggH/A4MB/wOFAf8DiAH/A4sB/wONAf8DjgH/A5EB/wOSAf8DkwH/A5QB/wOUAf8DlAH/
A5QB/wOUAf8DkwH/A5MB/wOSAf8DkAH/A44B/wOMAf8DigH/A4cB/wOFAf8DgwH/A4IB/wN9Af8DYwH/
A3oB3QJZAYYBxwILAZ8B/wIzAbUB/wE2ATgBuAH/ATcBOgG5Af8BOgE/AbsB/wE+AUMBvgH/AUEBSAHA
Af8BRAFMAcEB/wFGAVABwwH/AUkBVQHFAf8BSwFXAcUB/wFNAVkBxgH/AU8BXAHHAf8BTwFdAcYB/wFP
AV0BxwH/AU8BXQHHAf8BTwFdAcYB/wFOAVwBxgH/AU0BWQHGAf8BSwFXAcUB/wFJAVQBxAH/AUcBUAHC
Af8BQwFMAcEB/wFAAUcBvwH/AT0BQwG9Af8BOgE+AbsB/wE3ATsBuQH/ATYBNwG4Af8CMgG0Af8CDQGh
Af8CVgGOAd0EAAMYASADTwGAA1QBgANWAYADWAGAA1kBgANbAYADXAGAA10BgANeAYADXwGAA2wBgAN0
AYADdQGAA3QBgANrAYADdAGAA3UBgAN1AYADdQGAA2YBgANdAYADXAGAA1sBgANZAYADVwGAA1YBgANU
AYADTQGAAw8BFAgAATABKgEoAT8BugFFARMB/wHbAVsBHwH/Ad4BZgEsAf8B4AFtATYB/wHjAXMBQQH/
AeUBfQFNAf8B5gGFAVYB/wHnAYoBXQH/AegBjwFiAf8B6AGSAWcB/wH1AdYBxwH/A/4F/wH+AfwB+wH/
AfMBzgG8Af8B/gL8Df8B7wG0AZcB/wHnAYkBXAH/AeYBhQFVAf8B5AF8AUsB/wHiAXMBPwH/AeABbQE0
Af8B3QFlASsB/wHaAVoBHQH/AawBPwESAf8BHwEcARsBJwQAA3UByANeAf8DegH/A30B/wN+Af8DgQH/
A4MB/wOEAf8DhgH/A4gB/wOKAf8DiwH/A40B/wONAf8DjQH/A40B/wONAf8DjQH/A40B/wOMAf8DiwH/
A4kB/wOHAf8DhQH/A4QB/wODAf8DgQH/A30B/wN9Af8DegH/A2AB/wN4AdwCWAGGAcgBCQEIAZsB/wEv
ATABsAH/ATIBNAGzAf8BNAE3AbQB/wE2ATsBtgH/ATkBPgG4Af8BOwFBAbkB/wE9AUUBuwH/AT8BSAG8
Af8BQgFMAb0B/wFDAU4BvgH/AUYBUgHAAf8BRwFUAcAB/wFHAVQBwAH/AUcBVAHAAf8BRwFUAcAB/wFH
AVQBwAH/AUcBUwHAAf8BRQFRAb8B/wFDAU4BvgH/AUEBSwG9Af8BPwFHAbsB/wE8AUUBugH/AToBQQG5
Af8BOAE+AbcB/wE2AToBtQH/ATMBNgGzAf8BMgEzAbMB/wIvAbAB/wELAQoBnQH/AlQBjAHcBAADGAEg
A08BgANUAYADWAGAA1wBgANeAYADXwGAA18BgANgAYADYAGAA2ABgANhAYADbgGAA3UBgAN1AYADdQGA
A3UBgAN1AYADdQGAA3QBgANiAYADYAGAA18BgANfAYADXgGAA1sBgANYAYADVAGAA00BgAMPARQIAAEw
ASoBKAE/AboBRQETAf8B2wFcASAB/wHfAXEBOwH/AeIBggFTAf8B5AGNAWIB/wHmAZIBaQH/AecBlAFt
Af8B6AGWAW8B/wHoAZkBcQH/AegBmQFyAf8B6QGbAXUB/wH3Ad4B0xn/A/4B/wHqAaQBgwH/AegBlQFv
Af8B5wGUAWwB/wHmAZEBaAH/AeQBjQFhAf8B4gGBAVEB/wHfAXABOgH/AdoBWwEeAf8BrAE/ARIB/wEf
ARwBGwEnBAADdQHCA1cB/wN2Af8DeQH/A3oB/wN6Af8DfAH/A4EB/wOEAf8DhgH/A4gB/wOJAf8DiwH/
A4wB/wOMAf8DjAH/A4wB/wOMAf8DjAH/A4sB/wOJAf8DiAH/A4YB/wOEAf8DgQH/A3sB/wN6Af8DeQH/
A3gB/wN2Af8DWgH/A3YB0gJaAYMBwgIBAZIB/wIsAasB/wEvATABrwH/ATABMwGvAf8BMAE0Aa8B/wEy
ATgBsQH/ATgBPgG0Af8BPAFDAbYB/wE/AUcBuAH/AUIBSwG6Af8BRAFNAboB/wFGAVABvAH/AUcBUgG9
Af8BRwFTAb0B/wFIAVMBvAH/AUgBUwG9Af8BRwFSAb0B/wFHAVIBvQH/AUYBUAG8Af8BQwFMAboB/wFC
AUoBuQH/AT8BRwG4Af8BPAFCAbYB/wE4AT4BtAH/ATIBNwGwAf8BMAEzAa8B/wEvATIBrgH/AS4BMAGu
Af8CKwGrAf8CBAGVAf8CVgGGAdIEAAMYASADTwGAA1cBgANdAYADXgGAA18BgANfAYADYAGAA2EBgANh
AYADYQGAA2EBgANhAYADcQGAA3UBgAN1AYADdQGAA3UBgAN1AYADcgGAA2EBgANgAYADYAGAA18BgANf
AYADXgGAA1wBgANXAYADTQGAAw8BFAgAATABKgEoAT8BugFFARMB/wHcAWkBMwH/AeIBhwFcAf8B4wGO
AWMB/wHlAZIBaQH/AeYBlQFtAf8B5wGYAXIB/wHoAZoBdQH/AegBnAF2Af8B6AGcAXcB/wHoAZwBdwH/
AegBnwF7Af8B+gHtAecV/wH8AfQB8QH/AegBmwF3Af8B6AGZAXQB/wHnAZcBcQH/AeYBlQFsAf8B5QGS
AWkB/wHjAY0BYgH/AeEBhwFbAf8B3AFpATIB/wGsAT8BEgH/AR8BHAEbAScEAANpAZoDUQH/A3IB/wN2
Af8DdAH/A3gB/wOCAf8DhwH/A4kB/wOKAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjgH/A40B/wON
Af8DjAH/A4wB/wOKAf8DiQH/A4gB/wOGAf8DgQH/A3cB/wNzAf8DdQH/A3EB/wNSAf8DdAG8AlwBbwGa
AgABhwH/AigBpgH/ASwBLQGqAf8BKAErAakB/wEuATIBrAH/AT0BQQGyAf8BQwFHAbUB/wFFAUoBtwH/
AUcBTQG4Af8BSQFPAbkB/wFKAVIBugH/AUsBUwG7Af8BTAFUAbwB/wFMAVQBvAH/AU0BVQG8Af8BTQFV
AbwB/wFMAVQBuwH/AUwBUwG7Af8BSwFSAboB/wFKAVEBugH/AUgBTwG4Af8BRgFNAbcB/wFEAUkBtgH/
AUIBRgG1Af8BPAFAAbEB/wEuATEBqwH/ASgBKgGoAf8CLAGpAf8BKAEnAaUB/wIAAYoB/wJgAXwBvAQA
AxgBIANPAYADWAGAA14BgANfAYADXwGAA2ABgANhAYADYQGAA2IBgANiAYADYgGAA2kBgAN0AYADdQGA
A3UBgAN1AYADdQGAA3UBgANvAYADYgGAA2EBgANhAYADYAGAA18BgANeAYADXQGAA1gBgANNAYADDwEU
CAABMAEqASgBPwG6AUUBEwH/Ad0BbQE7Af8B4gGLAWMB/wHkAZEBaQH/AeUBlQFuAf8B5gGaAXQB/wHn
AZwBeAH/AegBngF7Af8B6QGgAXwB/wHpAaABfQH/AekBoAF9Af8B8AHFAbAB/wP+Ff8B+AHkAdoB/wHp
AaABfAH/AegBngF6Af8B5wGcAXcB/wHmAZoBcwH/AeUBlQFuAf8B5AGRAWgB/wHiAYsBYgH/Ad0BbQE5
Af8BrAE/ARIB/wEfARwBGwEnBAADSAFfA1EB/wNrAf8DbwH/A3MB/wOFAf8DiAH/A4gB/wOJAf8DigH/
A4sB/wOMAf8DjQH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/A40B/wOMAf8DiwH/A4kB/wOIAf8DiAH/
A4gB/wOEAf8DcwH/A28B/wNsAf8DTQH/A2MBhwJGAUkBXwIDAYMB/wEhASABoAH/AiUBowH/ASoBLAGm
Af8BQwFFAbEB/wFIAUsBtAH/AUcBSwG0Af8BSQFNAbUB/wFKAU8BtgH/AUsBUQG3Af8BTQFTAbcB/wFO
AVQBuAH/AU8BVQG5Af8BTwFWAbkB/wFQAVYBuQH/AVABVgG5Af8BTwFWAbkB/wFPAVUBuAH/AU4BUwG3
Af8BTQFSAbcB/wFLAVEBtgH/AUkBTwG1Af8BSAFNAbQB/wFHAUoBtAH/AUgBSwG0Af8BQgFEAbAB/wEq
ASwBpQH/AiUBowH/AiEBoAH/AgABfQH/Al0BZAGHBAADGAEgA04BgANZAYADXwGAA18BgANgAYADYQGA
A2EBgANiAYADYgGAA2cBgANtAYADdAGAA3UBgAN1AYADdQGAA3UBgAN1AYADdQGAA28BgANiAYADYgGA
A2EBgANhAYADYAGAA18BgANeAYADWAGAA00BgAMPARQIAAEwASoBKAE/AbkBRQETAf8B3gFyAUAB/wHj
AZIBagH/AeQBlQFwAf8B5QGYAXYB/wHnAZwBegH/AecBnwF9Af8B6AGhAYAB/wHpAaIBgQH/Ae0BuQGh
Af8B9QHbAc8B/wP+Gf8B+AHjAdoB/wHpAaQBgwH/AegBoAF+Af8B5wGfAXwB/wHmAZwBeQH/AeUBmAF1
Af8B5AGVAW8B/wHjAZEBaQH/Ad0BcQE/Af8BqwE/ARIB/wEfARwBGwEnBAADDwETA2EB/wNdAf8DaQH/
A3oB/wOMAf8DhwH/A4kB/wOLAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjwH/A48B/wOPAf8DjgH/
A44B/wONAf8DjQH/A4sB/wOKAf8DiQH/A4cB/wOLAf8DegH/A2gB/wNfAf8DWAH/AyoBNAMPARMCHQGL
Af8CDgGTAf8CHQGcAf8CNwGnAf8BUAFRAbMB/wFJAUsBsQH/AUsBTgGyAf8BTQFQAbQB/wFOAVIBtAH/
AU8BVAG1Af8BUAFVAbUB/wFRAVYBtgH/AVIBVwG2Af8BUgFYAbcB/wFTAVgBtwH/AVMBWAG3Af8BUwFX
AbcB/wFSAVgBtwH/AVEBVgG2Af8BUAFVAbUB/wFQAVQBtQH/AU4BUgG0Af8BTQFQAbIB/wFLAU4BsgH/
AUkBSwGxAf8BUAFSAbIB/wI3AacB/wEcAR0BnAH/AhABlQH/AhQBggH/AyoBNAQAAxgBIANOAYADWQGA
A18BgANgAYADYQGAA2IBgANkAYADagGAA3IBgAN0AYADdQGAA3UBgAN1AYADdQGAA3UBgAN1AYADdQGA
A3UBgAN1AYADcAGAA2QBgANiAYADYQGAA2EBgANgAYADXwGAA1kBgANNAYADDwEUCAABMAEqASgBPwG5
AUUBEwH/Ad4BdgFFAf8B4wGWAXEB/wHlAZoBdgH/AeYBngF7Af8B5wGhAYAB/wHpAaoBjQH/AfEBzQG8
Af8B+gHwAewB/wP+Jf8B+AHmAd4B/wHpAakBjQH/AecBogGCAf8B5gGhAX4B/wHmAZ4BewH/AeQBmgF1
Af8B4wGWAXAB/wHdAXUBRAH/AasBPgESAf8BHwEcARsBJwgAA2oB3QNPAf8DYwH/A3wB/wOPAf8DigH/
A4wB/wOMAf8DjgH/A44B/wOPAf8DkAH/A5AB/wOQAf8DkAH/A5AB/wOQAf8DkAH/A5AB/wOPAf8DjgH/
A44B/wOMAf8DiwH/A4oB/wOPAf8DfAH/A2IB/wNQAf8DaQHvCAACQAGCAd0CAAGDAf8CGAGVAf8BPQE8
AacB/wJYAbMB/wFQAVEBsAH/AVEBUgGyAf8BUwFUAbIB/wFVAVYBswH/AVUBWAG0Af8BVgFZAbQB/wFX
AVsBtQH/AVcBWwG1Af8BVwFbAbUB/wFYAVsBtQH/AVgBWwG1Af8BVwFbAbUB/wFXAVsBtQH/AVcBWwG1
Af8BVgFZAbQB/wFVAVcBtAH/AVUBVgGzAf8BUwFUAbIB/wFRAVIBsQH/AVABUQGwAf8CWAGzAf8CPAGn
Af8CFwGUAf8CAAGEAf8COQGEAe8IAAMYASADTgGAA1oBgANgAYADYgGAA2cBgANuAYADdAGAA3UBgAN1
AYADdQGAA3UBgAN1AYADdQGAA3QBgAN0AYADcgGAA3MBgAN1AYADdQGAA3UBgANxAYADZAGAA2IBgANi
AYADYQGAA2ABgANaAYADTQGAAw8BFAgAATABKgEoAT8BuAFGARMB/wHeAXoBSgH/AeQBmgF3Af8B5gGf
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMOARMIAAMpAT8BogE1
AQkB/wHDAUQBDQH/Ac0BSAEOAf8B1QFNAREB/wHaAVIBFgH/AdwBVgEbAf8B3QFZASAB/wHqAaIBgAH/
AecBlQFmAf8B3wFfASYB/wHfAV8BJgH/Ad8BXwEmAf8B3wFfASYB/wHfAV8BJgH/Ad8BXwEmAf8B3wFf
ASYB/wHfAV8BJgH/Ad8BXwEmAf8B3wFfASYB/wHtAbMBlwH/Ad8BYgErAf8B3QFZAR8B/wHbAVYBGgH/
AdkBUQEWAf8B1AFMARAB/wHMAUcBDgH/AcABQwENAf8BmgEvAQgB/wMbASYQAANQAZ0DYQH/A5QB/wOj
Af8DqgH/A6wB/wOxAf8DtQH/A7kB/wO8Af8DvgH/A78B/wO/Af8DvwH/A78B/wO9Af8DuwH/A7kB/wO1
Af8DsAH/A6wB/wOqAf8DowH/A5YB/wNgAf8DVQGyGAADUAGdAgcBqgH/ATYBOAHYAf8BTQFRAeIB/wFU
AVwB6QH/AVoBZgHoAf8BYQFyAeoB/wFqAYYB7AH/AXEBkAHtAf8BdgGXAe4B/wGCAZwB7wH/AYMBngHw
Af8BhAGfAfAB/wGEAZ4B8AH/AYMBngHwAf8BgQGbAe8B/wF1AZYB7gH/AXABjgHtAf8BaQGEAesB/wFg
AXAB6QH/AVkBZAHoAf8BVAFaAegB/wFMAVAB4gH/AjgB2QH/AgcBqQH/A1UBshAAAxcBIANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AagBNwEK
Af8BzgFJAQ4B/wHZAU8BEgH/AdwBVwEcAf8B3gFeASUB/wHfAWMBKgH/AeEBZwEwAf8B+gHrAeQB/wHx
AcEBqQH/AeIBawE3Af8B4gFrATcB/wHiAWsBNwH/AeIBawE3Af8B4gFrATcB/wHiAWsBNwH/AeIBawE3
Af8B4gFrATcB/wHiAWsBNwH/AeQBdQFFAf8B/QH8AfsB/wHxAcABqQH/AeEBZgEvAf8B3wFiASkB/wHd
AV0BIwH/AdwBVgEbAf8B2AFOAREB/wHNAUkBDgH/AZwBMQEIAf8DHAEnDAADNQFWA2QB/wOIAf8DnwH/
A6UB/wOmAf8DqgH/A68B/wOzAf8DtgH/A7gB/wO6Af8DugH/A7sB/wO7Af8DugH/A7kB/wO4Af8DtgH/
A7IB/wOuAf8DqQH/A6UB/wOkAf8DngH/A4oB/wNfAf8DOgFiEAADNQFWAhwBnQH/ASYBJwHNAf8BSAFL
AdwB/wFPAVQB4wH/AVMBXQHiAf8BWQFnAeQB/wFhAXIB5gH/AWgBhAHoAf8BbQGMAeoB/wFxAZIB7AH/
AXMBlQHtAf8BdAGXAe0B/wF1AZgB7QH/AXUBmAHtAf8BdAGWAe0B/wFyAZUB7QH/AXABkQHsAf8BbAGK
AeoB/wFnAYMB6AH/AV8BcAHmAf8BWAFmAeMB/wFRAVsB4gH/AU4BUwHjAf8BRwFKAdsB/wEoASkB0AH/
ARgBFwGYAf8DOgFiDAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgAMPARQIAAMpAT8BqwE5AQoB/wHVAUsBDwH/AdsBUwEYAf8B3QFdASMB/wHfAWMBKgH/
AeEBaAEyAf8B5wGMAVgB/wP+Af8B8wHEAa4B/wHkAXEBPwH/AeQBcQE/Af8B5AFxAT8B/wHkAXEBPwH/
AeQBcQE/Af8B5AFxAT8B/wHkAXEBPwH/AeQBcQE/Af8B5AFxAT8B/wHvAbQBmAX/Af0B+wH6Af8B4wFu
AToB/wHhAWcBMQH/Ad8BYgEpAf8B3QFcASEB/wHbAVMBFgH/AdMBSgEPAf8BngExAQkB/wMcAScLAAEB
A2gB6QNkAf8DmQH/A6AB/wOgAf8DpAH/A6kB/wOtAf8DsQH/A7QB/wO2Af8DuAH/A7kB/wO5Af8DuQH/
A7gB/wO3Af8DtgH/A7QB/wOwAf8DrAH/A6gB/wOjAf8DoAH/A58B/wOYAf8DaAH/A28B8QMBAQILAAEB
AmEBbgHpAgUBtAH/AUMBRAHVAf8BSQFNAd4B/wFMAVMB3AH/AVIBXAHeAf8BWQFmAeIB/wFfAW8B5QH/
AWUBgAHoAf8BaQGGAeoB/wFsAYsB7AH/AW4BjQHtAf8BcAGQAe0B/wFxAZEB7gH/AXABkQHuAf8BbwGP
Ae0B/wFuAY0B7AH/AWsBigHrAf8BaAGFAeoB/wFkAXUB5wH/AV4BbgHkAf8BWAFkAeEB/wFRAVsB3QH/
AUwBUwHbAf8BSAFMAd0B/wFCAUMB1AH/AgkBuAH/AloBewHxAwEBAggAAxcBIANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/Aa8BOgELAf8B2AFO
AREB/wHcAVgBHQH/Ad8BYAEnAf8B4QFnATAB/wHjAWsBOAH/AfEBvgGmBf8B8wHIAbIB/wHmAXUBRAH/
AeYBdgFEAf8B5gF2AUQB/wHmAXYBRAH/AeYBdgFEAf8B5gF2AUQB/wHmAXYBRAH/AeYBdgFEAf8B5gF2
AUQB/wH4AeMB2QX/AfgB4gHYAf8B5AFvAT4B/wHiAWsBNwH/AeEBZgEvAf8B3gFgASYB/wHcAVYBGwH/
AdcBTQEQAf8BogEzAQkB/wMcAScIAANCAXMDYAH/A4sB/wOZAf8DnAH/A54B/wOjAf8DpwH/A6sB/wOu
Af8DsAH/A7IB/wOzAf8DtQH/A7UB/wO1Af8DtQH/A7MB/wOyAf8DsAH/A60B/wOqAf8DpgH/A6IB/wOd
Af8DmwH/A5gB/wOMAf8DXAH/A0gBhggAA0IBcwIQAZ8B/wEvATABywH/AUIBRQHWAf8BRwFMAdcB/wFL
AVMB2AH/AVIBWwHbAf8BVwFjAd8B/wFdAWwB4gH/AWEBcgHkAf8BZQGBAeYB/wFoAYQB5wH/AWkBhwHo
Af8BawGKAekB/wFsAYsB6QH/AWwBiwHpAf8BawGKAekB/wFpAYYB5wH/AWcBhAHnAf8BZAGAAeYB/wFh
AXEB4wH/AVwBagHhAf8BVgFiAd4B/wFRAVoB2gH/AUsBUgHXAf8BRgFLAdYB/wFBAUQB1QH/ATABMQHM
Af8CDQGbAf8DSAGGCAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgAMPARQIAAMpAT8BsgE8AQsB/wHZAVABEwH/Ad0BWwEgAf8B3wFjASoB/wHmAYoBVQH/
Ae8BtgGcAf8B+wHxAewF/wH0AcoBtQH/AecBggFHAf8B5wGCAUcB/wHnAYIBRwH/AecBggFHAf8B5wGC
AUcB/wHnAYIBRwH/AecBggFHAf8B5wGCAUcB/wHqAZQBYgH/A/4F/wH0AcsBtwH/AeUBcwFBAf8B4wFu
ATwB/wHiAWkBNAH/Ad8BYgEpAf8B3AFaAR8B/wHZAU8BEgH/AaUBNQEJAf8DHAEnCAADYQHcA2EB/wOR
Af8DlgH/A5gB/wObAf8DnwH/A6MB/wOnAf8DqQH/A6sB/wOtAf8DrgH/A68B/wOvAf8DrwH/A68B/wOu
Af8DrQH/A6sB/wOpAf8DpgH/A6MB/wOfAf8DmgH/A5gB/wOWAf8DkQH/A2MB/wNvAfEIAAJcAWIB3AIF
Aa0B/wE7AT0BzAH/AUABRAHSAf8BRAFJAdEB/wFJAVEB1AH/AU8BWQHXAf8BVAFfAdoB/wFaAWcB3AH/
AV0BbQHeAf8BYAFxAeAB/wFiAXUB4QH/AWUBgAHiAf8BZgGDAeMB/wFmAYMB4wH/AWYBgwHjAf8BZgGD
AeIB/wFkAYAB4gH/AWIBdAHhAf8BYAFxAeAB/wFdAWwB3gH/AVkBZQHcAf8BUwFfAdoB/wFOAVcB1wH/
AUgBTwHTAf8BRAFJAdEB/wFAAUQB0gH/ATsBPAHLAf8BCAEHAa8B/wJWAXsB8QgAAxcBIANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AbUBPQEL
Af8B2gFSARUB/wHpAaABdQH/AfcB4QHWAf8B/gL9Df8B+gHtAecB/wHqAZIBXgH/AegBhQFLAf8B6AGF
AUsB/wHoAYUBSwH/AegBhQFLAf8B6AGFAUsB/wHoAYUBSwH/AegBhQFLAf8B8wHFAa8J/wHvAbEBkwH/
AeYBdQFDAf8B5AFvAT4B/wHiAWoBNgH/AeABZAErAf8B3QFbASEB/wHaAVEBFAH/AacBNQEKAf8DHAEn
BAADEAEWA28B/wNxAf8DjwH/A5IB/wOVAf8DmAH/A5sB/wOfAf8DogH/A6UB/wOnAf8DqAH/A6kB/wOq
Af8DqgH/A6oB/wOqAf8DqQH/A6gB/wOmAf8DpAH/A6IB/wOfAf8DmwH/A5gB/wOUAf8DkgH/A48B/wN0
Af8DaQH/AyIBMQMQARYBLQEsAaUB/wEcAR0BugH/ATkBOwHKAf8BPQFBAcwB/wFBAUcBzgH/AUYBTQHQ
Af8BSgFUAdMB/wFQAVoB1gH/AVQBYAHYAf8BWAFmAdoB/wFbAWoB2wH/AVwBbQHcAf8BXgFwAd0B/wFf
AXEB3gH/AV8BcgHeAf8BXwFxAd4B/wFfAXEB3gH/AV4BcAHdAf8BXAFsAdwB/wFaAWoB2wH/AVcBZQHZ
Af8BUwFfAdgB/wFPAVoB1gH/AUkBUwHTAf8BRQFMAdAB/wFBAUYBzQH/AT0BQQHMAf8BOQE7AckB/wIf
AbwB/wImAZ4B/wMiATEEAAMXASADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAAw8BFAgAAykBPwG3ATwBCwH/AdsBUgEWAf8B7gG1AZoB/wHzAcsBuAH/AfQB0AG/
Af8B9gHVAcYB/wH2AdkBywH/Af4B/AH7Bf8B+wHtAecB/wHrAZkBZwH/AegBhwFNAf8B6AGHAU0B/wHo
AYcBTQH/AegBhwFNAf8B6AGHAU0B/wHoAYcBTQH/AfsB7gHoBf8D/gH/AeoBlQFjAf8B5gF1AUQB/wHk
AXEBPwH/AeIBawE3Af8B4AFlASwB/wHdAVwBIwH/AdoBUgEVAf8BqQE1AQoB/wMcAScEAAM4AVwDXgH/
A4MB/wONAf8DjgH/A5EB/wOVAf8DmAH/A5sB/wOeAf8DoAH/A6IB/wOjAf8DpAH/A6UB/wOlAf8DpQH/
A6QB/wOkAf8DowH/A6EB/wOgAf8DnQH/A5oB/wOYAf8DlAH/A5EB/wOOAf8DjQH/A4QB/wNZAf8DSQGI
AzgBXAIRAZsB/wEqASsBvwH/ATgBOgHHAf8BOgE/AccB/wE+AUQBygH/AUMBSQHNAf8BRwFPAc8B/wFL
AVYB0QH/AVABWwHTAf8BUwFgAdUB/wFVAWQB1gH/AVcBZgHXAf8BWQFoAdgB/wFaAWoB2AH/AVoBawHZ
Af8BWgFrAdkB/wFZAWkB2AH/AVgBaAHYAf8BVgFmAdcB/wFUAWIB1gH/AVIBXwHVAf8BTwFaAdMB/wFK
AVUB0QH/AUYBTgHPAf8BQQFJAc0B/wE9AUIByQH/ATkBPQHHAf8BNwE5AccB/wErASwBvwH/AgwBlgH/
A0kBiAQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADDwEUCAADKQE/AbgBPAELAf8B2wFSARcB/wHeAV4BJAH/AeABZQEuAf8B4wFrATgB/wHlAXIBQAH/
AeYBdgFEAf8B7gGsAYwB/wL+Af0F/wH8AfIB7gH/AesBnQFtAf8B6AGHAU0B/wHoAYcBTQH/AegBhwFN
Af8B6AGHAU0B/wHtAaYBggn/Af0B+AH2Af8B5wGCAUgB/wHmAXYBRAH/AeQBcQE/Af8B4gFrATcB/wHg
AWUBLAH/Ad0BXQEjAf8B2gFSARUB/wGqATYBCgH/AxwBJwQAA1EBogNUAf8DhQH/A4oB/wOLAf8DjgH/
A5EB/wOUAf8DlwH/A5kB/wObAf8DnQH/A54B/wOfAf8DoAH/A6AB/wOgAf8DoAH/A58B/wOeAf8DnQH/
A5sB/wOYAf8DlgH/A5MB/wOQAf8DjQH/A4oB/wOJAf8DhQH/A1YB/wNZAb4DUQGiAgABmAH/AS8BMAG9
Af8BNQE3AcMB/wE3ATsBwgH/ATsBQAHFAf8BPwFGAcgB/wFDAUwBygH/AUcBUgHMAf8BSwFWAc0B/wFO
AVoBzwH/AVABXgHQAf8BUgFhAdEB/wFTAWIB0QH/AVQBZAHSAf8BVQFlAdMB/wFVAWQB0wH/AVQBZAHS
Af8BUwFiAdEB/wFRAWAB0QH/AVABXQHQAf8BTQFZAc8B/wFKAVYBzQH/AUcBUQHLAf8BQgFLAckB/wE+
AUUBxwH/AToBPwHEAf8BNQE5AcIB/wE0ATcBwgH/Ai8BvQH/AQEBAAGbAf8DWQG+BAADFwEgA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BuQE9
AQsB/wHbAVIBFwH/Ad4BXgEkAf8B4AFlAS4B/wHjAWsBOAH/AeUBcgFAAf8B5gF2AUQB/wHnAYIBSAH/
Ae8BsQGTAf8D/gX/AfwB9gH0Af8B7AGkAYAB/wHoAYcBTQH/AegBhwFNAf8B6AGHAU0B/wH1AdEBvwn/
AfgB4AHUAf8B5wGCAUcB/wHmAXYBRAH/AeQBcQE/Af8B4gFrATcB/wHgAWUBLAH/Ad0BXQEjAf8B2gFS
ARUB/wGrATcBCgH/AxwBJwQAA1wByQNXAf8DggH/A4YB/wOHAf8DigH/A40B/wOPAf8DkQH/A5QB/wOW
Af8DlwH/A5gB/wOaAf8DmgH/A5oB/wOaAf8DmgH/A5oB/wOYAf8DlwH/A5YB/wOTAf8DkQH/A48B/wON
Af8DiQH/A4YB/wOFAf8DggH/A1oB/wNgAdQDXAHJAgABngH/Ai4BuQH/ATEBNAG+Af8BMwE2Ab4B/wE3
ATwBwQH/AToBQQHDAf8BPQFGAcUB/wFBAUsBxwH/AUUBUAHJAf8BSAFUAcoB/wFKAVcBywH/AUsBWQHM
Af8BTQFbAc0B/wFOAV0BzQH/AU4BXQHNAf8BTgFdAc0B/wFOAV0BzQH/AU0BWwHNAf8BSwFYAcsB/wFK
AVYBywH/AUcBUwHKAf8BRAFQAcgB/wFBAUoBxgH/AT0BRQHFAf8BOgFAAcMB/wE2ATwBwAH/ATMBNgG9
Af8BMQEzAb0B/wItAbkB/wIDAaEB/wJcAWAB1AQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AboBPQELAf8B2wFTARcB/wHeAV4BJAH/
AeABZQEuAf8B4wFrATgB/wHlAXIBQAH/AeYBdgFEAf8B5wGCAUgB/wHoAYYBTAH/AfIBwAGoAf8D/gX/
Af0B+gH4Af8B7gGqAYgB/wHoAYcBTQH/Ae8BsAGRAf8B/gH9AfwJ/wHzAcIBqgH/AecBggFHAf8B5gF2
AUQB/wHkAXEBPwH/AeIBawE3Af8B4AFlASwB/wHdAV0BIwH/AdoBUgEVAf8BrAE3AQoB/wMcAScEAANZ
AccDWgH/A3YB/wOCAf8DgwH/A4UB/wOIAf8DiwH/A40B/wOOAf8DkQH/A5IB/wOTAf8DlAH/A5QB/wOU
Af8DlAH/A5QB/wOTAf8DkwH/A5IB/wOQAf8DjgH/A4wB/wOKAf8DhwH/A4UB/wODAf8DggH/A3UB/wNb
Af8DYgHdAlkBWgHHAgMBnwH/AisBtQH/AS4BMAG4Af8BLwEyAbkB/wEyATcBuwH/ATYBOwG+Af8BOQFA
AcAB/wE8AUQBwQH/AT4BSAHDAf8BQQFNAcUB/wFDAU8BxQH/AUUBUQHGAf8BRwFUAccB/wFHAVUBxgH/
AUcBVQHHAf8BRwFVAccB/wFHAVUBxgH/AUYBVAHGAf8BRQFRAcYB/wFDAU8BxQH/AUEBTAHEAf8BPwFI
AcIB/wE7AUQBwQH/ATgBPwG/Af8BNQE7Ab0B/wEyATYBuwH/AS8BMwG5Af8BLgEvAbgB/wIqAbQB/wIF
AaEB/wJYAWQB3QQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADDwEUCAADKQE/AboBPQELAf8B2wFTARcB/wHeAV4BJAH/AeABZQEuAf8B4wFrATkB/wHl
AXUBRQH/AeYBhQFOAf8B5wGKAVUB/wHoAY8BWgH/AegBkgFfAf8B9QHWAccB/wP+Bf8B/gH8AfsB/wHz
Ac4BvAH/Af4C/A3/Ae8BtAGXAf8B5wGJAVQB/wHmAYUBTQH/AeQBdAFDAf8B4gFrATcB/wHgAWUBLAH/
Ad0BXQEjAf8B2gFSARUB/wGsATcBCgH/AxwBJwQAA1sByANWAf8DcgH/A3UB/wN2Af8DgQH/A4MB/wOE
Af8DhgH/A4gB/wOKAf8DiwH/A40B/wONAf8DjQH/A40B/wONAf8DjQH/A40B/wOMAf8DiwH/A4kB/wOH
Af8DhQH/A4QB/wODAf8DgQH/A3UB/wN1Af8DcgH/A1gB/wNhAdwCWwFcAcgBAQEAAZsB/wEnASgBsAH/
ASoBLAGzAf8BLAEvAbQB/wEuATMBtgH/ATEBNgG4Af8BMwE5AbkB/wE1AT0BuwH/ATcBQAG8Af8BOgFE
Ab0B/wE7AUYBvgH/AT4BSgHAAf8BPwFMAcAB/wE/AUwBwAH/AT8BTAHAAf8BPwFMAcAB/wE/AUwBwAH/
AT8BSwHAAf8BPQFJAb8B/wE7AUYBvgH/ATkBQwG9Af8BNwE/AbsB/wE0AT0BugH/ATIBOQG5Af8BMAE2
AbcB/wEuATIBtQH/ASsBLgGzAf8BKgErAbMB/wInAbAB/wEDAQIBnQH/AlwBYQHcBAADFwEgA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BugE9
AQsB/wHbAVQBGAH/Ad8BaQEzAf8B4gGCAUsB/wHkAY0BWgH/AeYBkgFhAf8B5wGUAWUB/wHoAZYBZwH/
AegBmQFpAf8B6AGZAWoB/wHpAZsBbQH/AfcB3gHTGf8D/gH/AeoBpAGDAf8B6AGVAWcB/wHnAZQBZAH/
AeYBkQFgAf8B5AGNAVkB/wHiAYEBSQH/Ad8BaAEyAf8B2gFTARYB/wGsATcBCgH/AxwBJwQAA1kBwgNP
Af8DbgH/A3EB/wNyAf8DcgH/A3QB/wOBAf8DhAH/A4YB/wOIAf8DiQH/A4sB/wOMAf8DjAH/A4wB/wOM
Af8DjAH/A4wB/wOLAf8DiQH/A4gB/wOGAf8DhAH/A4EB/wNzAf8DcgH/A3EB/wNwAf8DbgH/A1IB/wNd
AdIDWQHCAgABkgH/AiQBqwH/AScBKAGvAf8BKAErAa8B/wEoASwBrwH/ASoBMAGxAf8BMAE2AbQB/wE0
ATsBtgH/ATcBPwG4Af8BOgFDAboB/wE8AUUBugH/AT4BSAG8Af8BPwFKAb0B/wE/AUsBvQH/AUABSwG8
Af8BQAFLAb0B/wE/AUoBvQH/AT8BSgG9Af8BPgFIAbwB/wE7AUQBugH/AToBQgG5Af8BNwE/AbgB/wE0
AToBtgH/ATABNgG0Af8BKgEvAbAB/wEoASsBrwH/AScBKgGuAf8BJgEoAa4B/wIjAasB/wIAAZUB/wJZ
AV0B0gQAAxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADDwEUCAADKQE/AboBPQELAf8B3AFhASsB/wHiAYcBVAH/AeMBjgFbAf8B5QGSAWEB/wHmAZUBZQH/
AecBmAFqAf8B6AGaAW0B/wHoAZwBbgH/AegBnAFvAf8B6AGcAW8B/wHoAZ8BcwH/AfoB7QHnFf8B/AH0
AfEB/wHoAZsBbwH/AegBmQFsAf8B5wGXAWkB/wHmAZUBZAH/AeUBkgFhAf8B4wGNAVoB/wHhAYcBUwH/
AdwBYQEqAf8BrAE3AQoB/wMcAScEAANQAZoDSQH/A2oB/wNuAf8DbAH/A3AB/wOCAf8DhwH/A4kB/wOK
Af8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjgH/A40B/wONAf8DjAH/A4wB/wOKAf8DiQH/A4gB/wOG
Af8DgQH/A28B/wNrAf8DbQH/A2kB/wNKAf8DWAG8A1ABmgIAAYcB/wIgAaYB/wEkASUBqgH/ASABIwGp
Af8BJgEqAawB/wE1ATkBsgH/ATsBPwG1Af8BPQFCAbcB/wE/AUUBuAH/AUEBRwG5Af8BQgFKAboB/wFD
AUsBuwH/AUQBTAG8Af8BRAFMAbwB/wFFAU0BvAH/AUUBTQG8Af8BRAFMAbsB/wFEAUsBuwH/AUMBSgG6
Af8BQgFJAboB/wFAAUcBuAH/AT4BRQG3Af8BPAFBAbYB/wE6AT4BtQH/ATQBOAGxAf8BJgEpAasB/wEg
ASIBqAH/AiQBqQH/ASABHwGlAf8CAAGKAf8DWAG8BAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BugE9AQsB/wHdAWUBMwH/AeIBiwFb
Af8B5AGRAWEB/wHlAZUBZgH/AeYBmgFsAf8B5wGcAXAB/wHoAZ4BcwH/AekBoAF0Af8B6QGgAXUB/wHp
AaABdQH/AfABxQGwAf8D/hX/AfgB5AHaAf8B6QGgAXQB/wHoAZ4BcgH/AecBnAFvAf8B5gGaAWsB/wHl
AZUBZgH/AeQBkQFgAf8B4gGLAVoB/wHdAWUBMQH/AawBNwEKAf8DHAEnBAADOQFfA0kB/wNjAf8DZwH/
A2sB/wOFAf8DiAH/A4gB/wOJAf8DigH/A4sB/wOMAf8DjQH/A44B/wOOAf8DjgH/A44B/wOOAf8DjgH/
A40B/wOMAf8DiwH/A4kB/wOIAf8DiAH/A4gB/wOEAf8DawH/A2cB/wNkAf8DRQH/A0kBhwM5AV8CAAGD
Af8BGQEYAaAB/wIdAaMB/wEiASQBpgH/ATsBPQGxAf8BQAFDAbQB/wE/AUMBtAH/AUEBRQG1Af8BQgFH
AbYB/wFDAUkBtwH/AUUBSwG3Af8BRgFMAbgB/wFHAU0BuQH/AUcBTgG5Af8BSAFOAbkB/wFIAU4BuQH/
AUcBTgG5Af8BRwFNAbgB/wFGAUsBtwH/AUUBSgG3Af8BQwFJAbYB/wFBAUcBtQH/AUABRQG0Af8BPwFC
AbQB/wFAAUMBtAH/AToBPAGwAf8BIgEkAaUB/wIdAaMB/wIZAaAB/wIAAXUB/wNJAYcEAAMXASADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAAw8BFAgAAykBPwG5
AT0BCwH/Ad4BagE4Af8B4wGSAWIB/wHkAZUBaAH/AeUBmAFuAf8B5wGcAXIB/wHnAZ8BdQH/AegBoQGA
Af8B6QGiAYEB/wHtAbkBoQH/AfUB2wHPAf8D/hn/AfgB4wHaAf8B6QGkAYMB/wHoAaABdgH/AecBnwF0
Af8B5gGcAXEB/wHlAZgBbQH/AeQBlQFnAf8B4wGRAWEB/wHdAWkBNwH/AasBNwEKAf8DHAEnBAADDgET
A1kB/wNVAf8DYQH/A3IB/wOMAf8DhwH/A4kB/wOLAf8DiwH/A4wB/wONAf8DjgH/A44B/wOOAf8DjwH/
A48B/wOPAf8DjgH/A44B/wONAf8DjQH/A4sB/wOKAf8DiQH/A4cB/wOLAf8DcgH/A2AB/wNXAf8DUAH/
AyMBNAMOARMCFQGLAf8CBgGTAf8CFQGcAf8CLwGnAf8BSAFJAbMB/wFBAUMBsQH/AUMBRgGyAf8BRQFI
AbQB/wFGAUoBtAH/AUcBTAG1Af8BSAFNAbUB/wFJAU4BtgH/AUoBTwG2Af8BSgFQAbcB/wFLAVABtwH/
AUsBUAG3Af8BSwFPAbcB/wFKAVABtwH/AUkBTgG2Af8BSAFNAbUB/wFIAUwBtQH/AUYBSgG0Af8BRQFI
AbIB/wFDAUYBsgH/AUEBQwGxAf8BSAFKAbIB/wIvAacB/wEUARUBnAH/AggBlQH/AgwBggH/AyMBNAQA
AxcBIANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEU
CAADKQE/AbkBPQELAf8B3gFuAT0B/wHjAZYBaQH/AeUBmgFuAf8B5gGeAXMB/wHnAaEBgAH/AekBqgGN
Af8B8QHNAbwB/wH6AfAB7AH/A/4l/wH4AeYB3gH/AekBqQGNAf8B5wGiAYIB/wHmAaEBdgH/AeYBngFz
Af8B5AGaAW0B/wHjAZYBaAH/Ad0BbQE8Af8BqwE2AQoB/wMcAScIAANeAd0DRwH/A1sB/wN0Af8DjwH/
A4oB/wOMAf8DjAH/A44B/wOOAf8DjwH/A5AB/wOQAf8DkAH/A5AB/wOQAf8DkAH/A5AB/wOQAf8DjwH/
A44B/wOOAf8DjAH/A4sB/wOKAf8DjwH/A3QB/wNaAf8DSAH/A2IB7wgAAlgBYwHdAgABgwH/AhABlQH/
ATUBNAGnAf8CUAGzAf8BSAFJAbAB/wFJAUoBsgH/AUsBTAGyAf8BTQFOAbMB/wFNAVABtAH/AU4BUQG0
Af8BTwFTAbUB/wFPAVMBtQH/AU8BUwG1Af8BUAFTAbUB/wFQAVMBtQH/AU8BUwG1Af8BTwFTAbUB/wFP
AVMBtQH/AU4BUQG0Af8BTQFPAbQB/wFNAU4BswH/AUsBTAGyAf8BSQFKAbEB/wFIAUkBsAH/AlABswH/
AjQBpwH/Ag8BlAH/AgABhAH/AkwBaQHvCAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgAMPARQIAAMpAT8BuAE+AQsB/wHeAXIBQgH/AeQBmgFvAf8B5gGf
AYAB/wHsAb0BpgH/AfYB4AHVAf8B/gL8Gf8D/gH/Af4B/QH8Af8B+gHyAe4B/wH8AfcB9A3/AfkB7gHp
Af8B6QGuAZIB/wHnAaMBhQH/AeYBogGBAf8B5QGdAXwB/wHkAZkBdgH/Ad4BegFKAf8BqwE+ARIB/wEf
ARwBGwEnCAADUgFwA0cB/wNZAf8DeQH/A5UB/wOMAf8DjgH/A48B/wOQAf8DkAH/A5IB/wOSAf8DkgH/
A5IB/wOTAf8DkwH/A5IB/wOSAf8DkgH/A5EB/wOQAf8DjwH/A48B/wOOAf8DjAH/A5QB/wN5Af8DWgH/
A0YB/wNiAYoIAAJNAVQBcAIAAXEB/wIKAY0B/wI6AaQB/wJhAbYB/wJWAa8B/wJXAbEB/wFYAVkBsgH/
AVkBWgGzAf8BWgFbAbMB/wFcAV0BtAH/AVwBXgG0Af8BXAFeAbUB/wFcAV8BtAH/AV0BXwG1Af8BXQFf
AbUB/wFcAV8BtAH/AVwBXgG0Af8BXAFfAbQB/wFbAV0BtAH/AVoBWwGzAf8BWQFaAbIB/wJYAbIB/wJX
AbEB/wFXAVYBrwH/AmEBtQH/AjoBpAH/AgsBjgH/AgABbgH/AlsBZAGKCAADGAEgA04BgANbAYADaAGA
A3IBgAN0AYADdQGAA3UBgAN1AYADdQGAA3QBgANzAYADbwGAA2wBgANoAYADZQGAA2QBgANmAYADcgGA
A3UBgAN1AYADdQGAA3EBgANjAYADYgGAA2IBgANhAYADWwGAA0wBgAMPARQIAAEwASoBKAE/AbYBRAET
Af8B3wF9AVAB/wHtAcIBrwH/AfsB9AHxAf8D/hH/Af4C/QH/AfwB9wH1Af8B9gHhAdkB/wHzAdMBxwH/
Ae0BvgGrAf8B6gGvAZcB/wHoAakBjwH/AesBtQGfAf8B+wH0AfEN/wH5AewB5gH/AegBqgGNAf8B5gGl
AYYB/wHlAaEBgwH/AeQBngF9Af8B3gF7AU8B/wGpAT0BEgH/AR8BHAEbAScMAANlAegDSAH/A3EB/wOb
Af8DkAH/A5EB/wORAf8DkgH/A5MB/wOUAf8DlAH/A5QB/wOUAf8DlQH/A5UB/wOUAf8DlAH/A5QB/wOU
Af8DkwH/A5IB/wORAf8DkQH/A5AB/wObAf8DcQH/A0kB/wNfAfMDBAEGDAACNQF/AegCAAFyAf8CMAGc
Af8CawG5Af8CXQGwAf8CXQGxAf8CXwGxAf8CXwGyAf8BYAFhAbMB/wFhAWIBtAH/AWEBYgG0Af8BYQFj
AbQB/wFiAWMBtAH/AWIBZAG1Af8BYgFkAbUB/wFiAWQBtAH/AWEBYgG0Af8BYQFiAbQB/wFhAWIBtAH/
AWABYQGzAf8CXwGyAf8CXwGxAf8BXgFdAbEB/wJdAbAB/wJrAbgB/wIwAZwB/wIAAXQB/wIsAX0B8wME
AQYIAAMYASADTgGAA1sBgANkAYADcQGAA3QBgAN0AYADcAGAA20BgANqAYADZgGAA2QBgANkAYADZAGA
A2QBgANkAYADZAGAA2QBgANmAYADcgGAA3UBgAN1AYADdQGAA28BgANjAYADYwGAA2IBgANbAYADTAGA
Aw8BFAgAATABKgEoAT8BswFEARMB/wHeAYIBVgH/AecBrAGRAf8B+QHtAegB/wP+Af8B/QH6AfkB/wH4
AeoB5AH/AfQB2QHNAf8B7wHKAbgB/wHqAbYBngH/AekBsAGUAf8B6AGuAZIB/wHoAa4BkgH/AegBrgGS
Af8B6AGuAZIB/wHoAa4BkgH/AegBrgGSAf8B6wG4AaAB/wH6AfIB7g3/AfYB4QHYAf8B5gGnAYsB/wHm
AaUBiAH/AeUBowGEAf8B3gGBAVUB/wGmAT0BEQH/AR8CHAEnDAADQgFVA0IB/wNVAf8DjgH/A54B/wOT
Af8DlQH/A5YB/wOWAf8DlwH/A5cB/wOXAf8DmAH/A5gB/wOYAf8DlwH/A5cB/wOXAf8DlgH/A5YB/wOV
Af8DlQH/A5MB/wOdAf8DjwH/A1YB/wM/Af8DUAFoEAACQAFDAVUCAAFmAf8CDAGCAf8CWwGuAf8CcQG6
Af8CYgGyAf8CZQGzAf8CZgGzAf8BZgFnAbMB/wJnAbQB/wJnAbQB/wFnAWgBtAH/AWgBaQG1Af8BaAFp
AbUB/wFoAWkBtQH/AWcBaQG1Af8BZwFoAbQB/wJnAbQB/wFmAWcBswH/AWYBZwGzAf8CZgGyAf8CZQGy
Af8CYgGxAf8CcQG5Af8CXAGvAf8CDAGEAf8CAAFgAf8CTQFQAWgMAAMYASADTQGAA1wBgANjAYADZAGA
A2YBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZAGAA2QBgANkAYADZgGA
A3EBgAN1AYADdQGAA3UBgANtAYADYwGAA2MBgANcAYADSwGAAw8BFAgAATABKgEoAT8BsAFDARMB/wHd
AYQBXAH/AeUBpAGKAf8B5wGsAZQB/wHpAbkBogH/AecBsAGVAf8B5wGvAZIB/wHoAa8BkwH/AegBsQGV
Af8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHo
AbABlQH/AegBsAGVAf8B6gG3AZ8B/wH6AfAB6w3/AfMB2QHNAf8B5gGmAYwB/wHlAaQBiQH/Ad0BhAFb
Af8BowE9AREB/wEfAhwBJxAAA2cBoQNAAf8DYgH/A6gB/wOhAf8DmQH/A5oB/wOaAf8DmwH/A5sB/wOb
Af8DmwH/A5sB/wObAf8DmwH/A5sB/wOaAf8DmgH/A5oB/wOaAf8DmQH/A6AB/wOoAf8DZAH/Az4B/wNt
AbYYAAJYAW4BoQIAAWEB/wIgAYwB/wKCAcAB/wJ3AboB/wJrAbQB/wJuAbUB/wJuAbUB/wJuAbYB/wFv
AW4BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/Am8BtgH/AW8BbgG1Af8CbgG1Af8CbgG1
Af8CbgG1Af8CawG0Af8CdgG5Af8CggHAAf8CIQGOAf8CAAFcAf8CWQF2AbYQAAMYASADTQGAA10BgANj
AYADZAGAA2QBgANkAYADZAGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANl
AYADZQGAA2YBgANxAYADdQGAA3UBgAN0AYADZQGAA2MBgANdAYADSwGAAw8BFAgAATABKgEoAT8BrgFD
ARMB/wHdAYoBZAH/AeUBqAGOAf8B5gGqAZAB/wHmAa0BkgH/AecBrgGUAf8B5wGuAZYB/wHoAa8BlwH/
AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGv
AZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHpAbUBoAH/AfgB7AHnCf8D/gH/AecBrwGYAf8B5QGo
AY0B/wHcAYoBYwH/AaIBPQERAf8BHwIcAScTAAEBA10B1QNDAf8DbAH/A7IB/wOuAf8DnwH/A6EB/wOh
Af8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A58B/wOrAf8DswH/A28B/wM9
Af8DXwHjAwYBCBsAAQECNwFwAdUCAAFnAf8CLwGRAf8CkQHHAf8CiwHDAf8CdQG3Af8CeAG5Af8CeQG5
Af8CeQG6Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeQG5Af8CeAG5
Af8CdQG3Af8CiAHBAf8CkgHIAf8CMwGVAf8CAAFbAf8CNwFzAeMDBgEIEAADGAEgA00BgANcAYADZAGA
A2QBgANkAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGA
A2UBgANlAYADZgGAA3ABgAN0AYADcgGAA2UBgANkAYADXAGAA0sBgAMPARQIAAEwASoBKAE/AawBQgES
Af8B2wGJAWEB/wHlAakBkgH/AeYBrAGUAf8B5gGvAZYB/wHmAbABmAH/AecBsQGZAf8B5wGwAZoB/wHn
AbIBmgH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGb
Af8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGyAZoB/wHoAbQBngH/AfgB6gHjAf8D/gH/AfoB8gHv
Af8B5gGvAZgB/wHlAakBkQH/AdoBigFhAf8BoQE8AREB/wEfARwBGwEnFAADDgESA1AB0QM/Af8DZAH/
A6kB/wPCAf8DsQH/A6cB/wOoAf8DqQH/A6kB/wOpAf8DqQH/A6kB/wOpAf8DqAH/A6cB/wOvAf8DwQH/
A64B/wNnAf8DOQH/A10B3AMWARsgAAMOARICKAFhAdECAAFfAf8CJwGJAf8ChQG/Af8CqAHSAf8CkQHE
Af8CgwG8Af8ChQG9Af8ChQG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChgG+Af8ChQG9Af8CgwG8
Af8CjgHCAf8CpwHRAf8CiwHDAf8CLAGLAf8CAAFSAf8COQFuAdwDFgEbFAADEQEXA0sBgANXAYADZAGA
A2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGAA2UBgANlAYADZQGA
A2UBgANlAYADZQGAA2UBgANnAYADZgGAA2UBgANkAYADVwGAA0kBfgMGAQgIAAEjASABHwEtAZ4BOwER
Af8B0QFuAT4B/wHmAasBlwH/AeYBsAGZAf8B5gGzAZoB/wHnAbQBmwH/AecBtQGcAf8B5wG2AZwB/wHn
AbYBnAH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGd
Af8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG1AZwB/wHnAbYBnAH/AecBtwGeAf8B6QG/AagB/wHn
AbcBnwH/AeYBsAGZAf8B5gGrAZcB/wHRAW8BPgH/AZIBNgERAfwBDQIMARAYAAMEAQUDZQGlAzkB/wNF
Af8DhAH/A7MB/wPKAf8DxAH/A70B/wO1Af8DswH/A7MB/wO0Af8DuwH/A8QB/wPKAf8DugH/A4wB/wNF
Af8DNQH/A2kBsQMKAQ0oAAMEAQUCVQFtAaUCAAFTAf8CAAFsAf8CVAGgAf8ClQHFAf8CtAHXAf8CrQHT
Af8CogHNAf8CmAHHAf8ClAHFAf8ClAHFAf8ClwHGAf8CoAHLAf8CrAHSAf8CtQHXAf8CngHKAf8CXgGm
Af8CAAFtAf8CAAFJAf8CVwFxAbEDCgENHAADPQFkA08BgANXAYADXgGAA2ABgANgAYADYAGAA2EBgANh
AYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYQGAA2EBgANhAYADYAGAA2ABgANg
AYADXgGAA1cBgANPAYADNwFVEAABcgE+ASwBxwG/AU0BFQH/AdIBcAFBAf8B3AGRAXAB/wHfAZwBeQH/
AeABmwF6Af8B4gGdAXoB/wHjAZ8BewH/AeMBnwF8Af8B4wGgAXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGf
AXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGfAXwB/wHjAZ8BfAH/AeMBnwF8Af8B4wGgAXwB/wHjAZ8BewH/
AeMBnwF7Af8B4gGcAXoB/wHgAZsBegH/Ad4BnAF5Af8B3AGRAXAB/wHSAXABQQH/AbsBSgEUAf8BZwFD
ATYBqSQAA00BdANPAeYDOAH/A0oB/wN9Af8DlwH/A6wB/wO/Af8DxgH/A8cB/wPBAf8DsQH/A5oB/wOD
Af8DSgH/AzQB/wNJAewDWAF7OAACRgFQAXQCHgFnAeYCAAFQAf8CBAFyAf8CSwGaAf8CbwGuAf8CjAG+
Af8CpgHNAf8CsAHTAf8CsQHUAf8CqQHPAf8CkgHCAf8CcgGwAf8CUgGeAf8CBAFyAf8CAAFIAf8CGAFg
AewCUwFaAXskAAMMAQ8DPQFkA0sBgANNAYADTQGAA00BgANOAYADTgGAA04BgANOAYADTgGAA04BgANO
AYADTgGAA04BgANOAYADTgGAA04BgANOAYADTgGAA04BgANOAYADTQGAA00BgANNAYADSgF/AzkBWwMG
AQgQAAEXARYBFQEdAXIBPgEsAccBngE7AREB/wGsAUIBEgH/Aa0BQwETAf8BsAFDARMB/wGzAUQBEwH/
AbYBRAETAf8BuAFFARMB/wG5AUUBEwH/AbkBRQETAf8BuQFFARMB/wG5AUUBEwH/AbkBRQETAf8BuQFF
ARMB/wG5AUUBEwH/AbkBRQETAf8BuQFFARMB/wG5AUUBEwH/AbgBRQETAf8BtgFEARMB/wGzAUQBEwH/
AbABQwETAf8BrQFDARMB/wGsAUIBEgH/AZoBOgERAf4BbAFBATIBtQENAgwBECgAAwoBDQNbAZADSQHe
AzwB/wM5Af8DQAH/A04B/wNiAf8DZAH/A1AB/wNAAf8DOgH/AzoB/wNPAeADYQGWAw4BEkAAAwoBDQJQ
AWABkAIcAV8B3gIAAVkB/wIAAVMB/wIAAWIB/wIJAXUB/wIlAYYB/wIoAYgB/wIMAXYB/wIAAWEB/wIA
AVQB/wIAAVQB/wIjAWMB4AJXAWUBlgMOARIwAAMSARcDGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEg
AxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASADGAEg
Aw4BEyAAASMBIAEfAS0BMAErASgBPwEwASsBKQE/ATABKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEp
AT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwEx
ASsBKQE/ATEBKwEpAT8BMQErASkBPwExASsBKQE/ATEBKwEpAT8BMQErASkBPwEwASsBKQE/ATABKwEp
AT8BMAErASgBPwEeARwBGwEmPAADMAFAA1UBigNYAb4DTAHsA0EB8gNAAfIDTQHuA1wBwANVAY0DNQFE
WAACLgEwAUACSgFaAYoCPAFlAb4CGgFkAewCCgFZAfICCgFZAfICGgFkAe4CQQFpAcACSgFZAY0CMwE1
AUT/AC0AAUIBTQE+BwABPgMAASgDAAGAAwABQAMAAQEBAAEBBgABBBYAA///AP8AAwAB/wHgAQMC/wHg
AQMB/wHAAgABAwHAAgABAwH/AgAC/wIAAf8BgAIAAQMBgAIAAQMB/gIAAX8B/gIAAX8BgAIAAQEBgAIA
AQEB+AIAAR8B+AIAAR8BgAIAAQEBgAIAAQEB8AIAAQ8B8AIAAQ8BgAIAAQEBgAIAAQEB8AIAAQcB8AIA
AQcBgAIAAQEBgAIAAQEB4AIAAQcB4AIAAQcBgAIAAQEBgAIAAQEBwAIAAQMBwAIAAQMBgAIAAQEBgAIA
AQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIAAQEBgAIA
AQEBgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGA
AgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEB
AYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGA
AgABAQGAAgABAQGAAgABAQGAAgABAQHAAgABAQHAAgABAQGAAgABAQGAAgABAQHAAgABAwHAAgABAwGA
AgABAQGAAgABAQHgAgABBwHgAgABBwGAAgABAQGAAgABAQHgAgABBwHgAgABBwGAAgABAQGAAgABAQHw
AgABDwHwAgABDwGAAgABAQGAAgABAQH4AgABHwH4AgABHwHAAgABAwHAAgABAwH+AgABfwH+AgABfwHA
AgABAwHAAgABAwH/AgAC/wIAAf8B8AIAAQ8B8AIAAQ8B/wHgAQcC/wHgAQcJ/ws=
Af8B6QGuAZIB/wHnAaMBhQH/AeYBogGBAf8B5QGdAXQB/wHkAZkBbgH/Ad4BcgFCAf8BqwE2AQoB/wMc
AScIAANAAXADPwH/A1EB/wNxAf8DlQH/A4wB/wOOAf8DjwH/A5AB/wOQAf8DkgH/A5IB/wOSAf8DkgH/
A5MB/wOTAf8DkgH/A5IB/wOSAf8DkQH/A5AB/wOPAf8DjwH/A44B/wOMAf8DlAH/A3EB/wNSAf8DPgH/
A0oBiggAA0ABcAIAAWkB/wICAY0B/wIyAaQB/wJZAbYB/wJOAa8B/wJPAbEB/wFQAVEBsgH/AVEBUgGz
Af8BUgFTAbMB/wFUAVUBtAH/AVQBVgG0Af8BVAFWAbUB/wFUAVcBtAH/AVUBVwG1Af8BVQFXAbUB/wFU
AVcBtAH/AVQBVgG0Af8BVAFXAbQB/wFTAVUBtAH/AVIBUwGzAf8BUQFSAbIB/wJQAbIB/wJPAbEB/wFP
AU4BrwH/AlkBtQH/AjIBpAH/AgMBjgH/AgABZgH/A0oBiggAAxcBIANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AbYBPAELAf8B3wF1AUgB/wHt
AcIBrwH/AfsB9AHxAf8D/hH/Af4C/QH/AfwB9wH1Af8B9gHhAdkB/wHzAdMBxwH/Ae0BvgGrAf8B6gGv
AZcB/wHoAakBjwH/AesBtQGfAf8B+wH0AfEN/wH5AewB5gH/AegBqgGNAf8B5gGlAYYB/wHlAaEBgwH/
AeQBngF1Af8B3gFzAUcB/wGpATUBCgH/AxwBJwwAA18B6ANAAf8DaQH/A5sB/wOQAf8DkQH/A5EB/wOS
Af8DkwH/A5QB/wOUAf8DlAH/A5QB/wOVAf8DlQH/A5QB/wOUAf8DlAH/A5QB/wOTAf8DkgH/A5EB/wOR
Af8DkAH/A5sB/wNpAf8DQQH/A18B8wMEAQYMAAJVAWcB6AIAAWoB/wIoAZwB/wJjAbkB/wJVAbAB/wJV
AbEB/wJXAbEB/wJXAbIB/wFYAVkBswH/AVkBWgG0Af8BWQFaAbQB/wFZAVsBtAH/AVoBWwG0Af8BWgFc
AbUB/wFaAVwBtQH/AVoBXAG0Af8BWQFaAbQB/wFZAVoBtAH/AVkBWgG0Af8BWAFZAbMB/wJXAbIB/wJX
AbEB/wFWAVUBsQH/AlUBsAH/AmMBuAH/AigBnAH/AgABbAH/AkQBbAHzAwQBBggAAxcBIANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGA
A0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADDwEUCAADKQE/AbMBPAEL
Af8B3gGCAU4B/wHnAawBkQH/AfkB7QHoAf8D/gH/Af0B+gH5Af8B+AHqAeQB/wH0AdkBzQH/Ae8BygG4
Af8B6gG2AZ4B/wHpAbABlAH/AegBrgGSAf8B6AGuAZIB/wHoAa4BkgH/AegBrgGSAf8B6AGuAZIB/wHo
Aa4BkgH/AesBuAGgAf8B+gHyAe4N/wH2AeEB2AH/AeYBpwGLAf8B5gGlAYgB/wHlAaMBhAH/Ad4BgQFN
Af8BpgE1AQkB/wMcAScMAAM1AVUDOgH/A00B/wOOAf8DngH/A5MB/wOVAf8DlgH/A5YB/wOXAf8DlwH/
A5cB/wOYAf8DmAH/A5gB/wOXAf8DlwH/A5cB/wOWAf8DlgH/A5UB/wOVAf8DkwH/A50B/wOPAf8DTgH/
AzcB/wM9AWgQAAM1AVUCAAFeAf8CBAGCAf8CUwGuAf8CaQG6Af8CWgGyAf8CXQGzAf8CXgGzAf8BXgFf
AbMB/wJfAbQB/wJfAbQB/wFfAWABtAH/AWABYQG1Af8BYAFhAbUB/wFgAWEBtQH/AV8BYQG1Af8BXwFg
AbQB/wJfAbQB/wFeAV8BswH/AV4BXwGzAf8CXgGyAf8CXQGyAf8CWgGxAf8CaQG5Af8CVAGvAf8CBAGE
Af8CAAFYAf8DPQFoDAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgAMPARQIAAMpAT8BsAE7AQsB/wHdAYQBVAH/AeUBpAGKAf8B5wGsAZQB/wHpAbkBogH/
AecBsAGVAf8B5wGvAZIB/wHoAa8BkwH/AegBsQGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6AGw
AZUB/wHoAbABlQH/AegBsAGVAf8B6AGwAZUB/wHoAbABlQH/AegBsAGVAf8B6gG3AZ8B/wH6AfAB6w3/
AfMB2QHNAf8B5gGmAYwB/wHlAaQBiQH/Ad0BhAFTAf8BowE1AQkB/wMcAScQAANSAaEDOAH/A1oB/wOo
Af8DoQH/A5kB/wOaAf8DmgH/A5sB/wObAf8DmwH/A5sB/wObAf8DmwH/A5sB/wObAf8DmgH/A5oB/wOa
Af8DmgH/A5kB/wOgAf8DqAH/A1wB/wM2Af8DVgG2GAADUgGhAgABWQH/AhgBjAH/AoIBwAH/Am8BugH/
AmMBtAH/AmYBtQH/AmYBtQH/AmYBtgH/AWcBZgG2Af8CZwG2Af8CZwG2Af8CZwG2Af8CZwG2Af8CZwG2
Af8CZwG2Af8BZwFmAbUB/wJmAbUB/wJmAbUB/wJmAbUB/wJjAbQB/wJuAbkB/wKCAcAB/wIZAY4B/wIA
AVQB/wNWAbYQAAMXASADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAAw8BFAgAAykBPwGuATsBCwH/Ad0BigFcAf8B5QGoAY4B/wHmAaoBkAH/AeYBrQGSAf8B5wGu
AZQB/wHnAa4BlgH/AegBrwGXAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/
AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AegBrwGYAf8B6AGvAZgB/wHoAa8BmAH/AekBtQGgAf8B+AHs
AecJ/wP+Af8B5wGvAZgB/wHlAagBjQH/AdwBigFbAf8BogE1AQkB/wMcAScTAAEBA14B1QM7Af8DZAH/
A7IB/wOuAf8DnwH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/A6EB/wOhAf8DoQH/
A58B/wOrAf8DswH/A2cB/wM1Af8DXwHjAwYBCBsAAQECWgFeAdUCAAFfAf8CJwGRAf8CkQHHAf8CiwHD
Af8CbQG3Af8CcAG5Af8CcQG5Af8CcQG6Af8CcQG5Af8CcQG5Af8CcQG5Af8CcQG5Af8CcQG5Af8CcQG5
Af8CcQG5Af8CcQG5Af8CcAG5Af8CbQG3Af8CiAHBAf8CkgHIAf8CKwGVAf8CAAFTAf8CWAFfAeMDBgEI
EAADFwEgA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgAMP
ARQIAAMpAT8BrAE6AQoB/wHbAYkBWQH/AeUBqQGSAf8B5gGsAZQB/wHmAa8BlgH/AeYBsAGYAf8B5wGx
AZkB/wHnAbABmgH/AecBsgGaAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/
AecBswGbAf8B5wGzAZsB/wHnAbMBmwH/AecBswGbAf8B5wGzAZsB/wHnAbIBmgH/AegBtAGeAf8B+AHq
AeMB/wP+Af8B+gHyAe8B/wHmAa8BmAH/AeUBqQGRAf8B2gGKAVkB/wGhATQBCQH/AxwBJxQAAw0BEgNY
AdEDNwH/A1wB/wOpAf8DwgH/A7EB/wOnAf8DqAH/A6kB/wOpAf8DqQH/A6kB/wOpAf8DqQH/A6gB/wOn
Af8DrwH/A8EB/wOuAf8DXwH/AzEB/wNcAdwDFAEbIAADDQESAlgBXAHRAgABVwH/Ah8BiQH/AoUBvwH/
AqgB0gH/ApEBxAH/AoMBvAH/AoUBvQH/AoUBvgH/AoYBvgH/AoYBvgH/AoYBvgH/AoYBvgH/AoYBvgH/
AoUBvQH/AoMBvAH/Ao4BwgH/AqcB0QH/AosBwwH/AiQBiwH/AgABSgH/AlgBYQHcAxQBGxQAAxEBFwNH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANGAX4DBgEICAADHwEt
AZ4BMwEJAf8B0QFmATYB/wHmAasBlwH/AeYBsAGZAf8B5gGzAZoB/wHnAbQBmwH/AecBtQGcAf8B5wG2
AZwB/wHnAbYBnAH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/
AecBuQGdAf8B5wG5AZ0B/wHnAbkBnQH/AecBuQGdAf8B5wG1AZwB/wHnAbYBnAH/AecBtwGeAf8B6QG/
AagB/wHnAbcBnwH/AeYBsAGZAf8B5gGrAZcB/wHRAWcBNgH/AX0BLgEZAfwDDAEQGAADBAEFA1MBpQMx
Af8DPQH/A4QB/wOzAf8DygH/A8QB/wO9Af8DtQH/A7MB/wOzAf8DtAH/A7sB/wPEAf8DygH/A7oB/wOM
Af8DPQH/Ay0B/wNWAbEDCgENKAADBAEFA1MBpQIAAUsB/wIAAWQB/wJMAaAB/wKVAcUB/wK0AdcB/wKt
AdMB/wKiAc0B/wKYAccB/wKUAcUB/wKUAcUB/wKXAcYB/wKgAcsB/wKsAdIB/wK1AdcB/wKeAcoB/wJW
AaYB/wIAAWUB/wIAAUEB/wNWAbEDCgENHAADOwFkA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADNQFVEAADWQHHAb8BRQENAf8B0gFoATkB/wHcAZEBaAH/Ad8BnAFxAf8B4AGb
AXIB/wHiAZ0BcgH/AeMBnwFzAf8B4wGfAXQB/wHjAaABdAH/AeMBnwF0Af8B4wGfAXQB/wHjAZ8BdAH/
AeMBnwF0Af8B4wGfAXQB/wHjAZ8BdAH/AeMBnwF0Af8B4wGfAXQB/wHjAaABdAH/AeMBnwFzAf8B4wGf
AXMB/wHiAZwBcgH/AeABmwFyAf8B3gGcAXEB/wHcAZEBaAH/AdIBaAE5Af8BuwFCAQwB/wNSAakkAANC
AXQDWAHmAzAB/wNCAf8DdQH/A5cB/wOsAf8DvwH/A8YB/wPHAf8DwQH/A7EB/wOaAf8DgwH/A0IB/wMs
Af8DWQHsA0QBezgAA0IBdAJSAWEB5gIAAUgB/wIAAWoB/wJDAZoB/wJnAa4B/wKMAb4B/wKmAc0B/wKw
AdMB/wKxAdQB/wKpAc8B/wKSAcIB/wJqAbAB/wJKAZ4B/wIAAWoB/wIAAUAB/wJKAWAB7ANEAXskAAML
AQ8DOwFkA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANH
AYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRwGAA0cBgANHAYADRgF/AzcBWwMGAQgQAAMVAR0DWQHH
AZ4BMwEJAf8BrAE6AQoB/wGtATsBCwH/AbABOwELAf8BswE8AQsB/wG2ATwBCwH/AbgBPQELAf8BuQE9
AQsB/wG5AT0BCwH/AbkBPQELAf8BuQE9AQsB/wG5AT0BCwH/AbkBPQELAf8BuQE9AQsB/wG5AT0BCwH/
AbkBPQELAf8BuQE9AQsB/wG4AT0BCwH/AbYBPAELAf8BswE8AQsB/wGwATsBCwH/Aa0BOwELAf8BrAE6
AQoB/wGKAToBEQH+A1UBtQMMARAoAAMKAQ0DTAGQA1oB3gM0Af8DMQH/AzgB/wNGAf8DWgH/A1wB/wNI
Af8DOAH/AzIB/wMyAf8DXgHgA04BlgMNARJAAAMKAQ0DTAGQAlUBXwHeAgABUQH/AgABSwH/AgABWgH/
AgEBbQH/Ah0BhgH/AiABiAH/AgQBbgH/AgABWQH/AgABTAH/AgABTAH/AlkBXwHgA04BlgMNARIwAAMR
BBcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMXASADFwEg
AxcBIAMXASADFwEgAxcBIAMXASADFwEgAxcBIAMOARMgAAMfAS0DKQE/AykBPwMpAT8DKQE/AykBPwMp
AT8DKQE/AykBPwMpAT8DKQE/AykBPwMpAT8DKQE/AykBPwMpAT8DKQE/AykBPwMpAT8DKQE/AykBPwMp
AT8DKQE/AxsBJjwAAyoBQANKAYoDWQG+A1kB7ANNAfIDTQHyA1QB7gNaAcADSgGNAywBRFgAAyoBQANK
AYoCVgFZAb4CSgFkAewCOQFZAfICOQFZAfICRgFhAe4CWAFaAcADSgGNAywBRP8ALQABQgFNAT4HAAE+
AwABKAMAAYADAAFAAwABAQEAAQEGAAEEFgAD//8A/wADAAH/AeABAwL/AeABAwH/AcACAAEDAcACAAED
Af8CAAL/AgAB/wGAAgABAwGAAgABAwH+AgABfwH+AgABfwGAAgABAQGAAgABAQH4AgABHwH4AgABHwGA
AgABAQGAAgABAQHwAgABDwHwAgABDwGAAgABAQGAAgABAQHwAgABBwHwAgABBwGAAgABAQGAAgABAQHg
AgABBwHgAgABBwGAAgABAQGAAgABAQHAAgABAwHAAgABAwGAAgABAQGAAgABAQGAAgABAQGAAgABAQGA
AgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQGAAgABAQgA
AYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIA
AQEBgAIAAQEIAAGAAgABAQGAAgABAQgAAYACAAEBAYACAAEBCAABgAIAAQEBgAIAAQEIAAGAAgABAQGA
AgABAQgAAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEBAYACAAEB
AYACAAEBAcACAAEBAcACAAEBAYACAAEBAYACAAEBAcACAAEDAcACAAEDAYACAAEBAYACAAEBAeACAAEH
AeACAAEHAYACAAEBAYACAAEBAeACAAEHAeACAAEHAYACAAEBAYACAAEBAfACAAEPAfACAAEPAYACAAEB
AYACAAEBAfgCAAEfAfgCAAEfAcACAAEDAcACAAEDAf4CAAF/Af4CAAF/AcACAAEDAcACAAEDAf8CAAL/
AgAB/wHwAgABDwHwAgABDwH/AeABBwL/AeABBwn/Cw==
</value>
</data>
<metadata name="FolderBrowserDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>154, 17</value>
</metadata>
<metadata name="TryConnectTimer.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>319, 17</value>
</metadata>
</root>

View File

@ -3,7 +3,6 @@ using System.Windows.Forms;
using SimConnect;
using SimConnect.Concrete;
using SimpleInjector;
using SimpleInjector.Diagnostics;
namespace FSFlightLogger
{

126
FSFlightLogger/Resources/i18n.Designer.cs generated Normal file
View File

@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
// <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 FSFlightLogger.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class i18n {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal i18n() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FSFlightLogger.Resources.i18n", typeof(i18n).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Flight logs.
/// </summary>
internal static string DefaultFolderName {
get {
return ResourceManager.GetString("DefaultFolderName", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connected.
/// </summary>
internal static string FlightSimulatorConnected {
get {
return ResourceManager.GetString("FlightSimulatorConnected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connecting....
/// </summary>
internal static string FlightSimulatorConnecting {
get {
return ResourceManager.GetString("FlightSimulatorConnecting", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Failed, will retry in {0} seconds....
/// </summary>
internal static string FlightSimulatorFailed {
get {
return ResourceManager.GetString("FlightSimulatorFailed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Closed, will retry in {0} seconds....
/// </summary>
internal static string FlightSimulatorQuit {
get {
return ResourceManager.GetString("FlightSimulatorQuit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Start recording.
/// </summary>
internal static string RecordButtonStart {
get {
return ResourceManager.GetString("RecordButtonStart", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &amp;Stop recording.
/// </summary>
internal static string RecordButtonStop {
get {
return ResourceManager.GetString("RecordButtonStop", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,141 @@
<?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.Runtime.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:import namespace="http://www.w3.org/XML/1998/namespace" />
<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" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</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" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</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=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DefaultFolderName" xml:space="preserve">
<value>Flight logs</value>
</data>
<data name="FlightSimulatorConnected" xml:space="preserve">
<value>Connected</value>
</data>
<data name="FlightSimulatorConnecting" xml:space="preserve">
<value>Connecting...</value>
</data>
<data name="FlightSimulatorFailed" xml:space="preserve">
<value>Failed, will retry in {0} seconds...</value>
</data>
<data name="FlightSimulatorQuit" xml:space="preserve">
<value>Closed, will retry in {0} seconds...</value>
</data>
<data name="RecordButtonStart" xml:space="preserve">
<value>&amp;Start recording</value>
</data>
<data name="RecordButtonStop" xml:space="preserve">
<value>&amp;Stop recording</value>
</data>
</root>

View File

@ -0,0 +1,20 @@
namespace FSFlightLogger
{
public class Settings
{
public int? MainFormLeft { get; set; }
public int? MainFormTop { get; set; }
public bool CSVEnabled { get; set; }
public string CSVPath { get; set; }
public bool KMLEnabled { get; set; }
public string KMLPath { get; set; }
public bool KMLLiveEnabled { get; set; }
public int KMLLivePort { get; set; }
public bool TriggerConnected { get; set; }
public bool TriggerWaitForMovement { get; set; }
public bool TriggerNewLogStationaryEnabled { get; set; }
public int TriggerNewLogStationarySeconds { get; set; }
}
}

View File

@ -0,0 +1,246 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FlightLoggerLib;
using FlightLoggerLib.Concrete;
using Nito.AsyncEx;
using SimConnect;
using SimConnect.Attribute;
using SimConnect.Lib;
namespace FSFlightLogger
{
public class SimConnectLogger : IAsyncDisposable
{
private readonly ISimConnectClient client;
private SimConnectLoggerWorker worker;
public class Config
{
public string CSVOutputPath { get; set; }
public string KMLOutputPath { get; set; }
public int? KMLLivePort { get; set; }
public TimeSpan IntervalTime { get; set; }
public int IntervalDistance { get; set; }
public bool WaitForMovement { get; set; }
public TimeSpan? NewLogWhenIdleSeconds { get; set; }
}
public SimConnectLogger(ISimConnectClient client)
{
this.client = client;
}
public async ValueTask DisposeAsync()
{
// Do not dispose of client, it is managed by the caller
await Stop();
}
public async Task Start(Config config)
{
await Stop();
worker = new SimConnectLoggerWorker(client, config);
}
public async Task Stop()
{
if (worker != null)
{
await worker.DisposeAsync();
worker = null;
}
}
private class SimConnectLoggerWorker : IAsyncDisposable
{
private readonly Config config;
private readonly List<IFlightLogger> loggers = new List<IFlightLogger>();
private IAsyncDisposable definition;
private IAsyncDisposable simEvent;
private IAsyncDisposable pauseEvent;
private DateTime lastTime;
private PositionData lastData = PositionData.Empty();
private bool waitingForMovement;
private DateTime lastStopped;
private readonly AsyncLock simStateLock = new AsyncLock();
private volatile bool paused = true;
private volatile bool running = false;
public SimConnectLoggerWorker(ISimConnectClient client, Config config)
{
this.config = config;
if (!string.IsNullOrEmpty(config.CSVOutputPath))
{
Directory.CreateDirectory(config.CSVOutputPath);
loggers.Add(new CSVFlightLogger(config.CSVOutputPath));
}
if (!string.IsNullOrEmpty(config.KMLOutputPath))
{
Directory.CreateDirectory(config.KMLOutputPath);
loggers.Add(new KMLFlightLogger(config.KMLOutputPath, TimeSpan.FromSeconds(5)));
}
if (config.KMLLivePort.HasValue)
loggers.Add(new KMLLiveFlightLogger(config.KMLLivePort.Value));
waitingForMovement = config.WaitForMovement;
lastStopped = DateTime.Now;
pauseEvent = client.SubscribeToSystemEvent(SimConnectSystemEvent.Pause, HandlePauseEvent);
simEvent = client.SubscribeToSystemEvent(SimConnectSystemEvent.Sim, HandleSimEvent);
definition = client.AddDefinition<PositionData>(HandlePositionData);
}
public async ValueTask DisposeAsync()
{
if (definition != null)
{
await definition.DisposeAsync();
definition = null;
}
if (pauseEvent != null)
{
await pauseEvent.DisposeAsync();
pauseEvent = null;
}
if (simEvent != null)
{
await simEvent.DisposeAsync();
simEvent = null;
}
foreach (var logger in loggers)
await logger.DisposeAsync();
}
private async Task HandlePauseEvent(SimConnectSystemEventArgs args)
{
using (await simStateLock.LockAsync())
{
paused = ((SimConnectPauseSystemEventArgs)args).Paused;
}
}
private async Task HandleSimEvent(SimConnectSystemEventArgs args)
{
using (await simStateLock.LockAsync())
{
running = ((SimConnectSimSystemEventArgs)args).SimRunning;
}
}
private async Task HandlePositionData(PositionData data)
{
if (paused || !running)
return;
var moving = data.Airspeed > 0;
if (!moving && waitingForMovement)
return;
waitingForMovement = false;
// TODO take vertical position into account when going straight up or down (not a common use case, but still)
var distanceMeters = LatLon.DistanceBetweenInMeters(lastData.Latitude, lastData.Longitude, data.Latitude, data.Longitude);
if (distanceMeters < config.IntervalDistance)
{
if (data.Airspeed < 0.1)
data.Airspeed = 0;
// Make an exception if we were last moving and have now stopped, so the 0 velocity record is logged as well
if (data.Airspeed > 0 || lastData.Airspeed == 0)
return;
}
var now = DateTime.Now;
if (config.NewLogWhenIdleSeconds.HasValue)
{
if (moving)
{
if (now - lastStopped >= config.NewLogWhenIdleSeconds)
{
await Task.WhenAll(loggers.Select(logger =>
logger.NewLog()));
}
}
lastStopped = now;
}
var time = now - lastTime;
if (time < config.IntervalTime)
return;
lastTime = now;
lastData = data;
// ReSharper disable once AccessToDisposedClosure - covered by disposing the client first
await Task.WhenAll(loggers.Select(logger =>
logger.LogPosition(now, new FlightPosition
{
Latitude = data.Latitude,
Longitude = data.Longitude,
Altitude = data.Altitude,
Airspeed = data.Airspeed
})));
}
}
// ReSharper disable once ClassNeverInstantiated.Local - it is, by the SimConnect client
public class PositionData
{
[SimConnectVariable("PLANE LATITUDE", "degrees")]
public float Latitude;
[SimConnectVariable("PLANE LONGITUDE", "degrees")]
public float Longitude;
[SimConnectVariable("PLANE ALTITUDE", "feet")]
public float Altitude;
[SimConnectVariable("AIRSPEED INDICATED", "knots")]
public float Airspeed;
public static PositionData Empty()
{
return new PositionData
{
Latitude = 0,
Longitude = 0,
Altitude = 0,
Airspeed = 0
};
}
}
}
}

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <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 FSFlightLogger {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class en_US {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal en_US() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FSFlightLogger.en-US", typeof(en_US).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 1.3
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">1.3</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1">this is my long string</data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
[base64 mime encoded serialized .NET Framework object]
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
[base64 mime encoded string representing a byte array form of the .NET Framework object]
</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="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>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -1,7 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.0.0" targetFramework="net472" />
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
<package id="Nito.AsyncEx.Context" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Coordination" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Interop.WaitHandles" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Oop" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Tasks" version="5.0.0" targetFramework="net472" />
<package id="Nito.Cancellation" version="1.0.5" targetFramework="net472" />
<package id="Nito.Collections.Deque" version="1.0.4" targetFramework="net472" />
<package id="Nito.Disposables" version="2.0.0" targetFramework="net472" />
<package id="SimpleInjector" version="5.0.3" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.5.2" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.2" targetFramework="net472" />
<package id="System.Collections.Immutable" version="1.4.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages>

View File

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

View File

@ -55,8 +55,17 @@
<Reference Include="CommandLine, Version=2.8.0.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
<HintPath>..\packages\CommandLineParser.2.8.0\lib\net461\CommandLine.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />

View File

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
@ -15,6 +16,7 @@ using SimConnect.Lib;
namespace FSFlightLoggerCmd
{
// TODO verb for converting CSV to KML
// TODO with the introduction of the GUI and SimConnectLogger, either port to SimConnectLogger for a single code base or remove this project
public class PositionData
@ -115,7 +117,7 @@ namespace FSFlightLoggerCmd
};
client.AddDefinition<PositionData>(data =>
client.AddDefinition<PositionData>(async data =>
{
// TODO take vertical position into account when going straight up or down (not a common use case, but still)
var distanceMeters = LatLon.DistanceBetweenInMeters(lastData.Latitude, lastData.Longitude, data.Latitude, data.Longitude);
@ -140,14 +142,14 @@ namespace FSFlightLoggerCmd
lastData = data;
// ReSharper disable once AccessToDisposedClosure - covered by disposing the client first
loggers.ForEach(logger =>
await Task.WhenAll(loggers.Select(logger =>
logger.LogPosition(now, new FlightPosition
{
Latitude = data.Latitude,
Longitude = data.Longitude,
Altitude = data.Altitude,
Airspeed = data.Airspeed
}));
})));
});
@ -163,8 +165,8 @@ namespace FSFlightLoggerCmd
stopEvent.Wait(Timeout.Infinite);
Console.WriteLine("Closing...");
client.Dispose();
loggers.ForEach(logger => logger.Dispose());
client.DisposeAsync();
loggers.ForEach(logger => logger.DisposeAsync());
if (!Debugger.IsAttached)
return;

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CommandLineParser" version="2.8.0" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages>

View File

@ -11,25 +11,30 @@ namespace FlightLoggerLib.Concrete
{
public class CSVFlightLogger : IFlightLogger
{
private readonly CsvWriter output;
private readonly string path;
private CsvWriter output;
public CSVFlightLogger(string path)
{
var filename = Path.Combine(path, DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss") + ".csv");
var header = !File.Exists(filename);
output = new CsvWriter(new StreamWriter(filename, true), new CsvConfiguration(CultureInfo.CurrentCulture)
{
SanitizeForInjection = false,
HasHeaderRecord = header
});
this.path = path;
}
public void Dispose()
public async ValueTask DisposeAsync()
{
output?.Dispose();
if (output != null)
await output.DisposeAsync();
}
public async Task NewLog()
{
if (output != null)
{
await output.DisposeAsync();
output = null;
}
}
@ -44,6 +49,18 @@ namespace FlightLoggerLib.Concrete
Airspeed = position.Airspeed
};
if (output == null)
{
var filename = Path.Combine(path, eventTime.ToString("yyyy-MM-dd HH.mm.ss") + ".csv");
var header = !File.Exists(filename);
output = new CsvWriter(new StreamWriter(filename, true), new CsvConfiguration(CultureInfo.CurrentCulture)
{
SanitizeForInjection = false,
HasHeaderRecord = header
});
}
await output.WriteRecordsAsync(Enumerable.Repeat(record, 1));
await output.FlushAsync();
}

View File

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq.Expressions;
using System.Threading.Tasks;
using SharpKml.Base;
using SharpKml.Dom;
@ -9,11 +8,12 @@ namespace FlightLoggerLib.Concrete
{
public class KMLFlightLogger : IFlightLogger
{
private readonly string filename;
private readonly string path;
private string filename;
private readonly System.TimeSpan flushInterval;
private readonly Document output;
private readonly Folder rootFolder;
private readonly LineString positionPath;
private Document output;
private Folder rootFolder;
private LineString positionPath;
private DateTime lastFlush = DateTime.MinValue;
private Vector lastPosition;
@ -23,11 +23,74 @@ namespace FlightLoggerLib.Concrete
public KMLFlightLogger(string path, System.TimeSpan flushInterval)
{
var dateString = DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss");
filename = Path.Combine(path, dateString + ".kml");
this.path = path;
this.flushInterval = flushInterval;
}
public async Task NewLog()
{
if (output == null)
return;
CheckEndPoint();
await Flush();
output = null;
}
public async ValueTask DisposeAsync()
{
if (output == null)
return;
CheckEndPoint();
await Flush();
}
protected void CheckEndPoint()
{
// TODO replace last "full stop" point if they match
if (lastPosition != null)
{
AddPoint(lastPosition, "End", "end");
lastPosition = null;
}
}
protected async Task AutoFlush()
{
var now = DateTime.Now;
var diff = now - lastFlush;
if (diff < flushInterval)
return;
await Flush();
lastFlush = now;
}
protected async Task Flush()
{
var serializer = new Serializer();
serializer.Serialize(output);
using (var writer = new StreamWriter(filename))
{
await writer.WriteAsync(serializer.Xml);
}
}
protected void EnsureOutput(DateTime eventTime)
{
if (output != null)
return;
var dateString = eventTime.ToString("yyyy-MM-dd HH.mm.ss");
filename = Path.Combine(path, dateString + ".kml");
// Create folder
rootFolder = new Folder
{
@ -65,38 +128,6 @@ namespace FlightLoggerLib.Concrete
output.AddFeature(rootFolder);
}
public void Dispose()
{
if (lastPosition != null)
AddPoint(lastPosition, "End", "end");
Flush().Wait();
}
protected async Task AutoFlush()
{
var now = DateTime.Now;
var diff = now - lastFlush;
if (diff < flushInterval)
return;
await Flush();
lastFlush = now;
}
protected async Task Flush()
{
var serializer = new Serializer();
serializer.Serialize(output);
using (var writer = new StreamWriter(filename))
{
await writer.WriteAsync(serializer.Xml);
}
}
protected void AddFlightPathStyleMap()
{
@ -185,6 +216,8 @@ namespace FlightLoggerLib.Concrete
public async Task LogPosition(DateTime eventTime, FlightPosition position)
{
EnsureOutput(eventTime);
var altitudeMeters = position.Altitude * MetersPerFoot;
var coordinate = new Vector(position.Latitude, position.Longitude, altitudeMeters);

View File

@ -0,0 +1,164 @@
using System;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Ionic.BZip2;
using Nito.AsyncEx;
using SharpKml.Base;
using SharpKml.Dom;
namespace FlightLoggerLib.Concrete
{
public class KMLLiveFlightLogger : IFlightLogger
{
private readonly CancellationTokenSource listenerCancellationTokenSource = new CancellationTokenSource();
private readonly HttpListener listener;
private readonly Task listenerTask;
private readonly string baseUrl;
private FlightPosition currentPosition;
private readonly AsyncLock currentPositionLock = new AsyncLock();
private byte[] entryDocument;
private Document dynamicDocument;
private Placemark dynamicPlacemark;
public KMLLiveFlightLogger(int port)
{
baseUrl = $"http://127.0.0.1:{port}/";
PrepareEntryDocument();
listener = new HttpListener();
listener.Prefixes.Add(baseUrl);
listener.Start();
listenerTask = Task.Run(RunServer);
}
public async ValueTask DisposeAsync()
{
listener?.Stop();
listenerCancellationTokenSource.Cancel();
await listenerTask;
}
protected void PrepareEntryDocument()
{
var networkLink = new NetworkLink
{
Name = "Refreshes every 5 seconds",
Link = new Link
{
Href = new Uri($"{baseUrl}live/dynamic", UriKind.Absolute),
RefreshMode = RefreshMode.OnInterval,
RefreshInterval = 5
}
};
var output = new Document();
output.AddFeature(networkLink);
var serializer = new Serializer();
serializer.Serialize(output);
entryDocument = Encoding.UTF8.GetBytes(serializer.Xml);
}
protected void PrepareDynamicDocument(Vector coordinate, float altitudeInFeet)
{
var name = $"Live location ({altitudeInFeet:#} feet)";
if (dynamicPlacemark != null)
{
dynamicPlacemark.Name = name;
((Point)dynamicPlacemark.Geometry).Coordinate = coordinate;
return;
}
var point = new Point { Coordinate = coordinate, AltitudeMode = AltitudeMode.Absolute };
dynamicPlacemark = new Placemark
{
Name = name,
//StyleUrl = new Uri("#" + styleMapId, UriKind.Relative),
Geometry = point,
Visibility = true
};
dynamicDocument = new Document();
dynamicDocument.AddFeature(dynamicPlacemark);
}
public Task NewLog()
{
return Task.CompletedTask;
}
public async Task LogPosition(DateTime eventTime, FlightPosition position)
{
using (await currentPositionLock.LockAsync())
{
currentPosition = position;
}
}
private const float MetersPerFoot = 0.3048f;
private async Task RunServer()
{
while (!listenerCancellationTokenSource.IsCancellationRequested)
{
var context = await listener.GetContextAsync();
switch (context.Request.Url.AbsolutePath)
{
case "/live":
context.Response.StatusCode = 200;
await context.Response.OutputStream.WriteAsync(entryDocument, 0, entryDocument.Length);
break;
case "/live/dynamic":
{
byte[] buffer;
using (await currentPositionLock.LockAsync())
{
var altitudeFeet = currentPosition?.Altitude ?? -10;
var altitudeMeters = altitudeFeet * MetersPerFoot;
PrepareDynamicDocument(new Vector
{
Latitude = currentPosition?.Latitude ?? 24.999979,
Longitude = currentPosition?.Longitude ?? -70.999997,
Altitude = altitudeMeters
},
altitudeFeet);
var serializer = new Serializer();
serializer.Serialize(dynamicDocument);
buffer = Encoding.UTF8.GetBytes(serializer.Xml);
}
context.Response.StatusCode = 200;
await context.Response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
break;
}
default:
context.Response.StatusCode = 404;
break;
}
context.Response.Close();
}
}
}
}

View File

@ -61,10 +61,37 @@
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Context, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Context.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Context.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Coordination, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Coordination.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Coordination.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Interop.WaitHandles, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Interop.WaitHandles.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Interop.WaitHandles.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Oop, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Oop.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Oop.dll</HintPath>
</Reference>
<Reference Include="Nito.AsyncEx.Tasks, Version=5.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.AsyncEx.Tasks.5.0.0\lib\netstandard2.0\Nito.AsyncEx.Tasks.dll</HintPath>
</Reference>
<Reference Include="Nito.Cancellation, Version=1.0.5.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Cancellation.1.0.5\lib\netstandard2.0\Nito.Cancellation.dll</HintPath>
</Reference>
<Reference Include="Nito.Collections.Deque, Version=1.0.4.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Collections.Deque.1.0.4\lib\netstandard2.0\Nito.Collections.Deque.dll</HintPath>
</Reference>
<Reference Include="Nito.Disposables, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nito.Disposables.2.0.0\lib\netstandard2.0\Nito.Disposables.dll</HintPath>
</Reference>
<Reference Include="SharpKml.Core, Version=5.1.3.0, Culture=neutral, PublicKeyToken=9bb853c026a5c0ac, processorArchitecture=MSIL">
<HintPath>..\packages\SharpKml.Core.5.1.3\lib\net45\SharpKml.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
@ -83,6 +110,7 @@
<ItemGroup>
<Compile Include="Concrete\KMLFlightLogger.cs" />
<Compile Include="Concrete\CSVFlightLogger.cs" />
<Compile Include="Concrete\KMLLiveFlightLogger.cs" />
<Compile Include="IFlightLogger.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

View File

@ -13,8 +13,9 @@ namespace FlightLoggerLib
public interface IFlightLogger : IDisposable
public interface IFlightLogger : IAsyncDisposable
{
Task NewLog();
Task LogPosition(DateTime eventTime, FlightPosition position);
//void LogEvent
}

View File

@ -4,7 +4,17 @@
<package id="DotNetZip.Reduced" version="1.9.1.8" targetFramework="net472" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" />
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net472" />
<package id="Nito.AsyncEx" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Context" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Coordination" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Interop.WaitHandles" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Oop" version="5.0.0" targetFramework="net472" />
<package id="Nito.AsyncEx.Tasks" version="5.0.0" targetFramework="net472" />
<package id="Nito.Cancellation" version="1.0.5" targetFramework="net472" />
<package id="Nito.Collections.Deque" version="1.0.4" targetFramework="net472" />
<package id="Nito.Disposables" version="2.0.0" targetFramework="net472" />
<package id="SharpKml.Core" version="5.1.3" targetFramework="net472" />
<package id="System.Collections.Immutable" version="1.4.0" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="System.Xml.XDocument" version="4.3.0" targetFramework="net472" />

View File

@ -5,7 +5,6 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace SimConnect.Concrete
{
@ -14,12 +13,16 @@ namespace SimConnect.Concrete
/// Requires the following DLL files to be present: FSX2020-SimConnect.dll, FSX-SE-SimConnect.dll, FSX-SE-SimConnect.dll, FSX-SimConnect.dll.
/// These are renamed versions of the SimConnect.dll from the various flight simulator installations.
/// </summary>
public class DefaultSimConnectClient : ISimConnectClient, IDisposable
public class DefaultSimConnectClient : ISimConnectClient
{
private readonly ISimConnectLibrary simConnectLibrary;
private SimConnectWorker worker;
private uint nextDefinitionID = 1;
private uint nextEventID = 1;
private readonly object observersLock = new object();
private readonly List<ISimConnectClientObserver> observers = new List<ISimConnectClientObserver>();
/// <summary>
@ -33,10 +36,12 @@ namespace SimConnect.Concrete
/// <inheritdoc />
public void Dispose()
public async ValueTask DisposeAsync()
{
worker?.Close().Wait();
simConnectLibrary?.Dispose();
worker?.Close();
if (simConnectLibrary != null)
await simConnectLibrary.DisposeAsync();
}
/// <summary>
@ -46,9 +51,7 @@ namespace SimConnect.Concrete
/// <returns></returns>
public async Task<bool> TryOpen(string appName)
{
if (worker != null)
await worker.Close();
worker?.Close();
worker = new SimConnectWorker(simConnectLibrary, appName);
return await worker.Open();
}
@ -57,12 +60,20 @@ namespace SimConnect.Concrete
/// <inheritdoc />
public void AttachObserver(ISimConnectClientObserver observer)
{
throw new NotImplementedException();
Monitor.Enter(observersLock);
try
{
observers.Add(observer);
}
finally
{
Monitor.Exit(observersLock);
}
}
/// <inheritdoc />
public IDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class
public IAsyncDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class
{
if (worker == null)
throw new InvalidOperationException("TryOpen must be called first");
@ -81,7 +92,41 @@ namespace SimConnect.Concrete
}
private class SimConnectDefinitionRegistration<T> : IDisposable where T : class
/// <inheritdoc />
public IAsyncDisposable SubscribeToSystemEvent(SimConnectSystemEvent systemEvent, SimConnectSystemEventAction onEvent)
{
if (worker == null)
throw new InvalidOperationException("TryOpen must be called first");
void HandleData(SimConnectRecvEvent recvEvent)
{
SimConnectSystemEventArgs args;
switch (systemEvent)
{
case SimConnectSystemEvent.Pause:
args = new SimConnectPauseSystemEventArgs { Paused = recvEvent.dwData == 1 };
break;
case SimConnectSystemEvent.Sim:
args = new SimConnectSimSystemEventArgs { SimRunning = recvEvent.dwData == 1 };
break;
default:
throw new ArgumentOutOfRangeException(nameof(systemEvent), systemEvent, null);
}
onEvent(args);
}
var eventRegistration = new SimConnectSystemEventRegistration(nextEventID, systemEvent, HandleData, worker);
nextEventID++;
return eventRegistration;
}
private class SimConnectDefinitionRegistration<T> : IAsyncDisposable where T : class
{
private readonly uint definitionID;
private readonly SimConnectWorker worker;
@ -96,9 +141,33 @@ namespace SimConnect.Concrete
}
public void Dispose()
public ValueTask DisposeAsync()
{
worker.UnregisterDefinition(definitionID);
return default;
}
}
private class SimConnectSystemEventRegistration : IAsyncDisposable
{
private readonly uint eventID;
private readonly SimConnectWorker worker;
public SimConnectSystemEventRegistration(uint eventID, SimConnectSystemEvent systemEvent, Action<SimConnectRecvEvent> onData, SimConnectWorker worker)
{
this.eventID = eventID;
this.worker = worker;
worker.SubscribeToSystemEvent(eventID, systemEvent, onData);
}
public ValueTask DisposeAsync()
{
worker.UnsubscribeFromSystemEvent(eventID);
return default;
}
}
@ -117,6 +186,7 @@ namespace SimConnect.Concrete
private readonly TaskCompletionSource<bool> openResult = new TaskCompletionSource<bool>();
private readonly ConcurrentDictionary<uint, Action<Stream>> definitionDataHandler = new ConcurrentDictionary<uint, Action<Stream>>();
private readonly ConcurrentDictionary<uint, Action<SimConnectRecvEvent>> eventDataHandler = new ConcurrentDictionary<uint, Action<SimConnectRecvEvent>>();
public SimConnectWorker(ISimConnectLibrary simConnectLibrary, string appName)
@ -135,12 +205,10 @@ namespace SimConnect.Concrete
}
public async Task Close()
public void Close()
{
closed = true;
workerPulse.Set();
await workerTask;
}
@ -170,6 +238,29 @@ namespace SimConnect.Concrete
}
public void SubscribeToSystemEvent(uint eventID, SimConnectSystemEvent systemEvent, Action<SimConnectRecvEvent> onData)
{
Enqueue(hSimConnect =>
{
eventDataHandler.AddOrUpdate(eventID, onData, (key, value) => onData);
var result = simConnectLibrary.SimConnect_SubscribeToSystemEvent(hSimConnect, eventID, systemEvent.ToString());
if (result == 0)
return;
});
}
public void UnsubscribeFromSystemEvent(uint eventID)
{
Enqueue(hSimConnect =>
{
eventDataHandler.TryRemove(eventID, out var unused);
simConnectLibrary.SimConnect_UnsubscribeFromSystemEvent(hSimConnect, eventID);
});
}
private void Enqueue(Action<IntPtr> work)
{
lock(workerLock)
@ -241,21 +332,30 @@ namespace SimConnect.Concrete
{
case SimConnectRecvID.Exception:
var recvException = Marshal.PtrToStructure<SimConnectRecvException>(dataPtr);
// TODO provide a way to get insight into exceptions
if (recvException.dwException == 0)
break;
break;
case SimConnectRecvID.Event:
var recvEvent = Marshal.PtrToStructure<SimConnectRecvEvent>(dataPtr);
if (!eventDataHandler.TryGetValue(recvEvent.uEventID, out var eventHandler))
break;
eventHandler(recvEvent);
break;
case SimConnectRecvID.SimobjectData:
case SimConnectRecvID.SimobjectDataByType:
var recvSimobjectData = Marshal.PtrToStructure<SimConnectRecvSimobjectData>(dataPtr);
if (!definitionDataHandler.TryGetValue((uint)recvSimobjectData.dwDefineID, out var dataHandler))
if (!definitionDataHandler.TryGetValue(recvSimobjectData.dwDefineID, out var dataHandler))
break;
unsafe
{
var streamOffset = Marshal.OffsetOf<SimConnectRecvSimobjectData>("dwData").ToInt32();
var stream = new UnmanagedMemoryStream((byte*)IntPtr.Add(dataPtr, streamOffset).ToPointer(), (long)dataSize - streamOffset);
var stream = new UnmanagedMemoryStream((byte*)IntPtr.Add(dataPtr, streamOffset).ToPointer(), dataSize - streamOffset);
dataHandler(stream);
}
break;

View File

@ -1,13 +1,50 @@
using System;
using System.Threading.Tasks;
namespace SimConnect
{
public enum SimConnectSystemEvent
{
/// <summary>
/// Request notifications when the flight is paused or unpaused. args will be of type SimConnectPauseSystemEventArgs.
/// </summary>
Pause,
/// <summary>
/// Request notifications when the flight is running or not. args will be of type SimConnectSimSystemEventArgs.
/// </summary>
Sim
}
public abstract class SimConnectSystemEventArgs
{
}
public class SimConnectPauseSystemEventArgs : SimConnectSystemEventArgs
{
public bool Paused { get; set; }
}
public class SimConnectSimSystemEventArgs : SimConnectSystemEventArgs
{
public bool SimRunning { get; set; }
}
public delegate Task SimConnectSystemEventAction(SimConnectSystemEventArgs args);
/// <summary>
/// Called when new data arrives from the SimConnect server.
/// </summary>
/// <param name="data">An instance of the data class as passed to AddDefinition containing the variable values</param>
/// <typeparam name="T">The data class as passed to AddDefinition</typeparam>
public delegate void SimConnectDataHandlerAction<in T>(T data) where T : class;
public delegate Task SimConnectDataHandlerAction<in T>(T data) where T : class;
/// <summary>
@ -26,7 +63,7 @@ namespace SimConnect
/// <summary>
/// Provides access to the SimConnect library.
/// </summary>
public interface ISimConnectClient : IDisposable
public interface ISimConnectClient : IAsyncDisposable
{
/// <summary>
/// Attaches the specified observer to receive status notifications.
@ -40,7 +77,16 @@ namespace SimConnect
/// </summary>
/// <param name="onData">A callback method which is called whenever a data update is received</param>
/// <typeparam name="T">A class defining the variables to monitor annotated using the SimConnectVariable attribute</typeparam>
/// <returns>An IDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception.</returns>
IDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class;
/// <returns>An IAsyncDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception.</returns>
IAsyncDisposable AddDefinition<T>(SimConnectDataHandlerAction<T> onData) where T : class;
/// <summary>
/// Subscribes to a SimConnect system event.
/// </summary>
/// <param name="systemEvent">The type of event to subscribe to.</param>
/// <param name="onEvent">The callback method called whenever the system event occurs.</param>
/// <returns>An IAsyncDisposable which can be disposed to unregister the definition. Dispose is not required to be called when the client is disconnected, but will not throw an exception.</returns>
IAsyncDisposable SubscribeToSystemEvent(SimConnectSystemEvent systemEvent, SimConnectSystemEventAction onEvent);
}
}

View File

@ -128,6 +128,17 @@ namespace SimConnect
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct SimConnectRecvEvent
{
public SimConnectRecv Recv;
public uint uGroupID;
public uint uEventID;
public uint dwData;
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct SimConnectRecvSimobjectData
{
@ -148,7 +159,7 @@ namespace SimConnect
/// <summary>
/// Provides a low-level interface to a compatible SimConnect.dll.
/// </summary>
public interface ISimConnectLibrary : IDisposable
public interface ISimConnectLibrary : IAsyncDisposable
{
uint SimConnect_Open(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex);
uint SimConnect_Close(IntPtr hSimConnect);
@ -158,6 +169,9 @@ namespace SimConnect
uint SimConnect_RequestDataOnSimObject(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0);
uint SimConnect_GetNextDispatch(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData);
uint SimConnect_SubscribeToSystemEvent(IntPtr hSimConnect, uint eventID, string systemEventName);
uint SimConnect_UnsubscribeFromSystemEvent(IntPtr hSimConnect, uint eventID);
}

View File

@ -56,8 +56,17 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -94,5 +103,9 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,10 +1,11 @@
using System;
using System.Threading.Tasks;
using SimConnect.Lib;
#pragma warning disable 1591
namespace SimConnect.Unmanaged
{
public class SimConnectDLLLibrary : ISimConnectLibrary, IDisposable
public class SimConnectDLLLibrary : ISimConnectLibrary, IAsyncDisposable
{
private delegate uint SimConnectOpenProc(out IntPtr phSimConnect, string szName, IntPtr hwnd, uint userEventWin32, IntPtr hEventHandle, uint configIndex);
private delegate uint SimConnectCloseProc(IntPtr hSimConnect);
@ -12,6 +13,9 @@ namespace SimConnect.Unmanaged
private delegate uint SimConnectClearDataDefinitionProc(IntPtr hSimConnect, uint defineID);
private delegate uint SimConnectRequestDataOnSimObjectProc(IntPtr hSimConnect, uint requestID, uint defineID, uint objectID, SimConnectPeriod period, uint flags, uint origin = 0, uint interval = 0, uint limit = 0);
private delegate uint SimConnectGetNextDispatchProc(IntPtr hSimConnect, out IntPtr ppData, out uint pcbData);
private delegate uint SimConnectSubscribeToSystemEventProc(IntPtr hSimConnect, uint eventID, string systemEventName);
private delegate uint SimConnectUnsubscribeFromSystemEventProc(IntPtr hSimConnect, uint eventID);
private readonly UnmanagedLibrary library;
@ -21,6 +25,8 @@ namespace SimConnect.Unmanaged
private readonly SimConnectClearDataDefinitionProc simConnectClearDataDefinition;
private readonly SimConnectRequestDataOnSimObjectProc simConnectRequestDataOnSimObject;
private readonly SimConnectGetNextDispatchProc simConnectGetNextDispatch;
private readonly SimConnectSubscribeToSystemEventProc simConnectSubscribeToSystemEvent;
private readonly SimConnectUnsubscribeFromSystemEventProc simConnectUnsubscribeFromSystemEvent;
@ -33,11 +39,14 @@ namespace SimConnect.Unmanaged
simConnectClearDataDefinition = library.GetUnmanagedFunction<SimConnectClearDataDefinitionProc>("SimConnect_ClearDataDefinition");
simConnectRequestDataOnSimObject = library.GetUnmanagedFunction<SimConnectRequestDataOnSimObjectProc>("SimConnect_RequestDataOnSimObject");
simConnectGetNextDispatch = library.GetUnmanagedFunction<SimConnectGetNextDispatchProc>("SimConnect_GetNextDispatch");
simConnectSubscribeToSystemEvent = library.GetUnmanagedFunction<SimConnectSubscribeToSystemEventProc>("SimConnect_SubscribeToSystemEvent");
simConnectUnsubscribeFromSystemEvent = library.GetUnmanagedFunction<SimConnectUnsubscribeFromSystemEventProc>("SimConnect_UnsubscribeFromSystemEvent");
}
public void Dispose()
public ValueTask DisposeAsync()
{
library?.Dispose();
return default;
}
@ -72,6 +81,17 @@ namespace SimConnect.Unmanaged
{
return simConnectGetNextDispatch(hSimConnect, out ppData, out pcbData);
}
public uint SimConnect_SubscribeToSystemEvent(IntPtr hSimConnect, uint eventID, string systemEventName)
{
return simConnectSubscribeToSystemEvent(hSimConnect, eventID, systemEventName);
}
public uint SimConnect_UnsubscribeFromSystemEvent(IntPtr hSimConnect, uint eventID)
{
return simConnectUnsubscribeFromSystemEvent(hSimConnect, eventID);
}
}
}
#pragma warning restore 1591

15
SimConnect/app.config Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<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-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl.AsyncInterfaces" version="1.1.1" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
</packages>

View File

@ -65,6 +65,12 @@
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=4.0.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -89,6 +95,7 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<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-4.0.6.0" newVersion="4.0.6.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FluentAssertions" version="5.10.3" targetFramework="net472" />
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net472" />
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net472" />
<package id="xunit" version="2.4.1" targetFramework="net472" />
<package id="xunit.abstractions" version="2.0.3" targetFramework="net472" />
<package id="xunit.analyzers" version="0.10.0" targetFramework="net472" />