using Arch.EntityFrameworkCore.UnitOfWork;
using CsvHelper;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using LogPlotService;
using Microsoft.Win32;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance;
using ProtocolCommon;
using RepeaterTAP.Common;
using RepeaterTAP.DB.Entity;
using ScottPlot;
using ScottPlot.WPF;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Arch.EntityFrameworkCore.UnitOfWork;
using Microsoft.Extensions.DependencyInjection;
using LogPlotService.Models;
using Comm;
using CmccProtocol;
using RepeaterTAP.Comm;
using RepeaterTAP.DB;
using ControlzEx.Standard;
using System.Globalization;
using Ubiety.Dns.Core;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Math;
using RepeaterTAP.Log;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
namespace OMT.ViewModel
{
public enum E_PlotMode
{
Real_Time,
Non_Real_Time
}
public class DevStateLogAnalModel: ViewModelBase
{
private PlotService pltService;
private String csvDirPath;
private IUnitOfWork unitOfWork;
private IRepository<DevStateLog> _devStateLogRepo;
private readonly List<DevStateLog> _insertCache = new List<DevStateLog>(300); // 批缓存
private const int BATCH_COUNT = 300; // 每 300 行刷一次
private ObservableCollection<HeadObject> _head;
private Dictionary<string, (WpfPlot chart,string)> _chartMap;
private SSHClient sshClient;
private DispatcherTimer refreshTimer;
private readonly Dictionary<WpfPlot, PlotService> _plotMap = new Dictionary<WpfPlot, PlotService>();
private string[] SelectedTypes;
public ObservableCollection<HeadObject> head
{
get => _head;
set
{
_head = value;
RaisePropertyChanged(nameof(head));
}
}
public String CsvDirPath
{
get { return csvDirPath; }
set
{
this.csvDirPath = value;
RaisePropertyChanged(nameof(CsvDirPath));
}
}
public string plotType= E_PlotMode.Non_Real_Time.ToString();
public string PlotType
{
get { return plotType; }
set
{
plotType = value;
if (plotType == E_PlotMode.Non_Real_Time.ToString())
{
IsRealTime = false;
IsNonRealTime = true;
DoStop();
}
else
{
IsRealTime = true;
IsNonRealTime = false;
}
RaisePropertyChanged(nameof(PlotType));
}
}
private bool isRealTime=false;
public bool IsRealTime
{
get { return isRealTime; }
set
{
isRealTime = value;
RaisePropertyChanged(nameof(IsRealTime));
}
}
private bool isNonRealTime=true ;
public bool IsNonRealTime
{
get { return isNonRealTime; }
set
{
isNonRealTime = value;
RaisePropertyChanged(nameof(IsNonRealTime));
}
}
private DateTime startDateTime=DateTime.Now;
public DateTime StartDateTime
{
get { return startDateTime; }
set
{
startDateTime = value;
RaisePropertyChanged(nameof(StartDateTime));
}
}
private DateTime endDateTime = DateTime.Now;
public DateTime EndDateTime
{
get { return endDateTime; }
set
{
endDateTime = value;
RaisePropertyChanged(nameof(EndDateTime));
}
}
private List<Device> deviceInfos;
public List<Device> DeviceInfos
{
get
{
return deviceInfos = unitOfWork.GetRepository<Device>().SearchList(p => p.DeviceType == DeviceTypeEnum.Module.ToString());
}
set
{
this.deviceInfos = value;
RaisePropertyChanged(nameof(DeviceInfos));
}
}
private Device selectedModule;
public Device SelectedModule
{
get { return selectedModule; }
set
{
this.selectedModule = value;
RaisePropertyChanged(nameof(SelectedModule));
}
}
public RelayCommand SelectCsvPath
{
get;
private set;
}
public RelayCommand StartTask
{
get;
private set;
}
public RelayCommand SelectAllCommand
{
get;
private set;
}
public RelayCommand ClearSelectionCommand
{
get;
private set;
}
public RelayCommand ApplySelectionCommand
{
get;
private set;
}
public RelayCommand SelectAllAntennaCommand { get; private set; }
public RelayCommand SelectAnt1Command { get; private set; }
public RelayCommand SelectAnt2Command { get; private set; }
public RelayCommand SelectAnt3Command { get; private set; }
public RelayCommand SelectAnt4Command { get; private set; }
public RelayCommand SelectAllTypeCommand { get; private set; }
public RelayCommand SelectPowerCommand { get; private set; }
public RelayCommand SelectCurrentCommand { get; private set; }
public RelayCommand SelectVoltageCommand { get; private set; }
public RelayCommand SelectTemperature { get; private set; }
public ICommand UpdateChartCommand { get; }
private WpfPlot powerChart;
public WpfPlot PowerChart
{
get => powerChart;
set
{
powerChart = value;
RaisePropertyChanged(nameof(PowerChart));
}
}
private WpfPlot ant1Chart;
public WpfPlot Ant1Chart
{
get => ant1Chart;
set
{
ant1Chart = value;
RaisePropertyChanged(nameof(Ant1Chart));
}
}
private WpfPlot ant2Chart;
public WpfPlot Ant2Chart
{
get => ant2Chart;
set
{
ant2Chart = value;
RaisePropertyChanged(nameof(Ant2Chart));
}
}
private WpfPlot currentChart;
public WpfPlot CurrentChart
{
get => currentChart;
set
{
currentChart = value;
RaisePropertyChanged(nameof(CurrentChart));
}
}
private WpfPlot ant3Chart;
public WpfPlot Ant3Chart
{
get => ant3Chart;
set
{
ant3Chart = value;
RaisePropertyChanged(nameof(Ant3Chart));
}
}
private WpfPlot voltageChart;
public WpfPlot VoltageChart
{
get => voltageChart;
set
{
voltageChart = value;
RaisePropertyChanged(nameof(VoltageChart));
}
}
private WpfPlot ant4Chart;
public WpfPlot Ant4Chart
{
get => ant4Chart;
set
{
ant4Chart = value;
RaisePropertyChanged(nameof(Ant4Chart));
}
}
private WpfPlot tempChart;
public WpfPlot TempChart
{
get => tempChart;
set
{
tempChart = value;
RaisePropertyChanged(nameof(TempChart));
}
}
private bool _isAnt1 = true;
public bool IsAnt1Visible
{
get => _isAnt1;
set { _isAnt1 = value; RaisePropertyChanged(nameof(IsAnt1Visible)); RaisePropertyChanged(nameof(IsPowerVisible)); }
}
public bool IsPowerVisible => !_isAnt1;
private bool _isAnt2 = true;
public bool IsAnt2Visible
{
get => _isAnt2;
set { _isAnt2 = value; RaisePropertyChanged(nameof(IsCurrenVisible)); RaisePropertyChanged(nameof(IsCurrenVisible)); }
}
public bool IsCurrenVisible => !_isAnt2;
private bool _isAnt3 = true;
public bool IsAnt3Visible
{
get => _isAnt3;
set { _isAnt3 = value; RaisePropertyChanged(nameof(IsVoltageVisible)); RaisePropertyChanged(nameof(IsVoltageVisible)); }
}
public bool IsVoltageVisible => !_isAnt3;
private bool _isAnt4 = true;
public bool IsAnt4Visible
{
get => _isAnt4;
set { _isAnt4 = value; RaisePropertyChanged(nameof(IsTempVisible)); RaisePropertyChanged(nameof(IsTempVisible)); }
}
public bool IsTempVisible => !_isAnt4;
public DevStateLogAnalModel()
{
SelectCsvPath = new RelayCommand(DoSelectCsvPath);
StartTask = new RelayCommand(DoStartTask);
SelectAllCommand = new RelayCommand(DoSelectAll);
ClearSelectionCommand = new RelayCommand(DoClear);
ApplySelectionCommand = new RelayCommand(DoApply);
SelectAllAntennaCommand = new RelayCommand(() => DoSelectType("ant1","ant2","ant3","ant4"));
SelectAnt1Command = new RelayCommand(()=> DoSelectType("ant1"));
SelectAnt2Command = new RelayCommand(()=>DoSelectType("ant2"));
SelectAnt3Command = new RelayCommand(() => DoSelectType("ant3"));
SelectAnt4Command = new RelayCommand(() => DoSelectType("ant4"));
SelectAllTypeCommand = new RelayCommand(() => DoSelectType("power", "cur", "vol","temp"));
SelectPowerCommand = new RelayCommand(()=> DoSelectType("power"));
SelectCurrentCommand = new RelayCommand(()=> DoSelectType("cur"));
SelectVoltageCommand = new RelayCommand(()=>DoSelectType( "vol"));
SelectTemperature = new RelayCommand(()=>DoSelectType("temp"));
unitOfWork = App.serviceProvider.GetService<IUnitOfWork>();
_devStateLogRepo = unitOfWork.GetRepository<DevStateLog>();
}
private void UpdateChart(WpfPlot chart,TimeSeries ts,string title, List<HeadObject> selectedHead)
{
if (!File.Exists(CsvDirPath))
{
MessageBox.Show($"{CsvDirPath} is not exits!");
return;
}
pltService = new PlotService(chart);
pltService.BuildChartByType(ts, selectedHead, title);
}
private void InitScottPlot()
{
}
private void DoSelectCsvPath()
{
OpenFileDialog MyFileDialog = new OpenFileDialog
{
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
Filter = "CSV file (*.csv)|*.csv",
DefaultExt = ".csv",
AddExtension = true
};
if (MyFileDialog.ShowDialog() != true)
{
MessageBox.Show("Select not CSV file!", "Message");
return;
}
CsvDirPath = MyFileDialog.FileName;
var csvHeads = CsvReaderService.ReadHead(CsvDirPath);
var removeList = new List<string>
{
"datetime",
"loopEnable",
"pa_vgs_ant1",
"pa_vgs_ant2",
"pa_vgs_ant3",
"pa_vgs_ant4"
};
csvHeads = CsvReaderService.FilterHead_BlackList(csvHeads, removeList);
head = new ObservableCollection<HeadObject>(
csvHeads.Select(h => new HeadObject { IsSelected = false, Name = h })
);
}
private void DoStartTask()
{
//sshClient?.Stop();
if (sshClient != null)
{
sshClient.OnLine -= OnCsvLine; // 先解事件
sshClient.Stop();
sshClient.Dispose();
sshClient = null;
}
if (_plotMap.Count == 0)
{
foreach (var wp in new[] { Ant1Chart, Ant2Chart, Ant3Chart, Ant4Chart,PowerChart, CurrentChart, VoltageChart, TempChart })
{
_plotMap[wp] = new PlotService(wp, window: 600);
}
//pltService = new PlotService(PowerOrAnt1Chart, window: 600);
refreshTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(33) };
refreshTimer.Tick += (s, e) => { foreach (var ps in _plotMap.Values) ps.Refresh(); } ;
refreshTimer.Start();
}
sshClient = new SSHClient(SelectedModule.IP,22,"root","root");
try
{
sshClient.Connect();
String csvHeadStr=sshClient.RunCommand("head -n 1 /tmp/devstate/devstatelog.csv").Result;
var csvHeads=csvHeadStr.Trim().Split(',').ToList();
csvHeads.RemoveAll(s => string.IsNullOrEmpty(s) || string.IsNullOrWhiteSpace(s));
head = new ObservableCollection<HeadObject>(
csvHeads.Select(h => new HeadObject { IsSelected = false, Name = h }));
sshClient.OnLine += OnCsvLine;
sshClient.StartTail("/tmp/devstate/devstatelog.csv",csvHeads);
}
catch (Exception)
{
throw;
}
}
private void OnCsvLine(string line, List<String> csvHeads)
{
var parts = line.Trim().Split(',', StringSplitOptions.None);
Array.Resize(ref parts, parts.Length - parts.Reverse().TakeWhile(s => s == "").Count());
if (parts.Length == csvHeads.Count)
{
var log = MapFieldToValue(parts, csvHeads);
//_insertCache.Add(log);
//if (_insertCache.Count >= BATCH_COUNT)
// await FlushLogsAsync();
lock (_insertCache) _insertCache.Add(log);
/* ---------- 5. 异步批量刷盘(不卡 UI) ---------- */
if (_insertCache.Count >= BATCH_COUNT)
_ = Task.Run(async () => await FlushLogsAsync()); // 🔥关键:同步签名里调
}
if (_chartMap == null)
{
_chartMap = new Dictionary<string, (WpfPlot, string)>()
{
["ant1"] = (Ant1Chart, "ant1"),
["power"] = (PowerChart, "power"),
["ant2"] = (Ant2Chart, "ant2"),
["cur"] = (CurrentChart, "current"),
["ant3"] = (Ant3Chart, "ant3"),
["vol"] = (VoltageChart, "voltage"),
["ant4"] = (Ant4Chart, "ant4"),
["temp"] = (TempChart, "temperature"),
};
}
if (string.IsNullOrWhiteSpace(line)) return;
if (line.Equals("tail -f /tmp/devstate/devstatelog.csv"))
return;
TimeSeries ts = null;
try
{
ts = CsvReaderService.ParseRtDevInfo(line, csvHeads);
}
catch (Exception ex)
{
RepeaterTAP.Log.Log.Debug(this.GetType().Name,$"Parse real time Device info fail, {ex.ToString()}");
return;
}
if (ts?.Timestamps.Count == 0) return;
DateTime t = ts.Timestamps[0]; // 这一行的时间戳
foreach (var kv in ts.Columns) // 每列一条曲线
{
double v = kv.Value[0]; // 这一列的数值
string col = kv.Key; // 列名(如 ant1)
if (SelectedTypes == null)
return;
string matchedType = SelectedTypes.FirstOrDefault(type => col.Contains(type));
if (matchedType == null) continue; // 都不匹配就跳过
// 2. 拿图和 PlotService
if (!_chartMap.TryGetValue(matchedType, out var tuple)) continue;
var plt = _plotMap[tuple.chart];
tuple.chart.plt.Title(tuple.Item2);
// 异步扔回 UI 线程
Application.Current.Dispatcher.BeginInvoke(() =>
plt.AddPoint(col, t, v));
}
}
private void DoSelectAll()
{
head?.ToList().ForEach(h => h.IsSelected = true);
}
private void DoClear()
{
head?.ToList().ForEach(h => h.IsSelected = false);
}
private async void DoApply()
{
//DoUpdateChart(PowerChart);
}
private async void DoUpdateChart(WpfPlot chart,TimeSeries ts,string title, List<HeadObject> selectedHead )
{
// 1. 立刻拍快照(值复制)
var snapshot = selectedHead.ToList();
await Task.Run(() => UpdateChart(chart,ts,title, snapshot));
chart.Refresh();
}
private void DoSelectType(params string[] typeNames)
{
SelectedTypes=typeNames;
//add 20250910
if (PlotType == E_PlotMode.Real_Time.ToString())
{
foreach (var wp in _plotMap.Keys)
{
wp.plt.Clear();
wp.Render();
_plotMap[wp].Reset(); // 如果你给 PlotService 做了 Reset 方法
}
}
//End
if (_chartMap == null)
{
_chartMap = new Dictionary<string, (WpfPlot, string)>()
{
["ant1"] = (Ant1Chart, "ant1"),
["power"] = (PowerChart, "power"),
["ant2"] = (Ant2Chart, "ant2"),
["cur"] = (CurrentChart, "current"),
["ant3"] = (Ant3Chart, "ant3"),
["vol"] = (VoltageChart, "voltage"),
["ant4"] = (Ant4Chart, "ant4"),
["temp"] = (TempChart, "temperature"),
};
}
IsAnt1Visible = typeNames.Contains("ant1");
IsAnt2Visible = typeNames.Contains("ant2");
IsAnt3Visible = typeNames.Contains("ant3");
IsAnt4Visible = typeNames.Contains("ant4");
if (PlotType==E_PlotMode.Real_Time.ToString())
{
foreach (string key in SelectedTypes)
{
if (_chartMap.TryGetValue(key, out var tuple)) {
//tuple.chart.Reset(); // 回到初始空白
//_plotMap[tuple.chart].Reset();
}
}
return;
}
List<HeadObject> SelectedHead=new List<HeadObject>();
head?.ToList().ForEach(h => h.IsSelected = false);
var ts = CsvReaderService.ReadCsv(CsvDirPath);
foreach (var tn in typeNames)
{
SelectedHead.Clear();
foreach (var h in head)
{
if (h.Name.IndexOf(tn, StringComparison.OrdinalIgnoreCase) >= 0)
{
//h.IsSelected = true;
SelectedHead.Add(h);
}
}
if (_chartMap.TryGetValue(tn, out var tuple) && tuple.chart != null)
{
DoUpdateChart(tuple.chart, ts,tuple.Item2, SelectedHead);
}
}
}
private void DoStop()
{
if (sshClient == null) return;
sshClient.OnLine -= OnCsvLine; // 事件解绑
sshClient.Stop(); // 网络停
sshClient.Dispose();
sshClient = null;
Dispatcher.CurrentDispatcher.BeginInvoke(async () => await FlushLogsAsync());
}
private async Task FlushLogsAsync()
{
if (_insertCache.Count == 0) return;
List<DevStateLog> batch;
lock (_insertCache)
{
batch = new List<DevStateLog>(_insertCache);
_insertCache.Clear();
}
await Task.Run(() =>
{
try
{
foreach (var log in batch)
_devStateLogRepo.Insert(log);
unitOfWork.SaveChanges(); // 一次事务
}
catch (Exception ex)
{
RepeaterTAP.Log.Log.Error(this.GetType().Name, $"Batch insert fail:{ex}");
}
});
}
// 把 csv 行转成 DeviceStateLog
private DevStateLog MapFieldToValue(string[] parts, List<string> heads)
{
var dict = heads.Zip(parts, (h, v) => new { h, v })
.ToDictionary(x => x.h, x => x.v);
return new DevStateLog
{
Datetime = DateTime.TryParse(dict["datetime"], out var dt) ? dt : DateTime.Now,
FwPowerAnt1 = dict.GetValueOrDefault("fw_power_ant1"),
FwPowerAnt2 = dict.GetValueOrDefault("fw_power_ant2"),
FwPowerAnt3 = dict.GetValueOrDefault("fw_power_ant3"),
FwPowerAnt4 = dict.GetValueOrDefault("fw_power_ant4"),
FbPowerAnt1 = dict.GetValueOrDefault("fb_power_ant1"),
FbPowerAnt2 = dict.GetValueOrDefault("fb_power_ant2"),
FbPowerAnt3 = dict.GetValueOrDefault("fb_power_ant3"),
FbPowerAnt4 = dict.GetValueOrDefault("fb_power_ant4"),
RflPowerAnt1 = dict.GetValueOrDefault("rfl_power_ant1"),
RflPowerAnt2 = dict.GetValueOrDefault("rfl_power_ant2"),
RflPowerAnt3 = dict.GetValueOrDefault("rfl_power_ant3"),
RflPowerAnt4 = dict.GetValueOrDefault("rfl_power_ant4"),
RxAdcPowerAnt1 = dict.GetValueOrDefault("rx_adc_power_ant1"),
RxAdcPowerAnt2 = dict.GetValueOrDefault("rx_adc_power_ant2"),
RxAdcPowerAnt3 = dict.GetValueOrDefault("rx_adc_power_ant3"),
RxAdcPowerAnt4 = dict.GetValueOrDefault("rx_adc_power_ant4"),
DclStatusAnt1 = dict.GetValueOrDefault("dcl_status_ant1"),
DclStatusAnt2 = dict.GetValueOrDefault("dcl_status_ant2"),
DclStatusAnt3 = dict.GetValueOrDefault("dcl_status_ant3"),
DclStatusAnt4 = dict.GetValueOrDefault("dcl_status_ant4"),
LoopEnable = dict.GetValueOrDefault("loopEnable"),
DlAttAnt1 = dict.GetValueOrDefault("dl_att_ant1"),
DlAttAnt2 = dict.GetValueOrDefault("dl_att_ant2"),
DlAttAnt3 = dict.GetValueOrDefault("dl_att_ant3"),
DlAttAnt4 = dict.GetValueOrDefault("dl_att_ant4"),
DpdInAtt = dict.GetValueOrDefault("dpd_in_att"),
FpgaTemp = dict.GetValueOrDefault("fpga_temp"),
DeviceTemp = dict.GetValueOrDefault("device_temp"),
AfeTemp = dict.GetValueOrDefault("afe_temp"),
GdcTemp = dict.GetValueOrDefault("gdc_temp"),
ClockTemp = dict.GetValueOrDefault("clock_temp"),
PaTempAnt1 = dict.GetValueOrDefault("pa_temp_ant1"),
PaTempAnt2 = dict.GetValueOrDefault("pa_temp_ant2"),
PaTempAnt3 = dict.GetValueOrDefault("pa_temp_ant3"),
PaTempAnt4 = dict.GetValueOrDefault("pa_temp_ant4"),
PaStatusAnt1 = dict.GetValueOrDefault("pa_status_ant1"),
PaStatusAnt2 = dict.GetValueOrDefault("pa_status_ant2"),
PaStatusAnt3 = dict.GetValueOrDefault("pa_status_ant3"),
PaStatusAnt4 = dict.GetValueOrDefault("pa_status_ant4"),
PaVgsAnt1 = dict.GetValueOrDefault("pa_vgs_ant1"),
PaVgsAnt2 = dict.GetValueOrDefault("pa_vgs_ant2"),
PaVgsAnt3 = dict.GetValueOrDefault("pa_vgs_ant3"),
PaVgsAnt4 = dict.GetValueOrDefault("pa_vgs_ant4"),
GdcInputVol = dict.GetValueOrDefault("gdc_input_vol"),
GdcInputCur = dict.GetValueOrDefault("gdc_input_cur"),
GdcOutputVol = dict.GetValueOrDefault("gdc_output_vol"),
GdcOutputCur = dict.GetValueOrDefault("gdc_output_cur"),
Ch1Cur28v = dict.GetValueOrDefault("ch1_cur_28v"),
Ch1Cur48v = dict.GetValueOrDefault("ch1_cur_48v"),
Ch2Cur28v = dict.GetValueOrDefault("ch2_cur_28v"),
Ch2Cur48v = dict.GetValueOrDefault("ch2_cur_48v"),
Ul1Status = dict.GetValueOrDefault("ul1_status"),
Ul2Status = dict.GetValueOrDefault("ul2_status"),
Ul1LnaCur = dict.GetValueOrDefault("ul1_lna_cur"),
Ul2LnaCur = dict.GetValueOrDefault("ul2_lna_cur"),
TddSyncMode = dict.GetValueOrDefault("tdd_sync_mode"),
TddSyncStatusCh1 = dict.GetValueOrDefault("tdd_sync_status_ch1"),
TddSyncStatusCh2 = dict.GetValueOrDefault("tdd_sync_status_ch2"),
JesdTotalAlarm = dict.GetValueOrDefault("jesd_total_alarm"),
AfeJesdAlm = dict.GetValueOrDefault("afe_jesd_alm"),
FpgaJesdAlm = dict.GetValueOrDefault("fpga_jesd_alm")
};
}
}
}
批量往数据库插入数据,会导致数据库存储量过大,该怎么办