diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c4f0cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,241 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +bin/ +Bin/ +obj/ +Obj/ + +# Visual Studio 2015 cache/options directory +.vs/ +/wwwroot/dist/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Microsoft Azure ApplicationInsights config file +ApplicationInsights.config + +# Windows Store app package directory +AppPackages/ +BundleArtifacts/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +orleans.codegen.cs + +# Ceriticate of identity server for production build +!certificate.pfx + +/node_modules + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# SQLite files +*.db + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe + +# FAKE - F# Make +.fake/ + +# macOS +*.DS_Store diff --git a/App.cs b/App.cs new file mode 100644 index 0000000..e87b804 --- /dev/null +++ b/App.cs @@ -0,0 +1,126 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using Grpc.Net.Client; +using WINGS.GrpcService; +using WINGS_TMTC_IF.Services; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF +{ + public class App + { + private readonly IConfiguration _configuration; + private readonly IOperationService _operationService; + private readonly IPortManager _portManager; + private readonly ITmPacketExtractor _tmPacketExtractor; + private readonly ITcPacketHandler _tcPacketHandler; + private readonly TmtcPacketService _tmtcPacketService; + + public App(IConfiguration configuration, + IOperationService operationService, + IPortManager portManager, + ITmPacketExtractor tmPacketExtractor, + ITcPacketHandler tcPacketHandler, + TmtcPacketService tmtcPacketService) + { + _configuration = configuration; + _operationService = operationService; + _portManager = portManager; + _tmPacketExtractor = tmPacketExtractor; + _tcPacketHandler = tcPacketHandler; + _tmtcPacketService = tmtcPacketService; + } + + public void Run() + { + // Select Opid + Task> opTask= Task.Run(() => _operationService.FetchOperationAsync()); + var opid = ConsoleSelectOperation(opTask.Result); + Console.WriteLine("Operation ID: {0}\n", opid); + + // Initialize Port (Automatically open Serial or LAN port correspond to the selected component) + _portManager.Initialize(); + + // Initialize Packet Handler + _tmPacketExtractor.Initialize(opid); + _tcPacketHandler.Initialize(opid); + + // Start gRPC Service + var client = ConfigureGrpcClient(); + var tmTask = _tmtcPacketService.TmPacketSendLoop(client); + var tcTask = _tmtcPacketService.TcPacketReceiveLoop(client); + Task.WaitAll(tmTask, tcTask); + Console.ReadKey(); + } + + private string ConsoleSelectOperation(List operations) + { + int num; + Console.WriteLine("Select operation and press enter"); + for (int i = 0; i < operations.Count; i++) + { + var operation = operations[i]; + Console.WriteLine("[ {0} ] : {1}", i, operation.PathNumber + " " + operation.Comment); + } + while (true) + { + try + { + num = int.Parse(Console.ReadLine()); + var operation = operations[num]; + if (num < operations.Count) + { + if (!operation.IsTmtcConnected) + { + return operation.Id; + } + else + { + Console.WriteLine("This operation is already connected by another client"); + } + } + else + { + Console.WriteLine("Error unexpeced input"); + } + } + catch + { + Console.WriteLine("Error unexpeced input"); + } + } + } + + private TmtcPacket.TmtcPacketClient ConfigureGrpcClient() + { + var env = _configuration["WINGS:Environment"]; + var grpcConnectionString = _configuration["WINGS:GrpcConnectionString"]; + GrpcChannel channel; + + switch (env) + { + case "Windows": // Windows Development Build + case "Docker": // Linux on Docker Production Build + var httpHandler = new HttpClientHandler(); + httpHandler.ServerCertificateCustomValidationCallback = + HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + channel = GrpcChannel.ForAddress(grpcConnectionString, + new GrpcChannelOptions { HttpHandler = httpHandler }); + break; + + case "Mac": // Mac Development Build + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + channel = GrpcChannel.ForAddress(grpcConnectionString); + break; + + default: + throw new Exception("unsupported environment"); + } + + return new TmtcPacket.TmtcPacketClient(channel); + } + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8bfb333 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Intelligent Space Systems Laboratory, The University of Tokyo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Models/DataEventArgs.cs b/Models/DataEventArgs.cs new file mode 100644 index 0000000..1ebb35d --- /dev/null +++ b/Models/DataEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace WINGS_TMTC_IF.Models +{ + public class DataEventArgs : EventArgs + { + public byte[] Data; + + public DataEventArgs(byte[] dataInByteArray) + { + Data = dataInByteArray; + } + } +} \ No newline at end of file diff --git a/Models/Operation.cs b/Models/Operation.cs new file mode 100644 index 0000000..2b2d0dd --- /dev/null +++ b/Models/Operation.cs @@ -0,0 +1,13 @@ +using System; + +namespace WINGS_TMTC_IF.Models +{ + public class Operation + { + public string Id { get; set; } + public string PathNumber { get; set; } + public string Comment { get; set; } + public string CommanderId { get; set; } + public bool IsTmtcConnected { get; set; } + } +} diff --git a/Models/RecievedDataConfig.cs b/Models/RecievedDataConfig.cs new file mode 100644 index 0000000..36785e1 --- /dev/null +++ b/Models/RecievedDataConfig.cs @@ -0,0 +1,10 @@ +namespace WINGS_TMTC_IF.Models +{ + public class ReceivedDataConfig + { + public int HeaderLength { get; set; } + public int BodyLength { get; set; } + public int FooterLength { get; set; } + public int TotalLength { get { return HeaderLength + BodyLength + FooterLength; } } + } +} diff --git a/Models/TmtcPacket.cs b/Models/TmtcPacket.cs new file mode 100644 index 0000000..c5a6486 --- /dev/null +++ b/Models/TmtcPacket.cs @@ -0,0 +1,13 @@ +namespace WINGS_TMTC_IF.Models +{ + public class TmPacketData + { + public string Opid { get; set; } + public byte[] TmPacket { get; set; } + } + public class TcPacketData + { + public string Opid { get; set; } + public byte[] TcPacket { get; set; } + } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..bc61e0a --- /dev/null +++ b/Program.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; +using WINGS_TMTC_IF.Services; +using WINGS_TMTC_IF.Services.MOBC; +using WINGS_TMTC_IF.Services.IsslCommon; + +namespace WINGS_TMTC_IF +{ + public class Program + { + public static void Main(string[] args) + { + var services = ConfigureServices(); + + var serviceProvider = services.BuildServiceProvider(); + + serviceProvider.GetService().Run(); + } + + private static IServiceCollection ConfigureServices() + { + IServiceCollection services = new ServiceCollection(); + + var configuration = LoadConfiguration(); + services.AddSingleton(configuration); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + ConfigureUserDefinedServices(services, configuration); + + return services; + } + + private static IConfiguration LoadConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + + return builder.Build(); + } + + private static void ConfigureUserDefinedServices(IServiceCollection services, IConfiguration configuration) + { + var component = ConsoleSelectComponent(configuration); + + switch (component) + { + case "MOBC_UART": + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + configuration["SerialPort:BaudRate:Using"] = configuration["SerialPort:BaudRate:MOBC_UART"]; + break; + + case "MOBC_RF": + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + configuration["SerialPort:BaudRate:Using"] = configuration["SerialPort:BaudRate:MOBC_RF"]; + break; + + case "ISSL_COMMON": + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + configuration["SerialPort:BaudRate:Using"] = configuration["SerialPort:BaudRate:MIF"]; + break; + + default: + throw new Exception(); + } + } + + private static string ConsoleSelectComponent(IConfiguration configuration) + { + int num; + var compos = configuration.GetSection("ComponentList").Get(); + + Console.WriteLine("Select component and press enter"); + for (int i = 0; i < compos.Length; i++) + { + Console.WriteLine("[ {0} ] : {1}", i, compos[i]); + } + while (true) + { + try + { + num = int.Parse(Console.ReadLine()); + if (num < compos.Length) + { + return compos[num]; + } + } + catch + { + } + Console.WriteLine("Error unexpected input"); + } + } + } +} diff --git a/Protos/tmtc.proto b/Protos/tmtc.proto new file mode 100644 index 0000000..a6d20f9 --- /dev/null +++ b/Protos/tmtc.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +option csharp_namespace = "WINGS.GrpcService"; + +package tmtc; + +service TmtcPacket { + rpc TmPacketTransfer (TmPacketDataRpc) returns (TmPacketResponseRpc); + rpc TcPacketTransfer (TcPacketRequestRpc) returns (stream TcPacketDataRpc); +} + +message TmPacketDataRpc { + string opid = 1; + bytes tmPacket = 2; +} + +message TmPacketResponseRpc { + string opid = 1; + bool ack = 2; +} + +message TcPacketRequestRpc { + string opid = 1; +} + +message TcPacketDataRpc { + string opid = 1; + bytes tcPacket = 2; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..11608ca --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# WINGS_TMTC_IF +WINGS_TMTC_IF is an interface software for WINGS. It can connect COM port or IP port to [WINGS](https://github.com/ut-issl/wings). + +## Getting Started for User +### Prerequisites +The application listed below is required: ++ [.NET SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) + +### Installing and Running +1. Open a terminal. +2. Navigate to the desired location for the repository. +3. Clone the repository. +4. + ``` + dotnet run + ``` + +### Settings +- You can set URL of WINGS, COM port, IP port, and Component List in appsettings.json diff --git a/Services/Abstracts/TcPacketHandlerBase.cs b/Services/Abstracts/TcPacketHandlerBase.cs new file mode 100644 index 0000000..b715edc --- /dev/null +++ b/Services/Abstracts/TcPacketHandlerBase.cs @@ -0,0 +1,47 @@ +using System; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public abstract class TcPacketHandlerBase + { + private readonly IPortManager _portManager; + private string _opid; + + public TcPacketHandlerBase(IPortManager portManager) + { + _portManager = portManager; + } + + public void Initialize(string opid) + { + _opid = opid; + } + + public string GetOpid() + { + return _opid; + } + + public bool MatchesOpid(string opid) + { + return opid == _opid; + } + + public virtual void HandlePacket(TcPacketData data) + { + Write(data.TcPacket); + TcPacketInfoWriteLine(data); + } + + protected void Write(byte[] packet) + { + _portManager.Write(packet, 0, packet.Length); + } + + protected virtual void TcPacketInfoWriteLine(TcPacketData data) + { + Console.WriteLine("[TcPacket]"); + } + } +} diff --git a/Services/Abstracts/TmPacketExtractorBase.cs b/Services/Abstracts/TmPacketExtractorBase.cs new file mode 100644 index 0000000..89c2de7 --- /dev/null +++ b/Services/Abstracts/TmPacketExtractorBase.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public abstract class TmPacketExtractorBase + { + private readonly IPortManager _portManager; + protected Queue _packetQueue; + protected ReceivedDataConfig _config; + protected string _opid; + protected List _buffer; + + public TmPacketExtractorBase(IPortManager portManager, + ReceivedDataConfig config) + { + _portManager = portManager; + _config = config; + _packetQueue = new Queue(); + _buffer = new List(); + } + + public virtual void Initialize(string opid) + { + _opid = opid; + _portManager.NewDataReceived += NewDataHandle; + } + + protected virtual void NewDataHandle(object sender, DataEventArgs e) + { + _buffer.AddRange(e.Data); + while (_buffer.Count >= _config.HeaderLength) + { + if (AnalyzeHeader()) + { + if (_buffer.Count >= _config.TotalLength) + { + if (AnalyzeFooter()) + { + var receivedDataInByteArray = _buffer.GetRange(0, _config.TotalLength).ToArray(); + var data = ConvertToTmPacketData(receivedDataInByteArray); + _packetQueue.Enqueue(data); + _buffer.RemoveRange(0, _config.TotalLength); + } + else + { + // トータルサイズ以上あるのにフッタを解釈できない + RemoveAllData(); + } + } + else + { + break; + } + } + else + { + // ヘッダ長以上あるのにヘッダを解釈できない + RemoveAllData(); + } + } + } + + protected abstract bool AnalyzeHeader(); + + protected abstract bool AnalyzeFooter(); + + protected virtual TmPacketData ConvertToTmPacketData(byte[] receivedDataInByteArray) + { + return new TmPacketData{ + Opid = _opid, + TmPacket = receivedDataInByteArray + }; + } + + protected virtual void RemoveAllData() + { + _buffer.Clear(); + Console.WriteLine("Remove garbage data"); + } + + public TmPacketData Dequeue() + { + return _packetQueue.Dequeue(); + } + + public bool PacketQueueExists() + { + return _packetQueue.Count != 0; + } + } +} diff --git a/Services/Interfaces/IOperationService.cs b/Services/Interfaces/IOperationService.cs new file mode 100644 index 0000000..99b3e11 --- /dev/null +++ b/Services/Interfaces/IOperationService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public interface IOperationService + { + Task> FetchOperationAsync(); + } +} diff --git a/Services/Interfaces/IPortManager.cs b/Services/Interfaces/IPortManager.cs new file mode 100644 index 0000000..005d1c6 --- /dev/null +++ b/Services/Interfaces/IPortManager.cs @@ -0,0 +1,36 @@ +using System; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public interface IPortManager + { + void Initialize(); + event EventHandler NewDataReceived; + void Write(byte[] data, int offset, int count); + string ConsoleSelectValue(string name, string[] options) + { + int num; + Console.WriteLine("Select {0} and press enter", name); + for (int i = 0; i < options.Length; i++) + { + Console.WriteLine("[ {0} ] : {1}", i, options[i]); + } + while (true) + { + try + { + num = int.Parse(Console.ReadLine()); + if (num < options.Length) + { + return options[num]; + } + } + catch + { + } + Console.WriteLine("Error unexpeced input"); + } + } + } +} diff --git a/Services/Interfaces/ITcPacketHandler.cs b/Services/Interfaces/ITcPacketHandler.cs new file mode 100644 index 0000000..c885ef3 --- /dev/null +++ b/Services/Interfaces/ITcPacketHandler.cs @@ -0,0 +1,12 @@ +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public interface ITcPacketHandler + { + void Initialize(string opid); + string GetOpid(); + bool MatchesOpid(string opid); + void HandlePacket(TcPacketData data); + } +} diff --git a/Services/Interfaces/ITmPacketExtractor.cs b/Services/Interfaces/ITmPacketExtractor.cs new file mode 100644 index 0000000..e22e587 --- /dev/null +++ b/Services/Interfaces/ITmPacketExtractor.cs @@ -0,0 +1,11 @@ +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public interface ITmPacketExtractor + { + void Initialize(string opid); + TmPacketData Dequeue(); + bool PacketQueueExists(); + } +} diff --git a/Services/LanPortManager.cs b/Services/LanPortManager.cs new file mode 100644 index 0000000..2b4a78e --- /dev/null +++ b/Services/LanPortManager.cs @@ -0,0 +1,309 @@ +using System; +using System.Net; +using System.Net.Sockets; +using System.Net.NetworkInformation; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public class LanPortManager : IPortManager + { + private bool _modEnable; + private TcpClient _modTcpClient; + private string _modIpAddress; + private int _modPortNum; + private bool _modIpAutoSearch; + private NetworkStream _modStream; + + private bool _demodEnable; + private UdpClient _demodUdpClient; + private string _demodIpAddress; + private int _demodPortNum; + private bool _demodIpAutoSearch; + private bool _demodConnected; + public event EventHandler NewDataReceived; + + public LanPortManager(IConfiguration configuration) + { + _modEnable = Convert.ToBoolean(configuration["LanPort:ModEnable"]); + if (_modEnable) + { + _modTcpClient = new TcpClient(); + _modIpAddress = configuration["LanPort:ModIpAddress"].ToString(); + _modPortNum = Convert.ToInt32(configuration["LanPort:ModPortNum"]); + _modIpAutoSearch = Convert.ToBoolean(configuration["LanPort:ModIpAutoSearch"]); + } + + _demodEnable = Convert.ToBoolean(configuration["LanPort:DemodEnable"]); + if (_demodEnable) + { + _demodUdpClient = new UdpClient(); + _demodIpAddress = configuration["LanPort:DemodIpAddress"].ToString(); + _demodPortNum = Convert.ToInt32(configuration["LanPort:DemodPortNum"]); + _demodIpAutoSearch = Convert.ToBoolean(configuration["LanPort:DemodIpAutoSearch"]); + _demodConnected = false; + } + } + + ~LanPortManager() + { + Dispose(false); + } + + public void Initialize() + { + if (_modEnable) + { + ModInitialze(); + } + if (_demodEnable) + { + DemodInitialze(); + } + } + + private (string ipAddress, int portNum) GetIpAddressAndPortNum(string ipAddressDefault, int portNumDefault, bool ipAutoSearch) + { + string ipAddressResult = ipAddressDefault; + int portNumResult = portNumDefault; + + Ping p = new Ping(); + var pingTimeout = 100; //[ms] + + if (ipAutoSearch) // search valid ip address from all address in the same network & select from valid lists + { + const string ipPrefix = "192.168.0."; + string ipTmp = string.Empty; + List validIpList = new List(); + Console.WriteLine("Searching LAN port (about {0}sec) ... ", Math.Ceiling(Convert.ToDecimal(pingTimeout * 255 / 1000))); + + for (int i = 1; i <= 255; i++) + { + ipTmp = ipPrefix + i.ToString(); + + PingReply pReply = p.Send(ipTmp, pingTimeout); + if (pReply.Status == IPStatus.Success) + { + validIpList.Add(ipTmp); + } + } + string[] validIps = validIpList.ToArray(); + + ipAddressResult = ((IPortManager)this).ConsoleSelectValue("lan port", validIps); + portNumResult = portNumDefault; + } + else // use default ip address written in appsettings.json + { + PingReply pReply = p.Send(ipAddressDefault, pingTimeout); + if (pReply.Status == IPStatus.Success) + { + Console.WriteLine("Ping check is successed"); + ipAddressResult = ipAddressDefault; + portNumResult = portNumDefault; + } + else + { + Console.WriteLine("Error default LAN port don't reponse to ping"); + Environment.Exit(0); + } + } + + return (ipAddressResult, portNumResult); + } + + private void ModInitialze() + { + string ipAddressDefault = _modIpAddress; + int portNumDefault = _modPortNum; + Console.WriteLine("Modulator default LAN port ... {0}:{1}", ipAddressDefault, portNumDefault); + + (_modIpAddress, _modPortNum) = GetIpAddressAndPortNum(ipAddressDefault, portNumDefault, _modIpAutoSearch); + + Console.WriteLine("Do you want to Open {0}:{1} as modulator ? [y/n]", _modIpAddress, _modPortNum); + string ans; + while (true) + { + try + { + ans = Console.ReadLine(); + if (ans == "y" || ans == "Y") + { + ModOpen(_modIpAddress, _modPortNum); + return; + } + else if (ans == "n" || ans == "N") + { + Console.WriteLine("Please start program from first"); + Environment.Exit(0); + } + } + catch + { + } + Console.WriteLine("Error unexpected input"); + } + } + + private void DemodInitialze() + { + string ipAddressDefault = _demodIpAddress; + int portNumDefault = _demodPortNum; + Console.WriteLine("Demodulator default LAN port ... {0}:{1}", ipAddressDefault, portNumDefault); + + (_demodIpAddress, _demodPortNum) = GetIpAddressAndPortNum(ipAddressDefault, portNumDefault, _demodIpAutoSearch); + + Console.WriteLine("Do you want to Open {0}:{1} as demodulator ? [y/n]", _demodIpAddress, _demodPortNum); + string ans; + while (true) + { + try + { + ans = Console.ReadLine() ; + if (ans == "y" || ans == "Y") + { + DemodOpen(_demodIpAddress, _demodPortNum); + return; + } + else if (ans == "n" || ans == "N") + { + Console.WriteLine("Please start program from first"); + Environment.Exit(0); + } + } + catch + { + } + Console.WriteLine("Error unexpected input"); + } + } + + private void ModOpen(string ipAddress, int portNum) + { + if (_modTcpClient.Connected) + { + _modTcpClient.Close(); + } + + _modIpAddress = ipAddress; + _modPortNum = portNum; + try + { + _modTcpClient.Connect(_modIpAddress, _modPortNum); + Console.WriteLine("Opening Modulator is Successed"); + } + catch + { + Console.WriteLine("Error opening lan port " + _modIpAddress + ":" + _modPortNum); + Environment.Exit(0); + } + } + + private void DemodOpen(string ipAddress, int portNum) + { + if (_demodConnected) + { + _demodUdpClient.Close(); + } + + _demodIpAddress = ipAddress; + _demodPortNum = portNum; + try + { + IPEndPoint localIpEndPoint = new IPEndPoint(IPAddress.Parse(_demodIpAddress), _demodPortNum); + _demodUdpClient.Client.Bind(localIpEndPoint); + + _demodConnected = true; + Console.WriteLine("Opening Demodulator is Successed"); + + Task.Run(() => DataReceiveTask()); + } + catch + { + Console.WriteLine("Error opening lan port " + _demodIpAddress + ":" + _demodPortNum); + Environment.Exit(0); + } + } + + private void ModClose() + { + _modTcpClient.Close(); + } + + private void DemodClose() + { + _demodUdpClient.Close(); + _demodConnected = false; + } + + public void Write(byte[] data, int offset, int count) + { + if (_modEnable && _modTcpClient.Connected) + { + try + { + _modStream = _modTcpClient.GetStream(); + } + catch (System.Exception) + { + return; + } + + _modStream.Write(data, offset, count); + + _modStream.Flush(); + } + } + + private void DataReceiveTask() + { + while (_demodConnected) + { + IPEndPoint remoteIpEndPoint = null; + try + { + byte[] data = _demodUdpClient.Receive(ref remoteIpEndPoint); + + if (NewDataReceived != null) + { + NewDataReceived(this, new DataEventArgs(data)); + } + } + catch (System.Exception) + { + return; + } + } + } + + private void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_modEnable) + { + if (_modTcpClient.Connected) + { + _modTcpClient.Close(); + } + _modTcpClient.Dispose(); + } + + if (_demodEnable) + { + if (_demodConnected) + { + _demodUdpClient.Close(); + _demodConnected = false; + } + _demodUdpClient.Dispose(); + } + } + } +} diff --git a/Services/OperationService.cs b/Services/OperationService.cs new file mode 100644 index 0000000..054f419 --- /dev/null +++ b/Services/OperationService.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using Newtonsoft.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public class OperationService : IOperationService + { + private readonly string _environment; + private readonly string _connectionString; + + public OperationService(IConfiguration configuraiton) + { + _environment = configuraiton["WINGS:Environment"]; + _connectionString = configuraiton["WINGS:ConnectionString"]; + } + + public async Task> FetchOperationAsync() + { + + var operations = new List(); + var url = _connectionString + "/api/operations"; + + var handler = GetHttpClientHandler(); + using (var client = new HttpClient(handler)) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Version = new Version(2, 0); + var response = await client.SendAsync(request); + if (response.StatusCode == HttpStatusCode.OK) + { + var jsonString = await response.Content.ReadAsStringAsync(); + var json = JsonConvert.DeserializeObject(jsonString); + operations = json.data; + } + } + return operations; + } + + private HttpClientHandler GetHttpClientHandler() + { + var handler = new HttpClientHandler(); + switch (_environment) + { + case "Windows": // Windows Development Build + case "Docker": // Linux on Docker Production Build + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + break; + + case "Mac": // Mac Development Build + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + break; + + default: + throw new Exception("unsupported environment"); + } + return handler; + } + } + + public class OperationResponse + { + public List data { get; set; } + } +} diff --git a/Services/SerialPortManager.cs b/Services/SerialPortManager.cs new file mode 100644 index 0000000..a24f4dd --- /dev/null +++ b/Services/SerialPortManager.cs @@ -0,0 +1,114 @@ +using System; +using System.Linq; +using System.IO.Ports; +using System.Collections.Generic; +using Microsoft.Extensions.Configuration; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public class SerialPortManager : IPortManager + { + private SerialPort _serialPort; + public event EventHandler NewDataReceived; + + public SerialPortManager(IConfiguration configuration) + { + _serialPort = new SerialPort(); + _serialPort.BaudRate = Convert.ToInt32(configuration["SerialPort:BaudRate:Using"]); + _serialPort.Parity = Parity.None; + _serialPort.StopBits = StopBits.One; + _serialPort.DataBits = 8; + _serialPort.Handshake = Handshake.None; + _serialPort.DataReceived += new SerialDataReceivedEventHandler(OnDataReceived); + } + + ~SerialPortManager() + { + Dispose(false); + } + + public void Initialize() + { + var portNames = SerialPort.GetPortNames() + .Where(name => name.Contains("COM") || name.Contains("tty.usb")) + .ToArray(); + var portName = ((IPortManager)this).ConsoleSelectValue("serial port", portNames); + Console.WriteLine("Open {0}...", portName); + Open(portName); + } + + private void Open(string portName) + { + if (_serialPort.IsOpen) + { + _serialPort.Close(); + } + + _serialPort.PortName = portName; + try + { + _serialPort.Open(); + Console.WriteLine("Success"); + } + catch + { + Console.WriteLine("Error opening serial port " + portName); + Environment.Exit(0); + } + } + + private void Close() + { + _serialPort.Close(); + } + + public void Write(byte[] data, int offset, int count) + { + if (_serialPort.IsOpen) + { + _serialPort.Write(data, offset, count); + } + } + + private void OnDataReceived(object sender, SerialDataReceivedEventArgs e) + { + if (_serialPort.IsOpen) + { + int dataLength = _serialPort.BytesToRead; + byte[] data = new byte[dataLength]; + + int numDataRead = _serialPort.Read(data, 0, dataLength); + if (numDataRead == 0) + { + return; + } + + if (NewDataReceived != null) + { + NewDataReceived(this, new DataEventArgs(data)); + } + } + } + + private void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _serialPort.DataReceived -= new SerialDataReceivedEventHandler(OnDataReceived); + } + + if (_serialPort.IsOpen) + { + _serialPort.Close(); + } + _serialPort.Dispose(); + } + } +} diff --git a/Services/TmtcPacketService.cs b/Services/TmtcPacketService.cs new file mode 100644 index 0000000..b18b4cd --- /dev/null +++ b/Services/TmtcPacketService.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Grpc.Core; +using Google.Protobuf; +using WINGS.GrpcService; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services +{ + public class TmtcPacketService + { + private readonly ITmPacketExtractor _tmPacketExtractor; + private readonly ITcPacketHandler _tcPacketHandler; + + public TmtcPacketService(ITmPacketExtractor tmPacketExtractor, + ITcPacketHandler tcPacketHandler) + { + _tmPacketExtractor = tmPacketExtractor; + _tcPacketHandler = tcPacketHandler; + } + + public async Task TmPacketSendLoop(TmtcPacket.TmtcPacketClient client) + { + while (true) + { + if (_tmPacketExtractor.PacketQueueExists()) + { + var dataRpc = ToRpcModel(_tmPacketExtractor.Dequeue()); + var response = await client.TmPacketTransferAsync(dataRpc); + Console.WriteLine("[TmPacketAck]: " + response.Ack.ToString()); + } + await Task.Delay(10); + } + } + + public async Task TcPacketReceiveLoop(TmtcPacket.TmtcPacketClient client) + { + Console.WriteLine("[TcPacket] : Request"); + var result = client.TcPacketTransfer(new TcPacketRequestRpc + { + Opid = _tcPacketHandler.GetOpid() + }); + var tokenSource = new CancellationTokenSource(); + try + { + await foreach (var data in result.ResponseStream.ReadAllAsync(tokenSource.Token)) + { + if (_tcPacketHandler.MatchesOpid(data.Opid)) + { + _tcPacketHandler.HandlePacket(FromRpcModel(data)); + } + else + { + Console.Write("Error operation id doesn't match"); + } + } + } + catch (RpcException e) + { + Console.WriteLine(e.ToString()); + } + } + + private TmPacketDataRpc ToRpcModel(TmPacketData data) + { + return new TmPacketDataRpc{ + Opid = data.Opid, + TmPacket = ByteString.CopyFrom(data.TmPacket) + }; + } + + private TcPacketData FromRpcModel(TcPacketDataRpc dataRpc) + { + return new TcPacketData{ + Opid = dataRpc.Opid, + TcPacket = dataRpc.TcPacket.ToByteArray() + }; + } + } +} diff --git a/Services/UserDefined/ISSL_COMMON/IsslCommonTcPacketHandler.cs b/Services/UserDefined/ISSL_COMMON/IsslCommonTcPacketHandler.cs new file mode 100644 index 0000000..638c7ef --- /dev/null +++ b/Services/UserDefined/ISSL_COMMON/IsslCommonTcPacketHandler.cs @@ -0,0 +1,9 @@ +namespace WINGS_TMTC_IF.Services.IsslCommon +{ + public class IsslCommonTcPacketHandler : TcPacketHandlerBase, ITcPacketHandler + { + public IsslCommonTcPacketHandler(IPortManager portManager) : base(portManager) + { + } + } +} diff --git a/Services/UserDefined/ISSL_COMMON/IsslCommonTmPacketExtractor.cs b/Services/UserDefined/ISSL_COMMON/IsslCommonTmPacketExtractor.cs new file mode 100644 index 0000000..7f42ed4 --- /dev/null +++ b/Services/UserDefined/ISSL_COMMON/IsslCommonTmPacketExtractor.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services.IsslCommon +{ + public class IsslCommonTmPacketExtractor : TmPacketExtractorBase, ITmPacketExtractor + { + private static readonly byte[] STX = new byte[]{0xeb, 0x90}; + private static readonly byte[] ETX = new byte[]{0xc5, 0x79}; + + public IsslCommonTmPacketExtractor(IPortManager portManager) + : base(portManager, new ReceivedDataConfig { + HeaderLength = 4, + BodyLength = 500, + FooterLength = 4 + }) + { + } + + protected override bool AnalyzeHeader() + { + if (!_buffer.GetRange(0, STX.Count()).SequenceEqual(STX)) + { + return false; + } + byte[] packet_tmp = _buffer.GetRange(2, 2).ToArray(); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(packet_tmp); + } + _config.BodyLength = BitConverter.ToUInt16(packet_tmp); + + return true; + } + + protected override bool AnalyzeFooter() + { + return _buffer.GetRange(_config.TotalLength - ETX.Count(), ETX.Count()).SequenceEqual(ETX); + } + + protected override TmPacketData ConvertToTmPacketData(byte[] receivedDataInByteArray) + { + Console.WriteLine("Received"); + return base.ConvertToTmPacketData(receivedDataInByteArray); + } + } +} diff --git a/Services/UserDefined/MOBC/MobcRfTcPacketHandler.cs b/Services/UserDefined/MOBC/MobcRfTcPacketHandler.cs new file mode 100644 index 0000000..b5d9bee --- /dev/null +++ b/Services/UserDefined/MOBC/MobcRfTcPacketHandler.cs @@ -0,0 +1,26 @@ +using System; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services.MOBC +{ + public class MobcRfTcPacketHandler : TcPacketHandlerBase, ITcPacketHandler + { + private const int _packetMaxLen = 1200; // limitation from modulator spec + + public MobcRfTcPacketHandler(IPortManager portManager) : base(portManager) + { + } + + public override void HandlePacket(TcPacketData data) + { + if (data.TcPacket.Length > _packetMaxLen) + { + Console.Write("TcPacket size {0} byte is too large: Modulator couldn't handle it.", data.TcPacket.Length); + return; + } + + Write(data.TcPacket); + TcPacketInfoWriteLine(data); + } + } +} diff --git a/Services/UserDefined/MOBC/MobcRfTmPacketExtractor.cs b/Services/UserDefined/MOBC/MobcRfTmPacketExtractor.cs new file mode 100644 index 0000000..8003156 --- /dev/null +++ b/Services/UserDefined/MOBC/MobcRfTmPacketExtractor.cs @@ -0,0 +1,548 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services.MOBC +{ + public class MobcRfTmPacketExtractor : TmPacketExtractorBase, ITmPacketExtractor + { + //STX + private enum Ver { Ver1 = 0b00, Ver2 = 0b01 } + private enum ScId { SampleSat = 0x000 } + private enum VirChId { Realtime = 0b000001, Replay = 0b000010, Fill = 0b111111 } + + //ETX + private enum CtlWrdType { CLCW = 0b0 } + private enum ClcwVer { Ver1 = 0b00 } + private enum COPinEff { COP1 = 0b01 } + private enum VCId { Default = 0b000000 } + private enum Spare { Fixed = 0b00 } + + //Reed-Solomon decoding + private const int ReedSolomonCodeLength = 64; //TODO: 他のインターリーブ深度にも対応するか? + private const int MM = 8; + private const int NN = 255; + private const int NROOTS = 32; + private const int FCR = 112; + private const int PRIM = 11; + private const int IPRIM = 116; + private const int A0 = NN; + private static readonly byte[] CCSDS_alpha_to = { + 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x87,0x89,0x95,0xad,0xdd,0x3d,0x7a,0xf4, + 0x6f,0xde,0x3b,0x76,0xec,0x5f,0xbe,0xfb,0x71,0xe2,0x43,0x86,0x8b,0x91,0xa5,0xcd, + 0x1d,0x3a,0x74,0xe8,0x57,0xae,0xdb,0x31,0x62,0xc4,0x0f,0x1e,0x3c,0x78,0xf0,0x67, + 0xce,0x1b,0x36,0x6c,0xd8,0x37,0x6e,0xdc,0x3f,0x7e,0xfc,0x7f,0xfe,0x7b,0xf6,0x6b, + 0xd6,0x2b,0x56,0xac,0xdf,0x39,0x72,0xe4,0x4f,0x9e,0xbb,0xf1,0x65,0xca,0x13,0x26, + 0x4c,0x98,0xb7,0xe9,0x55,0xaa,0xd3,0x21,0x42,0x84,0x8f,0x99,0xb5,0xed,0x5d,0xba, + 0xf3,0x61,0xc2,0x03,0x06,0x0c,0x18,0x30,0x60,0xc0,0x07,0x0e,0x1c,0x38,0x70,0xe0, + 0x47,0x8e,0x9b,0xb1,0xe5,0x4d,0x9a,0xb3,0xe1,0x45,0x8a,0x93,0xa1,0xc5,0x0d,0x1a, + 0x34,0x68,0xd0,0x27,0x4e,0x9c,0xbf,0xf9,0x75,0xea,0x53,0xa6,0xcb,0x11,0x22,0x44, + 0x88,0x97,0xa9,0xd5,0x2d,0x5a,0xb4,0xef,0x59,0xb2,0xe3,0x41,0x82,0x83,0x81,0x85, + 0x8d,0x9d,0xbd,0xfd,0x7d,0xfa,0x73,0xe6,0x4b,0x96,0xab,0xd1,0x25,0x4a,0x94,0xaf, + 0xd9,0x35,0x6a,0xd4,0x2f,0x5e,0xbc,0xff,0x79,0xf2,0x63,0xc6,0x0b,0x16,0x2c,0x58, + 0xb0,0xe7,0x49,0x92,0xa3,0xc1,0x05,0x0a,0x14,0x28,0x50,0xa0,0xc7,0x09,0x12,0x24, + 0x48,0x90,0xa7,0xc9,0x15,0x2a,0x54,0xa8,0xd7,0x29,0x52,0xa4,0xcf,0x19,0x32,0x64, + 0xc8,0x17,0x2e,0x5c,0xb8,0xf7,0x69,0xd2,0x23,0x46,0x8c,0x9f,0xb9,0xf5,0x6d,0xda, + 0x33,0x66,0xcc,0x1f,0x3e,0x7c,0xf8,0x77,0xee,0x5b,0xb6,0xeb,0x51,0xa2,0xc3,0x00, + }; + private static readonly int[] CCSDS_index_of = { + 255, 0, 1, 99, 2,198,100,106, 3,205,199,188,101,126,107, 42, + 4,141,206, 78,200,212,189,225,102,221,127, 49,108, 32, 43,243, + 5, 87,142,232,207,172, 79,131,201,217,213, 65,190,148,226,180, + 103, 39,222,240,128,177, 50, 53,109, 69, 33, 18, 44, 13,244, 56, + 6,155, 88, 26,143,121,233,112,208,194,173,168, 80,117,132, 72, + 202,252,218,138,214, 84, 66, 36,191,152,149,249,227, 94,181, 21, + 104, 97, 40,186,223, 76,241, 47,129,230,178, 63, 51,238, 54, 16, + 110, 24, 70,166, 34,136, 19,247, 45,184, 14, 61,245,164, 57, 59, + 7,158,156,157, 89,159, 27, 8,144, 9,122, 28,234,160,113, 90, + 209, 29,195,123,174, 10,169,145, 81, 91,118,114,133,161, 73,235, + 203,124,253,196,219, 30,139,210,215,146, 85,170, 67, 11, 37,175, + 192,115,153,119,150, 92,250, 82,228,236, 95, 74,182,162, 22,134, + 105,197, 98,254, 41,125,187,204,224,211, 77,140,242, 31, 48,220, + 130,171,231, 86,179,147, 64,216, 52,176,239, 38, 55, 12, 17, 68, + 111,120, 25,154, 71,116,167,193, 35, 83,137,251, 20, 93,248,151, + 46, 75,185, 96, 15,237, 62,229,246,135,165, 23, 58,163, 60,183, + }; + private static readonly int[] CCSDS_poly = { + 0,249, 59, 66, 4, 43,126,251, 97, 30, 3,213, 50, 66,170, 5, + 24, 5,170, 66, 50,213, 3, 30, 97,251,126, 43, 4, 66, 59,249, + 0, + }; + private static readonly byte[] Taltab = { + 0x00,0x7b,0xaf,0xd4,0x99,0xe2,0x36,0x4d,0xfa,0x81,0x55,0x2e,0x63,0x18,0xcc,0xb7, + 0x86,0xfd,0x29,0x52,0x1f,0x64,0xb0,0xcb,0x7c,0x07,0xd3,0xa8,0xe5,0x9e,0x4a,0x31, + 0xec,0x97,0x43,0x38,0x75,0x0e,0xda,0xa1,0x16,0x6d,0xb9,0xc2,0x8f,0xf4,0x20,0x5b, + 0x6a,0x11,0xc5,0xbe,0xf3,0x88,0x5c,0x27,0x90,0xeb,0x3f,0x44,0x09,0x72,0xa6,0xdd, + 0xef,0x94,0x40,0x3b,0x76,0x0d,0xd9,0xa2,0x15,0x6e,0xba,0xc1,0x8c,0xf7,0x23,0x58, + 0x69,0x12,0xc6,0xbd,0xf0,0x8b,0x5f,0x24,0x93,0xe8,0x3c,0x47,0x0a,0x71,0xa5,0xde, + 0x03,0x78,0xac,0xd7,0x9a,0xe1,0x35,0x4e,0xf9,0x82,0x56,0x2d,0x60,0x1b,0xcf,0xb4, + 0x85,0xfe,0x2a,0x51,0x1c,0x67,0xb3,0xc8,0x7f,0x04,0xd0,0xab,0xe6,0x9d,0x49,0x32, + 0x8d,0xf6,0x22,0x59,0x14,0x6f,0xbb,0xc0,0x77,0x0c,0xd8,0xa3,0xee,0x95,0x41,0x3a, + 0x0b,0x70,0xa4,0xdf,0x92,0xe9,0x3d,0x46,0xf1,0x8a,0x5e,0x25,0x68,0x13,0xc7,0xbc, + 0x61,0x1a,0xce,0xb5,0xf8,0x83,0x57,0x2c,0x9b,0xe0,0x34,0x4f,0x02,0x79,0xad,0xd6, + 0xe7,0x9c,0x48,0x33,0x7e,0x05,0xd1,0xaa,0x1d,0x66,0xb2,0xc9,0x84,0xff,0x2b,0x50, + 0x62,0x19,0xcd,0xb6,0xfb,0x80,0x54,0x2f,0x98,0xe3,0x37,0x4c,0x01,0x7a,0xae,0xd5, + 0xe4,0x9f,0x4b,0x30,0x7d,0x06,0xd2,0xa9,0x1e,0x65,0xb1,0xca,0x87,0xfc,0x28,0x53, + 0x8e,0xf5,0x21,0x5a,0x17,0x6c,0xb8,0xc3,0x74,0x0f,0xdb,0xa0,0xed,0x96,0x42,0x39, + 0x08,0x73,0xa7,0xdc,0x91,0xea,0x3e,0x45,0xf2,0x89,0x5d,0x26,0x6b,0x10,0xc4,0xbf, + }; + private static readonly byte[] Tal1tab = { + 0x00,0xcc,0xac,0x60,0x79,0xb5,0xd5,0x19,0xf0,0x3c,0x5c,0x90,0x89,0x45,0x25,0xe9, + 0xfd,0x31,0x51,0x9d,0x84,0x48,0x28,0xe4,0x0d,0xc1,0xa1,0x6d,0x74,0xb8,0xd8,0x14, + 0x2e,0xe2,0x82,0x4e,0x57,0x9b,0xfb,0x37,0xde,0x12,0x72,0xbe,0xa7,0x6b,0x0b,0xc7, + 0xd3,0x1f,0x7f,0xb3,0xaa,0x66,0x06,0xca,0x23,0xef,0x8f,0x43,0x5a,0x96,0xf6,0x3a, + 0x42,0x8e,0xee,0x22,0x3b,0xf7,0x97,0x5b,0xb2,0x7e,0x1e,0xd2,0xcb,0x07,0x67,0xab, + 0xbf,0x73,0x13,0xdf,0xc6,0x0a,0x6a,0xa6,0x4f,0x83,0xe3,0x2f,0x36,0xfa,0x9a,0x56, + 0x6c,0xa0,0xc0,0x0c,0x15,0xd9,0xb9,0x75,0x9c,0x50,0x30,0xfc,0xe5,0x29,0x49,0x85, + 0x91,0x5d,0x3d,0xf1,0xe8,0x24,0x44,0x88,0x61,0xad,0xcd,0x01,0x18,0xd4,0xb4,0x78, + 0xc5,0x09,0x69,0xa5,0xbc,0x70,0x10,0xdc,0x35,0xf9,0x99,0x55,0x4c,0x80,0xe0,0x2c, + 0x38,0xf4,0x94,0x58,0x41,0x8d,0xed,0x21,0xc8,0x04,0x64,0xa8,0xb1,0x7d,0x1d,0xd1, + 0xeb,0x27,0x47,0x8b,0x92,0x5e,0x3e,0xf2,0x1b,0xd7,0xb7,0x7b,0x62,0xae,0xce,0x02, + 0x16,0xda,0xba,0x76,0x6f,0xa3,0xc3,0x0f,0xe6,0x2a,0x4a,0x86,0x9f,0x53,0x33,0xff, + 0x87,0x4b,0x2b,0xe7,0xfe,0x32,0x52,0x9e,0x77,0xbb,0xdb,0x17,0x0e,0xc2,0xa2,0x6e, + 0x7a,0xb6,0xd6,0x1a,0x03,0xcf,0xaf,0x63,0x8a,0x46,0x26,0xea,0xf3,0x3f,0x5f,0x93, + 0xa9,0x65,0x05,0xc9,0xd0,0x1c,0x7c,0xb0,0x59,0x95,0xf5,0x39,0x20,0xec,0x8c,0x40, + 0x54,0x98,0xf8,0x34,0x2d,0xe1,0x81,0x4d,0xa4,0x68,0x08,0xc4,0xdd,0x11,0x71,0xbd, + }; + + public MobcRfTmPacketExtractor(IPortManager portManager) + : base(portManager, new ReceivedDataConfig + { + HeaderLength = 6, + BodyLength = 434, + FooterLength = 4 + }) + { + } + + protected override void NewDataHandle(object sender, DataEventArgs e) + { + _buffer.AddRange(e.Data); + while (_buffer.Count >= _config.HeaderLength) + { + if (AnalyzeHeader()) + { + if (_buffer.Count >= _config.TotalLength + ReedSolomonCodeLength) + { + var receivedDataInbyteArrayWithReedSolomonCodes = _buffer.GetRange(0, _config.TotalLength + ReedSolomonCodeLength).ToArray(); + ReedSolomonDecode(ref receivedDataInbyteArrayWithReedSolomonCodes); + + if (AnalyzeFooter()) + { + var receivedDataInByteArray = receivedDataInbyteArrayWithReedSolomonCodes.ToList().GetRange(0, _config.TotalLength).ToArray(); + var data = ConvertToTmPacketData(receivedDataInByteArray); + _packetQueue.Enqueue(data); + _buffer.RemoveRange(0, _config.TotalLength + ReedSolomonCodeLength); + } + else + { + // トータルサイズ以上あるのにフッタを解釈できない + RemoveAllData(); + } + } + else + { + break; + } + } + else + { + // ヘッダ長以上あるのにヘッダを解釈できない + RemoveAllData(); + } + } + } + + protected override bool AnalyzeHeader() + { + var STX = _buffer.GetRange(0, _config.HeaderLength).ToArray(); + //only use fixed bits + if (!ChkVer(STX, Ver.Ver2)) { return false; } + // if (!ChkScId(STX, ScId.SampleSat)) { return false; } + return true; + } + + protected override bool AnalyzeFooter() + { + var ETX = _buffer.GetRange(_config.TotalLength - _config.FooterLength, _config.FooterLength).ToArray(); + //only use fixed bits + if (!ChkCtlWrdType(ETX, CtlWrdType.CLCW)) { return false; } + if (!ChkClcwVer(ETX, ClcwVer.Ver1)) { return false; } + if (!ChkCOPinEff(ETX, COPinEff.COP1)) { return false; } + if (!ChkVCId(ETX, VCId.Default)) { return false; } + if (!ChkSpare(ETX, Spare.Fixed)) { return false; } + return true; + } + + protected override TmPacketData ConvertToTmPacketData(byte[] receivedDataInByteArray) + { + foreach (var x in receivedDataInByteArray) + { + Console.Write("{0:x2} ", x); + } + Console.WriteLine(); + return base.ConvertToTmPacketData(receivedDataInByteArray); + } + + protected override void RemoveAllData() + { + _buffer.Clear(); + } + + private bool ChkVer(byte[] STX, Ver ver) + { + int pos = 0; + byte mask = 0b_1100_0000; + byte val = (byte)((byte)ver << 6); + return (STX[pos] & mask) == val; + } + + private bool ChkScId(byte[] STX, ScId id) + { + int pos1 = 0; + byte mask1 = 0b_0011_1111; + byte val1 = (byte)((byte)id >> 2); + int pos2 = 1; + byte mask2 = 0b_1100_0000; + byte val2 = (byte)((byte)id << 6); + return ((STX[pos1] & mask1) == val1) & ((STX[pos2] & mask2) == val2); + } + + private bool ChkCtlWrdType(byte[] ETX, CtlWrdType type) + { + int pos = 0; + byte mask = 0b_1000_0000; + byte val = (byte)((byte)type << 7); + return (ETX[pos] & mask) == val; + } + + private bool ChkClcwVer(byte[] ETX, ClcwVer ver) + { + int pos = 0; + byte mask = 0b_0110_0000; + byte val = (byte)((byte)ver << 5); + return (ETX[pos] & mask) == val; + } + + private bool ChkCOPinEff(byte[] ETX, COPinEff eff) + { + int pos = 0; + byte mask = 0b_0000_0011; + byte val = (byte)((byte)eff); + return (ETX[pos] & mask) == val; + } + + private bool ChkVCId(byte[] ETX, VCId id) + { + int pos = 1; + byte mask = 0b_1111_1100; + byte val = (byte)((byte)id << 2); + return (ETX[pos] & mask) == val; + } + + private bool ChkSpare(byte[] ETX, Spare spare) + { + int pos = 1; + byte mask = 0b_0000_0011; + byte val = (byte)((byte)spare); + return (ETX[pos] & mask) == val; + } + + private void ReedSolomonDecode(ref byte[] byteArrayWithReedSolomonCode) // specific to interleave2(shortened) + { + // Add zero fill to head + byte[] zerofill = { 0, 0 }; + byte[] byteArrayWithReedSolomonCodeZeroFilled = zerofill.Concat(byteArrayWithReedSolomonCode).ToArray(); + + // divide to 2 byte array + byte[] byteArray1 = new byte[255]; + byte[] byteArray2 = new byte[255]; + for (int i = 0; i < 510; ++i) + { + int index = i / 2; + if (i % 2 == 0) // even + { + byteArray1[index] = byteArrayWithReedSolomonCodeZeroFilled[i]; + } + else // odd + { + byteArray2[index] = byteArrayWithReedSolomonCodeZeroFilled[i]; + } + } + + // decode each byte array block + ReedSolomonDecodeOneBlock(ref byteArray1); + ReedSolomonDecodeOneBlock(ref byteArray2); + + // joint 2 byte array + for (int i = 2; i < 510 - ReedSolomonCodeLength; ++i) + { + int index = i / 2; + if (i % 2 == 0) // even + { + byteArrayWithReedSolomonCodeZeroFilled[i] = byteArray1[index]; + } + else // odd + { + byteArrayWithReedSolomonCodeZeroFilled[i] = byteArray2[index]; + } + } + + // Remove zero fill + byteArrayWithReedSolomonCode = byteArrayWithReedSolomonCodeZeroFilled.ToList().GetRange(2, 508).ToArray(); + } + private void ReedSolomonDecodeOneBlock(ref byte[] byteArrayWithReedSolomonCode) + { + int[] erasures_pos = new int[NROOTS]; + int erasures_num = 0; + byte[] cdata = new byte[NN]; + + // Convert data from dual basis to conventional + for (int i = 0; i < NN; ++i) + cdata[i] = Tal1tab[byteArrayWithReedSolomonCode[i]]; + + int r = ReedSolomonErrorCheckAndCorrect(cdata, erasures_pos, erasures_num); + + if (r > 0) + { + // Convert from conventional to dual basis + for (int i = 0; i < NN; ++i) + byteArrayWithReedSolomonCode[i] = Taltab[cdata[i]]; + } + else + { + // Console.Write("No errors detected in RS codes"); + } + } + private int ReedSolomonErrorCheckAndCorrect(byte[] data, int[] erasures_pos, int erasures_num) + { + int[] lambda = new int[NROOTS + 1]; // Err+Eras Locator poly + int[] s = new int[NROOTS]; // syndrome poly + int[] b = new int[NROOTS + 1]; + int[] t = new int[NROOTS + 1]; + int[] omega = new int[NROOTS + 1]; + int[] root = new int[NROOTS]; + int[] reg = new int[NROOTS + 1]; + int[] loc = new int[NROOTS]; + int count; + + // form the syndromes; i.e., evaluate data(x) at roots of g(x) + for (int i = 0; i < NROOTS; ++i) + s[i] = data[0]; + + for (int j = 1; j < NN; ++j) + { + for (int i = 0; i < NROOTS; ++i) + { + if (s[i] == 0) + { + s[i] = data[j]; + } + else + { + s[i] = data[j] ^ CCSDS_alpha_to[mod255(CCSDS_index_of[s[i]] + (FCR + i) * PRIM)]; + } + } + } + + // Convert syndromes to index form, checking for nonzero condition + int syn_error = 0; + for (int i = 0; i < NROOTS; ++i) + { + syn_error |= s[i]; + s[i] = CCSDS_index_of[s[i]]; + } + + if (syn_error == 0) + { + // if syndrome is zero, data[] is a codeword and there are no errors to correct. So return data[] unmodified + count = 0; + goto finish; + } + lambda[0] = 1; + + if (erasures_num > 0) + { + int u = 0; + int u_tmp = 0; + // Init lambda to be the erasure locator polynomial + lambda[1] = CCSDS_alpha_to[mod255(PRIM * (NN - 1 - erasures_pos[0]))]; + for (int i = 1; i < erasures_num; ++i) + { + u = mod255(PRIM * (NN - 1 - erasures_pos[i])); + for (int j = i + 1; j > 0; --j) + { + u_tmp = CCSDS_index_of[lambda[j - 1]]; + if (u_tmp != A0) + lambda[j] ^= CCSDS_alpha_to[mod255(u + u_tmp)]; + } + } + } + for (int i = 0; i < NROOTS + 1; ++i) + b[i] = CCSDS_index_of[lambda[i]]; + + // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial + int r = erasures_num; + int el = erasures_num; + int discr_r = 0; + while (++r <= NROOTS) // r is the step number + { + // Compute discrepancy at the r-th step in poly-form + discr_r = 0; + for (int i = 0; i < r; ++i) + { + if ((lambda[i] != 0) && (s[r - i - 1] != A0)) + { + discr_r ^= CCSDS_alpha_to[mod255(CCSDS_index_of[lambda[i]] + s[r - i - 1])]; + } + } + discr_r = CCSDS_index_of[discr_r]; // Index form + if (discr_r == A0) + { + // 2 lines below: B(x) <-- x*B(x) + var b_list = new List(b); + b_list.Insert(0, A0); + b_list.RemoveAt(NROOTS + 1); + b = b_list.ToArray(); + } + else + { + // 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) + t[0] = lambda[0]; + for (int i = 0; i < NROOTS; ++i) + { + if (b[i] != A0) + t[i + 1] = lambda[i + 1] ^ CCSDS_alpha_to[mod255(discr_r + b[i])]; + else + t[i + 1] = lambda[i + 1]; + } + if (2 * el <= r + erasures_num - 1) + { + el = r + erasures_num - el; + // 2 lines below: B(x) <-- inv(discr_r) * lambda(x) + for (int i = 0; i <= NROOTS; ++i) + b[i] = (lambda[i] == 0) ? A0 : mod255(CCSDS_index_of[lambda[i]] - discr_r + NN); + } + else + { + // 2 lines below: B(x) <-- x*B(x) + var b_list = new List(b); + b_list.Insert(0, A0); + b_list.RemoveAt(NROOTS + 1); + b = b_list.ToArray(); + } + Array.Copy(t, lambda, NROOTS + 1); + } + } + + // Convert lambda to index form and compute deg(lambda(x)) + int deg_lambda = 0; + for (int i = 0; i < NROOTS + 1; ++i) + { + lambda[i] = CCSDS_index_of[lambda[i]]; + if (lambda[i] != A0) + deg_lambda = i; + } + // Find roots of the error+erasure locator polynomial by Chien search + Array.Copy(lambda, reg, NROOTS); + count = 0; // Number of roots of lambda(x) + int q = 0; + for (int i = 1, k = IPRIM - 1; i <= NN; ++i, k = mod255(k + IPRIM)) + { + q = 1; // lambda[0] is always 0 + for (int j = deg_lambda; j > 0; --j) + { + if (reg[j] != A0) + { + reg[j] = mod255(reg[j] + j); + q ^= CCSDS_alpha_to[reg[j]]; + } + } + if (q != 0) + continue; // Not a root + // store root (index-form) and error location number + root[count] = i; + loc[count] = k; + // If we've already found max possible roots, abort the search to save time + if (++count == deg_lambda) + break; + } + if (deg_lambda != count) + { + // deg(lambda) unequal to number of roots => uncorrectable error detected + Console.Write("deg(lambda) unequal to number of roots => uncorrectable error detected (deg_lambda={0} != count={1})\n", deg_lambda, count); + count = -1; + goto finish; + } + + // Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo x**NROOTS). in index form. Also find deg(omega). + int deg_omega = 0; + int omega_tmp = 0; + for (int i = 0; i < NROOTS; ++i) + { + omega_tmp = 0; + int j = (deg_lambda < i) ? deg_lambda : i; + for (; j >= 0; --j) + { + if ((s[i - j] != A0) && (lambda[j] != A0)) + omega_tmp ^= CCSDS_alpha_to[mod255(s[i - j] + lambda[j])]; + } + if (omega_tmp != 0) + deg_omega = i; + omega[i] = CCSDS_index_of[omega_tmp]; + } + omega[NROOTS] = A0; + + // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form + int num1 = 0; + int num2 = 0; + int den = 0; + for (int j = count - 1; j >= 0; --j) + { + num1 = 0; + for (int i = deg_omega; i >= 0; --i) + { + if (omega[i] != A0) + num1 ^= CCSDS_alpha_to[mod255(omega[i] + i * root[j])]; + } + num2 = CCSDS_alpha_to[mod255(root[j] * (FCR - 1) + NN)]; + den = 0; + + // lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] + for (int i = Math.Min(deg_lambda, NROOTS - 1) & ~1; i >= 0; i -= 2) + { + if (lambda[i + 1] != A0) + den ^= CCSDS_alpha_to[mod255(lambda[i + 1] + i * root[j])]; + } + if (den == 0) + { + count = -1; + goto finish; + } + // Apply error to data + if (num1 != 0) + { + data[loc[j]] ^= CCSDS_alpha_to[mod255(CCSDS_index_of[num1] + CCSDS_index_of[num2] + NN - CCSDS_index_of[den])]; + } + } + finish: + if (erasures_pos != null) + { + for (int i = 0; i < count; ++i) + erasures_pos[i] = loc[i]; + } + return count; + } + + private int mod255(int x) + { + while (x >= 255) + { + x -= 255; + x = (x >> 8) + (x & 255); + } + return x; + } + } +} diff --git a/Services/UserDefined/MOBC/MobcUartTcPacketHandler.cs b/Services/UserDefined/MOBC/MobcUartTcPacketHandler.cs new file mode 100644 index 0000000..6f3b635 --- /dev/null +++ b/Services/UserDefined/MOBC/MobcUartTcPacketHandler.cs @@ -0,0 +1,22 @@ +using System; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services.MOBC +{ + public class MobcUartTcPacketHandler : TcPacketHandlerBase, ITcPacketHandler + { + public MobcUartTcPacketHandler(IPortManager portManager) : base(portManager) + { + } + + protected override void TcPacketInfoWriteLine(TcPacketData data){ + UInt16 ccbyte1 = (UInt16)data.TcPacket[14]; + UInt16 ccbyte2 = (UInt16)data.TcPacket[15]; + UInt16 ccbyte1s = (UInt16)(ccbyte1 << 8); + UInt16 comcode = (UInt16)(ccbyte1s + ccbyte2); + var writtencomcode = Convert.ToString(comcode, 16); + writtencomcode = ("0000" + writtencomcode).Substring(writtencomcode.Length + 4 - 4); + Console.WriteLine("[TcPacket] : 0x{0:x}", writtencomcode); + } + } +} diff --git a/Services/UserDefined/MOBC/MobcUartTmPacketExtractor.cs b/Services/UserDefined/MOBC/MobcUartTmPacketExtractor.cs new file mode 100644 index 0000000..262e199 --- /dev/null +++ b/Services/UserDefined/MOBC/MobcUartTmPacketExtractor.cs @@ -0,0 +1,121 @@ +using System; +using System.Linq; +using WINGS_TMTC_IF.Models; + +namespace WINGS_TMTC_IF.Services.MOBC +{ + public class MobcUartTmPacketExtractor : TmPacketExtractorBase, ITmPacketExtractor + { + //STX + private enum Ver { Ver1 = 0b00, Ver2 = 0b01 } + private enum ScId { SampleSat = 0x000 } + private enum VirChId { Realtime = 0b000001, Replay = 0b000010, Fill = 0b111111 } + + //ETX + private enum CtlWrdType { CLCW = 0b0 } + private enum ClcwVer { Ver1 = 0b00 } + private enum COPinEff { COP1 = 0b01 } + private enum VCId { Default = 0b000000 } + private enum Spare { Fixed = 0b00 } + + + public MobcUartTmPacketExtractor(IPortManager portManager) + : base(portManager, new ReceivedDataConfig { + HeaderLength = 6, + BodyLength = 434, + FooterLength = 4 + }) + { + } + + protected override bool AnalyzeHeader() + { + var STX = _buffer.GetRange(0, _config.HeaderLength).ToArray(); + //only use fixed bits + if (!ChkVer(STX, Ver.Ver2)) { return false; } + // if (!ChkScId(STX, ScId.SampleSat)) { return false; } + return true; + } + + protected override bool AnalyzeFooter() + { + var ETX = _buffer.GetRange(_config.TotalLength - _config.FooterLength, _config.FooterLength).ToArray(); + //only use fixed bits + if (!ChkCtlWrdType(ETX, CtlWrdType.CLCW)) { return false; } + if (!ChkClcwVer(ETX, ClcwVer.Ver1)) { return false; } + if (!ChkCOPinEff(ETX, COPinEff.COP1)) { return false; } + if (!ChkVCId(ETX, VCId.Default)) { return false; } + if (!ChkSpare(ETX, Spare.Fixed) ){ return false; } + return true; + } + + protected override TmPacketData ConvertToTmPacketData(byte[] receivedDataInByteArray) + { + foreach (var x in receivedDataInByteArray) + { + Console.Write("{0:x2} ", x); + } + Console.WriteLine(); + return base.ConvertToTmPacketData(receivedDataInByteArray); + } + + private bool ChkVer(byte[] STX, Ver ver) + { + int pos = 0; + byte mask = 0b_1100_0000; + byte val = (byte)((byte)ver << 6); + return (STX[pos] & mask) == val; + } + + private bool ChkScId(byte[] STX, ScId id) + { + int pos1 = 0; + byte mask1 = 0b_0011_1111; + byte val1 = (byte)((byte)id >> 2); + int pos2 = 1; + byte mask2 = 0b_1100_0000; + byte val2 = (byte)((byte)id << 6); + return ((STX[pos1] & mask1) == val1) & ((STX[pos2] & mask2) == val2); + } + + private bool ChkCtlWrdType(byte[] ETX, CtlWrdType type) + { + int pos = 0; + byte mask = 0b_1000_0000; + byte val = (byte)((byte)type << 7); + return (ETX[pos] & mask) == val; + } + + private bool ChkClcwVer(byte[] ETX, ClcwVer ver) + { + int pos = 0; + byte mask = 0b_0110_0000; + byte val = (byte)((byte)ver << 5); + return (ETX[pos] & mask) == val; + } + + private bool ChkCOPinEff(byte[] ETX, COPinEff eff) + { + int pos = 0; + byte mask = 0b_0000_0011; + byte val = (byte)((byte)eff); + return (ETX[pos] & mask) == val; + } + + private bool ChkVCId(byte[] ETX, VCId id) + { + int pos = 1; + byte mask = 0b_1111_1100; + byte val = (byte)((byte)id << 2); + return (ETX[pos] & mask) == val; + } + + private bool ChkSpare(byte[] ETX, Spare spare) + { + int pos = 1; + byte mask = 0b_0000_0011; + byte val = (byte)((byte)spare); + return (ETX[pos] & mask) == val; + } + } +} diff --git a/WINGS_TMTC_IF.csproj b/WINGS_TMTC_IF.csproj new file mode 100644 index 0000000..b262dce --- /dev/null +++ b/WINGS_TMTC_IF.csproj @@ -0,0 +1,26 @@ + + + Exe + net6.0 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + PreserveNewest + + + \ No newline at end of file diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..f4a08e3 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,30 @@ +{ + "WINGS": { + "Environment": "Docker", + "ConnectionString": "https://localhost:5001", + "GrpcConnectionString": "https://localhost:5001" + }, + "SerialPort": { + "BaudRate": { + "Using": 0, + "MOBC_UART": 115200, + "MOBC_RF": 115200, + "ISSL_COMMON": 115200 + } + }, + "LanPort": { + "ModEnable": true, + "ModIpAddress": "192.168.0.100", + "ModPortNum": 50000, + "ModIpAutoSearch": false, + "DemodEnable": true, + "DemodIpAddress": "192.168.0.101", + "DemodPortNum": 50000, + "DemodIpAutoSearch": false + }, + "ComponentList": [ + "MOBC_UART", + "MOBC_RF", + "ISSL_COMMON" + ] +}