初始化 代码

This commit is contained in:
zhangzhuo 2025-06-04 09:42:48 +08:00
commit 210572ce49
27 changed files with 729 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

13
.idea/.idea.Seyounth.Auto.Hs/.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,13 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# Rider 忽略的文件
/projectSettingsUpdater.xml
/.idea.Seyounth.Auto.Hs.iml
/modules.xml
/contentModel.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -0,0 +1,67 @@
using Microsoft.Extensions.Logging;
namespace Seyounth.Auto.Hs.Runtime.Balances;
public class BalanceService : IBalanceService
{
public IReadOnlyList<IBalance> Balances => _balances;
private readonly List<IBalance> _balances = new List<IBalance>();
private readonly ILogger<BalanceService> _logger;
public BalanceService(ILogger<BalanceService> logger)
{
_logger = logger;
//todo:向_balances里添加Balance
}
public async Task StartAsync()
{
await Task.WhenAll(_balances.Select(balance => balance.ConnectAsync().ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
_logger.LogInformation($"Balance {balance.Id} connected successfully.");
else
_logger.LogError($"Balance {balance.Id} failed to connect, error: {t.Exception?.Message}");
})).ToArray());
}
public async Task StopAsync()
{
await Task.WhenAll(_balances.Select(balance => balance.DisconnectAsync().ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
_logger.LogInformation($"Balance {balance.Id} disconnected successfully.");
else
_logger.LogError($"Balance {balance.Id} failed to disconnect, error: {t.Exception?.Message}");
})).ToArray());
}
public async Task<decimal?> WeighAsync(int id)
{
decimal rs = 0;
var balance = _balances.FirstOrDefault(b => b.Id == id);
if (balance != null)
{
Func<decimal, Task> weightChangedHandler = (weight) =>
{
rs = weight;
return Task.CompletedTask;
};
balance.OnWeightChanged += weightChangedHandler;
while (rs == 0)
{
await Task.Delay(100);
}
balance.OnWeightChanged -= weightChangedHandler;
return rs;
}
_logger.LogError($"Balance {id} not found.");
return null;
}
}

View File

@ -0,0 +1,12 @@
namespace Seyounth.Auto.Hs.Runtime.Balances;
public interface IBalance
{
int Id { get; }
Task ConnectAsync();
Task DisconnectAsync();
event Func<decimal, Task> OnWeightChanged;
}

View File

@ -0,0 +1,17 @@
namespace Seyounth.Auto.Hs.Runtime.Balances;
public interface IBalanceService
{
IReadOnlyList<IBalance> Balances { get; }
Task StartAsync();
Task StopAsync();
/// <summary>
/// 使用指定的电子称称重
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<decimal?> WeighAsync(int id);
}

View File

@ -0,0 +1,15 @@
using MediatR;
namespace Seyounth.Auto.Hs.Runtime.Handlers;
public class OnWarning(int first, int second) : IRequest
{
public int First { get; } = first;
public int Second { get; } = second;
}
public abstract class OnWarningHandler : IRequestHandler<OnWarning>
{
public abstract Task Handle(OnWarning request, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,21 @@
using MediatR;
namespace Seyounth.Auto.Hs.Runtime.Handlers;
public class WeighBoxRequest : IRequest<string>
{
public string Barcode { get; }
public decimal? Weight { get; }
public WeighBoxRequest(string barcode, decimal? weight)
{
Barcode = barcode;
Weight = weight;
}
}
public abstract class WeighBoxRequestHandler : IRequestHandler<WeighBoxRequest, string>
{
public abstract Task<string> Handle(WeighBoxRequest request, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,20 @@
using MediatR;
namespace Seyounth.Auto.Hs.Runtime.Handlers;
public class WeighSpindleRequest : IRequest<string>
{
public decimal? Weight { get; }
public string Barcode { get; }
public WeighSpindleRequest(string barcode, decimal? weight)
{
Weight = weight;
Barcode = barcode;
}
}
public abstract class WeighSpindleRequestHandler : IRequestHandler<WeighSpindleRequest, string>
{
public abstract Task<string> Handle(WeighSpindleRequest request, CancellationToken cancellationToken);
}

View File

@ -0,0 +1,174 @@
using MediatR;
using Microsoft.Extensions.Logging;
using Seyounth.Auto.Hs.Runtime.Balances;
using Seyounth.Auto.Hs.Runtime.Handlers;
using Seyounth.Auto.Hs.Runtime.Plc;
using Seyounth.Auto.Hs.Runtime.Printer;
using Seyounth.Auto.Hs.Runtime.Scanner;
namespace Seyounth.Auto.Hs.Runtime;
public class HsAutoRuntime : IHsAutoRuntime
{
private readonly IMediator _mediator;
private readonly IBalanceService _balance;
private readonly IScannerService _scanners;
private readonly ILogger<HsAutoRuntime> _logger;
private readonly IPlcService _plcService;
private readonly IPrinterService _printers;
public HsAutoRuntime(IPlcService plcService, IMediator mediator, IBalanceService balances, IScannerService scanners,
ILogger<HsAutoRuntime> logger, IPrinterService printers)
{
_printers = printers;
_mediator = mediator;
_balance = balances;
_scanners = scanners;
_logger = logger;
_plcService = plcService;
_scanners.OnScanned += ScannersOnOnScanned;
plcService.OnWarning += PlcServiceOnOnWarning;
}
/// <summary>
/// 获取到报警信息处理逻辑
/// </summary>
/// <param name="warning"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private Task PlcServiceOnOnWarning(Tuple<short, short> warning)
{
throw new NotImplementedException();
}
private void ScannersOnOnScanned(IScanner scanner, string barcode)
{
if (scanner.Id == 1)
{
#pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
HandleFilmOnScanned(barcode);
#pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
}
else if (scanner.Id == 2)
{
#pragma warning disable CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
HandleBoxOnScanned(barcode);
#pragma warning restore CS4014 // 由于此调用不会等待,因此在调用完成前将继续执行当前方法
}
}
private async Task HandleFilmOnScanned(string barcode)
{
try
{
_logger.LogInformation("Film Scanner: {barcode}", barcode);
var weight = await _balance.WeighAsync(1);
_logger.LogInformation($"Film Barcode:{barcode}| Weight: {weight}");
var content = await _mediator.Send(new WeighSpindleRequest(barcode, weight));
_logger.LogInformation($"Film Barcode:{barcode}| print content: {content}");
await _printers.PrintAsync(1, content);
await _plcService.WriteFilmLabelPrintResult(1);
}
catch (Exception ex)
{
_logger.LogError($"Film Scanner: {barcode}| Exception: {ex.Message}");
await _plcService.WriteFilmLabelPrintResult(2);
}
}
private async Task HandleBoxOnScanned(string barcode)
{
try
{
_logger.LogInformation("Box Scanner: {barcode}", barcode);
int jackingFlag;
do
{
jackingFlag = await _plcService.GetJackingFlagAsync();
} while (jackingFlag == 0);
_logger.LogInformation($"Box Barcode:{barcode}| Jacking flag: {jackingFlag}");
var weight = await _balance.WeighAsync(2);
_logger.LogInformation($"Box Barcode:{barcode}| Weight: {weight}");
var content = await _mediator.Send(new WeighBoxRequest(barcode, weight));
_logger.LogInformation($"Box Barcode:{barcode}| WeighBoxResult: {content}");
await _printers.PrintAsync(2, content);
await _plcService.WriteBoxLabelPrintResult(1);
}
catch (Exception ex)
{
_logger.LogError(ex, "Box Scanner: {barcode}", barcode);
await _plcService.WriteBoxLabelPrintResult(2);
}
}
public Task RunAsync()
{
var tasks = new List<Task>
{
_balance.StartAsync()
.ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Balance connected successfully."
: $"Balance connection failed. error: {t.Exception?.Message}");
}),
_scanners.StartAsync()
.ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Scanner connected successfully."
: $"Scanner connection failed. error: {t.Exception?.Message}");
}),
_plcService.StartAsync()
.ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Plc connected successfully."
: $"Plc connection failed. error: {t.Exception?.Message}");
}),
_printers.StartAsync().ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Printer connected successfully."
: $"Printer connection failed. error: {t.Exception?.Message}");
})
};
return Task.WhenAll(tasks);
}
public Task StopAsync()
{
var tasks = new List<Task>
{
_balance.StopAsync()
.ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Balance stop successfully."
: $"Balance stop failed. error: {t.Exception?.Message}");
}),
_scanners.StopAsync()
.ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Scanner stop successfully."
: $"Scanner stop failed. error: {t.Exception?.Message}");
}),
_plcService.StopAsync()
.ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Plc stop successfully."
: $"Plc stop failed. error: {t.Exception?.Message}");
}),
_printers.StopAsync().ContinueWith(t =>
{
_logger.LogInformation(t.IsCompletedSuccessfully
? "Printer stop successfully."
: $"Printer stop failed. error: {t.Exception?.Message}");
})
};
return Task.WhenAll(tasks);
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.Extensions.Hosting;
namespace Seyounth.Auto.Hs.Runtime;
public class HsBackgroundService(IHsAutoRuntime hs) : BackgroundService
{
public override async Task StartAsync(CancellationToken cancellationToken)
{
await hs.RunAsync();
await base.StartAsync(cancellationToken);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(1000, stoppingToken);
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
await hs.StopAsync();
await base.StopAsync(cancellationToken);
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.Extensions.DependencyInjection;
using Seyounth.Auto.Hs.Runtime.Balances;
using Seyounth.Auto.Hs.Runtime.Handlers;
using Seyounth.Auto.Hs.Runtime.Plc;
using Seyounth.Auto.Hs.Runtime.Printer;
using Seyounth.Auto.Hs.Runtime.Scanner;
namespace Seyounth.Auto.Hs.Runtime;
public static class HsExtensions
{
/// <summary>
/// 添加HS手动包装服务
/// </summary>
/// <param name="services"></param>
/// <typeparam name="TOnWarningHandler">报警信息处理器</typeparam>
/// <typeparam name="TWeighBoxRequestHandler">纸箱称重处理器</typeparam>
/// <typeparam name="TWeighSpindleRequestHandler">丝锭称重处理器</typeparam>
/// <returns></returns>
public static IServiceCollection AddHs<TOnWarningHandler, TWeighBoxRequestHandler, TWeighSpindleRequestHandler>(
this IServiceCollection services)
where TOnWarningHandler : OnWarningHandler
where TWeighBoxRequestHandler : WeighSpindleRequestHandler
where TWeighSpindleRequestHandler : WeighSpindleRequestHandler
{
services.AddSingleton<IBalanceService, BalanceService>();
services.AddSingleton<IPlcService, PlcService>();
services.AddSingleton<IPrinterService, PrinterService>();
services.AddSingleton<IScannerService, ScannerService>();
services.AddSingleton<IHsAutoRuntime, HsAutoRuntime>();
services.AddHostedService<PlcBackgroundService>();
services.AddHostedService<HsBackgroundService>();
return services;
}
}

View File

@ -0,0 +1,8 @@
namespace Seyounth.Auto.Hs.Runtime;
public interface IHsAutoRuntime
{
Task RunAsync();
Task StopAsync();
}

View File

@ -0,0 +1,8 @@
namespace Seyounth.Auto.Hs.Runtime.Plc;
public abstract class HsPlcAddressAbstract
{
public abstract string { get; }
}

View File

@ -0,0 +1,46 @@
namespace Seyounth.Auto.Hs.Runtime.Plc;
public interface IPlcService
{
Task StartAsync();
Task StopAsync();
/// <summary>
/// 查询热缩机当前温度
/// </summary>
/// <returns></returns>
Task<short> GetTemperatureAsync();
/// <summary>
/// 查询报警信息
/// </summary>
/// <returns></returns>
Task QueryWarningInfo();
/// <summary>
/// 触发报警信息
/// </summary>
event Func<Tuple<short, short>, Task> OnWarning;
/// <summary>
/// 获取顶升机构状态
/// </summary>
/// <returns></returns>
Task<short> GetJackingFlagAsync();
/// <summary>
/// 写入外箱标签打印结果
/// </summary>
/// <param name="rs"></param>
/// <returns></returns>
Task WriteBoxLabelPrintResult(short rs);
/// <summary>
/// 写入外膜标签打印结果
/// </summary>
/// <param name="rs"></param>
/// <returns></returns>
Task WriteFilmLabelPrintResult(short rs);
}

View File

@ -0,0 +1,15 @@
using Microsoft.Extensions.Hosting;
namespace Seyounth.Auto.Hs.Runtime.Plc;
public class PlcBackgroundService(IPlcService plc) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await plc.QueryWarningInfo();
await Task.Delay(50, stoppingToken);
}
}
}

View File

@ -0,0 +1,56 @@
using Microsoft.Extensions.Logging;
using Seyounth.Extensions.Plc;
namespace Seyounth.Auto.Hs.Runtime.Plc;
public class PlcService : IPlcService
{
private readonly ILogger<PlcService> _logger;
private readonly IPlc _plc;
public PlcService(ILogger<PlcService> logger)
{
_logger = logger;
//todo:此处创建PLC对象
}
public async Task StartAsync()
{
await _plc.ConnectAsync();
}
public async Task StopAsync()
{
await _plc.DisconnectAsync();
}
public async Task<short> GetTemperatureAsync()
{
return (await _plc.ReadAsync<short>("D1000", 1))[0];
}
public async Task QueryWarningInfo()
{
var flags = await _plc.ReadAsync<short>("D1003", 2);
if (flags.Any(f => f != 0))
OnWarning?.Invoke(Tuple.Create(flags[0], flags[1]));
}
public event Func<Tuple<short, short>, Task> OnWarning;
public Task<short> GetJackingFlagAsync()
{
throw new NotImplementedException();
}
public Task WriteBoxLabelPrintResult(short rs)
{
throw new NotImplementedException();
}
public Task WriteFilmLabelPrintResult(short rs)
{
throw new NotImplementedException();
}
}

View File

@ -0,0 +1,10 @@
namespace Seyounth.Auto.Hs.Runtime.Printer;
public interface IPrinter
{
int Id { get; }
Task ConnectAsync();
Task DisconnectAsync();
Task PrintAsync(string content);
}

View File

@ -0,0 +1,12 @@
namespace Seyounth.Auto.Hs.Runtime.Printer;
public interface IPrinterService
{
IReadOnlyList<IPrinter> Printers { get; }
Task StartAsync();
Task StopAsync();
Task PrintAsync(int id, string content);
}

View File

@ -0,0 +1,50 @@
using Microsoft.Extensions.Logging;
namespace Seyounth.Auto.Hs.Runtime.Printer;
public class PrinterService : IPrinterService
{
public IReadOnlyList<IPrinter> Printers => _printers;
private readonly ILogger<PrinterService> _logger;
private readonly List<IPrinter> _printers = new List<IPrinter>();
public PrinterService(ILogger<PrinterService> logger)
{
_logger = logger;
//todo: load printers from configuration or new
}
public async Task StartAsync()
{
await Task.WhenAll(Printers.Select(x => x.ConnectAsync()
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
_logger.LogInformation($"Printer {x.Id} connected");
else
_logger.LogError(t.Exception, $"Printer {x.Id} failed to connect,error: {t.Exception.Message}");
})));
}
public async Task StopAsync()
{
await Task.WhenAll(Printers.Select(x => x.DisconnectAsync()
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
_logger.LogInformation($"Printer {x.Id} disconnected");
else
_logger.LogError(t.Exception, $"Printer {x.Id} failed to disconnect,error: {t.Exception.Message}");
})));
}
public Task PrintAsync(int id, string content)
{
var printer = Printers.FirstOrDefault(x => x.Id == id);
if (printer == null)
throw new ArgumentException("Printer not found", nameof(id));
return printer.PrintAsync(content);
}
}

View File

@ -0,0 +1,12 @@
namespace Seyounth.Auto.Hs.Runtime.Scanner;
public interface IScanner
{
int Id { get; }
Task ConnectAsync();
Task DisconnectAsync();
event Action<string> OnScanned;
}

View File

@ -0,0 +1,11 @@
namespace Seyounth.Auto.Hs.Runtime.Scanner;
public interface IScannerService
{
IReadOnlyList<IScanner> Scanners { get; }
Task StartAsync();
Task StopAsync();
event Action<IScanner, string> OnScanned;
}

View File

@ -0,0 +1,46 @@
using Microsoft.Extensions.Logging;
namespace Seyounth.Auto.Hs.Runtime.Scanner;
public class ScannerService : IScannerService
{
public ScannerService(ILogger<ScannerService> logger)
{
_logger = logger;
//todo:向_scanners里添加HikScanner
_scanners.ForEach(x => x.OnScanned += (barcode) => OnScanned?.Invoke(x, barcode));
}
public IReadOnlyList<IScanner> Scanners => _scanners;
private readonly ILogger<ScannerService> _logger;
private readonly List<IScanner> _scanners = new List<IScanner>();
public async Task StartAsync()
{
await Task.WhenAll(_scanners.Select(x => x.ConnectAsync()
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
_logger.LogInformation($"Scanner {x.Id} connected successfully.");
else
_logger.LogError($"Scanner {x.Id} failed to connect, error: {t.Exception?.Message}");
})));
}
public async Task StopAsync()
{
await Task.WhenAll(_scanners.Select(x => x.DisconnectAsync()
.ContinueWith(t =>
{
if (t.IsCompletedSuccessfully)
_logger.LogInformation($"Scanner {x.Id} disconnected.");
else
_logger.LogError($"Scanner {x.Id} failed to disconnect, error: {t.Exception?.Message}");
})));
}
public event Action<IScanner, string>? OnScanned;
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.5" />
<PackageReference Include="Seyounth.Extensions.Plc" Version="1.0.1" />
</ItemGroup>
</Project>

16
Seyounth.Auto.Hs.sln Normal file
View File

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Seyounth.Auto.Hs.Runtime", "Seyounth.Auto.Hs.Runtime\Seyounth.Auto.Hs.Runtime.csproj", "{6966BCFD-A22C-4C83-8171-96BB005F38D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{6966BCFD-A22C-4C83-8171-96BB005F38D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6966BCFD-A22C-4C83-8171-96BB005F38D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6966BCFD-A22C-4C83-8171-96BB005F38D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6966BCFD-A22C-4C83-8171-96BB005F38D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

7
nuget.config Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
<add key="Seyounth" value="http://8.134.253.216:8002/v3/index.json" />
</packageSources>
</configuration>