aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio')
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs27
-rw-r--r--Software/Visual_Studio/PPC/Tango.PPC.Common/Web/TelemetryGetCheckPointsResponse.cs2
-rw-r--r--Software/Visual_Studio/Utilities/Tango.Telemetry.Tester.IOT.CLI/Program.cs3
-rw-r--r--Software/Visual_Studio/Utilities/Tango.WebClientGenerator/Program.cs4
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Controllers/DownloadsController.cs93
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs52
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs3
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj27
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryCheckpointStore.cs161
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryDeviceRegistrationManager.cs154
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Web.config4
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/packages.config7
12 files changed, 480 insertions, 57 deletions
diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs
index 0363285f8..d0fb21150 100644
--- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs
+++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/PPCWebClientBase.cs
@@ -175,5 +175,32 @@ namespace Tango.PPC.Common.Web
return Post<Tango.PPC.Common.Web.MachineVersionsRequest, Tango.PPC.Common.Web.MachineVersionsResponse>("GetMachineVersions", request);
}
+ /// <summary>
+ /// Executes the SetTelemetryCheckPoints action and returns Tango.PPC.Common.Web.TelemetrySetCheckPointsResponse.
+ /// </summary>
+ /// <returns></returns>
+ public Task<Tango.PPC.Common.Web.TelemetrySetCheckPointsResponse> SetTelemetryCheckPoints(Tango.PPC.Common.Web.TelemetrySetCheckPointsRequest request)
+ {
+ return Post<Tango.PPC.Common.Web.TelemetrySetCheckPointsRequest, Tango.PPC.Common.Web.TelemetrySetCheckPointsResponse>("SetTelemetryCheckPoints", request);
+ }
+
+ /// <summary>
+ /// Executes the GetTelemetryCheckPoints action and returns Tango.PPC.Common.Web.TelemetryGetCheckPointsResponse.
+ /// </summary>
+ /// <returns></returns>
+ public Task<Tango.PPC.Common.Web.TelemetryGetCheckPointsResponse> GetTelemetryCheckPoints(Tango.PPC.Common.Web.TelemetryGetCheckPointsRequest request)
+ {
+ return Post<Tango.PPC.Common.Web.TelemetryGetCheckPointsRequest, Tango.PPC.Common.Web.TelemetryGetCheckPointsResponse>("GetTelemetryCheckPoints", request);
+ }
+
+ /// <summary>
+ /// Executes the GetTelemetryDeviceConnection action and returns Tango.PPC.Common.Web.TelemetryDeviceRegistrationResponse.
+ /// </summary>
+ /// <returns></returns>
+ public Task<Tango.PPC.Common.Web.TelemetryDeviceRegistrationResponse> GetTelemetryDeviceConnection(Tango.PPC.Common.Web.TelemetryDeviceRegistrationRequest request)
+ {
+ return Post<Tango.PPC.Common.Web.TelemetryDeviceRegistrationRequest, Tango.PPC.Common.Web.TelemetryDeviceRegistrationResponse>("GetTelemetryDeviceConnection", request);
+ }
+
}
}
diff --git a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/TelemetryGetCheckPointsResponse.cs b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/TelemetryGetCheckPointsResponse.cs
index ff167acc5..72943f61c 100644
--- a/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/TelemetryGetCheckPointsResponse.cs
+++ b/Software/Visual_Studio/PPC/Tango.PPC.Common/Web/TelemetryGetCheckPointsResponse.cs
@@ -7,7 +7,7 @@ using Tango.Transport.Web;
namespace Tango.PPC.Common.Web
{
- public class TelemetryGetCheckPointsResponse : WebRequestMessage
+ public class TelemetryGetCheckPointsResponse : WebResponseMessage
{
public List<TelemetryCheckPoint> Checkpoints { get; set; }
diff --git a/Software/Visual_Studio/Utilities/Tango.Telemetry.Tester.IOT.CLI/Program.cs b/Software/Visual_Studio/Utilities/Tango.Telemetry.Tester.IOT.CLI/Program.cs
index b11c75f6f..1b02b9066 100644
--- a/Software/Visual_Studio/Utilities/Tango.Telemetry.Tester.IOT.CLI/Program.cs
+++ b/Software/Visual_Studio/Utilities/Tango.Telemetry.Tester.IOT.CLI/Program.cs
@@ -42,7 +42,8 @@ namespace Tango.Telemetry.Tester.IOT.CLI
//publisher.RegisterSource(new JobRunsTestSource());
//publisher.RegisterSource(new DiagnosticsTestSource());
publisher.RegisterSource(new EventsTestSource());
- publisher.RegisterDestination(new TelemetryAzureHubDestination("HostName=iot-twine-dev-weu.azure-devices.net;DeviceId=telemetry-dev-01;SharedAccessKey=cZhCMhiVL+TF7p13fpX+lFmyxoy8ZqCkbxUwumWw18Q="));
+ //publisher.RegisterDestination(new TelemetryAzureHubDestination("HostName=iot-twine-dev-weu.azure-devices.net;DeviceId=telemetry-dev-01;SharedAccessKey=cZhCMhiVL+TF7p13fpX+lFmyxoy8ZqCkbxUwumWw18Q="));
+ publisher.RegisterDestination(new TelemetryMqttDestination("Telemetry MQTT"));
publisher.PublishResultAvailable += Publisher_PublishResultAvailable;
publisher.PublishingPackage += Publisher_PublishingPackage;
diff --git a/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/Program.cs b/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/Program.cs
index 501058016..d7567d73d 100644
--- a/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/Program.cs
+++ b/Software/Visual_Studio/Utilities/Tango.WebClientGenerator/Program.cs
@@ -17,13 +17,13 @@ namespace Tango.WebClientGenerator
static void Main(string[] args)
{
//Generate PPC client.
- //GenerateWebClient<PPC.Common.Web.LoginRequest, PPC.Common.Web.LoginResponse, MachineService.Controllers.PPCController>("Tango.PPC.Common.Web", "PPCWebClientBase", PathHelper.GetSolutionFolder() + @"\PPC\Tango.PPC.Common\Web");
+ GenerateWebClient<PPC.Common.Web.LoginRequest, PPC.Common.Web.LoginResponse, MachineService.Controllers.PPCController>("Tango.PPC.Common.Web", "PPCWebClientBase", PathHelper.GetSolutionFolder() + @"\PPC\Tango.PPC.Common\Web");
//Generate Machine Studio client.
//GenerateWebClient<MachineStudio.Common.Web.LoginRequest, MachineStudio.Common.Web.LoginResponse, MachineService.Controllers.MachineStudioController>("Tango.MachineStudio.Common.Web", "MachineStudioWebClientBase", PathHelper.GetSolutionFolder() + @"\MachineStudio\Tango.MachineStudio.Common\Web");
//Generate FSE client.
- GenerateWebClientV2<FSE.Web.Messages.LoginRequest, FSE.Web.Messages.LoginResponse, MachineService.Controllers.FSEController>("Tango.FSE.BL.Web", "FSEWebClientBase", PathHelper.GetSolutionFolder() + @"\FSE\Tango.FSE.BL\Web");
+ //GenerateWebClientV2<FSE.Web.Messages.LoginRequest, FSE.Web.Messages.LoginResponse, MachineService.Controllers.FSEController>("Tango.FSE.BL.Web", "FSEWebClientBase", PathHelper.GetSolutionFolder() + @"\FSE\Tango.FSE.BL\Web");
Console.WriteLine("Done");
}
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DownloadsController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DownloadsController.cs
index b2de177aa..c99cfcf6d 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DownloadsController.cs
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/DownloadsController.cs
@@ -21,62 +21,63 @@ namespace Tango.MachineService.Controllers
{
public class DownloadsController : Controller
{
- [Authorize]
public ActionResult Index()
{
- List<DownloadModel> downloads = new List<DownloadModel>();
+ return new RedirectResult("https://twine-srv.com/");
- using (ObservablesContext db = ObservablesWebContext.CreateContext())
- {
- foreach (var item in db.MachineStudioVersions.Where(x => x.InstallerBlobName != null).Include(x => x.User).Include(x => x.User.Contact).ToList())
- {
- DownloadModel download = new DownloadModel();
- download.App = DownloadModel.DownloadApp.MachineStudio;
- download.ID = item.InstallerBlobName;
- download.Name = $"Machine Studio v{item.Version}.exe";
- download.Version = item.Version;
- download.User = item.User.Contact.FullName;
- download.Date = item.LastUpdated;
- download.Comments = item.Comments;
+ //List<DownloadModel> downloads = new List<DownloadModel>();
- downloads.Add(download);
- }
+ //using (ObservablesContext db = ObservablesWebContext.CreateContext())
+ //{
+ // foreach (var item in db.MachineStudioVersions.Where(x => x.InstallerBlobName != null).Include(x => x.User).Include(x => x.User.Contact).ToList())
+ // {
+ // DownloadModel download = new DownloadModel();
+ // download.App = DownloadModel.DownloadApp.MachineStudio;
+ // download.ID = item.InstallerBlobName;
+ // download.Name = $"Machine Studio v{item.Version}.exe";
+ // download.Version = item.Version;
+ // download.User = item.User.Contact.FullName;
+ // download.Date = item.LastUpdated;
+ // download.Comments = item.Comments;
- foreach (var item in db.TangoVersions.Where(x => x.InstallerBlobName != null).Include(x => x.User).Include(x => x.User.Contact).Include(x => x.MachineVersion).Where(x => x.MachineVersion.Version == 1).ToList())
- {
- DownloadModel download = new DownloadModel();
- download.App = DownloadModel.DownloadApp.PPC;
- download.ID = item.InstallerBlobName;
- download.Name = $"PPC v{item.VersionAndTag}.exe";
- download.Version = item.Version;
- download.User = item.User.Contact.FullName;
- download.Date = item.LastUpdated;
- download.Comments = item.Comments;
+ // downloads.Add(download);
+ // }
- downloads.Add(download);
- }
+ // foreach (var item in db.TangoVersions.Where(x => x.InstallerBlobName != null).Include(x => x.User).Include(x => x.User.Contact).Include(x => x.MachineVersion).Where(x => x.MachineVersion.Version == 1).ToList())
+ // {
+ // DownloadModel download = new DownloadModel();
+ // download.App = DownloadModel.DownloadApp.PPC;
+ // download.ID = item.InstallerBlobName;
+ // download.Name = $"PPC v{item.VersionAndTag}.exe";
+ // download.Version = item.Version;
+ // download.User = item.User.Contact.FullName;
+ // download.Date = item.LastUpdated;
+ // download.Comments = item.Comments;
- foreach (var item in db.TangoVersions.Where(x => x.InstallerBlobName != null).Include(x => x.User).Include(x => x.User.Contact).Include(x => x.MachineVersion).Where(x => x.MachineVersion.Version == 2).ToList())
- {
- DownloadModel download = new DownloadModel();
- download.App = DownloadModel.DownloadApp.Eureka;
- download.ID = item.InstallerBlobName;
- download.Name = $"Twine4X v{item.VersionAndTag}.exe";
- download.Version = item.Version;
- download.User = item.User.Contact.FullName;
- download.Date = item.LastUpdated;
- download.Comments = item.Comments;
+ // downloads.Add(download);
+ // }
- downloads.Add(download);
- }
- }
+ // foreach (var item in db.TangoVersions.Where(x => x.InstallerBlobName != null).Include(x => x.User).Include(x => x.User.Contact).Include(x => x.MachineVersion).Where(x => x.MachineVersion.Version == 2).ToList())
+ // {
+ // DownloadModel download = new DownloadModel();
+ // download.App = DownloadModel.DownloadApp.Eureka;
+ // download.ID = item.InstallerBlobName;
+ // download.Name = $"Twine4X v{item.VersionAndTag}.exe";
+ // download.Version = item.Version;
+ // download.User = item.User.Contact.FullName;
+ // download.Date = item.LastUpdated;
+ // download.Comments = item.Comments;
+
+ // downloads.Add(download);
+ // }
+ //}
- downloads = downloads.OrderByDescending(x => x.Date).ToList();
+ //downloads = downloads.OrderByDescending(x => x.Date).ToList();
- IndexViewModel model = new IndexViewModel();
- model.Downloads = downloads;
+ //IndexViewModel model = new IndexViewModel();
+ //model.Downloads = downloads;
- return View(model);
+ //return View(model);
}
[Authorize]
@@ -100,4 +101,4 @@ namespace Tango.MachineService.Controllers
return Redirect(signature);
}
}
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs
index b60bc77c0..41266cda5 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Controllers/PPCController.cs
@@ -28,6 +28,10 @@ using Z.EntityFramework.Plus;
using Twilio;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
+using Microsoft.WindowsAzure.Storage;
+using Tango.Web;
+using Microsoft.WindowsAzure.Storage.Table;
+using Tango.MachineService.Telemetry;
namespace Tango.MachineService.Controllers
{
@@ -1114,5 +1118,53 @@ namespace Tango.MachineService.Controllers
}
#endregion
+
+ #region Telemetry
+
+ [HttpPost]
+ [JwtTokenFilter]
+ public TelemetrySetCheckPointsResponse SetTelemetryCheckPoints(TelemetrySetCheckPointsRequest request)
+ {
+ TelemetryCheckpointStore store = new TelemetryCheckpointStore(MachineServiceConfig.STORAGE_ACCOUNT);
+ store.SaveMany(RequestToken.Object.MachineGuid, request.Checkpoints);
+ return new TelemetrySetCheckPointsResponse();
+ }
+
+ [HttpPost]
+ [JwtTokenFilter]
+ public TelemetryGetCheckPointsResponse GetTelemetryCheckPoints(TelemetryGetCheckPointsRequest request)
+ {
+ TelemetryCheckpointStore store = new TelemetryCheckpointStore(MachineServiceConfig.STORAGE_ACCOUNT);
+ var checkPoints = store.GetAllForMachine(RequestToken.Object.MachineGuid).ToList();
+ return new TelemetryGetCheckPointsResponse() { Checkpoints = checkPoints };
+ }
+
+ [HttpPost]
+ [JwtTokenFilter]
+ public TelemetryDeviceRegistrationResponse GetTelemetryDeviceConnection(TelemetryDeviceRegistrationRequest request)
+ {
+ var response = new TelemetryDeviceRegistrationResponse();
+
+ using (ObservablesContext db = ObservablesWebContext.CreateContext())
+ {
+ var machine = db.Machines.SingleOrDefault(x => x.Guid == RequestToken.Object.MachineGuid);
+ if (machine == null)
+ throw new AuthenticationException("The specified machine could not be found.");
+
+ string serialNumber = machine.SerialNumber;
+ string storageAccount = MachineServiceConfig.STORAGE_ACCOUNT; // Azure Storage CS
+ string iotHubService = MachineServiceConfig.IOT_HUB_CONNECTION_STRING; // iothubowner SAS (service) CS
+
+ var mgr = new TelemetryDeviceRegistrationManager(storageAccount, iotHubService);
+
+ string iotHubDeviceConnectionString = mgr.GetOrCreateDeviceConnectionString(machine.Guid, serialNumber);
+
+ response.ConnectionString = iotHubDeviceConnectionString;
+ }
+
+ return response;
+ }
+
+ #endregion
}
}
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs b/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs
index 15c0637d6..8fb510941 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs
+++ b/Software/Visual_Studio/Web/Tango.MachineService/MachineServiceConfig.cs
@@ -32,5 +32,8 @@ namespace Tango.MachineService
public static String TWILIO_FROM_NUMBER => ConfigurationManager.AppSettings[nameof(TWILIO_FROM_NUMBER)].ToString();
public static bool TWILIO_ENABLE_SMS => bool.Parse(ConfigurationManager.AppSettings[nameof(TWILIO_ENABLE_SMS)].ToString());
public static bool TWILIO_ENABLE_ALPHA_SENDER => bool.Parse(ConfigurationManager.AppSettings[nameof(TWILIO_ENABLE_ALPHA_SENDER)].ToString());
+
+ public static String IOT_HUB_CONNECTION_STRING => ConfigurationManager.AppSettings[nameof(IOT_HUB_CONNECTION_STRING)].ToString();
+
}
} \ No newline at end of file
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj b/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj
index 9f8889d60..e9c766c5d 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Tango.MachineService.csproj
@@ -65,8 +65,17 @@
<Reference Include="Microsoft.AspNet.SignalR.SystemWeb, Version=2.4.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.SignalR.SystemWeb.2.4.1\lib\net45\Microsoft.AspNet.SignalR.SystemWeb.dll</HintPath>
</Reference>
+ <Reference Include="Microsoft.Azure.Amqp, Version=2.4.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Microsoft.Azure.Amqp.2.5.12\lib\net45\Microsoft.Azure.Amqp.dll</HintPath>
+ </Reference>
<Reference Include="Microsoft.Azure.Common, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<Reference Include="Microsoft.Azure.Common.NetFramework, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
+ <Reference Include="Microsoft.Azure.Devices, Version=1.39.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Microsoft.Azure.Devices.1.39.0\lib\net451\Microsoft.Azure.Devices.dll</HintPath>
+ </Reference>
+ <Reference Include="Microsoft.Azure.Devices.Shared, Version=1.30.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Microsoft.Azure.Devices.Shared.1.30.3\lib\net451\Microsoft.Azure.Devices.Shared.dll</HintPath>
+ </Reference>
<Reference Include="Microsoft.Azure.Management.Sql, Version=0.9.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Azure.ResourceManager, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
@@ -79,8 +88,8 @@
<Reference Include="Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms, Version=2.7.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.2.7.10707.1513-rc\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll</HintPath>
</Reference>
- <Reference Include="Microsoft.Owin, Version=4.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
- <HintPath>..\..\packages\Microsoft.Owin.4.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
+ <Reference Include="Microsoft.Owin, Version=4.2.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\Microsoft.Owin.4.2.2\lib\net45\Microsoft.Owin.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Owin.Cors, Version=4.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.Owin.Cors.4.0.1\lib\net45\Microsoft.Owin.Cors.dll</HintPath>
@@ -210,7 +219,15 @@
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.IdentityModel" />
+ <Reference Include="System.Net" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Numerics" />
+ <Reference Include="System.Runtime, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="System.Security" />
+ <Reference Include="System.Transactions" />
+ <Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
+ <HintPath>..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll</HintPath>
+ </Reference>
<Reference Include="System.Web.Cors, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.Cors.5.0.0\lib\net45\System.Web.Cors.dll</HintPath>
</Reference>
@@ -230,8 +247,6 @@
<Private>True</Private>
<HintPath>..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
</Reference>
- <Reference Include="System.Net.Http">
- </Reference>
<Reference Include="System.Net.Http.Formatting, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll</HintPath>
</Reference>
@@ -346,6 +361,8 @@
</Compile>
<Compile Include="Models\SessionVariables.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Telemetry\TelemetryCheckpointStore.cs" />
+ <Compile Include="Telemetry\TelemetryDeviceRegistrationManager.cs" />
<Compile Include="Views\FSEAccount\ResetPasswordVM.cs" />
<Compile Include="Views\FSEDownloads\IndexViewModel.cs" />
<Content Include="Images\eureka.png" />
@@ -508,7 +525,7 @@
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
- <UserProperties BuildVersion_AssemblyInfoFilename="Properties\AssemblyInfo.cs" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_BuildVersioningStyle="None.None.Increment.TimeStamp" BuildVersion_UseGlobalSettings="False" BuildVersion_StartDate="2000/1/1" />
+ <UserProperties BuildVersion_StartDate="2000/1/1" BuildVersion_UseGlobalSettings="False" BuildVersion_BuildVersioningStyle="None.None.Increment.TimeStamp" BuildVersion_UpdateAssemblyVersion="True" BuildVersion_AssemblyInfoFilename="Properties\AssemblyInfo.cs" />
</VisualStudio>
</ProjectExtensions>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryCheckpointStore.cs b/Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryCheckpointStore.cs
new file mode 100644
index 000000000..1cd40e523
--- /dev/null
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryCheckpointStore.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Table;
+using Tango.PPC.Common.Web;
+
+namespace Tango.MachineService.Telemetry
+{
+ internal class TelemetryCheckpointEntity : TableEntity
+ {
+ public TelemetryCheckpointEntity() { }
+
+ public TelemetryCheckpointEntity(string machineGuid, string sourceName)
+ {
+ PartitionKey = machineGuid;
+ RowKey = SanitizeRowKey(sourceName);
+ }
+
+ public DateTime Time { get; set; }
+ public int TotalCount { get; set; }
+
+ internal static string SanitizeRowKey(string value) =>
+ (value ?? string.Empty)
+ .Replace("/", "_").Replace("\\", "_")
+ .Replace("#", "_").Replace("?", "_");
+ }
+
+ /// <summary>
+ /// Store/get telemetry source checkpoints in Azure Table Storage.
+ /// PartitionKey = MachineGuid, RowKey = SourceName.
+ /// </summary>
+ public sealed class TelemetryCheckpointStore
+ {
+ private readonly CloudTable _table;
+
+ /// <param name="connectionString">Azure Storage connection string</param>
+ /// <param name="tableName">Table name (default: TelemetryCheckPoints)</param>
+ public TelemetryCheckpointStore(string connectionString, string tableName = "TelemetryCheckPoints")
+ {
+ if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString));
+ if (string.IsNullOrWhiteSpace(tableName)) throw new ArgumentNullException(nameof(tableName));
+
+ var storageAccount = CloudStorageAccount.Parse(connectionString);
+ var tableClient = storageAccount.CreateCloudTableClient();
+
+ _table = tableClient.GetTableReference(tableName);
+ _table.CreateIfNotExists(); // sync
+ }
+
+ /// <summary>Upsert a single checkpoint for a machine + source (synchronous).</summary>
+ public void Save(string machineGuid, TelemetryCheckPoint checkpoint)
+ {
+ if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
+ if (checkpoint == null) throw new ArgumentNullException(nameof(checkpoint));
+ if (string.IsNullOrWhiteSpace(checkpoint.SourceName)) throw new ArgumentNullException(nameof(checkpoint.SourceName));
+
+ var entity = new TelemetryCheckpointEntity(machineGuid, checkpoint.SourceName)
+ {
+ Time = checkpoint.Time,
+ TotalCount = checkpoint.TotalCount
+ };
+
+ var op = TableOperation.InsertOrReplace(entity);
+ _table.Execute(op);
+ }
+
+ /// <summary>Upsert up to N checkpoints in batches of 100 (Table Storage limit) — synchronous.</summary>
+ public void SaveMany(string machineGuid, IEnumerable<TelemetryCheckPoint> checkpoints)
+ {
+ if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
+ if (checkpoints == null) return;
+
+ var batch = new TableBatchOperation();
+ foreach (var cp in checkpoints)
+ {
+ if (cp == null || string.IsNullOrWhiteSpace(cp.SourceName)) continue;
+
+ var entity = new TelemetryCheckpointEntity(machineGuid, cp.SourceName)
+ {
+ Time = cp.Time,
+ TotalCount = cp.TotalCount
+ };
+
+ batch.InsertOrReplace(entity);
+
+ if (batch.Count == 100)
+ {
+ _table.ExecuteBatch(batch);
+ batch.Clear();
+ }
+ }
+
+ if (batch.Count > 0)
+ {
+ _table.ExecuteBatch(batch);
+ }
+ }
+
+ /// <summary>Get the checkpoint for a specific machine + source. Returns null if not found. (sync)</summary>
+ public TelemetryCheckPoint Get(string machineGuid, string sourceName)
+ {
+ if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
+ if (string.IsNullOrWhiteSpace(sourceName)) throw new ArgumentNullException(nameof(sourceName));
+
+ var rowKey = TelemetryCheckpointEntity.SanitizeRowKey(sourceName);
+ var op = TableOperation.Retrieve<TelemetryCheckpointEntity>(machineGuid, rowKey);
+ var result = _table.Execute(op);
+
+ var entity = result.Result as TelemetryCheckpointEntity;
+ return entity == null ? null : new TelemetryCheckPoint
+ {
+ SourceName = sourceName,
+ Time = entity.Time,
+ TotalCount = entity.TotalCount
+ };
+ }
+
+ /// <summary>Get all checkpoints for a machine (sync). Uses ExecuteQuery which handles paging internally.</summary>
+ public IReadOnlyList<TelemetryCheckPoint> GetAllForMachine(string machineGuid)
+ {
+ if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
+
+ var query = new TableQuery<TelemetryCheckpointEntity>()
+ .Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, machineGuid));
+
+ // ExecuteQuery is synchronous and iterates all segments internally
+ var entities = _table.ExecuteQuery(query);
+
+ var list = new List<TelemetryCheckPoint>();
+ foreach (var e in entities)
+ {
+ list.Add(new TelemetryCheckPoint
+ {
+ // RowKey is sanitized; ensure you pass sanitized name when saving & querying
+ SourceName = e.RowKey,
+ Time = e.Time,
+ TotalCount = e.TotalCount
+ });
+ }
+
+ return list;
+ }
+
+ /// <summary>Delete a specific checkpoint (sync). No-op if not found.</summary>
+ public void Delete(string machineGuid, string sourceName)
+ {
+ if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
+ if (string.IsNullOrWhiteSpace(sourceName)) throw new ArgumentNullException(nameof(sourceName));
+
+ var rowKey = TelemetryCheckpointEntity.SanitizeRowKey(sourceName);
+ var retrieve = TableOperation.Retrieve<TelemetryCheckpointEntity>(machineGuid, rowKey);
+ var result = _table.Execute(retrieve);
+ var entity = result.Result as TelemetryCheckpointEntity;
+ if (entity == null) return;
+
+ var del = TableOperation.Delete(entity);
+ _table.Execute(del);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryDeviceRegistrationManager.cs b/Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryDeviceRegistrationManager.cs
new file mode 100644
index 000000000..0bf8d2bdc
--- /dev/null
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Telemetry/TelemetryDeviceRegistrationManager.cs
@@ -0,0 +1,154 @@
+using System;
+using Microsoft.WindowsAzure.Storage;
+using Microsoft.WindowsAzure.Storage.Table; // same SDK as your sample
+using Microsoft.Azure.Devices; // Install-Package Microsoft.Azure.Devices
+using Microsoft.Azure.Devices.Common.Exceptions;
+
+namespace Tango.MachineService.Telemetry
+{
+ internal sealed class TelemetryDeviceRegistrationEntity : TableEntity
+ {
+ public TelemetryDeviceRegistrationEntity() { }
+
+ public TelemetryDeviceRegistrationEntity(string machineGuid, string serialNumber)
+ {
+ PartitionKey = machineGuid;
+ RowKey = SanitizeRowKey(serialNumber);
+ }
+
+ public String Environment { get; set; }
+ public string SerialNumber { get; set; }
+ public string DeviceId { get; set; }
+ public string ConnectionString { get; set; }
+ public DateTime CreatedUtc { get; set; }
+ public DateTime UpdatedUtc { get; set; }
+
+ internal static string SanitizeRowKey(string value) =>
+ (value ?? string.Empty)
+ .Replace("/", "_").Replace("\\", "_")
+ .Replace("#", "_").Replace("?", "_");
+ }
+
+ /// <summary>
+ /// Creates/gets IoT Hub device identities and caches the per-device connection string
+ /// in Azure Table Storage ("TelemetryDeviceRegistrations").
+ /// PartitionKey = MachineGuid, RowKey = SerialNumber (sanitized).
+ /// </summary>
+ public sealed class TelemetryDeviceRegistrationManager
+ {
+ private readonly string _iotHubServiceConnectionString;
+ private readonly string _iotHubHostName;
+ private readonly CloudTable _table;
+ private readonly object _locker = new object();
+
+ public TelemetryDeviceRegistrationManager(
+ string storageAccountConnectionString,
+ string iotHubServiceConnectionString,
+ string tableName = "TelemetryDeviceRegistrations")
+ {
+ if (string.IsNullOrWhiteSpace(storageAccountConnectionString))
+ throw new ArgumentNullException(nameof(storageAccountConnectionString));
+ if (string.IsNullOrWhiteSpace(iotHubServiceConnectionString))
+ throw new ArgumentNullException(nameof(iotHubServiceConnectionString));
+ if (string.IsNullOrWhiteSpace(tableName))
+ throw new ArgumentNullException(nameof(tableName));
+
+ _iotHubServiceConnectionString = iotHubServiceConnectionString;
+ _iotHubHostName = ExtractHostName(iotHubServiceConnectionString);
+
+ var storageAccount = CloudStorageAccount.Parse(storageAccountConnectionString);
+ var tableClient = storageAccount.CreateCloudTableClient();
+ _table = tableClient.GetTableReference(tableName);
+ _table.CreateIfNotExists(); // sync, as in your sample
+ }
+
+ /// <summary>
+ /// Returns the per-device IoT Hub connection string for (machineGuid, serialNumber).
+ /// Creates the IoT Hub device (DeviceId = serialNumber) if needed, persists the connection string,
+ /// then returns the cached value next time.
+ /// </summary>
+ public string GetOrCreateDeviceConnectionString(string machineGuid, string serialNumber)
+ {
+ if (string.IsNullOrWhiteSpace(machineGuid)) throw new ArgumentNullException(nameof(machineGuid));
+ if (string.IsNullOrWhiteSpace(serialNumber)) throw new ArgumentNullException(nameof(serialNumber));
+
+ // 1) Try table cache
+ var cached = TryGetFromTable(machineGuid, serialNumber);
+ if (!string.IsNullOrEmpty(cached))
+ return cached;
+
+ // 2) Create/get in IoT Hub (guard against concurrent callers)
+ lock (_locker)
+ {
+ // double-check after lock
+ cached = TryGetFromTable(machineGuid, serialNumber);
+ if (!string.IsNullOrEmpty(cached))
+ return cached;
+
+ var registry = RegistryManager.CreateFromConnectionString(_iotHubServiceConnectionString);
+
+ Device device;
+ try
+ {
+ device = registry.AddDeviceAsync(new Device(serialNumber)).GetAwaiter().GetResult();
+ }
+ catch (DeviceAlreadyExistsException)
+ {
+ device = registry.GetDeviceAsync(serialNumber).GetAwaiter().GetResult();
+ }
+
+ if (device == null)
+ throw new InvalidOperationException("Failed to get or create IoT Hub device.");
+
+ var primaryKey = device.Authentication?.SymmetricKey?.PrimaryKey;
+ if (string.IsNullOrEmpty(primaryKey))
+ throw new InvalidOperationException("Device has no symmetric key.");
+
+ var deviceConnectionString =
+ $"HostName={_iotHubHostName};DeviceId={serialNumber};SharedAccessKey={primaryKey}";
+
+ UpsertToTable(machineGuid, serialNumber, deviceConnectionString);
+
+ return deviceConnectionString;
+ }
+ }
+
+ // ---- Table helpers (sync) ----
+
+ private string TryGetFromTable(string machineGuid, string serialNumber)
+ {
+ var rowKey = TelemetryDeviceRegistrationEntity.SanitizeRowKey(serialNumber);
+ var retrieve = TableOperation.Retrieve<TelemetryDeviceRegistrationEntity>(machineGuid, rowKey);
+ var result = _table.Execute(retrieve);
+ var entity = result.Result as TelemetryDeviceRegistrationEntity;
+ return entity?.ConnectionString;
+ }
+
+ private void UpsertToTable(string machineGuid, string serialNumber, string connectionString)
+ {
+ var entity = new TelemetryDeviceRegistrationEntity(machineGuid, serialNumber)
+ {
+ SerialNumber = serialNumber,
+ DeviceId = serialNumber,
+ Environment = MachineServiceConfig.DEPLOYMENT_SLOT.ToString(),
+ ConnectionString = connectionString,
+ CreatedUtc = DateTime.UtcNow,
+ UpdatedUtc = DateTime.UtcNow
+ };
+
+ var op = TableOperation.InsertOrReplace(entity);
+ _table.Execute(op);
+ }
+
+ // ---- Utility ----
+
+ private static string ExtractHostName(string iotHubServiceConnectionString)
+ {
+ var parts = iotHubServiceConnectionString.Split(';');
+ foreach (var p in parts)
+ if (p.StartsWith("HostName=", StringComparison.OrdinalIgnoreCase))
+ return p.Substring("HostName=".Length);
+ throw new ArgumentException("IoT Hub service connection string is missing HostName=…");
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Web.config b/Software/Visual_Studio/Web/Tango.MachineService/Web.config
index 84b3d0a3a..85260c32c 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/Web.config
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Web.config
@@ -45,6 +45,8 @@
<add key="TWILIO_FROM_NUMBER" value="+16466815605" />
<add key="TWILIO_ENABLE_SMS" value="true" />
<add key="TWILIO_ENABLE_ALPHA_SENDER" value="true" />
+
+ <add key="IOT_HUB_CONNECTION_STRING" value="HostName=iot-twine-dev-weu.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=PUcTQPd+5dCkc9sHqlDO2P3fV/wDlr4VNAIoTFqVovY=" />
</appSettings>
<!--
@@ -198,7 +200,7 @@
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
+ <bindingRedirect oldVersion="0.0.0.0-4.2.2.0" newVersion="4.2.2.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Z.EntityFramework.Extensions" publicKeyToken="59b66d028979105b" culture="neutral" />
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/packages.config b/Software/Visual_Studio/Web/Tango.MachineService/packages.config
index ff05c3063..a944a6267 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/packages.config
+++ b/Software/Visual_Studio/Web/Tango.MachineService/packages.config
@@ -26,10 +26,14 @@
<package id="Microsoft.AspNet.WebApi.HelpPage" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebApi.WebHost" version="5.2.3" targetFramework="net45" />
<package id="Microsoft.AspNet.WebPages" version="3.2.3" targetFramework="net45" />
+ <package id="Microsoft.Azure.Amqp" version="2.5.12" targetFramework="net461" />
+ <package id="Microsoft.Azure.Devices" version="1.39.0" targetFramework="net461" />
+ <package id="Microsoft.Azure.Devices.Shared" version="1.30.3" targetFramework="net461" />
<package id="Microsoft.CodeDom.Providers.DotNetCompilerPlatform" version="1.0.3" targetFramework="net45" />
+ <package id="Microsoft.CSharp" version="4.7.0" targetFramework="net461" />
<package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="5.0.5" targetFramework="net461" />
<package id="Microsoft.Net.Compilers" version="2.4.0" targetFramework="net46" developmentDependency="true" />
- <package id="Microsoft.Owin" version="4.0.1" targetFramework="net461" />
+ <package id="Microsoft.Owin" version="4.2.2" targetFramework="net461" />
<package id="Microsoft.Owin.Cors" version="4.0.1" targetFramework="net461" />
<package id="Microsoft.Owin.Host.SystemWeb" version="2.1.0" targetFramework="net461" />
<package id="Microsoft.Owin.Security" version="2.1.0" targetFramework="net461" />
@@ -43,6 +47,7 @@
<package id="System.Data.SQLite.Core" version="1.0.108.0" targetFramework="net46" />
<package id="System.Data.SQLite.EF6" version="1.0.108.0" targetFramework="net46" />
<package id="System.Data.SQLite.Linq" version="1.0.108.0" targetFramework="net46" />
+ <package id="System.ValueTuple" version="4.5.0" targetFramework="net461" />
<package id="Twilio" version="7.6.0" targetFramework="net461" />
<package id="WebGrease" version="1.5.2" targetFramework="net45" />
<package id="Z.EntityFramework.Extensions" version="5.1.6" targetFramework="net461" />