This commit is contained in:
Roland Drabesch
2023-03-06 15:13:26 +01:00
24 changed files with 707 additions and 219 deletions
@@ -327,6 +327,10 @@
<Project>{F013A56B-7BE6-4852-9CD8-B971CD1ADD0C}</Project>
<Name>GenesisConfig</Name>
</ProjectReference>
<ProjectReference Include="..\GenesisPwd\GenesisPwd.csproj">
<Project>{AC434B18-4D7B-49BA-9AD5-0527C79E8297}</Project>
<Name>GenesisPwd</Name>
</ProjectReference>
<ProjectReference Include="..\Protocols\ProtocolCore\ProtocolCore.csproj">
<Project>{8BDD2789-96D4-43EA-AAD7-0F49D0347998}</Project>
<Name>ProtocolCore</Name>
@@ -26,6 +26,7 @@ using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.EventArguments;
using Xylem.Common.Hardware.WaterMeter.Genesis.DataPackages.MeasurementRecords;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisConfig;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd;
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.ProtocolCore;
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol;
using Xylem.Common.Hardware.WaterMeter.Genesis.Protocols.RequestProtocol.Consts;
@@ -959,13 +960,13 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
/// <remarks date="2023-Jan-19" author="T.Wiedebusch">
/// - Password had been reset to empty string if read from file, corrected!
/// </remarks>
/// <remarks date="2023-Feb-28" author="T.Wiedebusch">
/// - Used <see cref="MeterPwdHandlerDb.GetPasswordFromDb"/>
/// </remarks>
private String GetPassword()
{
var url = ServiceUrls.GenesisGetPasswordServiceUrl();
var password = "";
//without PCB ID it is not possible to login,
//but if it is already assigned to this Gensis, don't read it again
// without PCB ID it is not possible to login,
// but if it is already assigned to this Genesis, don't read it again
if (string.IsNullOrEmpty(PcbId))
{
//GetPcbId fills the PsbId property
@@ -976,41 +977,29 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore
{
return "";
}
}
}
url += PcbId;
try
// on unknown password try to get it from DB
if (MeterPwdHandlerDb.GetPasswordFromDb(PcbId, out var password))
{
//use
var http = (HttpWebRequest)WebRequest.Create(url);
var response = (HttpWebResponse)http.GetResponse();
var responseStream = response.GetResponseStream();
if (responseStream == null)
{
throw new InvalidOperationException();
}
using (var sr = new StreamReader(responseStream))
{
var responseJson = sr.ReadToEnd();
password = responseJson.Trim('"');
}
_logger.Info($"Slot:{Slot} - Got password from GenesisPasswordServiceUrl, URL({url})");
_logger.Info($"Slot:{Slot} - Got password from GenesisPasswordService, " +
$"URL({ServiceUrls.GenesisGetPasswordServiceUrl() + PcbId})");
}
catch (Exception ex)
else
{
_logger.Info($"Slot:{Slot} - Cannot get password from GenesisPasswordService, " +
$"URL({url}), Error({ex.Message})");
if (File.Exists(ProgramConfig.OfflineInfoFile))
$"URL({ServiceUrls.GenesisGetPasswordServiceUrl() + PcbId}))");
// try to read the password from an offline password file
if (!File.Exists(ProgramConfig.OfflineInfoFile)) return password;
var listOfOfflinePasswords = JsonConvert.DeserializeObject<List<OfflinePasswordItem>>(
File.ReadAllText(ProgramConfig.OfflineInfoFile));
password = listOfOfflinePasswords.First(x => x.PcbId == PcbId).Password;
if (!string.IsNullOrEmpty(password))
{
var listOfOfflinePasswords = JsonConvert.DeserializeObject<List<OfflinePasswordItem>>(
File.ReadAllText(ProgramConfig.OfflineInfoFile));
password = listOfOfflinePasswords.First(x => x.PcbId == PcbId).Password;
if (!string.IsNullOrEmpty(password))
{
_logger.Info($"Slot:{Slot} - Got password from password file!");
}
_logger.Info($"Slot:{Slot} - Got password for PcbId:{PcbId} from password file!");
}
}
@@ -8,42 +8,53 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd
/// - passwords,
/// - password-hashes (12 char from password - SHA1),
/// - Order number,
/// - Radio address and
/// - Radio address,
/// - Key set id,
/// - Password file id and
/// - Skeleton key.
/// </summary>
public class MeterPwdDb
{
//password file length is 160 bytes (8 passwords a 20 bytes SHA1)
/// <summary>
/// password file length is 160 bytes (8 passwords a 20 bytes SHA1)
/// </summary>
public const Int32 PwdFileLength = 160;
/// <summary>
/// Skeleton key index in password list.
/// </summary>
public const Int32 SkeletonKeyIdx = 2;
/// <summary>
/// Password Level 8 index in 8 elements password list.
/// </summary>
public const Int32 PwdLevel8Idx = 7;
/// <summary>
/// Order number for this Genesis.
/// </summary>
public Int64? OrderNumber = null;
/// <summary>
/// Optional radio address.
/// </summary>
public Int64? RadioAddress = null;
/// <summary>
/// Skeleton key for login if password file is not installed.
/// </summary>
public String Skeleton = "";
/// <summary>
/// Password, which is the Skeleton, if password file not installed, or the PwdLevel8, if the password file is installed .
/// </summary>
public String Password = "";
/// <summary>
/// List of hashes.
/// </summary>
public List<Byte[]> ListOfHashes = new List<Byte[]>();
/// <summary>
/// List of passwords.
/// </summary>
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
@@ -26,29 +24,31 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisPwd
/// <remarks date="2023-Feb-23" author="Roland Drabesch/Thomas Wiedebusch">
/// - Initial.
/// </remarks>
/// <remarks date="2023-Feb-28" author="Roland Drabesch/Thomas Wiedebusch">
/// - Used JsonConvert instead of StreamReader.
/// </remarks>
public static Boolean GetPasswordFromDb(String pcbId, out String password)
{
var url = ServiceUrls.GenesisGetPasswordServiceUrl();
password = "";
if (string.IsNullOrEmpty(pcbId))
{
return false;
}
try
{
//todo: roland umstellen auf localwebrequest
var http = (HttpWebRequest)WebRequest.Create(url + pcbId);
var response = (HttpWebResponse)http.GetResponse();
var responseStream = response.GetResponseStream();
if (responseStream == null)
var url = ServiceUrls.GenesisGetPasswordServiceUrl();
var response = LocalWebRequest.GetRequest(url + pcbId, 2000);
password = JsonConvert.DeserializeObject<String>(response);
if (string.IsNullOrEmpty(password))
{
password = "";
return false;
}
using (var sr = new StreamReader(responseStream))
{
var responseJson = sr.ReadToEnd();
password = responseJson.Trim('"');
}
}
catch (Exception )
{
password = "";
return false;
}
@@ -0,0 +1,125 @@
using System;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus
{
/// <summary>
/// Status of Cordonel like:
/// - Battery drainage,
/// - Production date of electronics,
/// - Due date for production,
/// - Remaining life time,
/// - Special permissions for production capability check for
/// - Battery Drainage in %,
/// - Remaining Life Time in years,
/// - Production Due Date as DateTime.
/// </summary>
public class GenesisStatus
{
/// <summary>
/// Data base primary key
/// </summary>
public Int32 Id;
/// <summary>
/// Order number
/// </summary>
public Int32 OrderNumber;
/// <summary>
/// PcbId.
/// </summary>
public String PcbId;
/// <summary>
/// Skip radio parameter check
/// </summary>
public Boolean SkipRadioParametersCheck;
/// <summary>
/// Skip FW check. Normally the FW will be assigned to the latest released version
/// and taken from the data base. Here special versions can be used for FW tests.
/// </summary>
public Boolean SkipFwCheck;
/// <summary>
/// Skip the look-up-table installation as this device may be used for development.
/// </summary>
public Boolean SkipLutCheck;
/// <summary>
/// Internally used to signal that production and shipping is proved and validated.
/// </summary>
public Boolean ProductionAccepted;
/// <summary>
/// Date and time of production of electronics.
/// </summary>
public DateTime KitronProductionDate;
/// <summary>
/// Last date until production has to be finished, can be extended by special permission
/// </summary>
public DateTime ProductionDueDate;
/// <summary>
/// Remaining life time for Cordonel in years based on battery drainage.
/// </summary>
public Double RemainingLifeTimeYears;
/// <summary>
/// Exceeded life time of all batteries in seconds.
/// </summary>
public UInt64 ExceededLifeTime_s;
/// <summary>
/// Required life time in years for assembly, is dependent on region and special permission.
/// </summary>
public Double RequiredLifeTimeYearsAssembly;
/// <summary>
/// Required life time in years for shipping, is dependent on region and special permission.
/// </summary>
public Double RequiredLifeTimeYearsShipping;
/// <summary>
/// Battery drainage in percent.
/// </summary>
public Double DrainedBatteryLoadPercent;
/// <summary>
/// Maximum drained Battery load in percent.
/// </summary>
public Double MaxDrainedBatteryLoadPercent;
/// <summary>
/// Number of batteries attached to Cordonel.
/// </summary>
public Int32 BatteryQuantity;
/// <summary>
/// Initial capacity of one battery in mAh.
/// </summary>
public UInt64 InitialBatteryLoad_mAh;
/// <summary>
/// Drained capacity of all batteries in uAs.
/// </summary>
public UInt64 DrainedBatteryPower_uAs;
/// <summary>
/// Comment for approval and permissions reasons:
/// e.g. "On customer requirement for test purposes the life time isn't of importance".
/// </summary>
public String ApprovalComment;
/// <summary>
/// Name of approver for special permissions.
/// </summary>
public String ApproverName;
/// <summary>
/// Date of approval.
/// </summary>
public DateTime ApprovalDate;
}
}
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0F646872-D8DC-4EE5-989F-D4C73F0ACE3F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus</RootNamespace>
<AssemblyName>Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\packages\Newtonsoft.Json.12.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="GenesisStatusHandler.cs" />
<Compile Include="GenesisStatus.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\CommonCore.Configuration\CommonCore.Configuration.csproj">
<Project>{9EB1F659-6F73-4F65-BCAB-4114FD94F5FC}</Project>
<Name>CommonCore.Configuration</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\..\SoftwareAccessHelper\SoftwareAccessHelper.csproj">
<Project>{A33E3C6D-EAAE-4A20-A0EE-F9E6922FD029}</Project>
<Name>SoftwareAccessHelper</Name>
</ProjectReference>
<ProjectReference Include="..\GenesisCore\GenesisCore.csproj">
<Project>{04201E95-90A0-4B73-A72F-379739B2A3AE}</Project>
<Name>GenesisCore</Name>
</ProjectReference>
<ProjectReference Include="..\Registers\Registers.csproj">
<Project>{F4AAF7E6-7333-4284-B735-E32DEB076C64}</Project>
<Name>Registers</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,136 @@
using System;
using Newtonsoft.Json;
using Xylem.Common.CommonCore.Configuration;
using Xylem.Common.Hardware.WaterMeter.Genesis.GenesisCore;
using Xylem.Common.Hardware.WaterMeter.Genesis.Registers;
using Xylem.Common.Logic.SoftwareAccessHelper;
namespace Xylem.Common.Hardware.WaterMeter.Genesis.GenesisStatus
{
/// <summary>
/// Handler for the production status check. This will be feed by the DB.
/// - Calculation of drained battery,
/// - Assembly capability check,
/// - Shipping capability check.
/// </summary>
public static class GenesisStatusHandler
{
/// <summary>
/// Get all production infos for skip process checks or limits for production like:
/// - Battery drained percentage upper limit,
/// - Production due date for a special register,
/// - Required life time for assembly line,
/// - Required life time for shipping line.
/// </summary>
/// <param name="pcbId"></param>
/// <param name="orderNumber"></param>
/// <param name="status">production status</param>
/// <returns>true if successfully executed</returns>
/// <remarks date="2023-Mar-02" author="Roland Drabesch/Thomas Wiedebusch">
/// - Initial.
/// </remarks>
public static Boolean GetProductionSkipAndLifeTimeInfoFromDb(String pcbId, Int32 orderNumber,
out GenesisStatus status)
{
if (string.IsNullOrEmpty(pcbId))
{
status = null;
return false;
}
try
{
var url = ServiceUrls.GetProductionSkipAndLifeTimeInfoServiceUrl();
url += $"{pcbId}&orderNumber={orderNumber}";
var response = LocalWebRequest.GetRequest(url, 2000);
status = JsonConvert.DeserializeObject<GenesisStatus>(response);
}
catch (Exception )
{
status = null;
return false;
}
return true;
}
/// <summary>
/// Read and calculate life time information.
/// </summary>
/// <param name="currentGenesis"></param>
/// <param name="status"></param>
/// <returns></returns>
public static Boolean BuildLifeTimeInformation(GenesisMeter currentGenesis, GenesisStatus status)
{
Boolean retVal;
if (currentGenesis == null || status == null) return false;
// read information from genesis
try
{
if (!currentGenesis.IsLoggedOn) currentGenesis.Login();
status.ExceededLifeTime_s = RegisterConverter.ConvertTo<UInt32>(
currentGenesis.ReadRegister(Register.Powermon.BatteryExceededSeconds));
status.DrainedBatteryPower_uAs = RegisterConverter.ConvertTo<UInt64>(
currentGenesis.ReadRegister(Register.Powermon.BatteryDrainedLoad, 8));
status.BatteryQuantity = RegisterConverter.ConvertTo<Int32>(
currentGenesis.ReadRegister(Register.Powermon.BatteryQuantity));
status.InitialBatteryLoad_mAh = RegisterConverter.ConvertTo<UInt32>(
currentGenesis.ReadRegister(Register.Powermon.BatteryInitialLoad));
currentGenesis.Logout();
retVal = CalculateLifeTime(status);
}
catch (Exception)
{
retVal = false;
}
return retVal;
}
/// <summary>
/// Calculation of life time values.
/// </summary>
/// <param name="status"></param>
/// <returns>true if all calculations could be executed</returns>
public static Boolean CalculateLifeTime(GenesisStatus status)
{
var retVal = true;
// Calculate the initial overall battery load of all batteries in uAs as the drained load uses this unit.
var initialBatteryLoad_uAs = 1000 * 3600 * status.InitialBatteryLoad_mAh * (UInt64)status.BatteryQuantity;
// Relation of drained to initial load, set the battery to fully unloaded for failed check to force a recalculation
var drainedBatteryLoad_rel = 1.0;
if (initialBatteryLoad_uAs != 0)
{
drainedBatteryLoad_rel = (Double)status.DrainedBatteryPower_uAs / initialBatteryLoad_uAs;
}
else
{
retVal = false;
}
status.DrainedBatteryLoadPercent = drainedBatteryLoad_rel * 100;
// The estimated life time uses the drained load over the actual exceeded life time and assumes, that further on
// an identical load will be consumed over the remaining life cycle. The initial load will be multiplied with the
// drained load relation. Set the estimated time to the exceeded time to force a recalculation on failed input.
var estimatedLifeTime_s = (Double)status.ExceededLifeTime_s;
if (drainedBatteryLoad_rel != 0.0)
{
estimatedLifeTime_s = status.ExceededLifeTime_s / drainedBatteryLoad_rel;
}
else
{
retVal = false;
}
var remainingLifetime_s = estimatedLifeTime_s - status.ExceededLifeTime_s;
// convert the seconds to years
status.RemainingLifeTimeYears = remainingLifetime_s / 3600 / 24 / 365.25;
return retVal;
}
}
}
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GenesisStatus")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Microsoft")]
[assembly: AssemblyProduct("GenesisStatus")]
[assembly: AssemblyCopyright("Copyright © Microsoft 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0f646872-d8dc-4ee5-989f-d4c73f0ace3f")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net40" />
</packages>
@@ -89,6 +89,9 @@ namespace Xylem.Common.Hardware.WaterMeter.Genesis.Registers
{
public static readonly String BatteryVoltage = "POWERMON_BatteryVoltage";
public static readonly String BatteryQuantity = "POWERMON_BatteryQuantity";
public static readonly String BatteryDrainedLoad = "POWERMON_TotalUsedCharge";
public static readonly String BatteryInitialLoad = "POWERMON_BatteryMilliAHrRating";
public static readonly String BatteryExceededSeconds = "POWERMON_TotalUsedSeconds";
}