diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..013007b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.preferCSharpExtension": true +} \ No newline at end of file diff --git a/.windsurfrules b/.windsurfrules new file mode 100644 index 0000000..aa2e1ff --- /dev/null +++ b/.windsurfrules @@ -0,0 +1,220 @@ +我需要开发的一个软网关,需要实现的功能是: +#1. Web API调用接口获取数据,使用RestSharp调用: + ##1.1. 要求文档示例如下: + #### + 一、适用范围 + 本标准适用于机车检修段工控设备与段内其他系统平台的数据共享,数据类型包括但不限于部件二维码、部件编号、下车号、位别、试验数据、试验时间、设备状态数据。 + 二、调用方案 + 1.功能说明 + 原则上产生试验数据的工控设备均应具备请求段内二维码信息的能力,试验软件界面应配备部件二维码字符串的输入框,用于建立机车配件与试验数据间的 绑定关系,需同步存入本地及服务器端数据库中。通过扫码枪将二维码信息录入至输入框后,软件自动请求段内API接口进而获得该部件对应的部件编号、下车号、下车位别等信息并填入对应输入框内,具备请求失败可手动录入的功能。 + 2.设备扫描获取信息示例 + 用途:通过设备扫描二维码可以获取该工序的信息:车型、车号、位别、修程、部件名称、部件编码。 + + 1.接口url + http://172.16.3.203:2324/mes/order/iotPartInfo/getPartInfo + + 2.请求参数:PartQRCodeId(二维码零件id),DeviceCode(设备编码) + 3.请求方式:GET + 4.返回参数 + { + "status":200 + "msg":"success" + "data":{ + "vehicle_model": "HXD1D", //机型 + "locomotive_number": "0001",//下车号 + "repair_process": "C5",//修程 + "component_name": "车轴"//部件名称 + "part_position": "1" //位别 + "part_num": "部件编号"//部件编号 + "part_qrCode": "0280828b20b14dae9989f7a633eb6adc" //部件二维码id + ........... //需要返回更多参数请提交字段说明 + } + } + + 三、接入方案 + 1.接入架构 + 设备厂商负责建立统一的数据采集组件,于工控设备端部署本地数据库、数据发送服务或设备,服务器端部署数据接收服务,将接受到的数据写入相应数据库中,同时在服务器端部署API,用于段内各应用获取相应数据。 + + + 2.接入组件功能 + 接口应具备下列功能: + a)传输实时的设备状态数据,如运行状态、实时电流、电压、传感器数据等。 + b)传输设备故障、报警数据。 + c)传输设备产生的所有试验数据。 + d)具备断点续传和数据缓存能力,防止网络不稳定。 + e)提供修改采集设备及服务端IP地址的入口,防止IP地址变更后无法通讯。 + + 3.数据内容 + a)设备状态数据 + 设备的实时工作状态,比如开启/关闭状态、运行中、待机。 + b)试验测试数据(详细查看附件) +  尺寸、重量、温度、流量、转速、振动、光学测量。 +  静态压力、动态压力、差压、峰值压力、压力脉冲、环境压力。 +  实时电压和电流监测,包括运行曲线及上下限值。 +  运行、待机、停机、故障时间统计。 +  设备利用率计算。 +  故障件次统计。 +  环境温度、湿度监测,液位、系统压力、VOC浓度、粉尘指数检测。 +  液压站状态、附属计量部件状态提示。 +  机体水平度、振动检测。 +  作业次数统计。 + c)设备故障信息 + 故障设备:发生故障的设备,小件故障,以大部件上报。 + 故障时间:故障发生时间 + 故障描述:故障原因 + + 4.数据规范及接口协议 + a)设备状态、故障信息对接采用WebSocket、MQTT协议。 + b)试验数据上传采用。 + c)数据库结构应符合三大范式。 + d)支持配置多SecretKey,防止非法请求 + e)返回数据的格式采用json格式,返回的数据中应包含请求成功、失败的状态码。 + + 5.接口规范示例 + 示例一 + 用途:通过试验配件的二维码id、设备编号(支持多个同类型设备)获取对应的试验数据。 + + 1.接口url + http://厂家服务器地址:端口号/getTestdata + 2.请求参数:PartQRCodeId(二维码零件id),DeviceCode(设备编码) + 3.请求方式:GET + 4.返回参数 + { + "status": 200, + "msg":"请求成功/失败原因", + "data":, + { + "vehicle_model": "HXD1D", //车型 + "locomotive_number": "0001", //下车号 + "repair_process": "C5", //修程 + "component_name": "车轴" //部件名称 + "process_id": "1231231231231311312321" + "part position"::"1"//位别 + "part num:"部件编号"//部件编号 + "part_qrCode": "0280828b20b14dae9989f7a633eb6adc" //部件二维码id + + "date": "2023-12-22 15:00:34" + 试验数据… + } + } + + 示例二 + 用途:通过设备编号获取相应工控设备状态信息,如运行时间、待机时间、停机时间、故障时间、作业次数统计、设备利用率。 + + 1.接口url + http://厂家服务器地址:端口号/getTestBenchstatus + 2.请求参数: DeviceCode(设备编码) + 3.请求方式:GET + 4.返回参数 + { + "status": 200, + "msg":"请求成功/失败原因", + "data":, + { + "running_time": "10345", + "running _number": "36", + …… + } + } + + + 四、部署方案 + 同一厂家的设备应建立统一的数据接收组件或平台,部署在该厂家服务器上,对部分设备少且资源消耗小的可向物资设备科(信息)申请虚拟机部署。 + + 五、文档交接要求 + 1.提供服务端数据接受组件或平台的部署文档。 + 2.提供API开发文档及示例接口说明。 + 3.提供数据库用户名、密码及字段说明文档。 + + ### + ##1.2. 帮我封装这些功能供我使用,我还有其他的逻辑调用这些 + +#2. 设备实时状态推送数据,使用HslCommunication WebSocket + ##2.1. 接口标准示例如下: + ### + 1.协议:WebSocket + 2.推送频次:1秒/次,支持通过工控软件或配置文件自定义设置。 + 3.推送内容: + (1)设备基础信息:含设备编号、设备名称、设备厂商(规范全称)。 + (2)设备实时状态:含待机、运行、故障。 + (3)设备故障信息:含故障发生时间、故障代码、故障说明,无故障返回空值。 + (4)被试部件信息:含车型、修程、车号、位别、部件名称、部件编号、部件二维码id,若为多个部件返回多个部件信息,若非运行状态则返回空值。 + (5)实时试验数据:即各设备工控软件界面的所有实时数据、目标数据。 + (6)实时传感器数据:除实时试验数据以外,其他可以反应设备状态的传感器数据,如电流、电压、压力、位移、扭矩、角度等,根据各自设备特性,应传尽传。 + 4.接口示例: + 就多功能轮轴组装机举例: + (1)既有工控软件界面: + + (2)示例推送JSON内容: + { + "status": 200, // 状态码, 200表示成功 + "msg": "success", // 状态信息, 成功时为"success", 失败时为具体错误信息 + "data": { + "DeviceCode": "1234567890", // 设备编号 + "DeviceName": "多功能轮轴组装机1", // 设备名称 + "DeviceManufacturer": "北京新联铁集团股份有限公司", // 设备厂商 + "Status": 1, // 设备状态, 代码含义自定 + "FaultDetails": { + "code": 101, // 错误代码, 无错误为空 + "description": "压力传感器故障,请检查电源电压是否正确。", // 错误描述,无错误为空 + "time": "2021-01-01 12:00:00" // 错误发生时间,无错误为空 + }, + "ComponentsInfo": [{ // 不论多部件还是单部件均采用数组,若为单部件则数组中仅有一个对象 + "vehicle_model": "HXD1D", // 车型 + "locomotive_number": "0001", // 车号 + "repair_process": "C5", // 修程 + "part_position": 3, // 位别 + "component_name": "车轴", // 部件名称 + "part_num": "123154412", // 部件编号 + "part_qrid1": "145sdfsdsdfsd4ff5sdfwe1gd2sdfg", // 二维码id + }, + { + "vehicle_model": "HXD1D", // 车型 + "locomotive_number": "0001", // 车号 + "repair_process": "C5", // 修程 + "part_position": 3, // 位别 + "component_name": "轮饼", // 部件名称2 + "part_num": "1234567890", // 部件编号2 + "part_qrid": "145sdf422342sd4ff5sdfwe1gd2sdfg", // 二维码id + }], + "TestData": { + "current_process": "小车前进到压装位", // 当前工序 + "next_process": "小车运行到压装位", // 下一工序 + "top_pressure_left": 100, // 左顶尖压力 + "top_offset_left": 0, // 左顶尖位移 + "top_offset_target_left": 190.38, // 左顶尖位移目标值 + "measuring_offset_left": 0, // 左测量臂位移 + "measuring_offset_target_left": 305.11, // 左测量臂位移目标值 + "ultimate_pressure_left": 100, // 左最终压力 + "top_pressure_right": 100, // 右顶尖压力 + "top_offset_right": 0, // 右顶尖位移 + "top_offset_target_right": 190.38, // 右顶尖位移目标值 + "measuring_offset_right": 0, // 右测量臂位移 + "measuring_offset_target_right": 1608.31, // 右测量臂位移目标值 + "ultimate_pressure_right": 100, // 右最终压力 + }, + "SensorData": { + "electric_current": 0.001, // 电流 + "electric_voltage": 230, // 电压 + ... // 其他传感器数据 + } + } + } + + 二、设备健康状态数据采集 + 1.功能说明:设备运行状态下,以一定的频次记录设备可采集的传感器数据,在试验结束后将传感器数据以“,”分隔的形式写入数据库中,用于后期对设备健康状态的分析评估。 + 2.采集频次:1秒/次,支持通过工控软件或配置文件自定义设置。 + 3.采集内容:如电流、电压、压力、位移、扭矩、角度等,根据各自设备特性,应采尽采。 + 4.数据库结构示例: + 字段名 字段含义 内容示例 + Id 索引id 1 + Pid 父级id,关联试验信息的数据表,即试验数据表 3518 + frequency 采样频次,秒/次 1 + electric_current_A A相电流 0.13,0.54,0.56,0.28,0.59,0.63… + electric_current_B B相电流 0.23,0.14,0.56,0.28,0.59,0.63… + electric_current_C C相电流 0.43,0.52,0.56,0.28,0.59,0.63… + electric_voltage 电压 220.01,220.56,210.23,201.12… + … 其他传感器数据 + + ### + ##2.2. 帮我封装这些功能供我使用,我还有其他的逻辑调用这些 \ No newline at end of file diff --git a/Movicon.Models/Movicon.Models.csproj b/Movicon.Models/Movicon.Models.csproj new file mode 100644 index 0000000..22aff7c --- /dev/null +++ b/Movicon.Models/Movicon.Models.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Movicon.Models/TestData.cs b/Movicon.Models/TestData.cs new file mode 100644 index 0000000..bb5a3fc --- /dev/null +++ b/Movicon.Models/TestData.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Movicon.Models +{ + public class TestData + { + + } +} diff --git a/MoviconHub.App/App.config b/MoviconHub.App/App.config new file mode 100644 index 0000000..6765931 --- /dev/null +++ b/MoviconHub.App/App.config @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MoviconHub.App/Com/ApiConfig.cs b/MoviconHub.App/Com/ApiConfig.cs new file mode 100644 index 0000000..abeb002 --- /dev/null +++ b/MoviconHub.App/Com/ApiConfig.cs @@ -0,0 +1,69 @@ +using Newtonsoft.Json; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Com +{ + /// + /// API配置类 + /// + public class ApiConfig + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private const string ConfigFileName = "apiconfig.json"; + + public string ServerAddress { get; set; } = "172.16.3.203"; + public int ServerPort { get; set; } = 2324; + public string SecretKey { get; set; } = ""; + + /// + /// 保存配置到文件 + /// + public void SaveConfig() + { + try + { + string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName); + string json = JsonConvert.SerializeObject(this, Formatting.Indented); + File.WriteAllText(configPath, json); + Logger.Info("API配置已保存"); + } + catch (Exception ex) + { + Logger.Error(ex, "保存API配置时发生错误"); + } + } + + /// + /// 从文件加载配置 + /// + /// API配置对象 + public static ApiConfig LoadConfig() + { + try + { + string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName); + if (File.Exists(configPath)) + { + string json = File.ReadAllText(configPath); + var config = JsonConvert.DeserializeObject(json); + Logger.Info("已成功加载API配置"); + return config; + } + } + catch (Exception ex) + { + Logger.Error(ex, "加载API配置时发生错误"); + } + + // 如果配置文件不存在或加载失败,则返回默认配置 + Logger.Info("使用默认API配置"); + return new ApiConfig(); + } + } +} diff --git a/MoviconHub.App/Com/ConfigHelper.cs b/MoviconHub.App/Com/ConfigHelper.cs new file mode 100644 index 0000000..7acc98d --- /dev/null +++ b/MoviconHub.App/Com/ConfigHelper.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Com +{ + public class ConfigHelper + { + Configuration cfa = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + /// + /// 根据Key取Value值 + /// + /// + public static string GetValue(string key) + { + return ConfigurationManager.AppSettings[key].ToString().Trim(); + } + + /// + /// 根据Key修改Value + /// + /// 要修改的Key + /// 要修改为的值 + public static void SetValue(string key, string value) + { + //ConfigurationManager.AppSettings.Set(key, value); + //cfa.AppSettings.Settings[key].Value = value; + //cfa.Save(); + + Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + configuration.AppSettings.Settings[key].Value = value; + configuration.Save(); + ConfigurationManager.RefreshSection("appSettings"); + } + + /// + /// 添加新的Key ,Value键值对 + /// + /// Key + /// Value + public static void Add(string key, string value) + { + ConfigurationManager.AppSettings.Add(key, value); + } + + /// + /// 根据Key删除项 + /// + /// Key + public static void Remove(string key) + { + ConfigurationManager.AppSettings.Remove(key); + } + + } +} diff --git a/MoviconHub.App/Com/WebSocketConfig.cs b/MoviconHub.App/Com/WebSocketConfig.cs new file mode 100644 index 0000000..0eec248 --- /dev/null +++ b/MoviconHub.App/Com/WebSocketConfig.cs @@ -0,0 +1,81 @@ +using Newtonsoft.Json; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Com +{ + /// + /// WebSocket客户端配置类 + /// + public class WebSocketConfig + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private const string ConfigFileName = "websocket_config.json"; + + // WebSocket客户端配置 + public string ServerAddress { get; set; } = "127.0.0.1"; // 服务器地址,默认使用与API相同的地址 + public int ServerPort { get; set; } = 8303; // 服务器端口 + public bool AutoReconnect { get; set; } = true; // 自动重连 + public int ReconnectInterval { get; set; } = 5000; // 重连间隔(毫秒) + public int HeartbeatInterval { get; set; } = 30000; // 心跳间隔(毫秒) + public int ConnectionTimeout { get; set; } = 5000; // 连接超时(毫秒) + public string DeviceCode { get; set; } = ""; // 设备编码 + public string SecretKey { get; set; } = ""; // 安全密钥 + public string Url { get; set; } = ""; // 安全密钥 + + /// + /// 循环周期数据 + /// + public int Cycle { get; set; } = 1; + + /// + /// 保存配置到文件 + /// + public void SaveConfig() + { + try + { + string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName); + string json = JsonConvert.SerializeObject(this, Formatting.Indented); + File.WriteAllText(configPath, json); + Logger.Info("WebSocket客户端配置已保存"); + } + catch (Exception ex) + { + Logger.Error(ex, "保存WebSocket客户端配置时发生错误"); + } + } + + /// + /// 从文件加载配置 + /// + /// WebSocket配置对象 + public static WebSocketConfig LoadConfig() + { + try + { + string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName); + if (File.Exists(configPath)) + { + string json = File.ReadAllText(configPath); + var config = JsonConvert.DeserializeObject(json); + Logger.Info("已成功加载WebSocket客户端配置"); + return config; + } + } + catch (Exception ex) + { + Logger.Error(ex, "加载WebSocket客户端配置时发生错误"); + } + + // 如果配置文件不存在或加载失败,则返回默认配置 + Logger.Info("使用默认WebSocket客户端配置"); + return new WebSocketConfig(); + } + } +} diff --git a/MoviconHub.App/FRemoteSqlContext.cs b/MoviconHub.App/FRemoteSqlContext.cs new file mode 100644 index 0000000..6799283 --- /dev/null +++ b/MoviconHub.App/FRemoteSqlContext.cs @@ -0,0 +1,20 @@ +using MoviconHub.App.Com; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App +{ + /// + /// 远程的数据库 + /// + public class FRemoteSqlContext + { + public static IFreeSql FDb = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.SqlServer, ConfigHelper.GetValue("RemoteConnecting")) + .UseAutoSyncStructure(true) //自动同步实体结构到数据库 + .Build(); //请务必定义成 Singleton 单例模式 + } +} diff --git a/MoviconHub.App/FSqlContext.cs b/MoviconHub.App/FSqlContext.cs new file mode 100644 index 0000000..3a82a6a --- /dev/null +++ b/MoviconHub.App/FSqlContext.cs @@ -0,0 +1,12 @@ +using MoviconHub.App.Com; + +namespace MoviconHub.App +{ + public class FSqlContext + { + public static IFreeSql FDb = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.SqlServer, ConfigHelper.GetValue("connecting")) + .UseAutoSyncStructure(false) //自动同步实体结构到数据库 + .Build(); //请务必定义成 Singleton 单例模式 + } +} diff --git a/MoviconHub.App/Models/AlarmModel.cs b/MoviconHub.App/Models/AlarmModel.cs new file mode 100644 index 0000000..a4eeb43 --- /dev/null +++ b/MoviconHub.App/Models/AlarmModel.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 报警模型 + /// + public class AlarmModel + { + private readonly IFreeSql _fsql; + private bool _isActive; + private DateTime _startTime; + + /// + /// 构造函数 + /// + /// FreeSql实例 + public AlarmModel(IFreeSql fsql) + { + _fsql = fsql; + _isActive = false; + DeviceCode = string.Empty; + DeviceName = string.Empty; + AlarmMessage = string.Empty; + DeviceState = 0; + } + + + /// + /// 设备编号 + /// + public string DeviceCode { get; set; } + + /// + /// 设备名称 + /// + public string DeviceName { get; set; } + + /// + /// 报警信息 + /// + public string AlarmMessage { get; set; } + + /// + /// 设备状态码 + /// + public int DeviceState { get; set; } + + /// + /// Index + /// + public int Index { get; set; } + + /// + /// 报警是否激活 + /// + public bool IsActive + { + get => _isActive; + set + { + // 如果报警状态从激活变为不激活,保存报警记录 + if (_isActive && !value) + { + SaveAlarmRecord(); + } + // 如果报警状态从不激活变为激活,记录开始时间 + else if (!_isActive && value) + { + _startTime = DateTime.Now; + } + + _isActive = value; + } + } + + /// + /// 更新设备码数据 + /// + public void UpdateDeviceInfo(string deviceName, string DeviceCode) + { + this.DeviceName = deviceName; + this.DeviceCode= DeviceCode; + } + + + /// + /// 保存报警记录到数据库 + /// + private void SaveAlarmRecord() + { + try + { + var endTime = DateTime.Now; + + // 创建设备报警记录 + var deviceAlarm = new DeviceAlarm + { + DeviceCode = this.DeviceCode, + DeviceName = this.DeviceName, + DeviceState = this.DeviceState, + AlarmMessage = this.AlarmMessage, + StartTime = _startTime, + EndTime = endTime + }; + + // 保存到数据库 + _fsql.Insert(deviceAlarm).ExecuteAffrows(); + + Console.WriteLine($"报警记录已保存: {DeviceCode}, 开始时间: {_startTime}, 结束时间: {endTime}"); + } + catch (Exception ex) + { + Console.WriteLine($"保存报警记录失败: {ex.Message}"); + } + } + + } +} diff --git a/MoviconHub.App/Models/ClearAction.cs b/MoviconHub.App/Models/ClearAction.cs new file mode 100644 index 0000000..f929a9d --- /dev/null +++ b/MoviconHub.App/Models/ClearAction.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 清洗动作 + /// + public class ClearAction + { + /// + /// 清洗事件 + /// + public event EventHandler ClearActionEvent; + + + private bool _ClearEnd; + /// + /// 清洗完成信号 + /// + public bool ClearEnd + { + get { return _ClearEnd; } + set + { + if (_ClearEnd != value) + { + _ClearEnd = value; + //清洗完成 + if (_ClearEnd) + { + ClearActionEvent.BeginInvoke(this, "清洗完成", null, null); + } + } + } + } + + + + private bool _DeviceClose; + /// + /// 设备关机信号 + /// + public bool DeviceClose + { + get { return _DeviceClose; } + set + { + if (_DeviceClose != value) + { + _DeviceClose = value; + //清洗完成 + if (_DeviceClose) + { + ClearActionEvent.BeginInvoke(this, "设备关机", null, null); + } + } + } + } + + + + } +} diff --git a/MoviconHub.App/Models/ClearData.cs b/MoviconHub.App/Models/ClearData.cs new file mode 100644 index 0000000..b8f616c --- /dev/null +++ b/MoviconHub.App/Models/ClearData.cs @@ -0,0 +1,190 @@ +using FreeSql.DataAnnotations; +using System; + +namespace MoviconHub.App.Models +{ + /// + /// 清洗数据 + /// + [Table(Name = "ClearData")] + public class ClearData + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string DeviceName { get; set; } + + /// + /// 程序进程 + /// + [Column(Name = "program_process", StringLength = 100, IsNullable = true)] + public string program_process { get; set; } + + /// + /// 车型 + /// + [Column(Name = "vehicle_model", StringLength = 100, IsNullable = true)] + public string vehicle_model { get; set; } + + /// + /// 下车号 + /// + [Column(Name = "locomotive_number", StringLength = 100, IsNullable = true)] + public string locomotive_number { get; set; } + + /// + /// 修程 + /// + [Column(Name = "repair_process", StringLength = 100, IsNullable = true)] + public string repair_process { get; set; } + + /// + /// 部件名称 + /// + [Column(Name = "component_name", StringLength = 100, IsNullable = true)] + public string component_name { get; set; } + + /// + /// 位别 + /// + [Column(Name = "part_position", StringLength = 100, IsNullable = true)] + public string part_position { get; set; } + + /// + /// 部件编号 + /// + [Column(Name = "part_num", StringLength = 100, IsNullable = true)] + public string part_num { get; set; } + + /// + /// 部件二维码 + /// + [Column(Name = "part_qrid", StringLength = 100, IsNullable = true)] + public string part_qrid { get; set; } + + /// + /// 程序进程百分比​ + /// + [Column(Name = "Test_FrameworkProgramProcessPercentage", StringLength = 100, IsNullable = true)] + public string Test_FrameworkProgramProcessPercentage { get; set; } + + /// + /// 程序进程​ + /// + [Column(Name = "Test_FrameworkProgramProcess", StringLength = 100, IsNullable = true)] + public string Test_FrameworkProgramProcess { get; set; } + + + /// + /// 构架单个机型清洗时长 + /// + [Column(Name = "Test_FrameworkPerModelCleaningDuration", StringLength = 100, IsNullable = true)] + public string Test_FrameworkPerModelCleaningDuration { get; set; } + + /// + /// 构架单个机型用清洗剂量 + /// + [Column(Name = "Test_FrameworkPerModelCleaningAgentUsage", StringLength = 100, IsNullable = true)] + public string Test_FrameworkPerModelCleaningAgentUsage { get; set; } + + /// + /// 构架单个机型用水量 + /// + [Column(Name = "Test_FrameworkPerModelWaterUsage", StringLength = 100, IsNullable = true)] + public string Test_FrameworkPerModelWaterUsage { get; set; } + + /// + /// 水罐温度 + /// + [Column(Name = "WaterTank_Temp", StringLength = 100, IsNullable = true)] + public string WaterTank_Temp { get; set; } + + /// + /// 清洗剂罐温度 + /// + [Column(Name = "AgentTank_Temp", StringLength = 100, IsNullable = true)] + public string AgentTank_Temp { get; set; } + + /// + /// 水罐液位 + /// + [Column(Name = "WaterTank_Level", StringLength = 100, IsNullable = true)] + public string WaterTank_Level { get; set; } + + + /// + /// 清洗剂罐液位 + /// + [Column(Name = "AgentTank_Level", StringLength = 100, IsNullable = true)] + public string AgentTank_Level { get; set; } + + /// + /// 浸泡池1温度 + /// + [Column(Name = "SoakingTank1_Temp", StringLength = 100, IsNullable = true)] + public string SoakingTank1_Temp { get; set; } + + /// + /// 浸泡池2温度 + /// + [Column(Name = "SoakingTank2_Temp", StringLength = 100, IsNullable = true)] + public string SoakingTank2_Temp { get; set; } + + + /// + /// ​热水罐加热模式运行​​ + /// + [Column(Name = "Test_WaterTankHeat", StringLength = 100, IsNullable = true)] + public string Test_WaterTankHeat { get; set; } + + + /// + /// ​热水罐补水模式运行​​ + /// + [Column(Name = "Test_WaterTankAdd", StringLength = 100, IsNullable = true)] + public string Test_WaterTankAdd { get; set; } + + /// + /// ​清洗剂罐加热模式运行 ​​ + /// + [Column(Name = "Test_CleaningAgentTankHeat", StringLength = 100, IsNullable = true)] + public string Test_CleaningAgentTankHeat { get; set; } + + /// + /// ​清洗剂罐补水模式运行​​ + /// + [Column(Name = "Test_CleaningAgentTankAdd", StringLength = 100, IsNullable = true)] + public string Test_CleaningAgentTankAdd { get; set; } + + /// + /// ​电能监控​​ + /// + [Column(Name = "Test_ElectricSurveillance", StringLength = 100, IsNullable = true)] + public string Test_ElectricSurveillance { get; set; } + + /// + /// ​蒸汽监控​​ + /// + [Column(Name = "Test_SteamSurveillance", StringLength = 100, IsNullable = true)] + public string Test_SteamSurveillance { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconHub.App/Models/ComponentsInfo.cs b/MoviconHub.App/Models/ComponentsInfo.cs new file mode 100644 index 0000000..7e97e5f --- /dev/null +++ b/MoviconHub.App/Models/ComponentsInfo.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 不论多部件还是单部件均采用数组,若为单部件则数组中仅有一个对象 + /// + public class ComponentsInfo + { + /// + /// 车型 + /// + [JsonProperty("part_Vehicle_model")] + public string part_Vehicle_model { get; set; } + + /// + /// 车号 + /// + [JsonProperty("part_locomotive_number")] + public string part_locomotive_number { get; set; } + + /// + /// 修程 + /// + [JsonProperty("part_repair_process")] + public string part_repair_process { get; set; } + + /// + /// 位别 + /// + [JsonProperty("part_position")] + public string part_position { get; set; } + + /// + /// 部件名称 + /// + [JsonProperty("component_name")] + public string part_name { get; set; } + + /// + /// 部件编号 + /// + [JsonProperty("part_num")] + public string part_num { get; set; } + + /// + /// 二维码id + /// + [JsonProperty("part_qrid")] + public string part_qrid { get; set; } + + } +} diff --git a/MoviconHub.App/Models/CurRunClearState.cs b/MoviconHub.App/Models/CurRunClearState.cs new file mode 100644 index 0000000..9471c79 --- /dev/null +++ b/MoviconHub.App/Models/CurRunClearState.cs @@ -0,0 +1,199 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 当前运行的清洗状态 + /// + [Table(Name = "CurRunClearState")] + public class CurRunClearState + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string DeviceName { get; set; } + + /// + /// 程序进程 + /// + [Column(Name = "program_process", StringLength = 100, IsNullable = true)] + public string program_process { get; set; } + + /// + /// 车型 + /// + [Column(Name = "vehicle_model", StringLength = 100, IsNullable = true)] + public string vehicle_model { get; set; } + + /// + /// 下车号 + /// + [Column(Name = "locomotive_number", StringLength = 100, IsNullable = true)] + public string locomotive_number { get; set; } + + /// + /// 修程 + /// + [Column(Name = "repair_process", StringLength = 100, IsNullable = true)] + public string repair_process { get; set; } + + /// + /// 部件名称 + /// + [Column(Name = "component_name", StringLength = 100, IsNullable = true)] + public string component_name { get; set; } + + /// + /// 位别 + /// + [Column(Name = "part_position", StringLength = 100, IsNullable = true)] + public string part_position { get; set; } + + /// + /// 部件编号 + /// + [Column(Name = "part_num", StringLength = 100, IsNullable = true)] + public string part_num { get; set; } + + /// + /// 部件二维码 + /// + [Column(Name = "part_qrid", StringLength = 100, IsNullable = true)] + public string part_qrid { get; set; } + + /// + /// 程序进程百分比​ + /// + [Column(Name = "Test_FrameworkProgramProcessPercentage", StringLength = 100, IsNullable = true)] + public string Test_FrameworkProgramProcessPercentage { get; set; } + + /// + /// 程序进程​ + /// + [Column(Name = "Test_FrameworkProgramProcess", StringLength = 100, IsNullable = true)] + public string Test_FrameworkProgramProcess { get; set; } + + /// + /// 零部件设备状态 + /// + [Column(Name = "Test_PartsEquipmentStatus", StringLength = 100, IsNullable = true)] + public string Test_PartsEquipmentStatus { get; set; } + + /// + /// 构架单个机型清洗时长 + /// + [Column(Name = "Test_FrameworkPerModelCleaningDuration", StringLength = 100, IsNullable = true)] + public string Test_FrameworkPerModelCleaningDuration { get; set; } + + /// + /// 构架单个机型用清洗剂量 + /// + [Column(Name = "Test_FrameworkPerModelCleaningAgentUsage", StringLength = 100, IsNullable = true)] + public string Test_FrameworkPerModelCleaningAgentUsage { get; set; } + + /// + /// 构架单个机型用水量 + /// + [Column(Name = "Test_FrameworkPerModelWaterUsage", StringLength = 100, IsNullable = true)] + public string Test_FrameworkPerModelWaterUsage { get; set; } + + /// + /// 水罐温度 + /// + [Column(Name = "WaterTank_Temp", StringLength = 100, IsNullable = true)] + public string WaterTank_Temp { get; set; } + + /// + /// 清洗剂罐温度 + /// + [Column(Name = "AgentTank_Temp", StringLength = 100, IsNullable = true)] + public string AgentTank_Temp { get; set; } + + /// + /// 水罐液位 + /// + [Column(Name = "WaterTank_Level", StringLength = 100, IsNullable = true)] + public string WaterTank_Level { get; set; } + + + /// + /// 清洗剂罐液位 + /// + [Column(Name = "AgentTank_Level", StringLength = 100, IsNullable = true)] + public string AgentTank_Level { get; set; } + + /// + /// 浸泡池1温度 + /// + [Column(Name = "SoakingTank1_Temp", StringLength = 100, IsNullable = true)] + public string SoakingTank1_Temp { get; set; } + + /// + /// 浸泡池2温度 + /// + [Column(Name = "SoakingTank2_Temp", StringLength = 100, IsNullable = true)] + public string SoakingTank2_Temp { get; set; } + + + /// + /// ​热水罐加热模式运行​​ + /// + [Column(Name = "Test_WaterTankHeat", StringLength = 100, IsNullable = true)] + public string Test_WaterTankHeat { get; set; } + + + /// + /// ​热水罐补水模式运行​​ + /// + [Column(Name = "Test_WaterTankAdd", StringLength = 100, IsNullable = true)] + public string Test_WaterTankAdd { get; set; } + + /// + /// ​清洗剂罐加热模式运行 ​​ + /// + [Column(Name = "Test_CleaningAgentTankHeat", StringLength = 100, IsNullable = true)] + public string Test_CleaningAgentTankHeat { get; set; } + + /// + /// ​清洗剂罐补水模式运行​​ + /// + [Column(Name = "Test_CleaningAgentTankAdd", StringLength = 100, IsNullable = true)] + public string Test_CleaningAgentTankAdd { get; set; } + + /// + /// ​电能监控​​ + /// + [Column(Name = "Test_ElectricSurveillance", StringLength = 100, IsNullable = true)] + public string Test_ElectricSurveillance { get; set; } + + /// + /// ​蒸汽监控​​ + /// + [Column(Name = "Test_SteamSurveillance", StringLength = 100, IsNullable = true)] + public string Test_SteamSurveillance { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconHub.App/Models/DeviceAlarm.cs b/MoviconHub.App/Models/DeviceAlarm.cs new file mode 100644 index 0000000..9180605 --- /dev/null +++ b/MoviconHub.App/Models/DeviceAlarm.cs @@ -0,0 +1,62 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 设备报警 + /// + [Table(Name = "DeviceAlarm")] + public class DeviceAlarm + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string DeviceName { get; set; } + + + /// + /// 设备状态 + /// + [Column(Name = "DeviceState", IsNullable = true)] + public int? DeviceState { get; set; } + + /// + /// 报警信息 + /// + [Column(Name = "AlarmMessage", StringLength = 180, IsNullable = true)] + public string AlarmMessage { get; set; } + + + /// + /// 开始时间 + /// + //[Column(CanUpdate = false)] + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + //[Column(CanUpdate = false)] + public DateTime EndTime { get; set; } + + } +} diff --git a/MoviconHub.App/Models/DeviceState.cs b/MoviconHub.App/Models/DeviceState.cs new file mode 100644 index 0000000..106b3f1 --- /dev/null +++ b/MoviconHub.App/Models/DeviceState.cs @@ -0,0 +1,97 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 设备状态 + /// + [Table(Name = "DeviceState")] + public class DeviceState + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string DeviceName { get; set; } + + + /// + /// 开机时长(status为1) + /// + [Column(Name = "PowerOnTime", IsNullable = true)] + public int PowerOnTime { get; set; } + + /// + /// 运行时长(status为2) + /// + [Column(Name = "RunTime", IsNullable = true)] + public int RunTime { get; set; } + + /// + /// 待机时长(status为3) + /// + [Column(Name = "StandbyTime", IsNullable = true)] + public int StandbyTime { get; set; } + + /// + /// 故障时长(status为4) + /// + [Column(Name = "FaultTime", IsNullable = true)] + public int FaultTime { get; set; } + + /// + /// 关机时长(status为5) + /// + [Column(Name = "ShutdownTime", IsNullable = true)] + public int ShutdownTime { get; set; } + + ///// + ///// 使用率(PowerOnTime+ RunTime+ StandbyTime+ FaultTime)/( EndTime- StartTime) 从ts_sh_device_status_change_log中计算 + ///// + //[Column(Name = "UseRatio", IsNullable = true)] + //public int UseRatio { get; set; } + + /// + /// 使用率(PowerOnTime+ RunTime+ StandbyTime+ FaultTime)/( EndTime- StartTime) 从ts_sh_device_status_change_log中计算 + /// + [Column(Name = "UseRatio", StringLength = 100, IsNullable = true)] + public string UseRatio { get; set; } + + + /// + /// 故障次数 + /// + [Column(Name = "FaultNum", IsNullable = true)] + public int FaultNum { get; set; } + + /// + /// 作业次数,建议从作业数据表中取数据 + /// + [Column(Name = "JobNum", IsNullable = true)] + public int JobNum { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconHub.App/Models/DeviceStateStaticModel.cs b/MoviconHub.App/Models/DeviceStateStaticModel.cs new file mode 100644 index 0000000..4cb2c47 --- /dev/null +++ b/MoviconHub.App/Models/DeviceStateStaticModel.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 设备状态统计模型 + /// + public class DeviceStateStaticModel + { + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 设备名称 + /// + public int Value { get; set; } + + /// + /// Index + /// + public int Index { get; set; } + + } +} diff --git a/MoviconHub.App/Models/DeviceStatusData.cs b/MoviconHub.App/Models/DeviceStatusData.cs new file mode 100644 index 0000000..43e05ce --- /dev/null +++ b/MoviconHub.App/Models/DeviceStatusData.cs @@ -0,0 +1,64 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 设备状态数据模型 + /// Websocket数据格式 + /// + public class DeviceStatusData + { + /// + /// 设备ID + /// + [JsonProperty("device_id")] + public string DeviceId { get; set; } + + /// + /// 设备名称 + /// + [JsonProperty("device_name")] + public string DeviceName { get; set; } + + /// + /// 设备状态(开启/关闭/运行中/待机) + /// + [JsonProperty("status")] + public string Status { get; set; } + + /// + /// 电压值 + /// + [JsonProperty("voltage")] + public double Voltage { get; set; } + + /// + /// 电流值 + /// + [JsonProperty("current")] + public double Current { get; set; } + + /// + /// 温度值 + /// + [JsonProperty("temperature")] + public double Temperature { get; set; } + + /// + /// 其他自定义状态参数 + /// + [JsonProperty("parameters")] + public Dictionary Parameters { get; set; } = new Dictionary(); + + /// + /// 记录时间 + /// + [JsonProperty("record_time")] + public DateTime RecordTime { get; set; } = DateTime.Now; + } +} diff --git a/MoviconHub.App/Models/EnvironmentData.cs b/MoviconHub.App/Models/EnvironmentData.cs new file mode 100644 index 0000000..01d054a --- /dev/null +++ b/MoviconHub.App/Models/EnvironmentData.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 环境数据模型 + /// + public class EnvironmentData + { + /// + /// 环境温度 + /// + [JsonProperty("env_temperature")] + public double Temperature { get; set; } + + /// + /// 环境湿度 + /// + [JsonProperty("env_humidity")] + public double Humidity { get; set; } + + /// + /// VOC浓度 + /// + [JsonProperty("voc_concentration")] + public double VocConcentration { get; set; } + + /// + /// 粉尘指数 + /// + [JsonProperty("dust_index")] + public double DustIndex { get; set; } + } + +} diff --git a/MoviconHub.App/Models/FaultDetails.cs b/MoviconHub.App/Models/FaultDetails.cs new file mode 100644 index 0000000..b4bf613 --- /dev/null +++ b/MoviconHub.App/Models/FaultDetails.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 设备故障数据模型 + /// + public class FaultDetails + { + /// + ///错误代码, 无错误为空 + /// + [JsonProperty("Fault_Code")] + public string Fault_Code { get; set; } + + /// + /// 错误发生时间,无错误为空 + /// + [JsonProperty("Fault_Time")] + public DateTime Fault_Time { get; set; } = DateTime.Now; + + /// + /// 错误描述,无错误为空 + /// + [JsonProperty("Fault_Description")] + public string Fault_Description { get; set; } + + } + +} diff --git a/MoviconHub.App/Models/MessageType.cs b/MoviconHub.App/Models/MessageType.cs new file mode 100644 index 0000000..72f7cbf --- /dev/null +++ b/MoviconHub.App/Models/MessageType.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// WebSocket消息类型枚举 + /// + public enum MessageType + { + /// + /// 心跳消息 + /// + Heartbeat = 0, + + /// + /// 设备状态数据 + /// + DeviceStatus = 1, + + /// + /// 试验测试数据 + /// + TestData = 2, + + /// + /// 设备故障信息 + /// + DeviceFault = 3, + + /// + /// 认证消息 + /// + Authentication = 4, + + /// + /// 系统消息 + /// + SystemMessage = 99 + } + +} diff --git a/MoviconHub.App/Models/PartInfo.cs b/MoviconHub.App/Models/PartInfo.cs new file mode 100644 index 0000000..2832866 --- /dev/null +++ b/MoviconHub.App/Models/PartInfo.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 部件信息数据模型 + /// + public class PartInfo + { + [JsonProperty("part_id")] + public string PartId { get; set; } + + [JsonProperty("order_id")] + public string OrderId { get; set; } + + [JsonProperty("vehicle_model")] + public string VehicleModel { get; set; } + + [JsonProperty("locomotive_number")] + public string LocomotiveNumber { get; set; } + + [JsonProperty("repair_process")] + public string RepairProcess { get; set; } + + [JsonProperty("component_name")] + public string ComponentName { get; set; } + + [JsonProperty("part_position")] + public string PartPosition { get; set; } + + [JsonProperty("part_num")] + public string PartNum { get; set; } + + [JsonProperty("order_type_id")] + public string OrderTypeId { get; set; } + + [JsonProperty("product_type_id")] + public string ProductTypeId { get; set; } + + [JsonProperty("part_qrCode")] + public string PartQrCode { get; set; } + + [JsonProperty("traceable_code")] + public string TraceableCode { get; set; } + + [JsonProperty("part_code")] + public string PartCode { get; set; } + } + +} diff --git a/MoviconHub.App/Models/PartInfoResponse.cs b/MoviconHub.App/Models/PartInfoResponse.cs new file mode 100644 index 0000000..064a132 --- /dev/null +++ b/MoviconHub.App/Models/PartInfoResponse.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 部件信息响应模型 + /// + public class PartInfoResponse + { + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("msg")] + public string Message { get; set; } + + [JsonProperty("data")] + public PartInfo Data { get; set; } + } +} diff --git a/MoviconHub.App/Models/RTVar.cs b/MoviconHub.App/Models/RTVar.cs new file mode 100644 index 0000000..cd58919 --- /dev/null +++ b/MoviconHub.App/Models/RTVar.cs @@ -0,0 +1,33 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 试验箱湿度 表设置 + /// + public class RTVar + { + /// + /// Name + /// + [Column(Name = "Name",StringLength =255)] + public string Name { get; set; } + + /// + /// Val + /// + [Column(Name = "Val", StringLength = 255)] + public string Val { get; set; } + + /// + /// LastTime + /// + [Column(Name = "LastTime")] + public DateTime LastTime { get; set; } + } +} diff --git a/MoviconHub.App/Models/SglModel.cs b/MoviconHub.App/Models/SglModel.cs new file mode 100644 index 0000000..d063ba7 --- /dev/null +++ b/MoviconHub.App/Models/SglModel.cs @@ -0,0 +1,38 @@ +using System; + +namespace MoviconHub.App.Models +{ + /// + /// 信号模型 + /// + public class SglModel + { + public event EventHandler SglModelChanged; + + private string _CodeReady; + /// + /// 条码信息OK + /// + public string CodeReady + { + get + { + return _CodeReady; + } + set + { + if (value != _CodeReady) + { + if (!string.IsNullOrEmpty(value)) + { + SglModelChanged(this, "CodeReady");//开始动作 + } + _CodeReady = value; + } + + + } + } + + } +} diff --git a/MoviconHub.App/Models/TestData.cs b/MoviconHub.App/Models/TestData.cs new file mode 100644 index 0000000..0f853e1 --- /dev/null +++ b/MoviconHub.App/Models/TestData.cs @@ -0,0 +1,220 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 测试数据模型 - 可根据实际测试数据结构进行扩展 + /// + public class TestData + { + /// + /// 程序进程百分比​ + /// + [JsonProperty("Test_FrameworkProgramProcessPercentage")] + public string Test_FrameworkProgramProcessPercentage { get; set; } + + /// + /// 程序进程​ + /// + [JsonProperty("Test_FrameworkProgramProcess")] + public string Test_FrameworkProgramProcess { get; set; } + + /// + /// 构架设备状态​ + /// + [JsonProperty("Test_FrameworkEquipmentStatus")] + public string Test_FrameworkEquipmentStatus { get; set; } + + /// + /// 零部件设备状态​ + /// + [JsonProperty("Test_PartsEquipmentStatus")] + public string Test_PartsEquipmentStatus { get; set; } + + /// + /// ​构架累计用清洗剂量​ + /// + [JsonProperty("Test_FrameworkCumulativeCleaningAgentUsage")] + public string Test_FrameworkCumulativeCleaningAgentUsage { get; set; } + + /// + /// 构架累计用水量​ + /// + [JsonProperty("Test_FrameworkCumulativeWaterUsage")] + public string Test_FrameworkCumulativeWaterUsage { get; set; } + + /// + /// 构架累计清洗时长​ + /// + [JsonProperty("Test_FrameworkCumulativeCleaningDuration")] + public string Test_FrameworkCumulativeCleaningDuration { get; set; } + + /// + /// 零部件累计用水量 + /// + [JsonProperty("Test_PartsCumulativeWaterUsage")] + public string Test_PartsCumulativeWaterUsage { get; set; } + + /// + /// ​零部件累计清洗时长​ + /// + [JsonProperty("Test_PartsCumulativeCleaningDuration")] + public string Test_PartsCumulativeCleaningDuration { get; set; } + + + /// + /// 累计检修数量​ + /// + [JsonProperty("Test_CumulativeMaintenanceCount")] + public string Test_CumulativeMaintenanceCount { get; set; } + + + /// + /// ​机器人1状态​ + /// + [JsonProperty("Test_Robot1Status")] + public string Test_Robot1Status { get; set; } + + + /// + /// 机器人2状态​ + /// + [JsonProperty("Test_Robot2Status")] + public string Test_Robot2Status { get; set; } + + + /// + /// 构架清洗数量 + /// + [JsonProperty("Test_FrameworkCleaningCount")] + public string Test_FrameworkCleaningCount { get; set; } + + + /// + /// 构架单个机型清洗时长​ + /// + [JsonProperty("Test_FrameworkPerModelCleaningDuration")] + public string Test_FrameworkPerModelCleaningDuration { get; set; } + + /// + /// ​​构架单个机型用清洗剂量​ + /// + [JsonProperty("Test_FrameworkPerModelCleaningAgentUsage")] + public string Test_FrameworkPerModelCleaningAgentUsage { get; set; } + + + /// + /// ​构架单个机型用水量​ + /// + [JsonProperty("Test_FrameworkPerModelWaterUsage")] + public string Test_FrameworkPerModelWaterUsage { get; set; } + + + /// + /// ​​零部件单次清洗时长​ + /// + [JsonProperty("Test_PartsSingleCleaningDuration")] + public string Test_PartsSingleCleaningDuration { get; set; } + + + /// + /// 零部件单次用水量​ + /// + [JsonProperty("Test_PartsSingleWaterUsage")] + public string Test_PartsSingleWaterUsage { get; set; } + + + /// + /// ​水罐温度​ + /// + [JsonProperty("Test_WaterTankTemperature")] + public string Test_WaterTankTemperature { get; set; } + + + /// + /// ​​清洗剂罐温度​ + /// + [JsonProperty("Test_CleaningAgentTankTemperature")] + public string Test_CleaningAgentTankTemperature { get; set; } + + + /// + /// 浸泡池1温度​ + /// + [JsonProperty("Test_SoakingTank1Temperature")] + public string Test_SoakingTank1Temperature { get; set; } + + + /// + /// ​浸泡池2温度​ + /// + [JsonProperty("Test_SoakingTank2Temperature")] + public string Test_SoakingTank2Temperature { get; set; } + + + /// + /// ​水罐液位​​ + /// + [JsonProperty("Test_WaterTankLevel")] + public string Test_WaterTankLevel { get; set; } + + + + /// + /// ​清洗剂罐液位​​ + /// + [JsonProperty("Test_CleaningAgentTankLevel")] + public string Test_CleaningAgentTankLevel { get; set; } + + + /// + /// ​热水罐加热模式运行​​ + /// + [JsonProperty("Test_WaterTankHeat")] + public string Test_WaterTankHeat { get; set; } + + + /// + /// ​热水罐补水模式运行​​ + /// + [JsonProperty("Test_WaterTankAdd")] + public string Test_WaterTankAdd { get; set; } + + /// + /// ​清洗剂罐加热模式运行 ​​ + /// + [JsonProperty("Test_CleaningAgentTankHeat")] + public string Test_CleaningAgentTankHeat { get; set; } + + /// + /// ​清洗剂罐补水模式运行​​ + /// + [JsonProperty("Test_CleaningAgentTankAdd")] + public string Test_CleaningAgentTankAdd { get; set; } + + /// + /// ​电能监控​​ + /// + [JsonProperty("Test_ElectricSurveillance")] + public string Test_ElectricSurveillance { get; set; } + + /// + /// ​蒸汽监控​​ + /// + [JsonProperty("Test_SteamSurveillance")] + public string Test_SteamSurveillance { get; set; } + + + /// + /// 测试数据值集合 + /// + [JsonProperty("test_values")] + public Dictionary TestValues { get; set; } = new Dictionary(); + } + +} diff --git a/MoviconHub.App/Models/TestDataResponse.cs b/MoviconHub.App/Models/TestDataResponse.cs new file mode 100644 index 0000000..0af3cbe --- /dev/null +++ b/MoviconHub.App/Models/TestDataResponse.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// 试验数据响应模型 + /// + public class TestDataResponse + { + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("msg")] + public string Message { get; set; } + + [JsonProperty("data")] + public Dictionary Data { get; set; } + } +} diff --git a/MoviconHub.App/Models/WebSocketConnectEventArgs.cs b/MoviconHub.App/Models/WebSocketConnectEventArgs.cs new file mode 100644 index 0000000..fea81cf --- /dev/null +++ b/MoviconHub.App/Models/WebSocketConnectEventArgs.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// WebSocket连接事件参数 + /// + public class WebSocketConnectEventArgs : EventArgs + { + /// + /// 服务器地址 + /// + public string ServerAddress { get; set; } + + /// + /// 服务器端口 + /// + public int ServerPort { get; set; } + + /// + /// 是否是重新连接 + /// + public bool IsReconnection { get; set; } + + /// + /// 连接时间 + /// + public DateTime ConnectTime { get; set; } = DateTime.Now; + } +} diff --git a/MoviconHub.App/Models/WebSocketData.cs b/MoviconHub.App/Models/WebSocketData.cs new file mode 100644 index 0000000..dedc88f --- /dev/null +++ b/MoviconHub.App/Models/WebSocketData.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// WebSocket数据模型 + /// + public class WebSocketData + { + /// + /// 设备编号 + /// + [JsonProperty("DeviceCode")] + public string Device_Code { get; set; } = ""; + + /// + /// 设备名称 + /// + [JsonProperty("DeviceName")] + public string Device_Name { get; set; } = ""; + + /// + /// 设备厂商 + /// + [JsonProperty("DeviceManufacturer")] + public string Device_Manufacturer { get; set; } = ""; + + /// + /// 设备状态, 代码含义自定 + /// + [JsonProperty("Status")] + public string Device_Status { get; set; } = ""; + + /// + /// 设备错误 + /// + [JsonProperty("FaultDetails")] + public FaultDetails FaultDetails { get; set; } = new FaultDetails(); + + /// + /// 部件信息列表 + /// + [JsonProperty("ComponentsInfo")] + public List ListComponentsInfo { get; set; } + + /// + /// 测试数据 + /// + [JsonProperty("TestData")] + public TestData TestData { get; set; } = new TestData(); + + + + } +} diff --git a/MoviconHub.App/Models/WebSocketErrorEventArgs.cs b/MoviconHub.App/Models/WebSocketErrorEventArgs.cs new file mode 100644 index 0000000..8b81aee --- /dev/null +++ b/MoviconHub.App/Models/WebSocketErrorEventArgs.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// WebSocket错误事件参数 + /// + public class WebSocketErrorEventArgs : EventArgs + { + /// + /// 错误信息 + /// + public string ErrorMessage { get; set; } + + /// + /// 异常对象 + /// + public Exception Exception { get; set; } + + /// + /// 错误时间 + /// + public DateTime ErrorTime { get; set; } = DateTime.Now; + } +} diff --git a/MoviconHub.App/Models/WebSocketMessage.cs b/MoviconHub.App/Models/WebSocketMessage.cs new file mode 100644 index 0000000..bac7275 --- /dev/null +++ b/MoviconHub.App/Models/WebSocketMessage.cs @@ -0,0 +1,107 @@ +using Newtonsoft.Json; +using System; + +namespace MoviconHub.App.Models +{ + /// + /// WebSocket消息模型 + /// + public class WebSocketMessage + { + /// + /// 状态码, 200表示成功 + /// + [JsonProperty("status")] + public string Status { get; set; } = "200"; + + /// + /// 状态信息 + /// + [JsonProperty("msg")] + public string Msg { get; set; } = "200"; + + /// + /// 消息数据 + /// + [JsonProperty("data")] + public object Data { get; set; } + + /// + /// 快速创建设备状态消息 + /// + public static WebSocketMessage CreateDeviceData(object DeviceData) + { + return new WebSocketMessage + { + Msg = "success", + Status = "200", + Data = DeviceData + }; + } + + /// + /// 快速创建设备状态消息 + /// + public static WebSocketMessage CreateDeviceStatusMessage(string deviceCode, object statusData) + { + return new WebSocketMessage + { + Msg = "success", + Status = "200", + Data = statusData + }; + } + + /// + /// 快速创建测试数据消息 + /// + public static WebSocketMessage CreateTestDataMessage(string deviceCode, object testData) + { + return new WebSocketMessage + { + Msg = "success", + Status = "200", + Data = testData + }; + } + + /// + /// 快速创建故障信息消息 + /// + public static WebSocketMessage CreateFaultMessage(string deviceCode, object faultData) + { + return new WebSocketMessage + { + Msg = "success", + Status = "200", + Data = faultData + }; + } + + /// + /// 创建心跳消息 + /// + public static WebSocketMessage CreateHeartbeat(string deviceCode) + { + return new WebSocketMessage + { + Msg = "success", + Status = "200", + Data = new { status = "online" } + }; + } + + /// + /// 创建认证消息 + /// + public static WebSocketMessage CreateAuthMessage(string deviceCode, string secretKey) + { + return new WebSocketMessage + { + Msg = "success", + Status = "200", + Data = new { code = deviceCode, key = secretKey } + }; + } + } +} diff --git a/MoviconHub.App/Models/WebSocketMessageEventArgs.cs b/MoviconHub.App/Models/WebSocketMessageEventArgs.cs new file mode 100644 index 0000000..a699464 --- /dev/null +++ b/MoviconHub.App/Models/WebSocketMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Models +{ + /// + /// WebSocket消息事件参数 + /// + public class WebSocketMessageEventArgs : EventArgs + { + /// + /// 解析后的消息对象 + /// + public WebSocketMessage Message { get; set; } + + /// + /// 原始消息内容 + /// + public string RawMessage { get; set; } + } +} diff --git a/MoviconHub.App/Movicon.ico b/MoviconHub.App/Movicon.ico new file mode 100644 index 0000000..f70cd47 Binary files /dev/null and b/MoviconHub.App/Movicon.ico differ diff --git a/MoviconHub.App/MoviconHub.App.csproj b/MoviconHub.App/MoviconHub.App.csproj new file mode 100644 index 0000000..0506e40 --- /dev/null +++ b/MoviconHub.App/MoviconHub.App.csproj @@ -0,0 +1,192 @@ + + + + + Debug + AnyCPU + {8888B1D2-AAD3-4BD5-B934-20C0E6E84DD9} + WinExe + MoviconHub.App + MoviconHub.App + v4.8.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + Movicon.ico + + + MoviconHub.App.Program + + + + ..\packages\FreeSql.3.5.202\lib\net451\FreeSql.dll + + + ..\packages\FreeSql.Provider.SqlServer.3.5.202\lib\net451\FreeSql.Provider.SqlServer.dll + + + ..\packages\HslCommunication.12.3.0\lib\net451\HslCommunication.dll + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll + + + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + + + ..\packages\NLog.5.4.0\lib\net46\NLog.dll + + + ..\packages\ReaLTaiizor.3.8.1.2\lib\net481\ReaLTaiizor.dll + + + ..\packages\RestSharp.112.1.0\lib\net48\RestSharp.dll + + + + ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll + + + + + ..\packages\System.Data.SqlClient.4.8.6\lib\net461\System.Data.SqlClient.dll + + + + + ..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Text.Encodings.Web.8.0.0\lib\net462\System.Text.Encodings.Web.dll + + + ..\packages\System.Text.Json.8.0.4\lib\net462\System.Text.Json.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + Form + + + frmMain.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + frmMain.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + PreserveNewest + + + Designer + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + PreserveNewest + + + + + + + + + + \ No newline at end of file diff --git a/MoviconHub.App/NLog.config b/MoviconHub.App/NLog.config new file mode 100644 index 0000000..da3c96d --- /dev/null +++ b/MoviconHub.App/NLog.config @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MoviconHub.App/NLog.xsd b/MoviconHub.App/NLog.xsd new file mode 100644 index 0000000..d167472 --- /dev/null +++ b/MoviconHub.App/NLog.xsd @@ -0,0 +1,3728 @@ + + + + + + + + + + + + + + + Watch config file for changes and reload automatically. + + + + + Print internal NLog messages to the console. Default value is: false + + + + + Print internal NLog messages to the console error output. Default value is: false + + + + + Write internal NLog messages to the specified file. + + + + + Log level threshold for internal log messages. Default value is: Info. + + + + + Global log level threshold for application log messages. Messages below this level won't be logged. + + + + + Throw an exception when there is an internal error. Default value is: false. Not recommend to set to true in production! + + + + + Throw an exception when there is a configuration error. If not set, determined by throwExceptions. + + + + + Gets or sets a value indicating whether Variables should be kept on configuration reload. Default value is: false. + + + + + Write internal NLog messages to the System.Diagnostics.Trace. Default value is: false. + + + + + Write timestamps for internal NLog messages. Default value is: true. + + + + + Use InvariantCulture as default culture instead of CurrentCulture. Default value is: false. + + + + + Perform message template parsing and formatting of LogEvent messages (true = Always, false = Never, empty = Auto Detect). Default value is: empty. + + + + + + + + + + + + + + Make all targets within this section asynchronous (creates additional threads but the calling thread isn't blocked by any target writes). + + + + + + + + + + + + + + + + + Prefix for targets/layout renderers/filters/conditions loaded from this assembly. + + + + + Load NLog extensions from the specified file (*.dll) + + + + + Load NLog extensions from the specified assembly. Assembly name should be fully qualified. + + + + + + + + + + Filter on the name of the logger. May include wildcard characters ('*' or '?'). + + + + + Comma separated list of levels that this rule matches. + + + + + Minimum level that this rule matches. + + + + + Maximum level that this rule matches. + + + + + Level that this rule matches. + + + + + Comma separated list of target names. + + + + + Ignore further rules if this one matches. + + + + + Enable this rule. Note: disabled rules aren't available from the API. + + + + + Rule identifier to allow rule lookup with Configuration.FindRuleByName and Configuration.RemoveRuleByName. + + + + + + + + + + + + + + + Default action if none of the filters match. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the file to be included. You could use * wildcard. The name is relative to the name of the current config file. + + + + + Ignore any errors in the include file. + + + + + + + + Variable value. Note, the 'value' attribute has precedence over this one. + + + + + + Variable name. + + + + + Variable value. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events that should be processed in a batch by the lazy writer thread. + + + + + Whether to use the locking queue, instead of a lock-free concurrent queue The locking queue is less concurrent when many logger threads, but reduces memory allocation + + + + + Limit of full s to write before yielding into Performance is better when writing many small batches, than writing a single large batch + + + + + Action to be taken when the lazy writer thread request queue count exceeds the set limit. + + + + + Limit on the number of requests in the lazy writer thread request queue. + + + + + Time in milliseconds to sleep between batches. (1 or less means trigger on new activity) + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + Delay the flush until the LogEvent has been confirmed as written + + + + + Condition expression. Log events who meet this condition will cause a flush on the wrapped target. + + + + + Only flush when LogEvent matches condition. Ignore explicit-flush, config-reload-flush and shutdown-flush + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Number of log events to be buffered. + + + + + Timeout (in milliseconds) after which the contents of buffer will be flushed if there's no write in the specified period of time. Use -1 to disable timed flushes. + + + + + Action to take if the buffer overflows. + + + + + Indicates whether to use sliding timeout. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Maximum queue size. + + + + + Maximum current connections. 0 = no maximum. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + NDLC item separator. + + + + + NDC item separator. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include dictionary contents. + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Viewer parameter name. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-check if the console is available. - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + Enables output using ANSI Color Codes + + + + + The encoding for writing messages to the . + + + + + Indicates whether the error stream (stderr) should be used instead of the output stream (stdout). + + + + + Indicates whether to auto-flush after + + + + + Indicates whether to auto-check if the console has been redirected to file - Disables coloring logic when System.Console.IsOutputRedirected = true + + + + + Indicates whether to use default row highlighting rules. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Condition that must be met in order to set the specified foreground and background color. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + Compile the ? This can improve the performance, but at the costs of more memory usage. If false, the Regex Cache is used. + + + + + Condition that must be met before scanning the row for highlight of words + + + + + Indicates whether to ignore case when comparing texts. + + + + + Regular expression to be matched. You must specify either text or regex. + + + + + Text to be matched. You must specify either text or regex. + + + + + Indicates whether to match whole words only. + + + + + Background color. + + + + + Foreground color. + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether to auto-flush after + + + + + Indicates whether to auto-check if the console is available - Disables console writing if Environment.UserInteractive = False (Windows Service) - Disables console writing if Console Standard Input is not available (Non-Console-App) + + + + + The encoding for writing messages to the . + + + + + Indicates whether to send the log messages to the standard error instead of the standard output. + + + + + Whether to enable batch writing using char[]-buffers, instead of using + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Obsolete - value will be ignored! The logging code always runs outside of transaction. Gets or sets a value indicating whether to use database transactions. Some data providers require this. + + + + + Indicates whether to keep the database connection open between the log events. + + + + + Name of the database provider. + + + + + Database password. If the ConnectionString is not provided this value will be used to construct the "Password=" part of the connection string. + + + + + Database host name. If the ConnectionString is not provided this value will be used to construct the "Server=" part of the connection string. + + + + + Database user name. If the ConnectionString is not provided this value will be used to construct the "User ID=" part of the connection string. + + + + + Name of the connection string (as specified in <connectionStrings> configuration section. + + + + + Connection string. When provided, it overrides the values specified in DBHost, DBUserName, DBPassword, DBDatabase. + + + + + Database name. If the ConnectionString is not provided this value will be used to construct the "Database=" part of the connection string. + + + + + Connection string using for installation and uninstallation. If not provided, regular ConnectionString is being used. + + + + + Configures isolated transaction batch writing. If supported by the database, then it will improve insert performance. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Text of the SQL command to be run on each log level. + + + + + Type of the SQL command to be run on each log level. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Convert format of the property value + + + + + Culture used for parsing property string-value for type-conversion + + + + + Value to assign on the object-property + + + + + Name for the object-property + + + + + Type of the object-property + + + + + + + + + + + + + + Type of the command. + + + + + Connection string to run the command against. If not provided, connection string from the target is used. + + + + + Indicates whether to ignore failures. + + + + + Command text. + + + + + + + + + + + + + + + + + + + Database parameter name. + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Database parameter DbType. + + + + + Database parameter size. + + + + + Database parameter precision. + + + + + Database parameter scale. + + + + + Type of the parameter. + + + + + Whether empty value should translate into DbNull. Requires database column to allow NULL values. + + + + + Convert format of the database parameter value. + + + + + Culture used for parsing parameter string-value for type-conversion + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Layout that renders event Category. + + + + + Optional entry type. When not set, or when not convertible to then determined by + + + + + Layout that renders event ID. + + + + + Name of the Event Log to write to. This can be System, Application or any user-defined name. + + + + + Name of the machine on which Event Log service is running. + + + + + Maximum Event log size in kilobytes. + + + + + Message length limit to write to the Event Log. + + + + + Value to be used as the event Source. + + + + + Action to take if the message is larger than the option. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether to return to the first target after any successful write. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + File encoding. + + + + + Line ending mode. + + + + + Maximum days of archive files that should be kept. + + + + + Indicates whether to compress archive files into the zip archive format. + + + + + Way file archives are numbered. + + + + + Name of the file to be used for an archive. + + + + + Is the an absolute or relative path? + + + + + Indicates whether to automatically archive log files every time the specified time passes. + + + + + Size in bytes above which log files will be automatically archived. Warning: combining this with isn't supported. We cannot create multiple archive files, if they should have the same name. Choose: + + + + + Maximum number of archive files that should be kept. + + + + + Indicates whether the footer should be written only when the file is archived. + + + + + Maximum number of log file names that should be stored as existing. + + + + + Indicates whether to delete old log file on startup. + + + + + File attributes (Windows only). + + + + + Indicates whether to create directories if they do not exist. + + + + + Cleanup invalid values in a filename, e.g. slashes in a filename. If set to true, this can impact the performance of massive writes. If set to false, nothing gets written when the filename is wrong. + + + + + Value of the file size threshold to archive old log file on startup. + + + + + Indicates whether to archive old log file on startup. + + + + + Value specifying the date format to use when archiving files. + + + + + Indicates whether to enable log file(s) to be deleted. + + + + + Indicates whether to write BOM (byte order mark) in created files + + + + + Indicates whether to replace file contents on each write instead of appending log message at the end. + + + + + Indicates whether file creation calls should be synchronized by a system global mutex. + + + + + Gets or set a value indicating whether a managed file stream is forced, instead of using the native implementation. + + + + + Is the an absolute or relative path? + + + + + Name of the file to write to. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Indicates whether concurrent writes to the log file by multiple processes on different network hosts. + + + + + Maximum number of seconds that files are kept open. If this number is negative the files are not automatically closed after a period of inactivity. + + + + + Number of files to be kept open. Setting this to a higher value may improve performance in a situation where a single File target is writing to many files (such as splitting by level or by logger). + + + + + Indicates whether to keep log file open instead of opening and closing it on each logging event. + + + + + Whether or not this target should just discard all data that its asked to write. Mostly used for when testing NLog Stack except final write + + + + + Indicates whether concurrent writes to the log file by multiple processes on the same host. + + + + + Number of times the write is appended on the file before NLog discards the log message. + + + + + Delay in milliseconds to wait before attempting to write to the file again. + + + + + Log file buffer size in bytes. + + + + + Maximum number of seconds before open files are flushed. If this number is negative or zero the files are not flushed by timer. + + + + + Indicates whether to automatically flush the file buffers after each log message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Condition expression. Log events who meet this condition will be forwarded to the wrapped target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Windows domain name to change context to. + + + + + Required impersonation level. + + + + + Type of the logon provider. + + + + + Logon Type. + + + + + User account password. + + + + + Indicates whether to revert to the credentials of the process instead of impersonating another user. + + + + + Username to change context to. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Interval in which messages will be written up to the number of messages. + + + + + Maximum allowed number of messages written per . + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Endpoint address. + + + + + Name of the endpoint configuration in WCF configuration file. + + + + + Indicates whether to use a WCF service contract that is one way (fire and forget) or two way (request-reply) + + + + + Client ID. + + + + + Indicates whether to include per-event properties in the payload sent to the server. + + + + + Indicates whether to use binary message encoding. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + Layout that should be use to calculate the value for the parameter. + + + + + Name of the parameter. + + + + + Type of the parameter. + + + + + Type of the parameter. Obsolete alias for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Text to be rendered. + + + + + Header. + + + + + Footer. + + + + + Indicates whether NewLine characters in the body should be replaced with tags. + + + + + Priority used for sending mails. + + + + + Encoding to be used for sending e-mail. + + + + + BCC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + CC email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Indicates whether to add new lines between log entries. + + + + + Indicates whether to send message as HTML instead of plain text. + + + + + Sender's email address (e.g. joe@domain.com). + + + + + Mail message body (repeated for each log message send in one mail). + + + + + Mail subject. + + + + + Recipients' email addresses separated by semicolons (e.g. john@domain.com;jane@domain.com). + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Indicates the SMTP client timeout. + + + + + SMTP Server to be used for sending. + + + + + SMTP Authentication mode. + + + + + Username used to connect to SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Password used to authenticate against SMTP server (used when SmtpAuthentication is set to "basic"). + + + + + Indicates whether SSL (secure sockets layer) should be used when communicating with SMTP server. + + + + + Port number that SMTP Server is listening on. + + + + + Indicates whether the default Settings from System.Net.MailSettings should be used. + + + + + Folder where applications save mail messages to be processed by the local SMTP server. + + + + + Specifies how outgoing email messages will be handled. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Max number of items to have in memory + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Class name. + + + + + Method name. The method must be public and static. Use the AssemblyQualifiedName , https://msdn.microsoft.com/en-us/library/system.type.assemblyqualifiedname(v=vs.110).aspx e.g. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Encoding to be used. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Indicates whether to keep connection open whenever possible. + + + + + Maximum current connections. 0 = no maximum. + + + + + Maximum queue size. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Encoding to be used. + + + + + Instance of that is used to format log messages. + + + + + End of line value if a newline is appended at the end of log message . + + + + + Maximum message size in bytes. + + + + + Indicates whether to append newline at the end of log message. + + + + + Get or set the SSL/TLS protocols. Default no SSL/TLS is used. Currently only implemented for TCP. + + + + + Network address. + + + + + Size of the connection cache (number of connections which are kept alive). + + + + + The number of seconds a connection will remain idle before the first keep-alive probe is sent + + + + + Maximum queue size. + + + + + Maximum current connections. 0 = no maximum. + + + + + Action that should be taken if the will be more connections than . + + + + + Action that should be taken if the message is larger than maxMessageSize. + + + + + Indicates whether to keep connection open whenever possible. + + + + + NDLC item separator. + + + + + NDC item separator. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + Indicates whether to include NLog-specific extensions to log4j schema. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include stack contents. + + + + + Indicates whether to include dictionary contents. + + + + + Indicates whether to include dictionary contents. + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + AppInfo field. By default it's the friendly name of the current AppDomain. + + + + + Renderer for log4j:event logger-xml-attribute (Default ${logger}) + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Indicates whether to perform layout calculation. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Indicates whether performance counter should be automatically created. + + + + + Name of the performance counter category. + + + + + Counter help text. + + + + + Name of the performance counter. + + + + + Performance counter type. + + + + + The value by which to increment the counter. + + + + + Performance counter instance name. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Default filter to be applied when no specific rule matches. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + Condition to be tested. + + + + + Resulting filter to be applied when the condition matches. + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Number of times to repeat each log message. + + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Number of retries that should be attempted on the wrapped target in case of a failure. + + + + + Time to wait between retries in milliseconds. + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Layout used to format log messages. + + + + + Forward to (Instead of ) + + + + + Always use independent of + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Name of the target. + + + + + Target supports reuse of internal buffers, and doesn't have to constantly allocate new buffers Required for legacy NLog-targets, that expects buffers to remain stable after Write-method exit + + + + + Should we include the BOM (Byte-order-mark) for UTF? Influences the property. This will only work for UTF-8. + + + + + Web service method name. Only used with Soap. + + + + + Web service namespace. Only used with Soap. + + + + + Protocol to be used when calling web service. + + + + + Custom proxy address, include port separated by a colon + + + + + Encoding. + + + + + Web service URL. + + + + + Value of the User-agent HTTP header. + + + + + Value whether escaping be done according to the old NLog style (Very non-standard) + + + + + Value whether escaping be done according to Rfc3986 (Supports Internationalized Resource Identifiers - IRIs) + + + + + Indicates whether to pre-authenticate the HttpWebRequest (Requires 'Authorization' in parameters) + + + + + Name of the root XML element, if POST of XML document chosen. If so, this property must not be null. (see and ). + + + + + (optional) root namespace of the XML document, if POST of XML document chosen. (see and ). + + + + + Proxy configuration when calling web service + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + Custom column delimiter value (valid when ColumnDelimiter is set to 'Custom'). + + + + + Column delimiter. + + + + + Quote Character. + + + + + Quoting mode. + + + + + Indicates whether CVS should include header. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Layout of the column. + + + + + Name of the column. + + + + + Override of Quoting mode + + + + + + + + + + + + + + + + + + + + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Option to render the empty object value {} + + + + + Option to suppress the extra spaces in the output json + + + + + Option to exclude null/empty properties from the log event (as JSON) + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as JSON) + + + + + Option to include all properties from the log event (as JSON) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the JSON serializer follow object references before backing off + + + + + + + + + + + + + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + Determines whether or not this attribute will be Json encoded. + + + + + Should forward slashes be escaped? If true, / will be converted to \/ + + + + + Indicates whether to escape non-ascii characters + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + + Footer layout. + + + + + Header layout. + + + + + Body layout (can be repeated multiple times). + + + + + + + + + + + + + + + + + + + + + + + Option to include all properties from the log events + + + + + Indicates whether to include call site (class and method name) in the information sent over the network. + + + + + Option to include all properties from the log events + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include contents of the stack. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include source info (file name and line number) in the information sent over the network. + + + + + + + + + + + + + + Layout text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Option to include all properties from the log event (as XML) + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use for rendering IList-collections items + + + + + How far should the XML serializer follow object references before backing off + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Option to include all properties from the log event (as XML) + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + List of property names to exclude when is true + + + + + XML element name to use when rendering properties + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Auto indent and create new lines + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Value inside the root XML element + + + + + Name of the root XML element + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Name of the element + + + + + Value inside the element + + + + + Whether a ElementValue with empty value should be included in the output + + + + + Auto indent and create new lines + + + + + List of property names to exclude when is true + + + + + Option to include all properties from the log event (as XML) + + + + + Option to include all properties from the log event (as XML) + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + Indicates whether to include contents of the dictionary. + + + + + How far should the XML serializer follow object references before backing off + + + + + XML element name to use for rendering IList-collections items + + + + + XML attribute name to use when rendering property-key When null (or empty) then key-attribute is not included + + + + + XML element name to use when rendering properties + + + + + XML attribute name to use when rendering property-value When null (or empty) then value-attribute is not included and value is formatted as XML-element-value + + + + + + + + + + + + + Layout that will be rendered as the attribute's value. + + + + + Name of the attribute. + + + + + Determines whether or not this attribute will be Xml encoded. + + + + + Whether an attribute with empty value should be included in the output + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Condition expression. + + + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + Substring to be matched. + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + String to compare the layout to. + + + + + Indicates whether to ignore case when comparing strings. + + + + + Layout to be used to filter log messages. + + + + + + + + + + + + + + + + + + + + + + + + Action to be taken when filter matches. + + + + + Default number of unique filter values to expect, will automatically increase if needed + + + + + Applies the configured action to the initial logevent that starts the timeout period. Used to configure that it should ignore all events until timeout. + + + + + Layout to be used to filter log messages. + + + + + Max number of unique filter values to expect simultaneously + + + + + Max length of filter values, will truncate if above limit + + + + + How long before a filter expires, and logging is accepted again + + + + + Default buffer size for the internal buffers + + + + + Reuse internal buffers, and doesn't have to constantly allocate new buffers + + + + + Append FilterCount to the when an event is no longer filtered + + + + + Insert FilterCount value into when an event is no longer filtered + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MoviconHub.App/Program.cs b/MoviconHub.App/Program.cs new file mode 100644 index 0000000..5e1e1e6 --- /dev/null +++ b/MoviconHub.App/Program.cs @@ -0,0 +1,66 @@ +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace MoviconHub.App +{ + static class Program + { + //日志的实例化 + private static Logger logger = LogManager.GetCurrentClassLogger(); + + public static bool IsActive { get; private set; } + + public static DateTime StartTime = DateTime.Now; + + public static string SystemName = ""; + + /// + /// 应用程序的主入口点。 + /// + [STAThread] + static void Main() + { + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + bool isAppRunning = false; + Mutex mutex = new Mutex(true, System.Diagnostics.Process.GetCurrentProcess().ProcessName, out isAppRunning); + if (!isAppRunning) + { + MessageBox.Show("程序已运行,不能再次打开!"); + Environment.Exit(1); + } + + // 授权 12.3 + //V12.3.0: d8868ab9 - 4494 - 4056 - 98c6 - b669e2434e25 + //V12.2.0: fe49cdb6 - b388 - 4c05 - 9b66 - 0e3f1ad3627f + //V12.1.3: 2fb771c7 - 4c29 - 445d - bddd - a7b8a75de397 + //V12.1.2: b23b00e2 - ce46 - 4bfc - b33c - 71c47c2c11c2 + //V12.1.1: 95057912 - 579c - 42d1 - ad31 - eb598f73706f + //V11.8.2: b980977c - 3323 - 4876 - b633 - c0bef93d75c1 + if (!HslCommunication.Authorization.SetAuthorizationCode("d8868ab9-4494-4056-98c6-b669e2434e25")) + { + //active failed + MessageBox.Show("授权失败!当前程序只能使用8小时!"); + // return; + } + else + { + IsActive = true; + } + + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new frmMain()); + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Exception ex = e.ExceptionObject as Exception; + logger.Error(String.Format("ErrSource : {0} ErrMsg : {1}", ex.StackTrace.ToString(), ex.Message.ToString())); + } + } +} diff --git a/MoviconHub.App/Properties/AssemblyInfo.cs b/MoviconHub.App/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a53f052 --- /dev/null +++ b/MoviconHub.App/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("MoviconHub.App")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MoviconHub.App")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 会使此程序集中的类型 +//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型 +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("8888b1d2-aad3-4bd5-b934-20c0e6e84dd9")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/MoviconHub.App/Properties/Resources.Designer.cs b/MoviconHub.App/Properties/Resources.Designer.cs new file mode 100644 index 0000000..dc244ed --- /dev/null +++ b/MoviconHub.App/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本: 4.0.30319.42000 +// +// 对此文件的更改可能导致不正确的行为,如果 +// 重新生成代码,则所做更改将丢失。 +// +//------------------------------------------------------------------------------ + +namespace MoviconHub.App.Properties +{ + + + /// + /// 强类型资源类,用于查找本地化字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// 返回此类使用的缓存 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MoviconHub.App.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 重写当前线程的 CurrentUICulture 属性,对 + /// 使用此强类型资源类的所有资源查找执行重写。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/MoviconHub.App/Properties/Resources.resx b/MoviconHub.App/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/MoviconHub.App/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/MoviconHub.App/Properties/Settings.Designer.cs b/MoviconHub.App/Properties/Settings.Designer.cs new file mode 100644 index 0000000..31c91d5 --- /dev/null +++ b/MoviconHub.App/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace MoviconHub.App.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/MoviconHub.App/Properties/Settings.settings b/MoviconHub.App/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/MoviconHub.App/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/MoviconHub.App/Services/ApiHelper.cs b/MoviconHub.App/Services/ApiHelper.cs new file mode 100644 index 0000000..5dfdf1c --- /dev/null +++ b/MoviconHub.App/Services/ApiHelper.cs @@ -0,0 +1,77 @@ +using MoviconHub.App.Com; +using MoviconHub.App.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Services +{ + public static class ApiHelper + { + private static ApiService _apiService; + private static ApiConfig _apiConfig; + + /// + /// 初始化API帮助类 + /// + public static void Initialize() + { + //_apiConfig = ApiConfig.LoadConfig(); + //string baseUrl = $"http://{_apiConfig.ServerAddress}:{_apiConfig.ServerPort}/"; + string baseUrl = $"http://172.16.3.203:8000"; + _apiService = new ApiService(baseUrl); + } + + /// + /// 获取部件信息 + /// + /// 二维码零件ID + /// 设备编码 + /// 部件信息 + public static async Task GetPartInfoAsync(string partQrCodeId, string deviceCode) + { + if (_apiService == null) + { + Initialize(); + } + //当前不需要设备条码 + return await _apiService.GetPartInfoAsync(partQrCodeId, deviceCode); + } + + /// + /// 获取测试数据 + /// + /// 二维码零件ID + /// 设备编码 + /// 测试数据 + public static async Task GetTestDataAsync(string partQrCodeId, string deviceCode) + { + if (_apiService == null) + { + Initialize(); + } + return await _apiService.GetTestDataAsync(partQrCodeId, deviceCode); + } + + /// + /// 更新服务器配置 + /// + /// 服务器地址 + /// 端口号 + public static void UpdateServerConfig(string serverAddress, int port) + { + if (_apiService == null || _apiConfig == null) + { + Initialize(); + } + + _apiConfig.ServerAddress = serverAddress; + _apiConfig.ServerPort = port; + _apiConfig.SaveConfig(); + + _apiService.ConfigureServerAddress(serverAddress, port); + } + } +} diff --git a/MoviconHub.App/Services/ApiService.cs b/MoviconHub.App/Services/ApiService.cs new file mode 100644 index 0000000..6c593d4 --- /dev/null +++ b/MoviconHub.App/Services/ApiService.cs @@ -0,0 +1,155 @@ +using MoviconHub.App.Models; +using Newtonsoft.Json; +using NLog; +using RestSharp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Services +{ + /// + /// Api 服务类 + /// + public class ApiService + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly string _baseUrl; + private readonly RestClient _client; + + /// + /// 实例化函数 + /// + /// + public ApiService(string baseUrl) + { + _baseUrl = baseUrl; + _client = new RestClient(baseUrl); + } + + private string GetFullRequestUrl(RestRequest request) + { + var resource = request.Resource; + var queryParams = string.Join("&", request.Parameters + .Where(p => p.Type == ParameterType.QueryString) + .Select(p => $"{p.Name}={Uri.EscapeDataString(p.Value?.ToString() ?? "")}")); + + return $"{_baseUrl.TrimEnd('/')}/{resource.TrimStart('/')}{(string.IsNullOrEmpty(queryParams) ? "" : "?" + queryParams)}"; + } + + /// + /// 获取部件信息 + /// + /// 二维码零件ID + /// 设备编码 + /// 部件信息 + public async Task GetPartInfoAsync(string partQrCodeId, string deviceCode) + { + try + { + var request = new RestRequest("/mes/order/iotPartInfo/getPartInfo", Method.Get); + request.AddParameter("PartQRCodeId", partQrCodeId); + //request.AddParameter("DeviceCode", deviceCode); + + var response = await _client.ExecuteAsync(request); + + if (response.IsSuccessful) + { + Logger.Info($"{response.Content}"); + var Data = JsonConvert.DeserializeObject(response.Content); + Logger.Info($"{Data.ToString()}"); + return JsonConvert.DeserializeObject(response.Content); + + + } + else + { + Logger.Error($"获取部件信息失败:{response.ErrorMessage}, 状态码:{response.StatusCode}"); + return new PartInfoResponse + { + Status = (int)response.StatusCode, + Message = response.ErrorMessage, + Data = null + }; + } + } + catch (Exception ex) + { + Logger.Error(ex, "获取部件信息时发生异常"); + return new PartInfoResponse + { + Status = 500, + Message = $"发生异常:{ex.Message}", + Data = null + }; + } + } + + /// + /// 获取测试数据 + /// + /// 二维码零件ID + /// 设备编码 + /// 测试数据 + public async Task GetTestDataAsync(string partQrCodeId, string deviceCode) + { + try + { + var request = new RestRequest("getTestdata", Method.Get); + request.AddParameter("PartQRCodeId", partQrCodeId); + request.AddParameter("DeviceCode", deviceCode); + + var response = await _client.ExecuteAsync(request); + + if (response.IsSuccessful) + { + return JsonConvert.DeserializeObject(response.Content); + } + else + { + Logger.Error($"获取测试数据失败:{response.ErrorMessage}, 状态码:{response.StatusCode}"); + return new TestDataResponse + { + Status = (int)response.StatusCode, + Message = response.ErrorMessage, + Data = null + }; + } + } + catch (Exception ex) + { + Logger.Error(ex, "获取测试数据时发生异常"); + return new TestDataResponse + { + Status = 500, + Message = $"发生异常:{ex.Message}", + Data = null + }; + } + } + + /// + /// 配置服务器地址和端口 + /// + /// 服务器地址 + /// 端口号 + public void ConfigureServerAddress(string serverAddress, int port) + { + string newBaseUrl = $"http://{serverAddress}:{port}/"; + // 创建新的RestClient实例 + var newClient = new RestClient(newBaseUrl); + + // 使用反射替换私有字段,因为RestClient实例不能直接修改BaseUrl + var clientType = _client.GetType(); + var baseUrlField = clientType.GetField("_baseUrl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (baseUrlField != null) + { + baseUrlField.SetValue(_client, newBaseUrl); + } + + Logger.Info($"服务器地址已更新为:{newBaseUrl}"); + } + } +} diff --git a/MoviconHub.App/Services/DBServices.cs b/MoviconHub.App/Services/DBServices.cs new file mode 100644 index 0000000..f661309 --- /dev/null +++ b/MoviconHub.App/Services/DBServices.cs @@ -0,0 +1,510 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MoviconHub.App.Models; +using NLog; + +namespace MoviconHub.App.Services +{ + /// + /// Db Server + /// + public class DBServices : IDisposable + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private Timer _dataPollingTimer; + private readonly object _lockObject = new object(); + private bool _isRunning = false; + private int _pollingInterval = 1000; // 默认轮询间隔1秒 + private WebSocketData _realtimeData; + private CancellationTokenSource _cancellationTokenSource; + + /// + /// 实时数据对象,用于缓存从RTVar表中读取的数据 + /// + public WebSocketData RealtimeData + { + get { return _realtimeData; } + } + + public SglModel SglModel { get; } + + /// + /// 实时数据更新事件 + /// + public event EventHandler DataUpdated; + + /// + /// 初始化数据库服务 + /// + public DBServices(SglModel sglModel) + { + _realtimeData = new WebSocketData(); + + _cancellationTokenSource = new CancellationTokenSource(); + SglModel = sglModel; + } + + /// + /// 开始实时数据轮询 + /// + /// 轮询间隔,单位毫秒 + /// 是否成功启动 + public bool StartPolling(int interval = 2000) + { + if (_isRunning) + return false; + + lock (_lockObject) + { + if (_isRunning) + return false; + + _pollingInterval = interval; + _isRunning = true; + _cancellationTokenSource = new CancellationTokenSource(); + + // 使用Task来轮询数据 + Task.Run(async () => await PollDataAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token); + + Logger.Info($"实时数据轮询已启动,间隔: {_pollingInterval}ms"); + return true; + } + } + + /// + /// 停止实时数据轮询 + /// + public void StopPolling() + { + if (!_isRunning) + return; + + lock (_lockObject) + { + if (!_isRunning) + return; + + _cancellationTokenSource.Cancel(); + _isRunning = false; + Logger.Info("实时数据轮询已停止"); + } + } + + /// + /// 异步轮询数据任务 + /// + /// 取消标记 + private async Task PollDataAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + // 读取RTVar表数据 + await ReadRTVarDataAsync(); + + // 等待指定的轮询间隔 + await Task.Delay(_pollingInterval, cancellationToken); + } + catch (TaskCanceledException) + { + // 任务被取消,正常退出 + break; + } + catch (Exception ex) + { + Logger.Error(ex, "轮询RTVar数据时发生错误"); + // 发生错误时,等待一段时间后重试 + await Task.Delay(5000, cancellationToken); + } + } + } + + /// + /// 异步读取RTVar表数据 + /// + private async Task ReadRTVarDataAsync() + { + try + { + // 使用FSqlContext封装的FreeSql调用DB数据 + var rtVars = await FSqlContext.FDb.Select().ToListAsync(); + + // 更新实时数据对象 + UpdateWebSocketData(rtVars); + + // 触发数据更新事件 + //DataUpdated?.Invoke(this, _realtimeData); + } + catch (Exception ex) + { + Logger.Error(ex, "读取RTVar数据时发生错误"); + throw; + } + } + + /// + /// 使用RTVar数据更新WebSocketData模型 + /// + /// RTVar数据列表 + private void UpdateWebSocketData(List rtVars) + { + if (rtVars == null || rtVars.Count == 0) + return; + + lock (_lockObject) + { + + // 更新设备状态信息 + var deviceCodeVar = rtVars.FirstOrDefault(v => v.Name == "Device_Code"); + if (deviceCodeVar != null) + _realtimeData.Device_Code = deviceCodeVar.Val; + + var deviceNameVar = rtVars.FirstOrDefault(v => v.Name == "Device_Name"); + if (deviceNameVar != null) + _realtimeData.Device_Name = deviceNameVar.Val; + + var deviceManufacturerVar = rtVars.FirstOrDefault(v => v.Name == "Device_Manufacturer"); + if (deviceManufacturerVar != null) + _realtimeData.Device_Manufacturer = deviceManufacturerVar.Val; + + var statusVar = rtVars.FirstOrDefault(v => v.Name == "Device_Status"); + if (statusVar != null) + _realtimeData.Device_Status = statusVar.Val; + + // 解析故障信息 + UpdateFaultDetails(rtVars); + + // 解析组件信息 + UpdateComponentsInfo(rtVars); + + // 解析测试数据 + UpdateTestData(rtVars); + + //获取条码信息,确定是否需要搜索数据 + SglModel.CodeReady = rtVars.FirstOrDefault(v => v.Name == "part_qrid").Val; + + UpdateRemoteDb(RealtimeData); + + //把最新的数据赋值给WebSocketClient中 + WebSocketClientHelper.CurWebSocketData = RealtimeData; + + //更新数据到远程数据库 + + // 记录日志 + Logger.Debug("实时数据已更新"); + } + } + + /// + /// 更新远程数据库 + /// + /// + private void UpdateRemoteDb(WebSocketData webSocketData) + { + // 获取组件信息 + var component = webSocketData.ListComponentsInfo?.FirstOrDefault(); + + // 创建CurRunClearState对象并映射数据 + var curRunClearState = new CurRunClearState() + { + //只有一个,更新数据 + Id = 1, + + // 基本信息 + DeviceCode = webSocketData.Device_Code, + DeviceName = webSocketData.Device_Name, + + // 组件信息 + part_qrid = component?.part_qrid, + part_num = component?.part_num, + part_position = component?.part_position, + component_name = component?.part_name, + vehicle_model = component?.part_Vehicle_model, + locomotive_number = component?.part_locomotive_number, + repair_process = component?.part_repair_process, + + program_process = webSocketData.TestData.Test_FrameworkProgramProcess, + + // 程序进程 + Test_FrameworkProgramProcess = webSocketData.TestData.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage = webSocketData.TestData.Test_FrameworkProgramProcessPercentage, + + // 设备状态 + Test_PartsEquipmentStatus = webSocketData.TestData.Test_PartsEquipmentStatus, + + // 清洗时长和用量 + Test_FrameworkPerModelCleaningDuration = webSocketData.TestData.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkPerModelCleaningAgentUsage = webSocketData.TestData.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = webSocketData.TestData.Test_FrameworkPerModelWaterUsage, + + // 水箱和清洗剂罐信息 + WaterTank_Temp = webSocketData.TestData.Test_WaterTankTemperature, + AgentTank_Temp = webSocketData.TestData.Test_CleaningAgentTankTemperature, + WaterTank_Level = webSocketData.TestData.Test_WaterTankLevel, + AgentTank_Level = webSocketData.TestData.Test_CleaningAgentTankLevel, + + // 浸泡池温度 + SoakingTank1_Temp = webSocketData.TestData.Test_SoakingTank1Temperature, + SoakingTank2_Temp = webSocketData.TestData.Test_SoakingTank2Temperature, + + // 运行模式 + Test_WaterTankHeat = webSocketData.TestData.Test_WaterTankHeat, + Test_WaterTankAdd = webSocketData.TestData.Test_WaterTankAdd, + Test_CleaningAgentTankHeat = webSocketData.TestData.Test_CleaningAgentTankHeat, + Test_CleaningAgentTankAdd = webSocketData.TestData.Test_CleaningAgentTankAdd, + + // 监控信息 + Test_ElectricSurveillance = webSocketData.TestData.Test_ElectricSurveillance, + Test_SteamSurveillance = webSocketData.TestData.Test_SteamSurveillance + }; + + var Data = FRemoteSqlContext.FDb + .InsertOrUpdate() + .SetSource(curRunClearState) + .ExecuteAffrows(); + + if (Data > 0) + { + Logger.Debug("实时数据已更新到远程数据库"); + } + } + + + /// + /// 更新故障信息 + /// + /// RTVar数据列表 + private void UpdateFaultDetails(List rtVars) + { + var hasFault = rtVars.FirstOrDefault(v => v.Name.Contains("Fault_")); + if (hasFault != null && !string.IsNullOrEmpty(hasFault.Val)) + { + if (_realtimeData.FaultDetails == null) + _realtimeData.FaultDetails = new FaultDetails(); + + var faultDevice = rtVars.FirstOrDefault(v => v.Name == "Fault_Code"); + if (faultDevice != null) + _realtimeData.FaultDetails.Fault_Code = faultDevice.Val; + + var faultTime = rtVars.FirstOrDefault(v => v.Name == "Fault_Time"); + if (faultTime != null && DateTime.TryParse(faultTime.Val, out DateTime time)) + _realtimeData.FaultDetails.Fault_Time = time; + + var faultDescription = rtVars.FirstOrDefault(v => v.Name == "Fault_Description"); + if (faultDescription != null) + _realtimeData.FaultDetails.Fault_Description = faultDescription.Val; + } + else + { + // 没有故障时,置空故障信息 + _realtimeData.FaultDetails = null; + } + } + + /// + /// 更新组件信息 + /// + /// RTVar数据列表 + private void UpdateComponentsInfo(List rtVars) + { + var componentsCountVar = rtVars.FirstOrDefault(v => v.Name.Contains("part_")); + if (componentsCountVar != null) + { + if (_realtimeData.ListComponentsInfo == null) _realtimeData.ListComponentsInfo = new List() { new ComponentsInfo() }; + + //else + // _realtimeData.ListComponentsInfo.Clear(); + + var component = _realtimeData.ListComponentsInfo.FirstOrDefault(); + + //var partQRCode = rtVars.FirstOrDefault(v => v.Name == $"part_qrid"); + //if (partQRCode != null) + // component.part_qrid = partQRCode.Val; + + var partNum = rtVars.FirstOrDefault(v => v.Name == $"part_num"); + if (partNum != null) + component.part_num = partNum.Val; + + var partPosition = rtVars.FirstOrDefault(v => v.Name == $"part_position"); + if (partPosition != null) + component.part_position = partPosition.Val; + + var componentName = rtVars.FirstOrDefault(v => v.Name == $"part_name"); + if (componentName != null) + component.part_name = componentName.Val; + + // 车型 + var vehicleModel = rtVars.FirstOrDefault(v => v.Name == $"part_Vehicle_model"); + if (vehicleModel != null) + component.part_Vehicle_model = vehicleModel.Val; + + // 车号 + var locomotiveNumber = rtVars.FirstOrDefault(v => v.Name == $"part_locomotive_number"); + if (locomotiveNumber != null) + component.part_locomotive_number = locomotiveNumber.Val; + + // 修程 + var repairProcess = rtVars.FirstOrDefault(v => v.Name == $"part_repair_process"); + if (repairProcess != null) + component.part_repair_process = repairProcess.Val; + + // Id + var part_qrid = rtVars.FirstOrDefault(v => v.Name == $"part_qrid"); + if (part_qrid != null) + component.part_qrid = part_qrid.Val; + + } + else + { + // 没有组件信息时,初始化空列表 + if (_realtimeData.ListComponentsInfo == null) + _realtimeData.ListComponentsInfo = new List() { new ComponentsInfo() }; + //else + // _realtimeData.ListComponentsInfo.Clear(); + } + } + + ///// + ///// 更新组件信息 + ///// + ///// RTVar数据列表 + //private void UpdateComponentsInfoMulit(List rtVars) + //{ + // var componentsCountVar = rtVars.FirstOrDefault(v => v.Name == "ComponentsCount"); + // if (componentsCountVar != null && int.TryParse(componentsCountVar.Val, out int count) && count > 0) + // { + // if (_realtimeData.ListComponentsInfo == null) + // _realtimeData.ListComponentsInfo = new List(); + // else + // _realtimeData.ListComponentsInfo.Clear(); + + // // 解析多个组件信息 + // for (int i = 0; i < count; i++) + // { + // var component = new ComponentsInfo(); + + // var partQRCode = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_PartQRCode"); + // if (partQRCode != null) + // component.part_qrid = partQRCode.Val; + + // var partNum = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_PartNum"); + // if (partNum != null) + // component.part_num = partNum.Val; + + // var partPosition = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_PartPosition"); + // if (partPosition != null && int.TryParse(partPosition.Val, out int position)) + // component.part_position = position; + + // var componentName = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_ComponentName"); + // if (componentName != null) + // component.component_name = componentName.Val; + + // // 车型 + // var vehicleModel = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_VehicleModel"); + // if (vehicleModel != null) + // component.Vehicle_model = vehicleModel.Val; + + // // 车号 + // var locomotiveNumber = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_LocomotiveNumber"); + // if (locomotiveNumber != null) + // component.locomotive_number = locomotiveNumber.Val; + + // // 修程 + // var repairProcess = rtVars.FirstOrDefault(v => v.Name == $"Component_{i}_RepairProcess"); + // if (repairProcess != null) + // component.repair_process = repairProcess.Val; + + // // 添加到列表 + // _realtimeData.ListComponentsInfo.Add(component); + // } + // } + // else + // { + // // 没有组件信息时,初始化空列表 + // if (_realtimeData.ListComponentsInfo == null) + // _realtimeData.ListComponentsInfo = new List(); + // else + // _realtimeData.ListComponentsInfo.Clear(); + // } + //} + + + /// + /// 更新测试数据 + /// + /// RTVar数据列表 + private void UpdateTestData(List rtVars) + { + var hasTestData = rtVars.FirstOrDefault(v => v.Name.Contains("Test_")); + if (hasTestData != null && !string.IsNullOrEmpty(hasTestData.Val)) + { + if (_realtimeData.TestData == null) + _realtimeData.TestData = new TestData(); + + // 从RTVar中提取测试数据 + var testItems = rtVars.Where(v => v.Name.StartsWith("Test_")).ToList(); + foreach (var item in testItems) + { + // 根据命名规则解析测试数据项 + string itemName = item.Name; + + // 使用反射设置属性 + var property = typeof(TestData).GetProperty(itemName); + if (property != null && property.CanWrite) + { + if (property.PropertyType == typeof(string)) + { + property.SetValue(_realtimeData.TestData, item.Val); + } + else if (property.PropertyType == typeof(int) && int.TryParse(item.Val, out int intValue)) + { + property.SetValue(_realtimeData.TestData, intValue); + } + else if (property.PropertyType == typeof(double) && double.TryParse(item.Val, out double doubleValue)) + { + property.SetValue(_realtimeData.TestData, doubleValue); + } + else if (property.PropertyType == typeof(DateTime) && DateTime.TryParse(item.Val, out DateTime dateValue)) + { + property.SetValue(_realtimeData.TestData, dateValue); + } + else if (property.PropertyType == typeof(bool) && bool.TryParse(item.Val, out bool boolValue)) + { + property.SetValue(_realtimeData.TestData, boolValue); + } + } + } + } + else + { + // 没有测试数据时,置空测试数据对象 + _realtimeData.TestData = null; + } + } + + /// + /// 获取最新的RTVar数据(可在外部直接调用) + /// + /// WebSocketData对象 + public async Task GetLatestDataAsync() + { + await ReadRTVarDataAsync(); + return _realtimeData; + } + + /// + /// 释放资源 + /// + public void Dispose() + { + StopPolling(); + _cancellationTokenSource?.Dispose(); + _dataPollingTimer?.Dispose(); + } + } +} diff --git a/MoviconHub.App/Services/WebSocketClient.cs b/MoviconHub.App/Services/WebSocketClient.cs new file mode 100644 index 0000000..bcce040 --- /dev/null +++ b/MoviconHub.App/Services/WebSocketClient.cs @@ -0,0 +1,561 @@ +using HslCommunication; +using MoviconHub.App.Com; +using MoviconHub.App.Models; +using Newtonsoft.Json; +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MoviconHub.App.Services +{ + /// + /// WebSocket客户端类,基于HslCommunication库实现 + /// + public class WebSocketClient : IDisposable + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly WebSocketConfig _config; + private HslCommunication.WebSocket.WebSocketClient _client; + private Timer _heartbeatTimer; + private Timer _reconnectTimer; + private bool _isConnected = false; + private bool _reconnecting = false; + private bool _disposed = false; + private int _reconnectAttempts = 0; + private readonly int _maxReconnectInterval = 300000; // 最大重连间隔,默认5分钟 + private readonly object _lockObject = new object(); + + // 事件定义 + public event EventHandler MessageReceived; + public event EventHandler Connected; + public event EventHandler Disconnected; + public event EventHandler Error; + + /// + /// 初始化WebSocket客户端 + /// + public WebSocketClient() + { + _config = WebSocketConfig.LoadConfig(); + InitializeClient(); + } + + /// + /// 初始化WebSocket客户端 + /// + private void InitializeClient() + { + try + { + // 初始化客户端并设置服务器地址和端口 + _client = new HslCommunication.WebSocket.WebSocketClient(_config.ServerAddress, _config.ServerPort, _config.Url); + + // 设置日志记录器 + _client.LogNet = new HslCommunication.LogNet.LogNetSingle("websocket_logs.txt"); + + // 注册消息接收事件处理 + _client.OnClientApplicationMessageReceive += (message) => + { + try + { + Logger.Debug($"收到消息: {message.ToString()}"); + + // 尝试解析消息 + MoviconHub.App.Models.WebSocketMessage wsMessage = + JsonConvert.DeserializeObject(message.ToString()); + + if (wsMessage != null) + { + // 触发消息接收事件 + MessageReceived?.Invoke(this, new MoviconHub.App.Models.WebSocketMessageEventArgs + { + Message = wsMessage, + RawMessage = message.ToString() + }); + + } + } + catch (Exception ex) + { + Logger.Error(ex, "处理接收到的WebSocket消息时发生错误"); + } + }; + + // 注册连接成功事件 + _client.OnClientConnected += () => + { + Logger.Info($"已成功连接到WebSocket服务器 {_config.ServerAddress}:{_config.ServerPort}"); + _isConnected = true; + + + // 启动心跳定时器 + //StartHeartbeatTimer(); + + // 触发连接事件 + Connected?.Invoke(this, new WebSocketConnectEventArgs + { + ServerAddress = _config.ServerAddress, + ServerPort = _config.ServerPort, + IsReconnection = _reconnectAttempts > 0 + }); + }; + + // 注册网络错误事件 - 根据文档示例调整参数 + _client.OnNetworkError += (sender, e) => + { + string errorMessage = "WebSocket网络连接错误"; + Logger.Error(errorMessage); + HandleDisconnection(); + Error?.Invoke(this, new WebSocketErrorEventArgs { ErrorMessage = errorMessage }); + }; + + } + catch (Exception ex) + { + Logger.Error(ex, "初始化WebSocket客户端时发生错误"); + } + } + + /// + /// 连接到WebSocket服务器 + /// + /// 连接结果 + public async Task ConnectAsync() + { + if (_disposed) + throw new ObjectDisposedException(nameof(WebSocketClient)); + + if (_isConnected) + return true; + + try + { + lock (_lockObject) + { + if (_isConnected) + return true; + + _reconnecting = false; + _reconnectAttempts = 0; + } + + // 确保设置了服务器地址和端口 + _client.IpAddress = _config.ServerAddress; + _client.Port = _config.ServerPort; + + Logger.Info($"正在连接到WebSocket服务器: ws://{_config.ServerAddress}:{_config.ServerPort}"); + + // 创建一个任务来包装同步连接方法 + var tcs = new TaskCompletionSource(); + var connectTask = Task.Run(() => + { + try + { + var result = _client.ConnectServer(); + if (result.IsSuccess) + { + tcs.SetResult(true); + } + else + { + Logger.Warn($"WebSocket连接失败: {result.Message}"); + tcs.SetResult(false); + } + } + catch (Exception ex) + { + Logger.Error(ex, "WebSocket连接过程中发生异常"); + tcs.SetResult(false); + } + }); + + // 添加超时控制 + var timeoutTask = Task.Delay(_config.ConnectionTimeout); + var completedTask = await Task.WhenAny(tcs.Task, timeoutTask); + + if (completedTask == timeoutTask) + { + Logger.Warn($"连接到WebSocket服务器 {_config.ServerAddress}:{_config.ServerPort} 超时"); + Error?.Invoke(this, new WebSocketErrorEventArgs + { + Exception = new TimeoutException("WebSocket连接超时"), + ErrorMessage = "连接超时" + }); + + if (_config.AutoReconnect) + StartReconnectTimer(); + + return false; + } + + bool connectionSuccess = await tcs.Task; + + if (!connectionSuccess && _config.AutoReconnect) + { + StartReconnectTimer(); + } + + return connectionSuccess; + } + catch (Exception ex) + { + Logger.Error(ex, $"连接到WebSocket服务器 {_config.ServerAddress}:{_config.ServerPort} 时发生错误"); + Error?.Invoke(this, new WebSocketErrorEventArgs { Exception = ex, ErrorMessage = ex.Message }); + + if (_config.AutoReconnect) + StartReconnectTimer(); + + return false; + } + } + + /// + /// 断开与WebSocket服务器的连接 + /// + public void Disconnect() + { + if (_disposed) + return; + + try + { + // 停止定时器 + StopHeartbeatTimer(); + StopReconnectTimer(); + + if (_isConnected) + { + _client.ConnectClose(); + _isConnected = false; + + Logger.Info("已断开与WebSocket服务器的连接"); + Disconnected?.Invoke(this, new WebSocketConnectEventArgs + { + ServerAddress = _config.ServerAddress, + ServerPort = _config.ServerPort, + IsReconnection = false + }); + } + } + catch (Exception ex) + { + Logger.Error(ex, "断开WebSocket连接时发生错误"); + } + } + + /// + /// 发送消息 + /// + /// 要发送的消息 + /// 是否发送成功 + public bool SendMessage(MoviconHub.App.Models.WebSocketMessage message) + { + if (_disposed) + throw new ObjectDisposedException(nameof(WebSocketClient)); + + if (!_isConnected) + { + Logger.Warn("未连接到WebSocket服务器,无法发送消息"); + return false; + } + + try + { + string json = JsonConvert.SerializeObject(message); + var sendResult = _client.SendServer(json); + + if (sendResult.IsSuccess) + { + Logger.Debug($"消息已发送: {json}"); + return true; + } + else + { + Logger.Warn($"发送消息失败: {sendResult.Message}"); + HandleDisconnection(); + return false; + } + } + catch (Exception ex) + { + Logger.Error(ex, "发送WebSocket消息时发生错误"); + HandleDisconnection(); + return false; + } + } + + /// + /// 发送设备数据 + /// 全量数据 + /// + /// 状态数据 + /// 是否发送成功 + public bool SendDeviceData(WebSocketData webSocketData) + { + MoviconHub.App.Models.WebSocketMessage message = + MoviconHub.App.Models.WebSocketMessage.CreateDeviceData( webSocketData); + return SendMessage(message); + + } + /// + /// 发送设备状态数据 + /// + /// 状态数据 + /// 是否发送成功 + public bool SendDeviceStatus(DeviceStatusData statusData) + { + MoviconHub.App.Models.WebSocketMessage message = + MoviconHub.App.Models.WebSocketMessage.CreateDeviceStatusMessage(_config.DeviceCode, statusData); + return SendMessage(message); + } + + /// + /// 发送设备故障信息 + /// + /// 故障数据 + /// 是否发送成功 + public bool SendDeviceFault(FaultDetails faultData) + { + MoviconHub.App.Models.WebSocketMessage message = + MoviconHub.App.Models.WebSocketMessage.CreateFaultMessage(_config.DeviceCode, faultData); + return SendMessage(message); + } + + /// + /// 发送测试数据 + /// + /// 测试数据 + /// 是否发送成功 + public bool SendTestData(TestData testData) + { + MoviconHub.App.Models.WebSocketMessage message = + MoviconHub.App.Models.WebSocketMessage.CreateTestDataMessage(_config.DeviceCode, testData); + return SendMessage(message); + } + + /// + /// 更新配置 + /// + /// 服务器地址 + /// 端口 + /// 设备编码 + /// 是否重连 + public async Task UpdateConfig(string serverAddress, int port, string deviceCode, bool reconnect = true) + { + bool wasConnected = _isConnected; + + if (wasConnected) + Disconnect(); + + _config.ServerAddress = serverAddress; + _config.ServerPort = port; + _config.DeviceCode = deviceCode; + _config.SaveConfig(); + + // 重新创建客户端 + _client.ConnectClose(); + InitializeClient(); + + if (wasConnected && reconnect) + await ConnectAsync(); + } + + /// + /// 处理断开连接情况 + /// + private void HandleDisconnection() + { + if (!_isConnected) + return; + + lock (_lockObject) + { + if (!_isConnected) + return; + + _isConnected = false; + StopHeartbeatTimer(); + + Logger.Warn("WebSocket连接已断开"); + + Disconnected?.Invoke(this, new WebSocketConnectEventArgs + { + ServerAddress = _config.ServerAddress, + ServerPort = _config.ServerPort, + IsReconnection = false + }); + + if (_config.AutoReconnect && !_reconnecting) + { + StartReconnectTimer(); + } + } + } + + + /// + /// 启动心跳定时器 + /// + private void StartHeartbeatTimer() + { + StopHeartbeatTimer(); + + _heartbeatTimer = new Timer(SendHeartbeat, null, + _config.HeartbeatInterval, + _config.HeartbeatInterval); + + Logger.Debug($"心跳定时器已启动,间隔: {_config.HeartbeatInterval}毫秒"); + } + + /// + /// 停止心跳定时器 + /// + private void StopHeartbeatTimer() + { + _heartbeatTimer?.Dispose(); + _heartbeatTimer = null; + } + + /// + /// 发送心跳消息 + /// + private void SendHeartbeat(object state) + { + if (!_isConnected) + return; + + try + { + MoviconHub.App.Models.WebSocketMessage heartbeat = + MoviconHub.App.Models.WebSocketMessage.CreateHeartbeat(_config.DeviceCode); + SendMessage(heartbeat); + Logger.Debug("已发送心跳消息"); + } + catch (Exception ex) + { + Logger.Error(ex, "发送心跳消息时发生错误"); + } + } + + /// + /// 启动重连定时器 + /// + private void StartReconnectTimer() + { + lock (_lockObject) + { + if (_reconnecting) + return; + + _reconnecting = true; + + StopReconnectTimer(); + + _reconnectTimer = new Timer(ReconnectCallback, null, + _config.ReconnectInterval, + Timeout.Infinite); // 只执行一次,在重连方法中再次设置定时器 + + Logger.Info($"重连定时器已启动,间隔: {_config.ReconnectInterval}毫秒"); + } + } + + /// + /// 重连回调方法 + /// + private void ReconnectCallback(object stateObj) + { + if (_isConnected || _disposed) + return; + + // 使用Task执行异步重连 + Task.Run(async () => + { + await ReconnectAsync(); + }); + } + + /// + /// 停止重连定时器 + /// + private void StopReconnectTimer() + { + _reconnectTimer?.Dispose(); + _reconnectTimer = null; + } + + /// + /// 执行重连 + /// + private async Task ReconnectAsync() + { + if (_isConnected || _disposed) + return; + + _reconnectAttempts++; + Logger.Info($"正在尝试重连,第 {_reconnectAttempts} 次"); + + bool success = await ConnectAsync(); + + if (!success && _config.AutoReconnect && !_disposed) + { + lock (_lockObject) + { + if (!_isConnected && !_disposed) + { + // 计算指数退避重连间隔 + int nextInterval = Math.Min( + _config.ReconnectInterval * (int)Math.Pow(2, Math.Min(9, _reconnectAttempts - 1)), // 最多512倍,避免间隔过长 + _maxReconnectInterval); // 最大重试间隔 + + Logger.Info($"重连失败,{nextInterval}毫秒后将再次重试"); + + _reconnectTimer = new Timer(ReconnectCallback, null, nextInterval, Timeout.Infinite); + } + } + } + else if (success) + { + _reconnecting = false; + } + } + + /// + /// 是否已连接 + /// + public bool IsConnected => _isConnected; + + /// + /// 释放资源 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 释放资源 + /// + /// 是否由Dispose调用 + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + // 释放托管资源 + Disconnect(); + _heartbeatTimer?.Dispose(); + _reconnectTimer?.Dispose(); + _client?.Dispose(); + } + + _disposed = true; + } + } +} diff --git a/MoviconHub.App/Services/WebSocketClientHelper.cs b/MoviconHub.App/Services/WebSocketClientHelper.cs new file mode 100644 index 0000000..a55d681 --- /dev/null +++ b/MoviconHub.App/Services/WebSocketClientHelper.cs @@ -0,0 +1,193 @@ +using MoviconHub.App.Com; +using MoviconHub.App.Models; +using NLog; +using ReaLTaiizor.Controls; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MoviconHub.App.Services +{ + /// + /// WebSocket客户端辅助类 + /// + public static class WebSocketClientHelper + { + private static WebSocketClient _webSocketClient; + private static WebSocketConfig _webSocketConfig; + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + /// + /// ScanTask扫描Task + /// + private static Task ScanTask { get; set; } + + /// + /// 扫描线程使能 + /// + public static bool ThreadEnable { get; set; } = true; + + public static WebSocketData CurWebSocketData { get; set; } + + /// + /// 初始化WebSocket客户端 + /// + public static async void Initialize() + { + _webSocketConfig = WebSocketConfig.LoadConfig(); + _webSocketClient = new WebSocketClient(); + + // 注册事件处理程序 + _webSocketClient.MessageReceived += OnMessageReceived; + _webSocketClient.Connected += OnConnected; + _webSocketClient.Disconnected += OnDisconnected; + _webSocketClient.Error += OnError; + + await ConnectAsync(); + + Logger.Info("WebSocket客户端辅助类已初始化"); + + } + + /// + ///发布实时数据 + /// + public static void PubRtDataStart() + { + ScanTask = Task.Run(async () => + { + while (ThreadEnable) + { + try + { + await Task.Delay(_webSocketConfig.Cycle * 1000); + + if (SendDeviceData(CurWebSocketData)) + { + + } + else + { + Logger.Info("WebSocket客户端发送设备"); + } + //SendDeviceData(CurWebSocketData); + + } + catch (Exception ex) + { + + } + + } + + }); + } + + /// + /// 消息接收事件 + /// + public static event EventHandler MessageReceived; + + /// + /// 连接事件 + /// + public static event EventHandler Connected; + + /// + /// 断开连接事件 + /// + public static event EventHandler Disconnected; + + /// + /// 错误事件 + /// + public static event EventHandler Error; + + /// + /// 连接到WebSocket服务器 + /// + /// 连接结果 + public static async Task ConnectAsync() + { + if (_webSocketClient == null) + { + Initialize(); + } + + return await _webSocketClient.ConnectAsync(); + } + + /// + /// 断开连接 + /// + public static void Disconnect() + { + ThreadEnable = false; + _webSocketClient?.Disconnect(); + } + + /// + /// 发送设备状态数据 + /// + /// 状态数据 + /// 是否发送成功 + public static bool SendDeviceData(WebSocketData webSocketData) + { + if (_webSocketClient == null) + { + Initialize(); + } + + return _webSocketClient.SendDeviceData(webSocketData); + } + + /// + /// 更新配置 + /// + /// 服务器地址 + /// 端口 + /// 设备编码 + /// 是否重连 + public static async Task UpdateConfig(string serverAddress, int port, string deviceCode, bool reconnect = true) + { + if (_webSocketClient == null) + { + Initialize(); + } + + await _webSocketClient.UpdateConfig(serverAddress, port, deviceCode, reconnect); + } + + /// + /// 客户端是否已连接 + /// + public static bool IsConnected => _webSocketClient?.IsConnected ?? false; + + // 内部事件处理方法 + private static void OnMessageReceived(object sender, WebSocketMessageEventArgs e) + { + Logger.Debug($"接收到消息:{e.RawMessage}"); + MessageReceived?.Invoke(sender, e); + } + + private static void OnConnected(object sender, WebSocketConnectEventArgs e) + { + Logger.Info($"已连接到WebSocket服务器 {e.ServerAddress}:{e.ServerPort}"); + Connected?.Invoke(sender, e); + } + + private static void OnDisconnected(object sender, WebSocketConnectEventArgs e) + { + Logger.Info($"已断开与WebSocket服务器 {e.ServerAddress}:{e.ServerPort} 的连接"); + Disconnected?.Invoke(sender, e); + } + + private static void OnError(object sender, WebSocketErrorEventArgs e) + { + Logger.Error(e.Exception, $"WebSocket错误: {e.ErrorMessage}"); + Error?.Invoke(sender, e); + } + } +} diff --git a/MoviconHub.App/frmMain.Designer.cs b/MoviconHub.App/frmMain.Designer.cs new file mode 100644 index 0000000..cea19cb --- /dev/null +++ b/MoviconHub.App/frmMain.Designer.cs @@ -0,0 +1,220 @@ +namespace MoviconHub.App +{ + partial class frmMain + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要修改 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmMain)); + this.materialTabControl1 = new ReaLTaiizor.Controls.MaterialTabControl(); + this.tabPage1 = new System.Windows.Forms.TabPage(); + this.tabPage2 = new System.Windows.Forms.TabPage(); + this.materialMultiLineTextBoxEditWs = new ReaLTaiizor.Controls.MaterialMultiLineTextBoxEdit(); + this.tabPage3 = new System.Windows.Forms.TabPage(); + this.materialMultiLineTextBoxEditApi = new ReaLTaiizor.Controls.MaterialMultiLineTextBoxEdit(); + this.tabPage4 = new System.Windows.Forms.TabPage(); + this.imageList1 = new System.Windows.Forms.ImageList(this.components); + this.MainText = new System.Windows.Forms.TextBox(); + this.materialTabControl1.SuspendLayout(); + this.tabPage1.SuspendLayout(); + this.tabPage2.SuspendLayout(); + this.tabPage3.SuspendLayout(); + this.SuspendLayout(); + // + // materialTabControl1 + // + this.materialTabControl1.Controls.Add(this.tabPage1); + this.materialTabControl1.Controls.Add(this.tabPage2); + this.materialTabControl1.Controls.Add(this.tabPage3); + this.materialTabControl1.Controls.Add(this.tabPage4); + this.materialTabControl1.Depth = 0; + this.materialTabControl1.Dock = System.Windows.Forms.DockStyle.Fill; + this.materialTabControl1.ImageList = this.imageList1; + this.materialTabControl1.Location = new System.Drawing.Point(3, 64); + this.materialTabControl1.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER; + this.materialTabControl1.Multiline = true; + this.materialTabControl1.Name = "materialTabControl1"; + this.materialTabControl1.SelectedIndex = 0; + this.materialTabControl1.Size = new System.Drawing.Size(605, 453); + this.materialTabControl1.TabIndex = 0; + // + // tabPage1 + // + this.tabPage1.Controls.Add(this.MainText); + this.tabPage1.ImageKey = "set2.png"; + this.tabPage1.Location = new System.Drawing.Point(4, 31); + this.tabPage1.Name = "tabPage1"; + this.tabPage1.Padding = new System.Windows.Forms.Padding(3); + this.tabPage1.Size = new System.Drawing.Size(597, 418); + this.tabPage1.TabIndex = 0; + this.tabPage1.Text = "主界面"; + this.tabPage1.UseVisualStyleBackColor = true; + // + // tabPage2 + // + this.tabPage2.Controls.Add(this.materialMultiLineTextBoxEditWs); + this.tabPage2.ImageKey = "set1.png"; + this.tabPage2.Location = new System.Drawing.Point(4, 31); + this.tabPage2.Name = "tabPage2"; + this.tabPage2.Padding = new System.Windows.Forms.Padding(3); + this.tabPage2.Size = new System.Drawing.Size(597, 418); + this.tabPage2.TabIndex = 1; + this.tabPage2.Text = "WebSocket配置"; + this.tabPage2.UseVisualStyleBackColor = true; + // + // materialMultiLineTextBoxEditWs + // + this.materialMultiLineTextBoxEditWs.AnimateReadOnly = false; + this.materialMultiLineTextBoxEditWs.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.materialMultiLineTextBoxEditWs.CharacterCasing = System.Windows.Forms.CharacterCasing.Normal; + this.materialMultiLineTextBoxEditWs.Cursor = System.Windows.Forms.Cursors.IBeam; + this.materialMultiLineTextBoxEditWs.Depth = 0; + this.materialMultiLineTextBoxEditWs.Dock = System.Windows.Forms.DockStyle.Bottom; + this.materialMultiLineTextBoxEditWs.HideSelection = true; + this.materialMultiLineTextBoxEditWs.Location = new System.Drawing.Point(3, 0); + this.materialMultiLineTextBoxEditWs.MaxLength = 32767; + this.materialMultiLineTextBoxEditWs.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.OUT; + this.materialMultiLineTextBoxEditWs.Name = "materialMultiLineTextBoxEditWs"; + this.materialMultiLineTextBoxEditWs.PasswordChar = '\0'; + this.materialMultiLineTextBoxEditWs.ReadOnly = true; + this.materialMultiLineTextBoxEditWs.ScrollBars = System.Windows.Forms.ScrollBars.None; + this.materialMultiLineTextBoxEditWs.SelectedText = ""; + this.materialMultiLineTextBoxEditWs.SelectionLength = 0; + this.materialMultiLineTextBoxEditWs.SelectionStart = 0; + this.materialMultiLineTextBoxEditWs.ShortcutsEnabled = true; + this.materialMultiLineTextBoxEditWs.Size = new System.Drawing.Size(591, 415); + this.materialMultiLineTextBoxEditWs.TabIndex = 2; + this.materialMultiLineTextBoxEditWs.TabStop = false; + this.materialMultiLineTextBoxEditWs.Text = "Msg"; + this.materialMultiLineTextBoxEditWs.TextAlign = System.Windows.Forms.HorizontalAlignment.Left; + this.materialMultiLineTextBoxEditWs.UseSystemPasswordChar = false; + // + // tabPage3 + // + this.tabPage3.Controls.Add(this.materialMultiLineTextBoxEditApi); + this.tabPage3.ImageKey = "set3.png"; + this.tabPage3.Location = new System.Drawing.Point(4, 31); + this.tabPage3.Name = "tabPage3"; + this.tabPage3.Size = new System.Drawing.Size(597, 418); + this.tabPage3.TabIndex = 2; + this.tabPage3.Text = "Api设置"; + this.tabPage3.UseVisualStyleBackColor = true; + // + // materialMultiLineTextBoxEditApi + // + this.materialMultiLineTextBoxEditApi.AnimateReadOnly = false; + this.materialMultiLineTextBoxEditApi.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; + this.materialMultiLineTextBoxEditApi.CharacterCasing = System.Windows.Forms.CharacterCasing.Normal; + this.materialMultiLineTextBoxEditApi.Cursor = System.Windows.Forms.Cursors.IBeam; + this.materialMultiLineTextBoxEditApi.Depth = 0; + this.materialMultiLineTextBoxEditApi.Dock = System.Windows.Forms.DockStyle.Bottom; + this.materialMultiLineTextBoxEditApi.HideSelection = true; + this.materialMultiLineTextBoxEditApi.Location = new System.Drawing.Point(0, 3); + this.materialMultiLineTextBoxEditApi.MaxLength = 32767; + this.materialMultiLineTextBoxEditApi.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.OUT; + this.materialMultiLineTextBoxEditApi.Name = "materialMultiLineTextBoxEditApi"; + this.materialMultiLineTextBoxEditApi.PasswordChar = '\0'; + this.materialMultiLineTextBoxEditApi.ReadOnly = true; + this.materialMultiLineTextBoxEditApi.ScrollBars = System.Windows.Forms.ScrollBars.None; + this.materialMultiLineTextBoxEditApi.SelectedText = ""; + this.materialMultiLineTextBoxEditApi.SelectionLength = 0; + this.materialMultiLineTextBoxEditApi.SelectionStart = 0; + this.materialMultiLineTextBoxEditApi.ShortcutsEnabled = true; + this.materialMultiLineTextBoxEditApi.Size = new System.Drawing.Size(597, 415); + this.materialMultiLineTextBoxEditApi.TabIndex = 1; + this.materialMultiLineTextBoxEditApi.TabStop = false; + this.materialMultiLineTextBoxEditApi.Text = "Msg"; + this.materialMultiLineTextBoxEditApi.TextAlign = System.Windows.Forms.HorizontalAlignment.Left; + this.materialMultiLineTextBoxEditApi.UseSystemPasswordChar = false; + // + // tabPage4 + // + this.tabPage4.ImageKey = "Log.png"; + this.tabPage4.Location = new System.Drawing.Point(4, 31); + this.tabPage4.Name = "tabPage4"; + this.tabPage4.Size = new System.Drawing.Size(597, 418); + this.tabPage4.TabIndex = 3; + this.tabPage4.Text = "运行日志"; + this.tabPage4.UseVisualStyleBackColor = true; + // + // imageList1 + // + this.imageList1.ImageStream = ((System.Windows.Forms.ImageListStreamer)(resources.GetObject("imageList1.ImageStream"))); + this.imageList1.TransparentColor = System.Drawing.Color.Transparent; + this.imageList1.Images.SetKeyName(0, "set2.png"); + this.imageList1.Images.SetKeyName(1, "set1.png"); + this.imageList1.Images.SetKeyName(2, "set3.png"); + this.imageList1.Images.SetKeyName(3, "Log.png"); + // + // MainText + // + this.MainText.Dock = System.Windows.Forms.DockStyle.Bottom; + this.MainText.Location = new System.Drawing.Point(3, 6); + this.MainText.Multiline = true; + this.MainText.Name = "MainText"; + this.MainText.Size = new System.Drawing.Size(591, 409); + this.MainText.TabIndex = 1; + // + // frmMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(611, 520); + this.Controls.Add(this.materialTabControl1); + this.DrawerTabControl = this.materialTabControl1; + this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134))); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Margin = new System.Windows.Forms.Padding(4); + this.MaximizeBox = false; + this.Name = "frmMain"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "MoviconHub"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.frmMain_FormClosed); + this.Load += new System.EventHandler(this.frmMain_Load); + this.materialTabControl1.ResumeLayout(false); + this.tabPage1.ResumeLayout(false); + this.tabPage1.PerformLayout(); + this.tabPage2.ResumeLayout(false); + this.tabPage3.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private ReaLTaiizor.Controls.MaterialTabControl materialTabControl1; + private System.Windows.Forms.TabPage tabPage1; + private System.Windows.Forms.TabPage tabPage2; + private System.Windows.Forms.TabPage tabPage3; + private System.Windows.Forms.TabPage tabPage4; + private System.Windows.Forms.ImageList imageList1; + private ReaLTaiizor.Controls.MaterialMultiLineTextBoxEdit materialMultiLineTextBoxEditApi; + private ReaLTaiizor.Controls.MaterialMultiLineTextBoxEdit materialMultiLineTextBoxEditWs; + private System.Windows.Forms.TextBox MainText; + } +} + diff --git a/MoviconHub.App/frmMain.cs b/MoviconHub.App/frmMain.cs new file mode 100644 index 0000000..65fe915 --- /dev/null +++ b/MoviconHub.App/frmMain.cs @@ -0,0 +1,958 @@ +using HslCommunication; +using HslCommunication.Profinet.Melsec; +using MoviconHub.App.Com; +using MoviconHub.App.Models; +using MoviconHub.App.Services; +using NLog; +using ReaLTaiizor.Forms; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace MoviconHub.App +{ + public partial class frmMain : MaterialForm + { + public frmMain() + { + InitializeComponent(); + } + + /// + /// 当前的日志记录 + /// + private Logger Logger { get; set; } = LogManager.GetCurrentClassLogger(); + + /// + /// 当前的信号模型 + /// + public SglModel CurSglModel { get; set; } + + + /// + /// PLCScanTask扫描Task + /// + private static Task PLCScanTask { get; set; } + + /// + /// 扫描线程使能 + /// + public bool ThreadEnable { get; set; } = true; + + /// + /// 三菱连接驱动程序 + /// + public MelsecMcNet MelsecMcNetDrive { get; set; } + + /// + /// DB连接服务 + /// + public DBServices CurDBServices { get; set; } + private void frmMain_Load(object sender, EventArgs e) + { + CurSglModel = new SglModel(); + CurSglModel.SglModelChanged += CurSglModel_SglModelChanged; + + CurDBServices = new DBServices(CurSglModel); + CurDBServices.StartPolling(); + + // 初始化API服务 + ApiHelper.Initialize(); + + WebSocketClientHelper.Initialize(); + WebSocketClientHelper.PubRtDataStart(); + + ClearActionInstance = new ClearAction(); + ClearActionInstance.ClearActionEvent += ClearActionInstance_ClearActionEvent; + + ListAlarmModels = new List() + { + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="热水罐液位 H报警", + Index=0, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="热水罐液位 L报警", + Index=1, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="热水罐液位 LL报警", + Index=2, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="清洗剂罐液位 H报警", + Index=3, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="清洗剂罐液位 L报警", + Index=4, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="清洗剂罐液位 LL报警", + Index=5, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="桁架平移前进极限报警", + Index=6, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="桁架平移后退极限报警", + Index=7, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="桁架吊装上升极限报警", + Index=8, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="桁架吊装下降极限报警", + Index=9, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="漫射喷淋AXIS1 X1伺服报警", + Index=10, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="漫射喷淋AXIS2 伺服报警", + Index=11, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="漫射喷淋AXIS3 X2伺服报警", + Index=12, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="上位机 急停被按下报警", + Index=13, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="大部件 急停安全继电器未吸合报警", + Index=14, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="R08与FX3U 通讯丢失,请查看网线是否松动", + Index=15, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME02(零部件)变频器 CC LINK网络报警 站号:1", + Index=16, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME03(大部件)变频器 CC LINK网络报警 站号:2", + Index=17, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME04(ROB1高压泵)变频器 CC LINK网络报警 站号:3", + Index=18, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME05(ROB2高压泵)变频器 CC LINK网络报警 站号:4", + Index=19, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="机器人1 CC LINK网络报警 站号:5", + Index=20, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="机器人2 CC LINK网络报警 站号:9", + Index=21, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB03 A3119_1 远程IO从站 CC LINK网络报警 站号:13", + Index=22, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB03 A4119_1 远程IO从站 CC LINK网络报警 站号:14", + Index=23, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB01 A119_1 远程IO从站 CC LINK网络报警 站号:15", + Index=24, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB01 A124_1 远程IO从站 CC LINK网络报警 站号:16", + Index=25, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB01 A129_1 远程IO从站 CC LINK网络报警 站号:17", + Index=26, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB01 A200_1 远程IO从站 CC LINK网络报警 站号:18", + Index=27, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB01 A205_1 远程IO从站 CC LINK网络报警 站号:19", + Index=28, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="JCB02 A2119_1 远程IO从站 CC LINK网络报警 站号:20", + Index=29, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="ROB1 机器人故障报警", + Index=30, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="ROB2 机器人故障报警", + Index=31, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME02 变频器存在错误", + Index=32, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME03 变频器存在错误", + Index=33, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME04 变频器存在错误", + Index=34, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME05 变频器存在错误", + Index=35, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="ROB1第七轴伺服故障", + Index=36, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="ROB2第七轴伺服故障", + Index=37, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="零部件吊装伺服故障", + Index=38, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="零部件平移伺服故障", + Index=39, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME04 高压泵温度指示报警", + Index=40, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME04 高压泵压力指示报警", + Index=41, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME05 高压泵温度指示报警", + Index=42, + DeviceState=2, + }, + new AlarmModel(FRemoteSqlContext.FDb){ + DeviceName="机车构架及大部件自动化智能清洗设备", + DeviceCode="942010002", + AlarmMessage="N1C1 XME05 高压泵压力指示报警", + Index=43, + DeviceState=2, + }, + }; + + ListDeviceStateStaticModels = new List() + { + new DeviceStateStaticModel() + { + Name="开机时长", + Index=2 + }, + new DeviceStateStaticModel() + { + Name="运行时长", + Index=6 + }, + new DeviceStateStaticModel() + { + Name="待机时长", + Index=8 + }, + new DeviceStateStaticModel() + { + Name="关机时长", + Index=10 + }, + new DeviceStateStaticModel() + { + Name="故障时长", + Index=14 + }, + new DeviceStateStaticModel() + { + Name="使用率分子", + Index=18 + }, + new DeviceStateStaticModel() + { + Name="作业计次", + Index=100 + }, + new DeviceStateStaticModel() + { + Name="故障计次", + Index=102 + }, + }; + + InitCom(); + //扫描线程成功 + RtScanDeviceStart(); + + //dBServices.StartPolling(); + + //var data = ApiHelper.GetPartInfoAsync("df3976d89a8f482685d0fb2ba5ed11ba", ""); + + } + + /// + /// 清洗动作实例信息 + /// + /// + /// + /// + private void ClearActionInstance_ClearActionEvent(object sender, string Par) + { + switch (Par) + { + case "设备关机": + { + var Data = new DeviceState() + { + DeviceName = "机车构架及大部件自动化智能清洗设备", + DeviceCode = "942010002", + PowerOnTime = ListDeviceStateStaticModels.Find(a => a.Name == "开机时长").Value, + RunTime = ListDeviceStateStaticModels.Find(a => a.Name == "运行时长").Value, + StandbyTime = ListDeviceStateStaticModels.Find(a => a.Name == "待机时长").Value, + ShutdownTime = ListDeviceStateStaticModels.Find(a => a.Name == "关机时长").Value, + FaultTime = ListDeviceStateStaticModels.Find(a => a.Name == "故障时长").Value, + + JobNum = ListDeviceStateStaticModels.Find(a => a.Name == "作业计次").Value, + FaultNum = ListDeviceStateStaticModels.Find(a => a.Name == "故障计次").Value, + + //UseRatio = ListDeviceStateStaticModels.Find(a=>a.Name== "故障计次").Value, + }; + + var Result = FRemoteSqlContext.FDb.Insert(Data).ExecuteInserted(); + if (Result != null && Result.Count > 0) + { + Logger.Error("清洗时间统计数据完成"); + + BeginInvoke(new Action(() => + { + MainText.AppendText($"时间:{DateTime.Now}-Msg:清洗时间统计数据完成 {Environment.NewLine}"); + })); + } + + } + break; + case "清洗完成": + { + // 获取组件信息 + var component = CurDBServices.RealtimeData.ListComponentsInfo?.FirstOrDefault(); + if (component != null) + { + // 创建CurRunClearState对象并映射数据 + var curRunData = new ClearData() + { + // 基本信息 + DeviceCode = CurDBServices.RealtimeData.Device_Code, + DeviceName = CurDBServices.RealtimeData.Device_Name, + + // 组件信息 + part_qrid = component?.part_qrid, + part_num = component?.part_num, + part_position = component?.part_position, + component_name = component?.part_name, + vehicle_model = component?.part_Vehicle_model, + locomotive_number = component?.part_locomotive_number, + repair_process = component?.part_repair_process, + + program_process = CurDBServices.RealtimeData.TestData.Test_FrameworkProgramProcess, + + // 程序进程 + Test_FrameworkProgramProcess = CurDBServices.RealtimeData.TestData.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage = CurDBServices.RealtimeData.TestData.Test_FrameworkProgramProcessPercentage, + + //// 设备状态 + //Test_PartsEquipmentStatus = CurDBServices.RealtimeData.TestData.Test_PartsEquipmentStatus, + + // 清洗时长和用量 + Test_FrameworkPerModelCleaningDuration = CurDBServices.RealtimeData.TestData.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkPerModelCleaningAgentUsage = CurDBServices.RealtimeData.TestData.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = CurDBServices.RealtimeData.TestData.Test_FrameworkPerModelWaterUsage, + + // 水箱和清洗剂罐信息 + WaterTank_Temp = CurDBServices.RealtimeData.TestData.Test_WaterTankTemperature, + AgentTank_Temp = CurDBServices.RealtimeData.TestData.Test_CleaningAgentTankTemperature, + WaterTank_Level = CurDBServices.RealtimeData.TestData.Test_WaterTankLevel, + AgentTank_Level = CurDBServices.RealtimeData.TestData.Test_CleaningAgentTankLevel, + + // 浸泡池温度 + SoakingTank1_Temp = CurDBServices.RealtimeData.TestData.Test_SoakingTank1Temperature, + SoakingTank2_Temp = CurDBServices.RealtimeData.TestData.Test_SoakingTank2Temperature, + + // 运行模式 + Test_WaterTankHeat = CurDBServices.RealtimeData.TestData.Test_WaterTankHeat, + Test_WaterTankAdd = CurDBServices.RealtimeData.TestData.Test_WaterTankAdd, + Test_CleaningAgentTankHeat = CurDBServices.RealtimeData.TestData.Test_CleaningAgentTankHeat, + Test_CleaningAgentTankAdd = CurDBServices.RealtimeData.TestData.Test_CleaningAgentTankAdd, + + // 监控信息 + Test_ElectricSurveillance = CurDBServices.RealtimeData.TestData.Test_ElectricSurveillance, + Test_SteamSurveillance = CurDBServices.RealtimeData.TestData.Test_SteamSurveillance + }; + + var Result = FRemoteSqlContext.FDb.Insert(curRunData).ExecuteInserted(); + if (Result != null && Result.Count > 0) + { + Logger.Error("清洗当时数据记录完成"); + + BeginInvoke(new Action(() => + { + MainText.AppendText($"时间:{DateTime.Now}-Msg:清洗当时数据记录完成 {Environment.NewLine}"); + })); + } + + } + + } + break; + default: + break; + } + + } + + /// + /// 报警集合 + /// + public List ListAlarmModels { get; set; } + + /// + /// 清洗动作实例 + /// + public ClearAction ClearActionInstance { get; set; } + + /// + /// 运行状态统计模型 + /// + public List ListDeviceStateStaticModels { get; set; } + + /// + /// 报警结果集合 + /// + private OperateResult OperateResultAlarm { get; set; } + + /// + /// 清洗结束 + /// + private OperateResult OperateResultClearEnd { get; set; } + + /// + /// 设备关机 + /// + private OperateResult OperateResultDeviceClose { get; set; } + + + private OperateResult OperateResultClearAction { get; set; } + + /// + /// 报警结果集合 + /// + private OperateResult OperateResultDeviceStateStatic { get; set; } + + /// + /// 通信初始化 + /// + private void InitCom() + { + try + { + var PLCIP = ConfigHelper.GetValue("PLCIP"); + var PLCPort = 6000; + if (int.TryParse(ConfigHelper.GetValue("PLCPort"), out int PortResult)) + { + PLCPort = PortResult; + } + + //PLC通信的连接 + MelsecMcNetDrive = new MelsecMcNet(); + MelsecMcNetDrive.IpAddress = PLCIP; + MelsecMcNetDrive.Port = PLCPort; + MelsecMcNetDrive.ConnectClose(); + + MelsecMcNetDrive.ConnectTimeOut = 3000; // 连接3秒超时 + OperateResult connect = MelsecMcNetDrive.ConnectServer(); + if (connect.IsSuccess)//初始连接状态的显示判断 + { + //MessageBox.Show(HslCommunication.StringResources.Language.ConnectedSuccess); + //MelsecMcNetDrive.Write("M504", true); + //MessageBox.Show("PLC连接成功"); + + + } + else + { + + Logger.Error("初始PLC通信失败"); + //MessageBox.Show(connect.Message + Environment.NewLine + "ErrorCode: " + connect.ErrorCode); + } + } + catch (Exception) + { + + } + } + + /// + /// PLC扫描线程 + /// + private void RtScanDeviceStart() + { + PLCScanTask = Task.Run(async () => + { + while (ThreadEnable) + { + //await Task.CompletedTask; + + await Task.Delay(50); + + try + { + OperateResultAlarm = MelsecMcNetDrive.ReadBool("M150", 50); + if (OperateResultAlarm.IsSuccess) + { + foreach (var item in ListAlarmModels) + { + if (CurDBServices.RealtimeData != null && CurDBServices.RealtimeData.FaultDetails != null) + { + if (int.TryParse(CurDBServices.RealtimeData.FaultDetails.Fault_Code, out int FaultCodeResult)) + { + item.DeviceState = FaultCodeResult; + } + } + item.IsActive = OperateResultAlarm.Content[item.Index]; + } + } + + OperateResultDeviceStateStatic = MelsecMcNetDrive.ReadInt16("D4500", 150); + if (OperateResultDeviceStateStatic.IsSuccess) + { + foreach (var itemDeviceStateStatic in ListDeviceStateStaticModels) + { + itemDeviceStateStatic.Value = OperateResultDeviceStateStatic.Content[itemDeviceStateStatic.Index]; + } + } + + //OperateResultClearEnd = MelsecMcNetDrive.ReadBool("M210"); + //if (OperateResultClearEnd.IsSuccess) + //{ + // ClearActionInstance.ClearEnd = OperateResultClearEnd.Content; + //} + + OperateResultClearEnd = MelsecMcNetDrive.ReadBool("M211"); + if (OperateResultClearEnd.IsSuccess) + { + ClearActionInstance.ClearEnd = OperateResultClearEnd.Content; + } + + OperateResultDeviceClose = MelsecMcNetDrive.ReadBool("M210"); + if (OperateResultDeviceClose.IsSuccess) + { + ClearActionInstance.DeviceClose = OperateResultDeviceClose.Content; + } + + //OperateResultClearAction = MelsecMcNetDrive.ReadInt16("D5010"); + //if (OperateResultClearAction.IsSuccess) + //{ + // ClearActionInstance.ClearEnd = OperateResultClearAction.Content.GetBoolByIndex(12); + //} + + } + catch (Exception ex) + { + //LogService.Info($"时间:{DateTime.Now.ToString()}-【Meter】-{ex.Message}"); + + } + + } + }); + } + + /// + /// 信号信息改变事件 + /// + /// + /// + /// + private void CurSglModel_SglModelChanged(object sender, string Par) + { + switch (Par) + { + case "CodeReady": + // 处理条码信息OK信号 + // 这里可以添加您需要执行的操作 + // 获取零件二维码ID + var partQrId = CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault()?.part_qrid; + if (string.IsNullOrEmpty(partQrId)) + { + Logger.Error("二维码ID为空,无法获取部件信息"); + //MessageBox.Show("二维码ID为空,无法获取部件信息", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + Logger.Error("开始进行API请求"); + BeginInvoke(new Action(() => + { + MainText.AppendText($"时间:{DateTime.Now}-Msg:开始进行API请求 {Environment.NewLine}"); + })); + + Task.Run(async () => + { + try + { + var partInfoResponse = await ApiHelper.GetPartInfoAsync(CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_qrid, ""); + + BeginInvoke(new Action(() => + { + materialMultiLineTextBoxEditApi.Text = $"Datetime:{DateTime.Now}-Msg:请求了 Api-{partInfoResponse.Message}"; + })); + + // 解析响应数据 + if (partInfoResponse != null) + { + if (partInfoResponse.Status == 200) // 成功状态码 + { + var partInfo = partInfoResponse.Data; + if (partInfo != null) + { + // 这里可以根据需要处理和显示部件信息 + string partInfoDetails = $"车型: {partInfo.VehicleModel}\n" + + $"车号: {partInfo.LocomotiveNumber}\n" + + $"修程: {partInfo.RepairProcess}\n" + + $"部件名称: {partInfo.ComponentName}\n" + + $"位别: {partInfo.PartPosition}\n" + + $"部件编号: {partInfo.PartNum}\n" + + $"部件二维码: {partInfo.PartQrCode}"; + + // 显示或记录部件信息 + Logger.Info($"成功获取部件信息: {partInfoDetails}"); + + if (CurDBServices.RealtimeData.ListComponentsInfo != null && CurDBServices.RealtimeData.ListComponentsInfo.Count > 0) + { + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_Vehicle_model = partInfo.VehicleModel; + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_locomotive_number = partInfo.LocomotiveNumber; + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_repair_process = partInfo.RepairProcess; + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_position = partInfo.PartPosition; + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_name = partInfo.ComponentName; + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_num = partInfo.PartNum; + CurDBServices.RealtimeData.ListComponentsInfo.FirstOrDefault().part_qrid = partInfo.PartQrCode; + + } + + //写个PLC + FSqlContext.FDb.Update() + .Set(a => a.Val, partInfo.VehicleModel) + .Where(a => a.Name == "part_Vehicle_model") + .ExecuteAffrows(); + + FSqlContext.FDb.Update() + .Set(a => a.Val, partInfo.LocomotiveNumber) + .Where(a => a.Name == "part_locomotive_number") + .ExecuteAffrows(); + + FSqlContext.FDb.Update() + .Set(a => a.Val, partInfo.RepairProcess) + .Where(a => a.Name == "part_repair_process") + .ExecuteAffrows(); + + FSqlContext.FDb.Update() + .Set(a => a.Val, partInfo.PartPosition) + .Where(a => a.Name == "part_position") + .ExecuteAffrows(); + + FSqlContext.FDb.Update() + .Set(a => a.Val, partInfo.ComponentName) + .Where(a => a.Name == "part_name") + .ExecuteAffrows(); + + FSqlContext.FDb.Update() + .Set(a => a.Val, partInfo.PartNum) + .Where(a => a.Name == "part_num") + .ExecuteAffrows(); + + //FSqlContext.FDb.Update() + // .Set(a => a.Val, partInfo.VehicleModel) + // .Where(a => a.Name == "part_qrid") + // .ExecuteAffrows(); + + // 读取数据后,关闭文件流 + BeginInvoke(new Action(() => + { + MainText.AppendText($"时间:{DateTime.Now}-Msg:{partInfoDetails} {Environment.NewLine}"); + })); + + // 根据UI设计,可以将这些信息显示在相应的控件上 + // 例如:lblVehicleModel.Text = partInfo.VehicleModel; + + // 或者可以将整个部件信息对象保存到某个属性中,供其他地方使用 + // this.CurrentPartInfo = partInfo; + } + else + { + Logger.Error("API返回成功但部件信息为空"); + //MessageBox.Show($"API返回成功但部件信息为空", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + else + { + Logger.Error($"获取部件信息失败: {partInfoResponse.Message}\n状态码: {partInfoResponse.Status}"); + // 处理API返回的错误 + //MessageBox.Show($"获取部件信息失败: {partInfoResponse.Message}\n状态码: {partInfoResponse.Status}", + //"API错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + else + { + MessageBox.Show("API响应为空", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + catch (Exception ex) + { + Logger.Error(ex, "异步获取或解析部件信息时发生异常"); + } + }); + + break; + default: + break; + } + } + + private void frmMain_FormClosed(object sender, FormClosedEventArgs e) + { + WebSocketClientHelper.Disconnect(); + CurDBServices.StopPolling(); + ThreadEnable = false; + } + + //private Task Async ddd() + //{ + // // 初始化 + // WebSocketClientHelper.Initialize(); + + // // 注册消息接收事件 + // WebSocketClientHelper.MessageReceived += (sender, e) => + // { + // // 处理接收到的消息 + // if (e.Message.MessageType == MessageType.DeviceStatus) + // { + // // 处理设备状态消息 + // } + // }; + + // // 连接服务器 + // WebSocketClientHelper.ConnectAsync(); + + // // 发送数据 + // var statusData = new DeviceStatusData + // { + // DeviceId = "Device001", + // DeviceName = "测试设备", + // Status = "运行中", + // Voltage = 220.5, + // Current = 5.2, + // Temperature = 36.8 + // }; + + // WebSocketClientHelper.SendDeviceStatus(statusData); + //} + + //private void Request() + //{ + // // 假设您从输入框获取二维码和设备编码 + // string qrCode = txtQrCode.Text.Trim(); + // string deviceCode = txtDeviceCode.Text.Trim(); + + // if (string.IsNullOrEmpty(qrCode) || string.IsNullOrEmpty(deviceCode)) + // { + // MessageBox.Show("请输入二维码和设备编码", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning); + // return; + // } + + // try + // { + // // 显示加载中状态 + // SetControlsEnabled(false); + // lblStatus.Text = "正在获取数据..."; + + // // 调用API获取部件信息 + // var result = await ApiHelper.GetPartInfoAsync(qrCode, deviceCode); + + // if (result.Status == 200 && result.Data != null) + // { + // // 将获取到的数据填充到表单中 + // txtVehicleModel.Text = result.Data.VehicleModel; + // txtLocomotiveNumber.Text = result.Data.LocomotiveNumber; + // txtRepairProcess.Text = result.Data.RepairProcess; + // txtComponentName.Text = result.Data.ComponentName; + // txtPartPosition.Text = result.Data.PartPosition; + // txtPartNum.Text = result.Data.PartNum; + + // lblStatus.Text = "数据获取成功"; + // } + // else + // { + // // 显示错误信息 + // lblStatus.Text = $"获取数据失败:{result.Message}"; + // MessageBox.Show($"获取数据失败:{result.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + // } + // } + // catch (Exception ex) + // { + // lblStatus.Text = $"发生异常:{ex.Message}"; + // MessageBox.Show($"发生异常:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + // } + // finally + // { + // // 恢复控件状态 + // SetControlsEnabled(true); + // } + //} + + + } +} diff --git a/MoviconHub.App/frmMain.resx b/MoviconHub.App/frmMain.resx new file mode 100644 index 0000000..785c543 --- /dev/null +++ b/MoviconHub.App/frmMain.resx @@ -0,0 +1,1570 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + + AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w + LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 + ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAD0 + HwAAAk1TRnQBSQFMAgEBBAEAARgBAAEYAQABGAEAARgBAAT/ASEBAAj/AUIBTQE2BwABNgMAASgDAAFg + AwABMAMAAQEBAAEgBgABSP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A + /wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AN4AAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/JAABvwGJ + ASIB9QHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/iAAB2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AxgBIBQAAdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wFoAV4BTQG1TAAB2wGWARIB/wFyAWQBTQHAAdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + gAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wGX + AXcBPQHfDAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/RAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8YAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/4AAAdsBlgESAf8B2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wMJ + AQsQAAGbAXwBOgHhAdsBlgESAf8DNQFVQAABUAFPAUsBjwHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/ + AdsBlgESAf8B0gGTARsB+xgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AwQBBSgA + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + GAAB2wGWARIB/wMtAUUYAAHbAZYBEgH/AUcBRgFEAXwEAAHbAZYBEgH/AdsBlgESAf8YAAHbAZYBEgH/ + AdsBlgESAf9EAAHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8cAAHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/KAAB2wGWARIB/wMMARAsAAFrAWIBTgG6AdsBlgES + Af8YAAHbAZYBEgH/HAAB2wGWARIB/wGJAXEBRQHVBAAB2wGWARIB/wHbAZYBEgH/GAAB2wGWARIB/wHb + AZYBEgH/IAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAWABWgFOAaoB2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DBAEFFAAB2wGWARIB/zAAAUkBSAFG + AYAB2wGWARIB/xgAAdsBlgESAf8cAAHbAZYBEgH/AYkBcQFFAdUEAAHbAZYBEgH/AYkBcQFFAdUYAAHb + AZYBEgH/AdsBlgESAf8gAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8MAAHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/FAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHbAZYBEgH/MAABSQFIAUYBgAHb + AZYBEgH/GAAB2wGWARIB/wF+AWsBSQHLGAAB2wGWARIB/wGJAXEBRQHVBAAB2wGWARIB/wGJAXEBRQHV + GAAB2wGWARIB/wHbAZYBEgH/IAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + EAADNwFaBAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DEAEVAx0BKQMcAScDNQFVAdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8BiQFyAUYB1BwAAdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQA + AdsBlgESAf8IAAHTAZYBGAH9AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8IAAFJAUgBRgGAAdsBlgESAf8YAAHbAZYBEgH/AdsBlgESAf8YAAHbAZYBEgH/ + AYkBcQFFAdUEAAHbAZYBEgH/AWABWgFOAaoUAAHbAZYBEgH/AdsBlgESAf8DCQELDAAB2wGWARIB/wHb + AZYBEgH/Ax4BKwQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8DHgErBAAB2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8MAAM6 + AWAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/yQA + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHb + AZYBEgH/CAABfAFqAUoBygHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/CAABSQFIAUYBgAHbAZYBEgH/HAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AYkBcQFFAdUEAAHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wwA + AxUBHAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/IAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/KAADBgEH + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xgAAdsBlgESAf8wAAFJAUgBRgGA + AdsBlgESAf8gAAG6AYkBKgHxAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/CAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wMcAScQAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8kAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + LAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8wAAFJAUgBRgGAAdsBlgES + Af9wAAMqAUAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wwAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/yAAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8sAAHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/HAAB2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AYkBcQFFAdUBSQFIAUYBgAHb + AZYBEgH/IAABQgFBAUABcQHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wgAAXQBaAFMAcMB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8UAAMvAUkB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/FAAB2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8kAAHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/LAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8wAAFJ + AUgBRgGAAdsBlgESAf8cAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8BiQFxAUUB1QQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wGXAXcBPQHfFAAB2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8DCQELGAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8BgQFuAUkBziQAAdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/ywAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/xgAAdsBlgESAf8wAAFJAUgBRgGAAdsBlgESAf8YAAHbAZYBEgH/AdsBlgESAf8DJAE1 + FAAB2wGWARIB/wGJAXEBRQHVBAAB2wGWARIB/wGJAXEBRQHVFAAB2wGWARIB/wHbAZYBEgH/EAADHgEr + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/HAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/yAA + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/Ac8BjQEcAfokAAHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/FAAB2wGW + ARIB/wwAAXIBZAFNAcABcgFkAU0BwAFyAWQBTQHAAXIBZAFNAcAUAAFJAUgBRgGAAdsBlgESAf8YAAHb + AZYBEgH/Ac4BjQEcAfoYAAHbAZYBEgH/AYkBcQFFAdUEAAHbAZYBEgH/AYkBcQFFAdUYAAHbAZYBEgH/ + AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AasBfwEyAeoYAAHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/HAABbQFiAU4BuwHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AyEBMBwAAWsBYQFPAbgB2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQAAdsBlgESAf8IAAHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAFJAUgBRgGAAdsBlgESAf8YAAHbAZYBEgH/ + HAAB2wGWARIB/wGJAXEBRQHVBAAB2wGWARIB/wGJAXEBRQHVGAAB2wGWARIB/wHbAZYBEgH/CAAB2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wG/AYkBIgH1FAAB2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8UAAHbAZYBEgH/MAABSQFIAUYBgAHbAZYBEgH/GAAB2wGWARIB/xwAAdsBlgESAf8BiQFxAUUB1QQA + AdsBlgESAf8BiQFxAUUB1RgAAdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wFaAVYBTgGgGAAB2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wGJAXIBRgHU + FAAB2wGWARIB/zAAAUkBSAFGAYAB2wGWARIB/xgAAdsBlgESAf8DCQELGAAB2wGWARIB/wFXAVMBTAGc + BAAB2wGWARIB/wHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/CAAB2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/LAADBgEH + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8oAAHbAZYBEgH/MAABSQFI + AUYBgAHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/FAAB2wGWARIB/wHbAZYBEgH/CAAB2wGWARIB/wHb + AZYBEgH/FAABVwFTAUwBnAHbAZYBEgH/AVEBTwFKAZEMAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wMJAQswAAHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wMbASUoAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/xgAAwkBCwHbAZYBEgH/AdsBlgESAf8BaAFeAU0BtQMqAUADMwFQ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8BcgFkAU0BwAFy + AWQBTQHAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/EAABsQGDASwB7wMyAU8IAAFwAWUBTAG/AdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wFwAWUBTAG/DAAB2wGWARIB/zQAAdsBlgES + Af8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/LAABvwGMASUB9AHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/ + AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AXABZQFMAb8gAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8BYAFaAU4BqhQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/wHSAZMBGwH7KAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/SAAB2wGW + ARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/CAADBgEHAdsBlgESAf8B2wGWARIB/wHb + AZYBEgH/AdsBlgESAf8B2wGWARIB//AAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB/0gAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHbAZYBEgH/AdsBlgESAf8B2wGW + ARIB//QAAUYBRQFEAXoB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8BlgF3AT0B31AAAzkBXwMYASAUAAFZ + AVUBTQGfAxgBIHwAAUIBTQE+BwABPgMAASgDAAFgAwABMAMAAQEBAAEBBQABQAECFgAD//8AIgAF/wHH + AfwBPgE/A/8B8AE+AQcC/wEAAfwBHAEfA/8B4AEcAQMB/wH+AQAB/AEAAT8D/wHPAZgB8QH/Af4BOAH8 + AQABHwH4AQABDwHPAckB+QL/ATkB/AEAAR8B+QH/Ac8B3wHJAfkB/gEOATgBgAIAAfsB/wHPAd8ByQH5 + Af4BDgEAAYABPgEAAfsB/wLPAckB+QH+AQ8BQAEAAX8BAAH7AQACzwHJAfEBxAECAUcBAAH/AYAB+wEA + Ac8B4AEIAQMBgAEAAT8BwQH/AYEB+wH/Ac8B8AEYAQcBgAEAAT8B4QH/AcMB+wH/Ac8D/wEAAeABHwHh + Af8BwwH7AQABDwHwARgBDwGBAfABPwHhAf8BwwH7Af8BzwHgAQgBAwHhAfgBfwHBAf8BwQH7Af8BzwHH + AckB8wHDAfgBfwGAAf8BgAH7AYcCzwHJAfkBwQH4AX8BAAF/AQAB+wEHAc8B3wHJAfkBgQHwAT8BgAE+ + AQAB+wH/Ac8B3wHJAfkBgAFAAR8BgAIAAfsB/wLPAckB+QGAAQABPwH4AQABHwH7Af8CzwGZAfEBwAEA + AT8B/AEAAR8B+AEAAQ8BwAEcAQMBzAEHAX8B/AEAAT8B+AEAAQ8B8AE+AQcB/gEPAf8B/AEYAR8G/wH+ + AQ8B/wH8AT4BPwb/Af4BDwL/AT4BfwP/Cw== + + + + + + AAABAAcAgIAAAAEAGAAoyAAAdgAAADAwAAABABgAqBwAAJ7IAAAgIAAAAQAYAKgMAABG5QAAEBAAAAEA + GABoAwAA7vEAACAgAAABACAAqBAAAFb1AAAQEAAAAQAgAGgEAAD+BQEAMDAAAAEAIACoJQAAZgoBACgA + AACAAAAAAAEAAAEAGAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///35+PL29O39/fgAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v38+/r6+fj4+PX39vP29vL08+708+708+70 + 8+729fL29vL4+PX6+ff7+/n9/fwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/fr39e/r6NzV0cC7taCemIGA + emJjXkdQSjd7d2T29fMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+//78+/r29vPx7+rq6ODm4tjh3dHe28vc + 2Mfb1cTX08DU0L3TzrvRzLjNybXOyLXNyLTNyLXQyrfSzrrU0LzY1MHc2Mfg3M3k4tTp59zv7eb29PH7 + +vn+/v0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/Pn29O3k4dPL + xrOspo6IgmpkXkhBPSomIxQUEgkGBAAAAAAAAAAAAAAZGRnp6egAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v35+Pbv7ujn5Nvg + 3dDc18jY08HTz7vMx7TBvKuzrp6hno6QjYB/fXFxb2ZmZFtcWlNXVU9OTUdOTUdOTUdNTEZSUEpYVk9f + XFRpZ111cmeEgXOUkICloZC1sJ3EvqrPy7jb1sbl4dTu7OP39vL9/fwAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD8+vbu6+HY1MO7tZ+Uj3ZpZE5CPy0lIxcODQcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJCQmenp4A + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD8+/ry8ezn5dzf283a1cTW0b7Nyba6t6Wem41+e3BfXVZEQz8wLy4kIyIdHRwaGhoZGhoZGRoaGhsb + GxsbGxwcHB0cHB0cHBwbGxwaGhsZGRoZGRkXFxgXFxgWFhcYGBgdHBwlJSM2NTFNS0RnZFmFgXOkoY2/ + u6bTz7zj4NHw7ub6+fcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD8+/nv7eTb1sa7tZ+QinNiXUo5NikbGREEBAEAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAATExOlpaUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAD8/Pvx8Ovk4tfc2MjZ1MHSzru7t6aVkYVpZ15BQD0nJyUaGhoWFhcYGBkb + GxwgICAjIyMlJSYnJycpKSkqKiorKysrKyssLCwsLCwsLCwsLCwrKysrKysqKiopKSkoKCgnJyclJSUj + IyMhISEdHR0YGBkUFBURERIREhIZGRgtLChOTEN3dGahnInEwKvc2cjv7OP6+fcAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD39vDn5NfMyLOjnYZybVhCPzEaGRIFBAMAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYGBi3t7cAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD29fLn5Nvc2MnZ1MLRzbqzr5+BfnNKSUQl + JCMVFRUTFBQYGBkeHx8jIyMmJiYoKCgrKystLS0uLi4vLy8xMTEyMjIzMzM0NDQ1NTU1NTU1NTU1NTU1 + NTU1NTU0NDQzMzMzMzMxMTEwMDAvLy8tLS0rKyspKSkmJiYkJCQiIiIfHx8aGhsVFRUODxANDA0UFRQu + LShbWE2RjXvAu6ff3Mzz8ur+/v0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD39fDl49XGwq2Yk3xiXUwv + LSMMCwkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdHR3E + xMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/fzw7+nh3tHa + 1sPX0r+7t6iDgXZEQj4cHBsPDxATExQbHBwhISEkJCQoKCgrKysuLi4wMDAyMjI0NDQ2NjY3Nzc4ODg6 + Ojo7Ozs7Ozs7Ozs7Ozs8PDw8PDw8PDw8PDw7Ozs7Ozs7Ozs6Ojo6Ojo4ODg3Nzc2NjY0NDQyMjIwMDAu + Li4sLCwpKSklJSUiIiIfHx8bGxsWFhYODw8ICAkMDAwmJSBYVkqXk4DMx7Tr6d38+/gAAAAAAAAAAAD6 + +fbr6N3OyrafmoNjX04qJyAJCAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAdHR3FxcUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAD8/Pvt6+Td2szZ1sLQy7ijn5FYV1AgHx4NDQ0REhIaGhofHx8jIyMnJycrKysuLi4xMTEz + MzM2NjY4ODg5OTk7Ozs9PT0+Pj4/Pz9AQEBBQUFBQUFCQkJCQkJDQ0NDQ0NDQ0NDQ0NCQkJCQkJBQUFB + QUFAQEA/Pz8+Pj49PT07Ozs5OTk4ODg3Nzc0NDQyMjIvLy8rKysoKCglJSUhISEdHR0ZGRkUFBQMDA0E + BAUKCgovLSdybl+3sZ7m49b18+zf3My2sZt4dGI2NCoMCwkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeHh7GxsYAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/fzt7OXb2MrZ1MLLxrWNin49OzcPDw8LDAwVFRUcHBwh + ISElJSUpKSksLCwwMDA0NDQ2NjY5OTk7Ozs9PT0/Pz9AQEBCQkJERERFRUVGRkZGRkZHR0dISEhJSUlJ + SUlJSUlJSUlJSUlJSUlJSUlJSUlISEhHR0dGRkZGRkZFRUVERERCQkJAQEA/Pz8+Pj48PDw5OTk3Nzc0 + NDQxMTEuLi4qKiolJSUiIiIeHh4ZGRkVFRUPDw8GBgcBAQEZGBRbWEyWkX1dWkwcGxYAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc + HBzGxsYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw8Ord2szZ1MLKx7WC + f3UwLiwJCAoMDA0XFxcdHR0hISElJSUqKiouLi4yMjI1NTU4ODg7Ozs9PT0/Pz9BQUFDQ0NFRUVGRkZI + SEhJSUlKSkpLS0tMTExNTU1NTU1OTk5OTk5OTk5PT09PT09OTk5OTk5OTk5NTU1NTU1MTExLS0tKSkpK + SkpISEhGRkZFRUVERERCQkJAQEA9PT07Ozs5OTk2NjYyMjIuLi4rKysmJiYhISEdHR0YGBgUFBQPDw8H + BwcAAAEDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAWFha7u7sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAD39vPh3tPY1MLQzLqOi38wLysGBgYMDAwVFRYbGxsfHx8lJSUpKSkuLi4yMjI1NTU4ODg7Ozs+ + Pj5BQUFDQ0NFRUVHR0dJSUlLS0tMTExOTk5PT09QUFBRUVFRUVFSUlJTU1NTU1NUVFRUVFRUVFRUVFRU + VFRUVFRTU1NTU1NSUlJSUlJRUVFQUFBPT09OTk5MTExLS0tKSkpISEhGRkZERERBQUE/Pz88PDw5OTk2 + NjYzMzMvLy8qKiolJSUgICAcHBwXFxcRERENDQ0GBgcDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAODg6vr68AAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/fzq6ODZ1cTX07+lopQ+PTgFBQUICAkTExMZGRkeHh4jIyMo + KCgtLS0yMjI1NTU5OTk8PDw/Pz9CQkJERERHR0dJSUlLS0tMTExOTk5PT09RUVFSUlJUVFRVVVVVVVVW + VlZXV1dXV1dYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhXV1dXV1dWVlZWVlZVVVVTU1NSUlJRUVFQUFBP + T09NTU1LS0tJSUlHR0dERERCQkJAQEA9PT06Ojo2NjYzMzMuLi4pKSkkJCQfHx8aGhoVFRUPDw8KCgoF + BQUBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAHBwecnJwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD39vPe283a1cLEv65gX1YL + CwsCAwMPDw8VFRUbGxshISEmJiYsLCwwMDA1NTU5OTk8PDw/Pz9CQkJFRUVHR0dKSkpMTExPT09QUFBR + UVFTU1NUVFRWVlZXV1dYWFhZWVlZWVlaWlpbW1tcXFxcXFxcXFxdXV1dXV1dXV1dXV1dXV1cXFxcXFxb + W1tbW1taWlpZWVlYWFhXV1dWVlZVVVVTU1NSUlJQUFBOTk5MTExKSkpISEhFRUVDQ0NAQEA9PT05OTk1 + NTUxMTEsLCwnJychISEcHBwWFhYREREMDAwGBgYBAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACEhIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAADt6+TZ1sTY07+UkYQlJCEAAAAKCgoSEhIYGBgdHR0jIyMpKSkuLi4zMzM3Nzc7Ozs+Pj5BQUFF + RUVHR0dKSkpMTExPT09RUVFTU1NVVVVWVlZXV1dZWVlaWlpbW1tcXFxdXV1eXl5eXl5fX19fX19gYGBg + YGBgYGBgYGBgYGBgYGBgYGBgYGBfX19fX19fX19eXl5dXV1cXFxbW1taWlpZWVlYWFhXV1dVVVVTU1NR + UVFPT09NTU1KSkpISEhFRUVCQkI/Pz87Ozs4ODg0NDQvLy8pKSkkJCQeHh4ZGRkTExMNDQ0HBwcCAgIA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmZmb8/PwAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8/Pvk4tfZ1cPJxbNdW1IFBQUCAgINDQ0TExMZGRkfHx8kJCQr + KysxMTE1NTU5OTk9PT1BQUFERERHR0dKSkpMTExPT09RUVFUVFRWVlZYWFhZWVlaWlpcXFxdXV1eXl5f + X19gYGBhYWFhYWFiYmJjY2NjY2NjY2NkZGRkZGRkZGRkZGRkZGRkZGRjY2NjY2NjY2NiYmJiYmJhYWFg + YGBfX19eXl5dXV1cXFxbW1tZWVlXV1dWVlZUVFRSUlJPT09NTU1LS0tISEhFRUVBQUE+Pj46Ojo2NjYx + MTEsLCwmJiYgICAaGhoUFBQODg4ICAgDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAABCQkLy8vIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD39/Td283a1sKsqZsuLioA + AAAGBgYODg4UFBQaGhogICAnJyctLS0yMjI3Nzc7Ozs/Pz9DQ0NHR0dJSUlMTExPT09SUlJUVFRWVlZY + WFhaWlpcXFxdXV1eXl5gYGBhYWFiYmJjY2NkZGRkZGRlZWVmZmZmZmZnZ2dnZ2dnZ2doaGhoaGhoaGho + aGhnZ2dnZ2dnZ2dmZmZmZmZmZmZlZWVkZGRjY2NiYmJhYWFgYGBfX19dXV1cXFxaWlpYWFhWVlZVVVVS + UlJQUFBNTU1JSUlHR0dERERAQEA8PDw4ODgzMzMuLi4oKCghISEbGxsVFRUPDw8JCQkDAwMAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiIiLa2toAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAD08+7a18fX08GIhnsSEhAAAAAICAgODg4VFRUbGxshISEnJycuLi40NDQ4ODg9PT1BQUFERERI + SEhLS0tOTk5RUVFUVFRWVlZXV1daWlpcXFxeXl5fX19hYWFiYmJjY2NkZGRlZWVmZmZnZ2dnZ2doaGhp + aWlpaWlpaWlqampqampqampqampqampqampqampqamppaWlpaWlpaWloaGhoaGhnZ2dmZmZlZWVkZGRj + Y2NiYmJhYWFgYGBeXl5cXFxaWlpZWVlXV1dTU1NSUlJOTk5MTExJSUlFRUVBQUE+Pj45OTk0NDQvLy8p + KSkjIyMcHBwWFhYPDw8JCQkDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALCwu3t7cAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw7+rZ1cTSzrxqaF8EAwMAAAAICAgODg4VFRUcHBwhISEo + KCgvLy81NTU6Ojo+Pj5CQkJGRkZJSUlMTExQUFBSUlJVVVVXV1dZWVlcXFxdXV1fX19hYWFiYmJkZGRl + ZWVmZmZnZ2doaGhpaWlqampqampra2tsbGxsbGxsbGxtbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1sbGxs + bGxsbGxra2tra2tqamppaWloaGhnZ2dmZmZlZWVkZGRjY2NhYWFfX19dXV1cXFxaWlpYWFhVVVVTU1NQ + UFBNTU1KSkpGRkZDQ0M/Pz86Ojo2NjYwMDAqKiokJCQdHR0VFRUPDw8JCQkDAwMAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACCgoIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADu7efY1MPLyLZSUUoA + AAAAAAEHBwcODg4UFBQbGxsiIiIpKSkwMDA2NjY6Ojo/Pz9DQ0NGRkZKSkpNTU1RUVFUVFRWVlZZWVlb + W1tdXV1fX19hYWFjY2NlZWVmZmZnZ2dnZ2dpaWlqampra2tsbGxsbGxtbW1tbW1ubm5ubm5vb29vb29v + b29vb29vb29vb29vb29vb29vb29vb29ubm5ubm5tbW1tbW1sbGxsbGxra2tqampqamppaWlnZ2dmZmZl + ZWVjY2NhYWFgYGBeXl5cXFxaWlpXV1dUVFRRUVFOTk5LS0tISEhEREQ/Pz87Ozs2NjYwMDAqKiojIyMc + HBwWFhYPDw8ICAgCAgIAAAAAAAAAAAAAAAAAAAAAAAAAAABHR0f19fUAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAADu7ufZ1cPGwrJDQTsAAAABAQEHBwcNDQ0UFBQbGxsiIiIpKSkwMDA2NjY7OztAQEBERERI + SEhLS0tPT09SUlJVVVVYWFhaWlpdXV1fX19gYGBiYmJkZGRlZWVnZ2doaGhpaWlqampra2tsbGxtbW1u + bm5ubm5vb29vb29wcHBwcHBwcHBxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFwcHBwcHBwcHBwcHBvb29u + bm5ubm5tbW1sbGxsbGxra2tqamppaWlnZ2dmZmZkZGRjY2NhYWFfX19dXV1bW1tYWFhVVVVTU1NQUFBN + TU1JSUlFRUVAQEA8PDw3NzcxMTEqKiokJCQcHBwVFRUODg4ICAgCAgIAAAAAAAAAAAAAAAAAAAAcHBzT + 09MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADv7uja1sPDv683NjEAAAAAAAAGBgYNDQ0TExMaGhoi + IiIpKSkwMDA2NjY7Ozs/Pz9ERERISEhMTExQUFBSUlJVVVVYWFhbW1tdXV1gYGBiYmJjY2NlZWVnZ2do + aGhqampra2tsbGxtbW1ubm5vb29wcHBwcHBxcXFxcXFycnJycnJycnJzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nz + c3Nzc3Nzc3Nzc3NycnJycnJycnJxcXFxcXFwcHBwcHBvb29ubm5tbW1sbGxra2tqampoaGhnZ2dmZmZk + ZGRiYmJgYGBeXl5bW1tZWVlWVlZTU1NQUFBMTExJSUlERERBQUE8PDw3NzcwMDArKysjIyMcHBwUFBQO + Dg4ICAgBAQEAAAAAAAAAAAAAAABjYlwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8eza1sTGwrE2 + NTEAAAAAAAAEBAQLCwsSEhIZGRkgICAoKCgwMDA2NjY7OztAQEBERERISEhNTU1QUFBUVFRWVlZZWVlc + XFxeXl5hYWFjY2NlZWVmZmZoaGhpaWlra2tsbGxtbW1ubm5vb29wcHBxcXFxcXFycnJzc3Nzc3Nzc3N0 + dHR0dHR0dHR1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV0dHR0dHR0dHR0dHRzc3Nzc3NycnJycnJxcXFw + cHBvb29ubm5tbW1sbGxra2tpaWloaGhnZ2dlZWVjY2NhYWFfX19cXFxaWlpXV1dUVFRRUVFNTU1JSUlF + RUVBQUE8PDw2NjYxMTEqKioiIiIaGhoUFBQNDQ0GBgYAAAAAAAAAAAAAAAAPDgubmZEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAD29fHa1sfLx7U/PTgAAAAAAAADAwMKCgoREREZGRkgICAnJycvLy82NjY7OztAQEBE + RERISEhNTU1QUFBUVFRXV1daWlpdXV1fX19iYmJkZGRmZmZnZ2dpaWlqampsbGxtbW1ubm5wcHBxcXFx + cXFycnJzc3Nzc3N0dHR1dXV1dXV1dXV1dXV2dnZ2dnZ2dnZ3d3d3d3d2dnZ2dnZ3d3d3d3d2dnZ2dnZ2 + dnZ2dnZ1dXV1dXV0dHR0dHRzc3Nzc3NycnJycnJxcXFwcHBvb29tbW1sbGxra2tqampoaGhmZmZkZGRi + YmJfX19dXV1aWlpXV1dVVVVRUVFNTU1KSkpFRUVBQUE8PDw3NzcwMDAoKCghISEaGhoSEhILCwsEBAQA + AAAAAAAAAAAAAAAJCAampJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6+vjc2crRzbtNS0UAAAAAAAACAgIICAgPDw8W + FhYeHh4lJSUtLS00NDQ6Ojo/Pz9ERERISEhMTExRUVFVVVVYWFhaWlpdXV1fX19iYmJkZGRnZ2dpaWlq + ampra2tsbGxubm5vb29xcXFycnJzc3Nzc3N0dHR0dHR1dXV2dnZ2dnZ3d3d3d3d3d3d3d3d4eHh4eHh4 + eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh3d3d3d3d3d3d3d3d2dnZ2dnZ1dXV1dXV0dHRzc3Nzc3NycnJx + cXFvb29ubm5tbW1sbGxqamppaWlnZ2dlZWViYmJgYGBdXV1bW1tYWFhVVVVRUVFOTk5KSkpFRUVAQEA7 + Ozs1NTUvLy8nJycfHx8YGBgQEBAJCQkDAwMAAAAAAAAAAAAAAAAQDw27urUAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/f3h3tLX + 0sBlYlsAAAAAAAABAQEGBgYNDQ0UFBQcHBwkJCQrKyszMzM5OTk+Pj5DQ0NISEhMTExQUFBUVFRXV1da + WlpdXV1gYGBiYmJlZWVnZ2dpaWlra2tsbGxtbW1vb29wcHBxcXFycnJ0dHR0dHR1dXV1dXV2dnZ3d3d3 + d3d4eHh4eHh4eHh4eHh5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl5eXl4eHh4eHh4 + eHh3d3d3d3d2dnZ2dnZ0dHR0dHRzc3NycnJxcXFwcHBvb29ubm5tbW1ra2tpaWlnZ2dlZWVjY2NgYGBe + Xl5bW1tYWFhUVFRRUVFNTU1ISEhEREQ/Pz86Ojo0NDQtLS0lJSUdHR0WFhYODg4HBwcBAQEAAAAAAAAA + AAAAAAAhIB3W1tIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAADp59/Z1cKEgXcDAwMAAAAAAAADAwMLCwsSEhIaGhoiIiIqKioyMjI4ODg9 + PT1DQ0NHR0dMTExQUFBUVFRXV1daWlpdXV1gYGBiYmJlZWVnZ2dpaWlra2ttbW1ubm5vb29xcXFycnJz + c3N0dHR1dXV2dnZ2dnZ3d3d3d3d4eHh4eHh5eXl5eXl5eXl5eXl6enp6enp6enp6enp6enp5eXl6enp6 + enp6enp6enp6enp6enp6enp5eXl5eXl5eXl4eHh4eHh3d3d3d3d2dnZ1dXV1dXV0dHRzc3NycnJxcXFw + cHBvb29tbW1ra2tpaWlnZ2dlZWVjY2NhYWFeXl5bW1tXV1dUVFRRUVFMTExISEhDQ0M+Pj45OTkzMzMr + KysjIyMcHBwUFBQMDAwFBQUAAAAAAAAAAAAAAAAAAAA/Pjvu7ewAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8u7b1saopZcQEA4AAAAAAAABAQEI + CAgQEBAXFxcfHx8nJycvLy82NjY8PDxBQUFGRkZKSkpPT09TU1NWVlZaWlpdXV1gYGBiYmJkZGRnZ2dp + aWlra2ttbW1ubm5wcHBxcXFycnJzc3N0dHR1dXV2dnZ3d3d3d3d4eHh4eHh5eXl5eXl5eXl6enp6enp6 + enp6enp7e3t7e3t4eHh5eXl8fHx5eXl7e3t7e3t7e3t7e3t6enp6enp6enp6enp5eXl5eXl5eXl4eHh3 + d3d2dnZ4eHhycnJ1dXV1dXVzc3NycnJxcXFwcHBubm5tbW1ra2tpaWlnZ2dlZWVjY2NgYGBdXV1bW1tY + WFhUVFRQUFBMTExISEhCQkI+Pj43NzcxMTEpKSkhISEZGRkREREKCgoDAwMAAAAAAAAAAAAAAAAAAABr + amj+/v0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8 + /Pve28zGwrErKicAAAAAAAAAAAAFBQUNDQ0UFBQdHR0lJSUtLS00NDQ6OjpBQUFFRUVJSUlNTU1SUlJW + VlZZWVldXV1gYGBiYmJlZWVnZ2dpaWlra2ttbW1vb29wcHBxcXFycnJzc3N0dHR2dnZ2dnZ3d3d4eHh4 + eHh5eXl5eXl6enp6enp6enp7e3t7e3t7e3t8fHx5eXl3d3ePj4/Ozs7g4OCTk5N4eHh8fHx8fHx7e3t7 + e3t7e3t7e3t7e3t6enp6enp6enp5eXl6enrIyMjf39+hoaF1dXVycnJ1dXV0dHRzc3NxcXFwcHBvb29t + bW1ra2tqampoaGhmZmZjY2NgYGBeXl5bW1tXV1dTU1NPT09LS0tFRUVCQkI8PDw1NTUvLy8mJiYeHh4W + FhYODg4GBgYBAQEAAAAAAAAAAAAAAAAEBASnp6YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADo5t3X08BbWlIAAAAAAAAAAAADAwMKCgoSEhIZGRkiIiIqKioy + MjI4ODg+Pj5ERERISEhNTU1RUVFVVVVYWFhcXFxfX19iYmJlZWVnZ2dpaWlra2ttbW1ubm5wcHBycnJz + c3N0dHR1dXV2dnZ3d3d4eHh4eHh5eXl5eXl6enp6enp7e3t7e3t7e3t8fHx8fHx7e3t2dnaEhIS9vb30 + 9PT////////Y2Nh8fHx7e3t8fHx8fHx8fHx8fHx8fHx8fHx7e3t7e3t7e3t1dXWpqan////////9/f3T + 09ORkZFycnJycnJ0dHRzc3NycnJwcHBvb29tbW1sbGxqampoaGhlZWViYmJgYGBdXV1ZWVlWVlZSUlJO + Tk5JSUlEREQ/Pz85OTkzMzMrKysjIyMbGxsTExMLCwsEBAQAAAAAAAAAAAAAAAAAAAAeHR7d3N0AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD29fHd2MeVk4cEBAQAAAAA + AAABAQEGBgYODg4WFhYeHh4nJycvLy82NjY8PDxCQkJHR0dMTExQUFBVVVVYWFhbW1tfX19iYmJkZGRn + Z2dpaWlra2ttbW1vb29wcHBycnJzc3N0dHR1dXV2dnZ3d3d4eHh5eXl5eXl6enp6enp7e3t7e3t7e3t8 + fHx8fHx8fHx4eHh9fX2srKzq6ur////////////////+/v6oqKh4eHh9fX19fX19fX19fX19fX18fHx8 + fHx8fHx5eXmEhITr6+v////////////////4+PjCwsKEhIRwcHBzc3N0dHRycnJwcHBvb29ubm5sbGxq + ampoaGhlZWViYmJfX19dXV1ZWVlVVVVRUVFNTU1ISEhDQ0M9PT03NzcxMTEoKCggICAYGBgPDw8ICAgB + AQEAAAAAAAAAAAAAAAAAAABWVlb7+/sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAD+/v7i4NPGwrEmJSIAAAAAAAAAAAADAwMKCgoSEhIaGhojIyMrKyszMzM6OjpAQEBFRUVLS0tP + T09TU1NXV1dbW1teXl5hYWFkZGRmZmZpaWlra2ttbW1vb29wcHBycnJzc3N1dXV2dnZ2dnZ3d3d4eHh5 + eXl6enp6enp7e3t7e3t7e3t8fHx8fHx9fX15eXl5eXmcnJzb29v////////////////////////////o + 6OiEhIR8fHx9fX19fX19fX19fX19fX19fX19fX13d3e+vr7////////////////////////////v7++x + sbF6enpvb29zc3NycnJxcXFwcHBubm5sbGxpaWlnZ2dlZWViYmJfX19bW1tYWFhUVFRQUFBLS0tHR0dB + QUE7Ozs1NTUtLS0kJCQcHBwTExMMDAwEBAQAAAAAAAAAAAAAAAAAAAACAgKkpKQAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADx7+rb1sRpZ18AAAAAAAAAAAABAQEGBgYODg4WFhYf + Hx8oKCgwMDA4ODg9PT1DQ0NISEhNTU1SUlJWVlZaWlpdXV1gYGBjY2NmZmZoaGhra2tsbGxubm5wcHBy + cnJ0dHR1dXV2dnZ3d3d3d3d4eHh5eXl6enp6enp7e3t8fHx8fHx8fHx8fHx7e3t4eHiOjo7MzMz6+vr/ + //////////////////////////////////+7u7t4eHh9fX19fX19fX18fHx8fHx9fX15eXmSkpL29vb/ + ///////////////////////////////////i4uKenp5zc3Nvb29ycnJwcHBvb29tbW1ra2tpaWlnZ2dk + ZGRhYWFeXl5bW1tXV1dSUlJOTk5JSUlDQ0M/Pz84ODgyMjIpKSkhISEYGBgQEBAICAgBAQEAAAAAAAAA + AAAAAAAAAAAmJibo6OgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD9/f3i39Kzr58R + EA8AAAAAAAAAAAADAwMKCgoSEhIaGhojIyMsLCw0NDQ7OztBQUFHR0dLS0tQUFBVVVVYWFhcXFxfX19i + YmJlZWVoaGhqamptbW1vb29wcHBycnJzc3N1dXV2dnZ3d3d4eHh4eHh5eXl6enp7e3t7e3t8fHx8fHx8 + fHx9fX16enqDg4O6urry8vL////////////////////////////////////////////z8/OPj496enqA + gICCgoKBgYGAgIB8fHx5eXnS0tL////////////////////////////////////////////+/v7Q0NCN + jY1wcHBycnJxcXFvb29tbW1ra2toaGhmZmZjY2NgYGBdXV1ZWVlVVVVRUVFMTExHR0dCQkI8PDw2NjYt + LS0lJSUcHBwUFBQMDAwEBAQAAAAAAAAAAAAAAAAAAAAAAAB7e3sAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAADw7+nZ1cNVU0wAAAAAAAAAAAAAAAAGBgYODg4WFhYfHx8nJycxMTE3Nzc9PT1D + Q0NJSUlOTk5SUlJWVlZaWlpeXl5hYWFkZGRmZmZpaWlra2tubm5wcHBxcXFycnJ0dHR1dXV3d3d4eHh5 + eXl5eXl6enp6enp7e3t8fHx8fHx8fHx9fX16enqUlJTm5ub///////////////////////////////// + ///////////////////////j4+PV1dXi4uLm5ubl5eXh4eHU1NTQ0ND8/Pz///////////////////// + ///////////////////////////////29vavr69xcXFycnJwcHBubm5sbGxqampnZ2dkZGRiYmJfX19b + W1tXV1dSUlJPT09JSUlEREQ/Pz85OTkyMjIpKSkhISEXFxcPDw8HBwcAAAAAAAAAAAAAAAAAAAAAAAAV + FRXY2NgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v7j4NKuqpsMDAsAAAAAAAAAAAACAgIK + CgoSEhIaGhojIyMrKys1NTU7OztBQUFGRkZMTExQUFBVVVVZWVlcXFxfX19jY2NmZmZoaGhra2ttbW1v + b29xcXFycnJzc3N1dXV2dnZ3d3d4eHh5eXl6enp6enp7e3t8fHx8fHx9fX19fX19fX15eXmysrL///// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////f3990dHRycnJx + cXFvb29tbW1ra2tpaWlmZmZjY2NgYGBdXV1ZWVlVVVVRUVFMTExISEhBQUE8PDw1NTUtLS0lJSUcHBwU + FBQLCwsDAwMAAAAAAAAAAAAAAAAAAAAAAABra2sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD0 + 8+7a1cNVU0wAAAAAAAAAAAAAAAAEBAQNDQ0UFBQeHh4mJiYvLy84ODg+Pj5DQ0NISEhOTk5SUlJXV1db + W1teXl5iYmJlZWVnZ2dqampsbGxubm5wcHBycnJzc3N0dHR2dnZ3d3d4eHh5eXl6enp6enp7e3t8fHx8 + fHx9fX19fX19fX1+fn58fHyLi4vu7u7///////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////+/v6pqalxcXF0dHRycnJwcHBubm5tbW1qampoaGhlZWViYmJfX19cXFxXV1dTU1NO + Tk5KSkpEREQ+Pj45OTkxMTEoKCgfHx8WFhYODg4FBQUAAAAAAAAAAAAAAAAAAAAAAAAUFBTa2toAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADo5tu0sKAODg0AAAAAAAAAAAABAQEHBwcPDw8YGBghISEqKioz + MzM6OjpAQEBGRkZLS0tQUFBUVFRZWVldXV1gYGBkZGRmZmZpaWlra2tubm5wcHBycnJzc3N0dHR2dnZ3 + d3d4eHh5eXl6enp7e3t7e3t8fHx8fHx9fX19fX1+fn5+fn5+fn5+fn55eXmysrL///////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////b29t4eHh1dXV1dXV0dHRycnJwcHBubm5r + a2tpaWlnZ2dkZGRhYWFeXl5ZWVlWVlZRUVFMTExHR0dCQkI7Ozs0NDQsLCwiIiIaGhoSEhIJCQkBAQEA + AAAAAAAAAAAAAAAAAAAAAAB8fHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6+vje28poZl0AAAAAAAAA + AAAAAAADAwMKCgoTExMcHBwlJSUuLi42NjY9PT1DQ0NISEhOTk5SUlJXV1dbW1tfX19iYmJlZWVoaGhr + a2ttbW1vb29xcXFzc3N0dHR1dXV3d3d4eHh5eXl6enp7e3t7e3t8fHx8fHx9fX19fX1+fn5+fn5+fn5+ + fn5/f39+fn6BgYHi4uL///////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////6+vqY + mJhzc3N3d3d2dnZ0dHRzc3NxcXFvb29tbW1ra2toaGhlZWVjY2NfX19bW1tYWFhTU1NOTk5JSUlEREQ+ + Pj43NzcwMDAmJiYdHR0VFRUMDAwEBAQAAAAAAAAAAAAAAAAAAAAAAAAiIiLr6+sAAAAAAAAAAAAAAAAA + AAAAAAAAAADw7ufHw7IhIR4AAAAAAAAAAAAAAAAFBQUNDQ0VFRUfHx8oKCgxMTE4ODg/Pz9FRUVKSkpQ + UFBUVFRYWFhdXV1gYGBjY2NnZ2dpaWlsbGxubm5wcHBycnJ0dHR1dXV2dnZ3d3d4eHh5eXl6enp7e3t8 + fHx8fHx9fX19fX1+fn5+fn5+fn5+fn5/f39/f39/f396enqhoaH8/Pz///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////Jycl2dnZ4eHh4eHh3d3d1dXV0dHRycnJwcHBubm5sbGxqampnZ2dk + ZGRhYWFdXV1aWlpVVVVQUFBLS0tGRkZAQEA6OjoyMjIqKiogICAYGBgPDw8GBgYAAAAAAAAAAAAAAAAA + AAAAAAABAQGjo6MAAAAAAAAAAAAAAAAAAAAAAAD+/v7o5diRjoEBAQEAAAAAAAAAAAABAQEICAgQEBAY + GBgiIiIrKys0NDQ7OztCQkJHR0dNTU1SUlJWVlZaWlpeXl5iYmJlZWVoaGhqamptbW1vb29xcXFzc3N1 + dXV2dnZ3d3d4eHh5eXl6enp7e3t8fHx8fHx9fX19fX19fX1+fn5+fn5/f39/f39/f39/f39/f39/f399 + fX3Q0ND///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////x8fGLi4t3d3d5eXl4eHh3d3d2 + dnZ1dXVzc3NxcXFvb29tbW1ra2toaGhmZmZiYmJfX19bW1tXV1dTU1NOTk5JSUlCQkI8PDw1NTUtLS0j + IyMbGxsSEhIJCQkCAgIAAAAAAAAAAAAAAAAAAAAAAABLS0v+/v4AAAAAAAAAAAAAAAAAAAD7+/jc2MhQ + TkcAAAAAAAAAAAAAAAADAwMKCgoSEhIbGxslJSUuLi42NjY9PT1DQ0NISEhOTk5TU1NYWFhcXFxfX19j + Y2NmZmZoaGhra2ttbW1vb29xcXFzc3N1dXV2dnZ3d3d4eHh5eXl6enp7e3t8fHx8fHx9fX19fX1+fn5+ + fn5+fn5/f39/f39/f39/f39/f39/f396enqbm5v8/Pz///////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////+5ubl0dHR7e3t5eXl5eXl4eHh3d3d1dXV0dHRxcXFwcHBubm5sbGxpaWlmZmZjY2NgYGBcXFxY + WFhUVFRPT09KSkpFRUU+Pj43NzcvLy8nJycdHR0UFBQLCwsDAwMAAAAAAAAAAAAAAAAAAAAAAAARERHV + 1dUAAAAAAAAAAAAAAAAAAAD08u7Cvq0aGhgAAAAAAAAAAAAAAAAEBAQNDQ0VFRUeHh4nJycxMTE4ODg/ + Pz9FRUVKSkpPT09UVFRZWVldXV1hYWFkZGRnZ2dpaWlsbGxubm5wcHBycnJ0dHR2dnZ3d3d4eHh5eXl6 + enp7e3t8fHx8fHx9fX19fX1+fn5+fn5+fn5/f39/f39/f39/f39/f39/f39/f39/f3/Q0ND+/v7///// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////T09N8fHx6enp6enp5eXl4eHh3d3d1dXV0dHRycnJw + cHBvb29tbW1qampnZ2dkZGRhYWFeXl5ZWVlVVVVRUVFMTExGRkZAQEA5OTkyMjIpKSkgICAXFxcODg4F + BQUAAAAAAAAAAAAAAAAAAAAAAAAAAACVlZUAAAAAAAAAAAAAAAAAAADt6+GWk4YBAAEAAAAAAAAAAAAA + AAAGBgYPDw8XFxcgICAqKio0NDQ6OjpBQUFGRkZMTExRUVFWVlZaWlpeXl5iYmJlZWVoaGhra2ttbW1v + b29xcXFzc3N1dXV2dnZ3d3d4eHh5eXl6enp7e3t8fHx9fX19fX19fX1+fn5+fn5+fn5/f39/f39/f39/ + f39/f39/f397e3uwsLD////////////////////////////////////////////////////09PTd3d3I + yMi6urq2trbCwsLU1NTr6+v///////////////////////////////////////////////////+4uLh2 + dnZ7e3t6enp5eXl4eHh2dnZ1dXVzc3NxcXFwcHBubm5ra2tpaWlmZmZiYmJfX19bW1tXV1dSUlJNTU1I + SEhCQkI7Ozs1NTUsLCwiIiIZGRkQEBAHBwcBAQEAAAAAAAAAAAAAAAAAAAAAAABQUFD+/v4AAAAAAAAA + AAD+/v3m49ZfXVUAAAAAAAAAAAAAAAABAQEICAgREREaGhojIyMsLCw2NjY8PDxDQ0NISEhOTk5TU1NY + WFhcXFxfX19jY2NmZmZpaWlra2tubm5wcHBycnJ0dHR1dXV3d3d4eHh5eXl6enp7e3t8fHx8fHx9fX19 + fX1+fn5+fn5+fn5/f39/f39/f39/f39/f3+AgIB+fn6MjIzw8PD///////////////////////////// + ///////////////r6+u2traPj49/f397e3t6enp6enp7e3t9fX2Hh4ekpKTX19f9/f3///////////// + ///////////////////////////19fWQkJB4eHh6enp5eXl4eHh3d3d2dnZ0dHRycnJxcXFvb29sbGxq + ampmZmZkZGRgYGBcXFxYWFhUVFRPT09JSUlEREQ9PT03NzcuLi4kJCQcHBwSEhIJCQkCAgIAAAAAAAAA + AAAAAAAAAAAAAAAbGxvn5+cAAAAAAAAAAAD6+vfW08QyMi0AAAAAAAAAAAAAAAACAgIKCgoTExMcHBwl + JSUvLy83Nzc9PT1ERERKSkpPT09UVFRZWVldXV1gYGBkZGRnZ2dqampsbGxvb29xcXFzc3N0dHR2dnZ4 + eHh5eXl5eXl6enp7e3t8fHx9fX19fX1+fn5+fn5+fn5/f39/f39/f39/f39/f39/f3+AgIB7e3vCwsL/ + ///////////////////////////////////////8/PzExMSHh4d6enp9fX1/f3+AgICAgICAgICAgICA + gIB+fn57e3t+fn6oqKjt7e3////////////////////////////////////////Pz894eHh6enp6enp5 + eXl4eHh2dnZ0dHRzc3NxcXFvb29tbW1ra2toaGhkZGRhYWFdXV1ZWVlVVVVQUFBLS0tFRUU/Pz84ODgw + MDAnJycdHR0UFBQLCwsDAwMAAAAAAAAAAAAAAAAAAAAAAAAFBQW4uLgAAAAAAAAAAAD39vC+u6wSEhEA + AAAAAAAAAAAAAAADAwMMDAwVFRUeHh4nJycxMTE5OTk/Pz9GRkZLS0tRUVFVVVVaWlpeXl5hYWFlZWVo + aGhra2ttbW1vb29xcXFzc3N1dXV3d3d4eHh5eXl6enp7e3t8fHx8fHx9fX19fX1+fn5+fn5+fn59fX18 + fHx8fHx8fHx8fHx9fX17e3uKiorx8fH////////////////////////////////////5+fmurq56enp+ + fn6AgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB/f396enqPj4/h4eH///////////////////// + ///////////////5+fmTk5N0dHR3d3d2dnZ1dXVzc3NxcXFwcHBwcHBwcHBtbW1ra2toaGhmZmZjY2Nf + X19aWlpWVlZSUlJMTExHR0dAQEA6OjoyMjIpKSkfHx8WFhYNDQ0EBAQAAAAAAAAAAAAAAAAAAAAAAAAA + AACGhoYAAAAAAAAAAADy8eegnI8BAQEAAAAAAAAAAAAAAAAFBQUNDQ0XFxcgICApKSkyMjI6OjpAQEBH + R0dMTExSUlJWVlZbW1teXl5iYmJmZmZoaGhra2tubm5wcHBycnJ0dHR1dXV3d3d4eHh5eXl6enp7e3t8 + fHx8fHx9fX1+fn5+fn5+fn5+fn6Ojo6WlpaWlpaWlpaWlpaWlpaSkpK8vLz///////////////////// + ///////////////8/Pyurq56enqAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA + gIB8fHyMjIzp6en////////////////////////////////////KysqOjo6RkZGRkZGQkJCPj4+Ojo6L + i4t2dnZvb29ubm5sbGxpaWlmZmZjY2NfX19bW1tXV1dTU1NNTU1ISEhCQkI8PDw0NDQrKyshISEYGBgP + Dw8GBgYAAAAAAAAAAAAAAAAAAAAAAAAAAABZWVn+/v4AAAAAAADt6t1+fHEAAAAAAAAAAAAAAAAAAAAG + BgYODg4YGBghISEqKiozMzM7OztBQUFISEhNTU1TU1NXV1dcXFxfX19jY2NmZmZpaWlsbGxvb29wcHBy + cnJ0dHR2dnZ3d3d5eXl6enp6enp7e3t8fHx9fX19fX1+fn5+fn57e3uenp7z8/P9/f38/Pz8/Pz8/Pz8 + /Pz8/Pz////////////////////////////////////////ExMR8fHx/f3+AgICAgICAgICAgICAgICA + gICAgICAgICAgICAgICAgICAgICAgICAgICAgIB7e3udnZ34+Pj///////////////////////////// + ///////8/Pz8/Pz8/Pz8/Pz8/Pz8/Pz7+/u/v79ubm5ubm5tbW1qampnZ2dkZGRgYGBcXFxYWFhTU1NO + Tk5JSUlDQ0M9PT01NTUsLCwiIiIZGRkQEBAHBwcBAQEAAAAAAAAAAAAAAAAAAAAAAAAxMTH29vYAAAAA + AADn49NeXVQAAAAAAAAAAAAAAAAAAAAHBwcPDw8ZGRkiIiIsLCw1NTU8PDxCQkJJSUlOTk5TU1NYWFhc + XFxgYGBjY2NnZ2dpaWlsbGxvb29xcXFzc3N0dHR2dnZ4eHh5eXl6enp6enp7e3t8fHx9fX19fX1+fn5+ + fn56enqxsbH////////////////////////////////////////////////////////////////r6+uI + iIh+fn6AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB6enrG + xsb////////////////////////////////////////////////////////////////a2tpxcXFubm5t + bW1qampnZ2dkZGRhYWFdXV1ZWVlUVFRPT09KSkpDQ0M9PT02NjYuLi4kJCQaGhoREREICAgBAQEAAAAA + AAAAAAAAAAAAAAAAAAAVFRXj4+MAAAAAAADc2clEQjwAAAAAAAAAAAAAAAABAQEICAgQEBAaGhojIyMt + LS02NjY9PT1DQ0NKSkpPT09UVFRYWFhdXV1gYGBkZGRnZ2dqamptbW1vb29xcXFzc3N1dXV2dnZ4eHh5 + eXl6enp7e3t7e3t8fHx9fX19fX1+fn5+fn56enqwsLD///////////////////////////////////// + //////////////////////////+4uLh6enqAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA + gICAgICAgICAgICAgICAgICAgIB9fX2QkJD19fX///////////////////////////////////////// + ///////////////////X19dxcXFvb29tbW1qampoaGhlZWVhYWFdXV1ZWVlVVVVQUFBLS0tEREQ+Pj43 + NzcvLy8lJSUbGxsSEhIKCgoCAgIAAAAAAAAAAAAAAAAAAAAAAAAHBwfJyckAAAAAAADRzb0yMS0AAAAA + AAAAAAAAAAABAQEJCQkREREbGxslJSUuLi43Nzc+Pj5ERERKSkpPT09UVFRZWVleXl5hYWFkZGRoaGhq + amptbW1wcHBxcXFzc3N1dXV2dnZ4eHh5eXl6enp7e3t8fHx8fHx9fX1+fn5+fn5+fn56enqwsLD///// + ///////////////////////////////////////////////////////19fWRkZF9fX2AgICAgICAgICA + gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB8fHzU1NT///////// + ///////////////////////////////////////////////////X19dycnJvb29ubm5ra2toaGhlZWVi + YmJeXl5aWlpWVlZQUFBLS0tFRUU/Pz84ODgwMDAnJycdHR0TExMKCgoDAwMAAAAAAAAAAAAAAAAAAAAA + AAACAgKxsbEAAAAAAADGwrInJyQAAAAAAAAAAAAAAAACAgIJCQkSEhIcHBwlJSUvLy83Nzc/Pz9FRUVL + S0tQUFBVVVVZWVleXl5hYWFlZWVoaGhra2ttbW1wcHBxcXFzc3N1dXV3d3d4eHh5eXl6enp7e3t8fHx9 + fX19fX1+fn5+fn5/f396enqwsLD///////////////////////////////////////////////////// + ///////f399/f3+AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA + gICAgICAgICAgIB6enqzs7P///////////////////////////////////////////////////////// + ///X19dycnJvb29ubm5ra2tpaWlmZmZiYmJeXl5bW1tWVlZRUVFMTExGRkZAQEA5OTkxMTEnJycdHR0U + FBQLCwsDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAACdnZ0AAAAAAAC7uKgeHhwAAAAAAAAAAAAAAAACAgIK + CgoTExMdHR0mJiYwMDA4ODg/Pz9FRUVLS0tQUFBVVVVaWlpeXl5iYmJlZWVoaGhra2tubm5wcHBycnJ0 + dHR1dXV3d3d4eHh6enp7e3t7e3t8fHx9fX19fX1+fn5+fn5/f396enqwsLD///////////////////// + ///////////////////////////////////////Jycl7e3uAgICAgICAgICAgICAgICAgICAgICAgICA + gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB8fHydnZ3+/v7///////////////////// + ///////////////////////////////////Y2NhycnJwcHBubm5sbGxpaWlmZmZjY2NfX19bW1tXV1dR + UVFMTExGRkZAQEA5OTkyMjIoKCgeHh4VFRUMDAwDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAACOjo4AAAAA + AAC1sqIaGRcAAAAAAAAAAAAAAAADAwMKCgoTExMdHR0nJycwMDA4ODg/Pz9FRUVMTExRUVFWVlZaWlpf + X19iYmJlZWVpaWlra2tubm5wcHBycnJ0dHR2dnZ3d3d5eXl6enp7e3t7e3t8fHx9fX19fX1+fn5+fn5/ + f396enqwsLD///////////////////////////////////////////////////////////+8vLx6enqA + gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB9 + fX2Tk5P6+vr////////////////////////////////////////////////////////X19dzc3NwcHBu + bm5sbGxpaWlmZmZjY2NfX19bW1tXV1dSUlJNTU1HR0dBQUE6OjoyMjIpKSkfHx8VFRUMDAwEBAQAAAAA + AAAAAAAAAAAAAAAAAAAAAACDg4MAAAAAAAC2s6QaGhcAAAAAAAAAAAAAAAADAwMLCwsUFBQdHR0nJycw + MDA4ODhAQEBGRkZMTExRUVFWVlZaWlpfX19iYmJlZWVpaWlra2tubm5wcHBycnJ0dHR2dnZ3d3d5eXl6 + enp7e3t7e3t8fHx9fX19fX1+fn5+fn5/f396enqwsLD///////////////////////////////////// + //////////////////////+6urp6enqAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA + gICAgICAgICAgICAgICAgICAgICAgIB9fX2SkpL5+fn///////////////////////////////////// + ///////////////////X19dzc3NwcHBvb29sbGxpaWlmZmZjY2NfX19bW1tXV1dSUlJNTU1HR0dBQUE6 + OjoyMjIpKSkfHx8VFRUMDAwEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAACDg4MAAAAAAAC2tKQaGhcAAAAA + AAAAAAAAAAADAwMLCwsUFBQdHR0nJycwMDA4ODhAQEBGRkZMTExRUVFWVlZaWlpfX19iYmJlZWVpaWlr + a2tubm5wcHBycnJ0dHR2dnZ3d3d5eXl6enp7e3t7e3t8fHx9fX19fX1+fn5+fn5/f396enqwsLD///// + ///////////////////////////////////////////////////////FxcV6enqAgICAgICAgICAgICA + gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB8fHyampr9/f3///// + ///////////////////////////////////////////////////X19dzc3NwcHBvb29sbGxpaWlmZmZj + Y2NfX19bW1tXV1dSUlJNTU1HR0dBQUE6OjozMzMpKSkfHx8WFhYMDAwEBAQAAAAAAAAAAAAAAAAAAAAA + AAAAAACDg4MAAAAAAAC4taYaGhcAAAAAAAAAAAAAAAADAwMLCwsTExMdHR0nJycwMDA4ODhAQEBGRkZM + TExRUVFWVlZaWlpfX19iYmJlZWVpaWlra2tubm5wcHBycnJ0dHR2dnZ3d3d5eXl6enp7e3t7e3t8fHx9 + fX19fX1+fn5+fn5/f396enqwsLD///////////////////////////////////////////////////// + ///////a2tp9fX2AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA + gICAgICAgICAgIB7e3usrKz///////////////////////////////////////////////////////// + ///X19dzc3NwcHBvb29sbGxpaWlmZmZjY2NfX19bW1tXV1dSUlJNTU1HR0dBQUE6OjoyMjIpKSkfHx8V + FRUMDAwEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAACCgoIAAAAAAAC8uascGxgAAAAAAAAAAAAAAAACAgIK + CgoTExMdHR0mJiYwMDA4ODg/Pz9FRUVMTExRUVFWVlZaWlpeXl5iYmJlZWVpaWlra2tubm5wcHBycnJ0 + dHR1dXV3d3d5eXl6enp7e3t7e3t8fHx9fX19fX1+fn5+fn5/f396enqwsLD///////////////////// + ///////////////////////////////////////w8PCKiop+fn6AgICAgICAgICAgICAgICAgICAgICA + gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB7e3vMzMz///////////////////////// + ///////////////////////////////////Y2NhycnJwcHBubm5sbGxpaWlmZmZiYmJfX19bW1tXV1dS + UlJNTU1GRkZAQEA6OjoyMjIoKCgeHh4VFRUMDAwEBAQAAAAAAAAAAAAAAAAAAAAAAAAAAACIiIgAAAAA + AADKx7skIyAAAAAAAAAAAAAAAAACAgIKCgoTExMcHBwmJiYvLy84ODg/Pz9FRUVLS0tQUFBVVVVaWlpe + Xl5hYWFlZWVoaGhra2tubm5wcHBycnJ0dHR1dXV3d3d4eHh6enp7e3t7e3t8fHx9fX19fX1+fn5+fn5/ + f396enqwsLD////////////////////////////////////////////////////////////+/v6rq6t7 + e3uAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIB+fn6J + iYnu7u7////////////////////////////////////////////////////////////X19dycnJwcHBu + bm5ra2tpaWlmZmZjY2NeXl5bW1tXV1dRUVFMTExGRkZAQEA5OTkyMjIoKCgeHh4VFRUMDAwDAwMAAAAA + AAAAAAAAAAAAAAAAAAAAAACXl5cAAAAAAADb2M4uLSkAAAAAAAAAAAAAAAACAgIJCQkSEhIcHBwlJSUv + Ly83Nzc+Pj5ERERLS0tQUFBVVVVZWVleXl5hYWFlZWVoaGhra2ttbW1wcHBxcXFzc3N1dXV3d3d4eHh5 + eXl6enp6enp7e3t7e3t8fHx9fX19fX1+fn56enqxsbH///////////////////////////////////// + ///////////////////////////h4eGBgYF/f3+AgICAgICAgICAgICAgICAgIB/f39/f39/f39/f39/ + f39/f3+AgICAgICAgICAgICAgIB5eXm2trb///////////////////////////////////////////// + ///////////////////Z2dlycnJvb29ubm5ra2tpaWlmZmZiYmJeXl5bW1tWVlZRUVFMTExGRkZAQEA5 + OTkxMTEnJycdHR0UFBQLCwsDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAACpqakAAAAAAADt6+M8OzYAAAAA + AAAAAAAAAAABAQEJCQkREREbGxskJCQuLi42NjY+Pj5ERERKSkpPT09UVFRZWVldXV1hYWFkZGRoaGhq + amptbW1wcHBxcXFzc3N1dXV3d3d4eHh5eXl6enp7e3t+fn5+fn5/f3+AgIB/f39+fn56enqoqKj9/f3/ + ///////////////////////////////////////////////////////////+/v67u7uNjY2RkZGSkpKT + k5ORkZGRkZGRkZGPj4+Ojo6MjIyKioqHh4eFhYWDg4OBgYGAgIB/f397e3uPj4/x8fH///////////// + ///////////////////////////////////////////////////Nzc1wcHBvb29ubm5ra2toaGhlZWVi + YmJeXl5aWlpWVlZQUFBLS0tFRUU/Pz84ODgwMDAmJiYcHBwTExMKCgoCAgIAAAAAAAAAAAAAAAAAAAAA + AAAFBQXAwMAAAAAAAAD8+/ZWVE8AAAAAAAAAAAAAAAABAQEHBwcQEBAaGhojIyMtLS02NjY9PT1DQ0NJ + SUlOTk5UVFRYWFhdXV1gYGBkZGRnZ2dqamptbW1vb29xcXFzc3N1dXV2dnZ4eHh4eHh7e3vKysrh4eHg + 4ODg4ODh4eHV1dWEhIR9fX2CgoKjo6Orq6uqqqqqqqqrq6uurq6vr6/Hx8f+/v7///////////////// + ///////////////39/ewsLCVlZWampqampqampqampqampqampqampqampqampqampqZmZmXl5eVlZWS + kpKMjIyKiorZ2dn////////////////////////////////////q6uqoqKinp6enp6enp6empqalpaWj + o6OCgoJwcHBvb29tbW1qampoaGhlZWVhYWFdXV1ZWVlVVVVPT09LS0tEREQ+Pj43NzcvLy8lJSUbGxsS + EhIJCQkCAgIAAAAAAAAAAAAAAAAAAAAAAAAODg7a2toAAAAAAAAAAAB5d3AAAAAAAAAAAAAAAAAAAAAG + BgYPDw8ZGRkiIiIsLCw1NTU8PDxCQkJISEhNTU1TU1NXV1ddXV1fX19jY2NnZ2dpaWlsbGxvb29wcHBz + c3N0dHR2dnZ3d3d4eHh9fX3q6ur////////////////29vaKiop8fHx+fn56enp8fHyBgYGIiIiOjo6S + kpKTk5Oamprs7Oz////////////////////////////////////x8fGsrKyUlJSYmJiZmZmZmZmZmZmZ + mZmZmZmZmZmZmZmZmZmZmZmZmZmampqXl5ecnJzZ2dn///////////////////////////////////// + //+5ublzc3N2dnZ1dXV0dHRzc3NxcXFvb29xcXFycnJvb29tbW1qampoaGhlZWVgYGBcXFxYWFhUVFRP + T09KSkpDQ0M9PT02NjYuLi4kJCQaGhoREREICAgBAQEAAAAAAAAAAAAAAAAAAAAAAAAlJSXw8PAAAAAA + AAAAAACgnpcAAAAAAAAAAAAAAAAAAAAGBgYODg4YGBghISEqKiozMzM7OztBQUFISEhNTU1SUlJXV1dc + XFxfX19jY2NmZmZpaWlsbGxubm5xcXFvb29vb290dHR3d3d2dnZ4eHjm5ub////////////////z8/OG + hoZ9fX2Hh4eNjY2SkpKXl5eampqampqZmZmZmZmUlJTDw8P///////////////////////////////// + ///////09PS7u7uXl5eWlpaZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmXl5eVlZWoqKji4uL///////// + ///////////////////////////////w8PCKiop5eXl7e3t6enp5eXl3d3d2dnZ0dHRzc3NxcXFvb29s + bGxqampnZ2dkZGRgYGBcXFxYWFhUVFROTk5JSUlDQ0M8PDw1NTUtLS0jIyMZGRkQEBAHBwcBAQEAAAAA + AAAAAAAAAAAAAAAAAABISEj8/PwAAAAAAAAAAADHxr8ICAYAAAAAAAAAAAAAAAAEBAQNDQ0WFhYfHx8o + KCgyMjI6OjpAQEBHR0dMTExSUlJWVlZbW1teXl5iYmJmZmZoaGhra2tubm5sbGySkpK3t7d/f39wcHB8 + fHytra319fX////////////////5+fm3t7eYmJiVlZWenp7Hx8e4uLiXl5eampqampqampqZmZmenp7r + 6+v////////////////////////////////////////+/v7b29uwsLCampqVlZWWlpaXl5eXl5eWlpaV + lZWXl5elpaXJycn19fX////////////////////////////////////////////BwcF4eHh6enp6enp5 + eXl4eHh3d3d1dXV0dHRycnJxcXFubm5sbGxpaWlmZmZjY2NfX19bW1tXV1dTU1NNTU1ISEhBQUE7Ozs0 + NDQqKiohISEYGBgODg4GBgYAAAAAAAAAAAAAAAAAAAAAAAAAAABzc3MAAAAAAAAAAAAAAADq6eQlJSEA + AAAAAAAAAAAAAAADAwMLCwsUFBQdHR0mJiYwMDA5OTk/Pz9FRUVLS0tQUFBVVVVaWlpdXV1hYWFlZWVn + Z2dqampsbGxycnLf39/////o6Oizs7Pe3t7////////////////////////////////n5+fBwcHo6Oj/ + ///y8vKioqKYmJiampqampqampqWlpa3t7f8/Pz///////////////////////////////////////// + ///8/Pzn5+fNzc27u7uysrKysrK3t7fFxcXd3d329vb///////////////////////////////////// + ///////////u7u6fn5+Tk5OHh4d7e3t4eHh4eHh3d3d1dXVzc3NxcXFwcHBtbW1ra2toaGhlZWViYmJe + Xl5aWlpWVlZRUVFMTExHR0dAQEA6OjoyMjIpKSkfHx8WFhYNDQ0EBAQAAAAAAAAAAAAAAAAAAAAAAAAB + AQGlpaUAAAAAAAAAAAAAAAD+/vxTUU0AAAAAAAAAAAAAAAABAQEJCQkSEhIbGxskJCQuLi43Nzc9PT1E + RERJSUlPT09UVFRZWVlcXFxgYGBkZGRnZ2dqampmZmapqan///////////////////////////////// + ///////////////////////////////////////Pz8+Xl5ebm5ubm5ubm5ubm5uYmJjOzs7+/v7///// + ///////////////////////////////////////////////////+/v7+/v7///////////////////// + ///////////////////////////////////////6+vq2traXl5ecnJycnJyVlZWFhYV4eHh1dXV0dHRz + c3NxcXFvb29tbW1qampnZ2dkZGRhYWFdXV1ZWVlVVVVQUFBKSkpFRUU+Pj44ODgwMDAmJiYdHR0UFBQL + CwsDAwMAAAAAAAAAAAAAAAAAAAAAAAAPDw/W1tYAAAAAAAAAAAAAAAAAAACRkIsAAAAAAAAAAAAAAAAB + AQEHBwcQEBAZGRkiIiIsLCw1NTU7OztCQkJISEhOTk5SUlJXV1dbW1tfX19jY2NmZmZmZmZ6enrs7Oz/ + ///////////////////////////r6+vW1tbT09Po6Oj9/f3////////////////////////5+fmtra2Y + mJibm5ubm5ubm5uYmJimpqb39/f///////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////V1dWWlpab + m5ubm5ubm5ucnJycnJyRkZF9fX1ycnJycnJxcXFvb29sbGxpaWlmZmZjY2NgYGBcXFxYWFhUVFRPT09J + SUlDQ0M9PT02NjYuLi4kJCQaGhoREREJCQkCAgIAAAAAAAAAAAAAAAAAAAAAAAA4ODj6+voAAAAAAAAA + AAAAAAAAAADR0MsMCwoAAAAAAAAAAAAAAAAFBQUODg4XFxcfHx8pKSkyMjI5OTlAQEBGRkZMTExRUVFW + VlZaWlpeXl5iYmJkZGRkZGS/v7/////////////////////////4+PjBwcGenp6YmJiYmJicnJy6urrz + 8/P////////////////////////g4OCcnJybm5ucnJycnJyYmJjOzs7///////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////r6+ugoKCbm5ucnJycnJycnJycnJydnZ2ampqGhoZycnJvb29ubm5ra2to + aGhlZWViYmJfX19aWlpWVlZSUlJNTU1HR0dCQkI7Ozs0NDQrKysiIiIYGBgQEBAHBwcBAQEAAAAAAAAA + AAAAAAAAAAAAAAB7e3sAAAAAAAAAAAAAAAAAAAAAAAD4+PdAPz0AAAAAAAAAAAAAAAADAwMMDAwUFBQd + HR0nJycwMDA3Nzc+Pj5ERERKSkpPT09UVFRYWFhdXV1hYWFjY2NlZWWpqanz8/P////////////////9 + /f2/v7+YmJicnJydnZ2dnZ2cnJyYmJi0tLT5+fn////////////////6+vrQ0NCenp6dnZ2dnZ2bm5ut + ra34+Pj///////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////Ly8uYmJidnZ2dnZ2dnZ2d + nZ2dnZ2enp6enp6NjY1ycnJsbGxqampnZ2dkZGRhYWFeXl5ZWVlVVVVQUFBLS0tGRkZAQEA5OTkyMjIp + KSkgICAWFhYODg4FBQUAAAAAAAAAAAAAAAAAAAAAAAAHBwe/v78AAAAAAAAAAAAAAAAAAAAAAAAAAACL + i4gAAAAAAAAAAAAAAAACAgIKCgoSEhIbGxskJCQtLS02NjY9PT1DQ0NJSUlOTk5TU1NXV1dcXFxgYGBj + Y2NlZWVlZWWCgoLQ0ND////////////k5OScnJydnZ2enp6enp6enp6enp6enp6amprY2Nj///////// + ///n5+eysrKcnJyenp6enp6dnZ2cnJzd3d3///////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////39/erq6ucnJyenp6enp6enp6enp6enp6enp6goKCSkpJwcHBoaGhnZ2djY2NgYGBdXV1Z + WVlUVFRPT09KSkpEREQ+Pj43NzcvLy8mJiYdHR0UFBQLCwsDAwMAAAAAAAAAAAAAAAAAAAAAAAAvLy/0 + 9PQAAAAAAAAAAAAAAAAAAAAAAAAAAADZ2NYRERAAAAAAAAAAAAAAAAAHBwcPDw8YGBghISEqKiozMzM6 + OjpBQUFHR0dMTExSUlJWVlZaWlpeXl5iYmJlZWVnZ2dhYWGampr////////////Ly8ubm5ufn5+fn5+f + n5+fn5+fn5+fn5+bm5u/v7/////////////Hx8eYmJifn5+fn5+fn5+bm5u5ubn9/f3///////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////a2tqcnJyenp6fn5+fn5+fn5+fn5+fn5+f + n5+hoaGTk5Nra2tkZGRiYmJeXl5bW1tXV1dSUlJNTU1ISEhCQkI8PDw1NTUsLCwjIyMaGhoREREICAgC + AgIAAAAAAAAAAAAAAAAAAAAAAACBgYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v1ZWVcAAAAAAAAA + AAAAAAAFBQUNDQ0VFRUfHx8oKCgxMTE4ODg/Pz9FRUVKSkpPT09UVFRYWFhcXFxgYGBjY2NmZmZiYmKh + oaH////////////Hx8ecnJygoKCgoKCgoKCgoKCgoKCgoKCdnZ27u7v////////////Nzc2cnJygoKCg + oKCfn5+jo6Pq6ur///////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////7 + +/u4uLidnZ2goKCgoKCgoKCgoKCgoKCgoKCgoKCjo6OOjo5kZGRgYGBdXV1ZWVlVVVVQUFBLS0tGRkZA + QEA5OTkyMjIpKSkgICAXFxcODg4GBgYAAAAAAAAAAAAAAAAAAAAAAAAPDw/T09MAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAC5ubgFBQUAAAAAAAAAAAACAgIKCgoSEhIbGxskJCQtLS02NjY8PDxCQkJISEhN + TU1SUlJWVlZaWlpeXl5hYWFgYGBpaWnAwMD////////////Y2Nienp6ioqKioqKioqKioqKioqKioqKe + np7MzMz////////////U1NSfn5+fn5+ioqKenp7Dw8P///////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////l5eWioqKhoaGioqKioqKioqKioqKioqKioqKioqKlpaV/ + f39cXFxbW1tXV1dTU1NOTk5JSUlEREQ9PT03NzcvLy8lJSUdHR0UFBQMDAwDAwMAAAAAAAAAAAAAAAAA + AAAAAABVVVX+/v4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6+vpBQUEAAAAAAAAAAAABAQEHBwcP + Dw8XFxcgICApKSkyMjI5OTk/Pz9FRUVLS0tQUFBUVFRZWVldXV1eXl56enrLy8v9/f3////////////1 + 9fWvr6+goKCjo6Ojo6Ojo6Ojo6OhoaGoqKju7u7////////////9/f3h4eG1tbWioqKgoKC8vLz5+fn/ + ///////////////////////////////////////////////////////19fXw8PD29vb5+fn6+vr4+Pj1 + 9fXz8/P////////////////////////////////////////////////////////////a2tqioqKjo6Oj + o6Ojo6Ojo6Ojo6Ojo6Ojo6Ojo6OkpKSfn59nZ2dYWFhVVVVRUVFMTExGRkZBQUE7Ozs0NDQrKysiIiIZ + GRkREREICAgBAQEAAAAAAAAAAAAAAAAAAAAEBAS7u7sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAACurq4DAwMAAAAAAAAAAAAEBAQMDAwVFRUcHBwlJSUuLi43Nzc9PT1DQ0NISEhNTU1SUlJXV1da + WlpbW1vHx8f////////////////////////l5eWrq6ugoKCioqKioqKhoaGoqKjd3d3///////////// + ///////////q6uqmpqakpKSjo6O1tbXi4uL+/v7///////////////////////////////////////// + ///7+/u6urqlpaWurq6ysrK0tLSxsbGtra2qqqrp6en///////////////////////////////////// + ///////////v7+/FxcWlpaWkpKSlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWlpaWoqKiEhIRUVFRTU1NO + Tk5JSUlEREQ+Pj44ODgxMTEnJycfHx8WFhYODg4FBQUAAAAAAAAAAAAAAAAAAAAAAABFRUX5+fkAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4+Pg/Pz8AAAAAAAAAAAABAQEICAgREREZGRkiIiIq + Kio0NDQ6OjpAQEBGRkZLS0tQUFBUVFRYWFhXV1eenp7+/v7////////////////////////w8PDLy8u4 + uLi3t7fHx8fr6+v////////////////////////////Jycmjo6Onp6enp6ekpKSlpaXBwcHs7Oz///// + ///////////////////////////////////b29ukpKSmpqalpaWlpaWkpKSlpaWlpaWjo6O9vb39/f3/ + ///////////////////////////////////4+PjT09OsrKyjo6Ompqanp6enp6enp6enp6enp6enp6en + p6enp6enp6enp6eoqKidnZ1bW1tQUFBMTExHR0dBQUE7Ozs1NTUsLCwkJCQbGxsTExMKCgoDAwMAAAAA + AAAAAAAAAAAAAAAEBAS1tbUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACysrIG + BgYAAAAAAAAAAAAFBQUNDQ0VFRUeHh4mJiYwMDA3Nzc9PT1DQ0NJSUlNTU1SUlJVVVVaWlqUlJTh4eH/ + ///////////////////////////////9/f39/f3////////////////////////////////o6Oipqamn + p6eoqKioqKioqKioqKikpKSrq6vOzs719fX////////////////////////////39/eysrKmpqaoqKio + qKioqKioqKioqKioqKioqKimpqbe3t7////////////////////////////9/f3f39+2trakpKSnp6eo + qKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKipqalpaWlLS0tJSUlEREQ+Pj45OTkx + MTEoKCggICAXFxcPDw8HBwcAAAAAAAAAAAAAAAAAAAAAAABNTU35+fkAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAD8/PxRUVEAAAAAAAAAAAACAgIJCQkREREZGRkiIiIrKyszMzM6OjpA + QEBGRkZKSkpQUFBSUlJfX1+goKC3t7f4+Pj////////t7e36+vr///////////////////////////// + ///////w8PD+/v7////8/Py/v7+np6epqampqampqampqampqampqampqammpqazs7Pa2tr7+/v///// + ///////////////R0dGmpqapqampqampqampqampqampqampqampqamnp6e2trb4+Pj///////////// + ///////q6urAwMCoqKinp6epqampqampqampqampqampqampqampqampqampqampqampqampqampqamp + qamtra13d3dISEhGRkZBQUE7Ozs1NTUsLCwkJCQbGxsTExMLCwsDAwMAAAAAAAAAAAAAAAAAAAAMDAzG + xsYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADPz88QEBAAAAAAAAAA + AAAGBgYODg4VFRUeHh4nJycvLy83Nzc9PT1DQ0NISEhNTU1PT09gYGCnp6epqanU1NTy8vLMzMytra2/ + v7/n5+f9/f3////////////////+/v7v7+/Ly8uurq7FxcXw8PDd3d2pqamsrKysrKysrKysrKysrKys + rKysrKysrKysrKyqqqqpqam+vr7n5+f////////////v7++vr6+rq6usrKysrKysrKysrKysrKysrKys + rKysrKysrKyoqKjU1NT////////////z8/PMzMytra2oqKirq6usrKysrKysrKysrKysrKysrKysrKys + rKysrKysrKysrKysrKysrKysrKysrKysrKywsLB+fn5FRUVDQ0M+Pj44ODgwMDAoKCggICAXFxcPDw8H + BwcBAQEAAAAAAAAAAAAAAAAAAABwcHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACDg4MAAAAAAAAAAAACAgIKCgoREREaGhoiIiIqKiozMzM5OTk/Pz9FRUVKSkpN + TU1eXl6pqamurq6wsLCzs7Oqqqqtra2qqqqwsLDx8fH////////////////4+Pi3t7eqqqqtra2rq6uy + srKxsbGtra2urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6rq6utra3Jycnw8PD5+fnGxsar + q6uurq6urq6urq6urq6urq6urq6urq6urq6urq6urq6tra2ysrLs7Oz4+PjZ2dm0tLSqqqqtra2urq6u + rq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6urq6ysrJ9fX1CQkJA + QEA7Ozs0NDQsLCwjIyMbGxsTExMLCwsEBAQAAAAAAAAAAAAAAAAAAAAnJyfn5+cAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADy8vI8PDwAAAAAAAAAAAAFBQUNDQ0V + FRUdHR0lJSUtLS01NTU7OztBQUFGRkZKSkpXV1enp6eysrKvr6+vr6+wsLCwsLCvr6+ysrLx8fH///// + ///////////4+Pi2travr6+wsLCwsLCvr6+vr6+wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCw + sLCwsLCwsLCvr6+srKy0tLS6urqvr6+wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCv + r6+1tbW6urqurq6urq6wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCw + sLCwsLCwsLCwsLCwsLC0tLR0dHQ+Pj49PT03NzcvLy8nJycfHx8XFxcODg4HBwcBAQEAAAAAAAAAAAAA + AAAGBgavr68AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAADKysoSEhIAAAAAAAACAgIICAgQEBAYGBghISEpKSkxMTE3Nzc9PT1DQ0NHR0dNTU2ampq1tbWy + srKysrKysrKysrKxsbG0tLTv7+/9/f39/f39/f39/f319fW4uLixsbGysrKysrKysrKysrKysrKysrKy + srKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKxsbGwsLCysrKysrKysrKysrKysrKy + srKysrKysrKysrKysrKysrKysrKysrKysrKxsbGwsLCysrKysrKysrKysrKysrKysrKysrKysrKysrKy + srKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKzs7Ozs7NgYGA7Ozs4ODgyMjIrKysiIiIa + GhoSEhIKCgoDAwMAAAAAAAAAAAAAAAAAAABubm4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACTk5MBAQEAAAAAAAAEBAQMDAwUFBQbGxsjIyMr + Kys0NDQ6OjpAQEBERERFRUV/f3+5ubm1tbW1tbW1tbW1tbW1tbW1tbXCwsLHx8fGxsbGxsbHx8fDw8O1 + tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1 + tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1 + tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW4 + uLimpqZHR0c5OTk1NTUuLi4lJSUdHR0VFRUODg4GBgYAAAAAAAAAAAAAAAAAAAA6Ojru7u4AAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7+/tj + Y2MAAAAAAAABAQEHBwcPDw8WFhYeHh4lJSUvLy81NTU7OztAQEBDQ0NZWVmxsbG5ubm3t7e3t7e3t7e3 + t7e3t7e1tbW1tbW1tbW1tbW1tbW1tbW3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3 + t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3 + t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e3 + t7e3t7e3t7e3t7e3t7e3t7e3t7e3t7e9vb1/f384ODg2NjYvLy8oKCgfHx8XFxcQEBAJCQkCAgIAAAAA + AAAAAAAAAAAYGBjPz88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs7Ow+Pj4AAAAAAAADAwMKCgoREREZGRkhISEoKCgwMDA3Nzc8 + PDxCQkJDQ0OIiIi/v7+5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5 + ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5 + ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5 + ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm8vLywsLBLS0s2NjYyMjIq + KioiIiIaGhoTExMLCwsEBAQAAAAAAAAAAAAAAAAJCQmqqqoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADa2tonJycAAAAA + AAAFBQUMDAwUFBQbGxsjIyMqKioyMjI4ODg+Pj5BQUFRUVGsrKzAwMC8vLy8vLy8vLy8vLy8vLy8vLy8 + vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8 + vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8 + vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8 + vLy8vLy9vb3BwcFycnI0NDQzMzMrKyskJCQdHR0VFRUODg4HBwcBAQEAAAAAAAAAAAABAQGLi4sAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAADKysocHBwAAAABAQEHBwcODg4VFRUdHR0kJCQsLCwzMzM5OTk/Pz8/Pz9m + Zma9vb3CwsK/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/ + v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/ + v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/ + v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7/FxcWQkJA5OTkzMzMtLS0mJiYfHx8XFxcQEBAJCQkC + AgIAAAAAAAAAAAAAAAB0dHT+/v4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC+vr4UFBQAAAADAwMJCQkQ + EBAXFxceHh4mJiYtLS00NDQ5OTk/Pz8/Pz91dXXCwsLExMTCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC + wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC + wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLC + wsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLIyMienp5AQEAz + MzMvLy8nJycgICAZGRkREREKCgoEBAQAAAAAAAAAAAAAAABoaGj5+fkAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAC3t7cVFRUAAAADAwMKCgoREREYGBgfHx8nJycuLi40NDQ6Ojo/Pz8+Pj52dnbDw8PI + yMjExMTExMTFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXF + xcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXF + xcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXFxcXF + xcXFxcXExMTGxsbLy8udnZ1DQ0MzMzMwMDAoKCghISEZGRkSEhILCwsEBAQAAAAAAAAAAAAAAABhYWH1 + 9fUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC6urodHR0AAAAFBQUMDAwSEhIZGRkg + ICAnJycvLy81NTU6Ojo+Pj4/Pz9sbGy7u7vNzc3IyMjHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH + x8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH + x8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH + x8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fLy8vKysqOjo4+Pj4zMzMvLy8pKSkhISEaGhoTExMN + DQ0GBgYBAQEAAAAAAAAAAABtbW319fUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAADHx8cnJycAAAAGBgYMDAwTExMaGhohISEoKCgvLy81NTU6Ojo+Pj4+Pj5ZWVmnp6fPz8/Nzc3L + y8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vL + y8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vL + y8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vR0dHAwMBzc3M5 + OTk0NDQwMDApKSkiIiIbGxsUFBQNDQ0HBwcCAgIAAAAAAAACAgJ8fHz6+voAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADX19c5OTkAAAAGBgYNDQ0UFBQbGxsiIiIoKCgu + Li40NDQ6Ojo+Pj4/Pz9ISEiCgoLCwsLU1NTPz8/Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O + zs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O + zs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O + zs7Ozs7Ozs7S0tLQ0NCgoKBSUlI1NTU1NTUwMDApKSkiIiIbGxsVFRUODg4ICAgCAgIAAAAAAAAMDAyU + lJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADo + 6OhbW1sAAAAEBAQNDQ0TExMaGhogICAnJycuLi40NDQ4ODg9PT1AQEBAQEBYWFiZmZnMzMzY2NjU1NTR + 0dHS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS + 0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS + 0tLS0tLS0tLS0tLS0tLS0tLS0tLR0dHS0tLW1tbV1dWzs7Nubm49PT02NjY1NTUvLy8oKCgiIiIbGxsU + FBQODg4ICAgCAgIAAAAAAAAdHR2ysrIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6+vqGhoYLCwsAAAANDQ0TExMZGRkgICAmJiYsLCwyMjI4 + ODg8PDw/Pz9AQEBDQ0NgYGCcnJzLy8vb29vZ2dnW1tbV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV + 1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV + 1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXY2Njc3NzW1tazs7N1dXVDQ0M3 + Nzc4ODg0NDQuLi4nJychISEaGhoUFBQODg4ICAgCAgIAAAAAAABCQkLT09MAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3t7cr + KysAAAAKCgoTExMZGRkeHh4lJSUrKysxMTE2NjY6Ojo+Pj5CQkJBQUFDQ0NbW1uNjY2+vr7Z2dnf39/c + 3Nza2trZ2dnY2NjZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ + 2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnY2NjY2NjZ2dnb29ve + 3t7e3t7Nzc2ioqJqampDQ0M5OTk5OTk3NzcyMjIsLCwmJiYgICAaGhoUFBQODg4ICAgCAgIAAAAICAh1 + dXXz8/MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADn5+dkZGQHBwcFBQUSEhIXFxcdHR0jIyMpKSkvLy80NDQ4ODg8 + PDw/Pz9DQ0NDQ0NDQ0NOTk5vb2+cnJzExMTb29vi4uLi4uLf39/d3d3c3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc + 3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc3Nzc + 3Nzc3Nzd3d3e3t7h4eHj4+Pf39/Q0NCvr6+AgIBVVVU/Pz88PDw8PDw5OTk1NTUvLy8qKiokJCQeHh4Y + GBgTExMNDQ0HBwcAAAAAAAAtLS20tLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACtra0uLi4B + AQEMDAwWFhYbGxshISEmJiYsLCwyMjI2NjY6Ojo+Pj5BQUFERERFRUVFRUVGRkZSUlJubm6RkZG0tLTP + z8/d3d3k5OTm5ubl5eXj4+Ph4eHh4eHg4ODg4ODg4ODg4ODg4ODg4ODf39/f39/f39/f39/f39/g4ODg + 4ODg4ODg4ODg4ODh4eHh4eHi4uLk5OTm5ubm5ubi4uLW1tbBwcGfn594eHhYWFhEREQ/Pz8/Pz8+Pj46 + Ojo3NzczMzMtLS0nJyciIiIcHBwXFxcRERELCwsFBQUAAAAJCQlxcXHn5+cAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAADp6el8fHwWFhYEBAQSEhIaGhofHx8kJCQqKiouLi40NDQ4ODg7Ozs+ + Pj5BQUFERERGRkZISEhHR0dHR0dMTExaWlpwcHCKioqkpKS8vLzOzs7a2tri4uLm5ubo6Ojp6enq6urq + 6urq6urp6enp6enp6enp6enp6enq6urq6urp6enp6enm5ubk5OTe3t7U1NTDw8Ourq6Tk5N5eXlgYGBN + TU1ERERCQkJCQkJCQkI/Pz88PDw4ODg0NDQwMDArKyslJSUfHx8aGhoVFRUPDw8KCgoAAAAAAABDQ0O/ + v78AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADPz89cXFwODg4J + CQkWFhYcHBwiIiImJiYsLCwwMDA0NDQ4ODg7Ozs+Pj5AQEBERERGRkZISEhKSkpKSkpJSUlJSUlMTExQ + UFBYWFhkZGRycnJ/f3+NjY2YmJiioqKrq6utra21tbW4uLi3t7e4uLiwsLCsrKylpaWcnJySkpKFhYV2 + dnZoaGhbW1tQUFBKSkpGRkZFRUVFRUVFRUVERERBQUE/Pz88PDw5OTk1NTUxMTEtLS0nJycjIyMdHR0Y + GBgTExMODg4DAwMAAAAtLS2cnJz39/cAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAD+/v6+vr5RUVEQEBALCwsXFxceHh4jIyMoKCgsLCwwMDA0NDQ3Nzc6Ojo9 + PT1AQEBCQkJERERGRkZISEhJSUlLS0tMTExNTU1MTExLS0tLS0tLS0tMTExMTExOTk5PT09QUFBQUFBQ + UFBQUFBOTk5OTk5MTExKSkpKSkpJSUlJSUlKSkpJSUlISEhISEhHR0dFRUVCQkJAQEA+Pj47Ozs4ODg1 + NTUyMjItLS0oKCgkJCQfHx8aGhoVFRUQEBAGBgYAAAAlJSWMjIzr6+sAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD7+/u+vr5cXFwYGBgL + CwsWFhYfHx8jIyMoKCgsLCwwMDAzMzM3Nzc5OTk8PDw+Pj5AQEBCQkJDQ0NFRUVHR0dISEhJSUlKSkpK + SkpLS0tMTExMTExNTU1NTU1NTU1NTU1NTU1NTU1MTExMTExLS0tLS0tKSkpJSUlJSUlHR0dFRUVERERC + QkJBQUE+Pj48PDw5OTk3Nzc0NDQxMTEsLCwpKSkkJCQfHx8bGxsXFxcQEBAFBQUCAgIuLi6QkJDo6OgA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAADQ0NB1dXUrKysODg4TExMcHBwjIyMnJycrKysvLy8yMjI1NTU4ODg5 + OTk8PDw+Pj4+Pj5AQEBCQkJDQ0NERERFRUVGRkZHR0dHR0dISEhISEhISEhISEhISEhISEhHR0dHR0dG + RkZGRkZFRUVERERDQ0NAQEA/Pz8+Pj49PT07Ozs4ODg1NTUyMjIvLy8sLCwoKCgkJCQgICAcHBwWFhYN + DQ0EBAQPDw9ISEilpaXv7+8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADn5+ehoaFSUlIg + ICAQEBAWFhYeHh4lJSUpKSksLCwvLy8yMjI1NTU2NjY4ODg6Ojo8PDw9PT09PT0/Pz9AQEBAQEBBQUFB + QUFCQkJCQkJCQkJCQkJBQUFBQUFAQEBAQEA/Pz8+Pj49PT08PDw6Ojo4ODg2NjY1NTUyMjIwMDAtLS0p + KSkmJiYjIyMfHx8ZGRkSEhIJCQkMDAwvLy94eHjKysr8/PwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAD+/v7U1NSUlJRQUFAlJSUVFRUWFhYeHh4kJCQoKCgrKysuLi4wMDAz + MzM0NDQ1NTU3Nzc4ODg5OTk5OTk6Ojo6Ojo7Ozs7Ozs7Ozs7Ozs6Ojo6Ojo5OTk5OTk4ODg3Nzc2NjY0 + NDQzMzMwMDAuLi4sLCwpKSknJyckJCQfHx8ZGRkSEhIMDAwTExMxMTFtbW23t7fv7+8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD+/v7Y2Nil + paVqamo9PT0iIiIYGBgYGBgdHR0jIyMnJycqKiosLCwuLi4vLy8xMTEyMjIzMzMzMzMzMzMzMzMzMzMz + MzMzMzMzMzMyMjIxMTExMTEvLy8tLS0rKyspKSkmJiYjIyMgICAaGhoUFBQREREWFhYnJydOTk6Hh4fB + wcHw8PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz8/PPz8+kpKR6enpSUlI1NTUlJSUdHR0bGxscHBwd + HR0fHx8iIiIkJCQmJiYnJycoKCgoKCgnJycnJycmJiYkJCQjIyMhISEeHh4bGxsZGRkWFhYYGBgcHBwo + KCg/Pz9iYmKOjo65ubnk5OT9/f0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAD6+vrs7OzS0tKzs7OZmZmAgIBpaWlXV1dISEg+Pj42NjYxMTEsLCwsLCwsLCwrKystLS0zMzM5 + OTlCQkJNTU1eXl5zc3OMjIympqbDw8Ph4eH29vYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6+vr09PTw8PDs + 7Ozn5+ff39/f39/f39/f39/i4uLr6+vu7u7y8vL39/f9/f0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////////// + ///////////////////////h/////////AAD///////4AP///////gAAAAf/////AAD//////+AAAAAA + f///8AAB//////8AAAAAAA///wAAA//////4AAAAAAAB//gAAAf/////4AAAAAAAAD/AAAAP/////wAA + AAAAAAAOAAAAH/////wAAAAAAAAAAAAAAD/////wAAAAAAAAAAAAAAB/////4AAAAAAAAAAAAAAA//// + /4AAAAAAAAAAAAAAAf////4AAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAH////+AAAAAAAAAAAAAAA + B////+AAAAAAAAAAAAAAAA/////AAAAAAAAAAAAAAAAf////gAAAAAAAAAAAAAAAP////wAAAAAAAAAA + AAAAAH////4AAAAAAAAAAAAAAAB////8AAAAAAAAAAAAAAAA////+AAAAAAAAAAAAAAAAf////AAAAAA + AAAAAAAAAAD////gAAAAAAAAAAAAAAAAf///wAAAAAAAAAAAAAAAAD///4AAAAAAAAAAAAAAAAAf//+A + AAAAAAAAAAAAAAAAD///AAAAAAAAAAAAAAAAAAf//gAAAAAAAAAAAAAAAAAH//4AAAAAAAAAAAAAAAAA + A//8AAAAAAAAAAAAAAAAAAH/+AAAAAAAAAAAAAAAAAAB//gAAAAAAAAAAAAAAAAAAP/wAAAAAAAAAAAA + AAAAAAD/8AAAAAAAAAAAAAAAAAAAf+AAAAAAAAAAAAAAAAAAAH/gAAAAAAAAAAAAAAAAAAA/4AAAAAAA + AAAAAAAAAAAAP8AAAAAAAAAAAAAAAAAAAB/AAAAAAAAAAAAAAAAAAAAfgAAAAAAAAAAAAAAAAAAAD4AA + AAAAAAAAAAAAAAAAAA+AAAAAAAAAAAAAAAAAAAAPgAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAA + AAcAAAAAAAAAAAAAAAAAAAAHAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAA + AAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAA + AAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAA + AAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAA + AAMAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAA + AAAAAAADgAAAAAAAAAAAAAAAAAAAA4AAAAAAAAAAAAAAAAAAAAeAAAAAAAAAAAAAAAAAAAAHgAAAAAAA + AAAAAAAAAAAAB8AAAAAAAAAAAAAAAAAAAAfAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAD+AA + AAAAAAAAAAAAAAAAAA/gAAAAAAAAAAAAAAAAAAAf4AAAAAAAAAAAAAAAAAAAH/AAAAAAAAAAAAAAAAAA + AB/wAAAAAAAAAAAAAAAAAAA/+AAAAAAAAAAAAAAAAAAAP/gAAAAAAAAAAAAAAAAAAH/8AAAAAAAAAAAA + AAAAAAB//AAAAAAAAAAAAAAAAAAA//4AAAAAAAAAAAAAAAAAAf//AAAAAAAAAAAAAAAAAAH//wAAAAAA + AAAAAAAAAAAD//+AAAAAAAAAAAAAAAAAB///wAAAAAAAAAAAAAAAAAf//8AAAAAAAAAAAAAAAAAP///g + AAAAAAAAAAAAAAAAH///8AAAAAAAAAAAAAAAAD////gAAAAAAAAAAAAAAAA////8AAAAAAAAAAAAAAAA + f////gAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAH/////gAAAAAAAAAAAAAAD/////8AAAAAAAAAA + AAAAD//////gAAAAAAAAAAAAAB//////8AAAAAAAAAAAAAA///////wAAAAAAAAAAAAAf//////+AAAA + AAAAAAAAAf///////4AAAAAAAAAAAAP////////AAAAAAAAAAAAP////////8AAAAAAAAAAAH/////// + //gAAAAAAAAAAH/////////+AAAAAAAAAAH//////////8AAAAAAAAAH///////////wAAAAAAAAH/// + /////////AAAAAAAAP////////////+AAAAAAAf/////////////+AAAAAA///////////////+AAAAH + /////////////////wAB//////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////ygA + AAAwAAAAYAAAAAEAGAAAAAAAABsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAACurq2trKqsq6isrKmurasAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjoqCXlo+opp+mpJsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/vrvS0MnU0cW2s6aYlYl9enFraWBhYFhhX1dpZ156d26V + koa3tKjFw7vFxMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChoJ2rqJ2Kh3lLST0fHhgKCQYBAQFL + S0sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsq6jS0MasqZ1qaGE8OzgpKSgo + KCgrKysvLy8xMTEyMjIxMTEvLy8sLCwoKCgjIyMiIyIzMy9jYViioJa0s6wAAAAAAAAAAACpp5+VkodO + TEIaGRQFBQQBAAAAAAAAAAABAQFgYGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADB + wLzMyb17eW8yMS4gICAoKCgxMTE4ODg8PDxAQEBDQ0NERERFRUVERERDQ0NBQUE9PT05OTkzMzMrKysg + ICAXFxclJCF1cmmsqqFbWU8VFBECAgEAAAAAAAAAAAAAAAAAAAACAgJeXl4AAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAACpqKfKyLxwbWQbGhodHR4rKys1NTU9PT1ERERJSUlNTU1QUFBSUlJUVFRU + VFRUVFRSUlJRUVFOTk5KSkpFRUU/Pz83NzctLS0gICATExMHBwcAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABNTU0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEw7yYlYkiIR8WFhYnJyc0NDQ+Pj5F + RUVMTExSUlJWVlZaWlpcXFxeXl5fX19fX19fX19eXl5dXV1aWlpXV1dSUlJNTU1HR0dAQEA2NjYoKCga + GhoLCwsBAQEAAAAAAAAAAAAAAAAAAABFRUUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADT + 0MZiYFgNDQwaGhorKys5OTlDQ0NLS0tTU1NYWFhdXV1hYWFkZGRlZWVnZ2doaGhoaGhoaGhnZ2dmZmZk + ZGRhYWFeXl5ZWVlUVFRNTU1FRUU7OzsuLi4eHh4NDQ0BAQEAAAAAAAAAAAAbGxuRkZEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAADNy782NTEJCQkbGxsuLi48PDxGRkZQUFBXV1ddXV1iYmJmZmZpaWls + bGxtbW1ubm5vb29vb29vb29ubm5tbW1sbGxqampnZ2djY2NfX19ZWVlRUVFJSUk+Pj4wMDAeHh4NDQ0B + AQEAAAAHBweampoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGxLcqKSYICAgZGRkuLi48PDxI + SEhTU1NaWlpgYGBlZWVpaWlsbGxvb29xcXFycnJzc3N0dHR0dHR0dHRzc3Nzc3NxcXFvb29ubm5qampm + ZmZhYWFbW1tUVFRKSko/Pz8wMDAeHh4LCwsAAAAWFhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADRzsQ5ODMEBAQWFhYpKSk7OztISEhTU1NbW1tiYmJoaGhsbGxwcHBzc3N0dHR2dnZ3d3d4eHh4eHh4 + eHh4eHh4eHh3d3d2dnZ1dXVzc3NwcHBtbW1oaGhjY2NdXV1VVVVLS0s9PT0uLi4ZGRkGBgYAAAAuLiwA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHxsBSUUoBAQEQEBAlJSU4ODhGRkZSUlJbW1tiYmJoaGhtbW1x + cXF0dHR2dnZ4eHh5eXl6enp6enp/f3+Kiop7e3t6enp6enp5eXl7e3uHh4d0dHRxcXFubm5paWljY2Nc + XFxUVFRJSUk7OzspKSkUFBQDAwMAAABYWFcAAAAAAAAAAAAAAAAAAAAAAACko6GTkIYDAwMICAgcHBwy + MjJCQkJPT09ZWVliYmJoaGhtbW1ycnJ1dXV4eHh5eXl7e3t8fHx+fn6lpaXs7Ozy8vKEhIR9fX19fX17 + e3u5ubn6+vrMzMyDg4NycnJubm5qampjY2NcXFxRUVFFRUU2NjYhISEMDAwAAAADAwOGhoYAAAAAAAAA + AAAAAAAAAADPzMMXFxUCAgISEhIpKSk8PDxLS0tWVlZgYGBoaGhtbW1ycnJ1dXV3d3d6enp7e3t8fHyU + lJTa2tr+/v7////////GxsaDg4OEhISKior19fX////////09PSzs7N3d3dubm5paWliYmJYWFhNTU0/ + Pz8tLS0XFxcDAwMAAAAYGBgAAAAAAAAAAAAAAAC3t7R0cmkAAAAICAgdHR00NDRFRUVSUlJcXFxlZWVr + a2txcXF0dHR3d3d6enp8fHx9fX2EhIT29vb////////////////9/f339/f4+Pj39/f///////////// + //////+/v79xcXFsbGxmZmZeXl5UVFRHR0c3NzciIiILCwsAAAAAAABgYGAAAAAAAAAAAADDwbgYFxUB + AQEPDw8nJyc8PDxLS0tXV1dhYWFpaWlvb290dHR3d3d6enp8fHx9fX1+fn5+fn7IyMj///////////// + ///////////////////////////////////39/eFhYV1dXVwcHBqampjY2NZWVlNTU0/Pz8rKysUFBQC + AgIAAAASEhIAAAAAAAAAAACTkYcBAQEDAwMXFxcvLy9CQkJQUFBcXFxlZWVsbGxycnJ2dnZ5eXl7e3t9 + fX1+fn5/f39/f3+Li4v29vb////////////////////////////////////////////BwcF4eHh2dnZy + cnJtbW1mZmZeXl5TU1NFRUUzMzMcHBwFBQUAAAAAAACIiIgAAAC1tbFEQj0AAAAHBwcdHR01NTVGRkZU + VFRfX19oaGhubm50dHR3d3d6enp8fHx9fX1+fn5/f39/f3+oqKj9/f3////////////t7e3Hx8e8vLzY + 2Nj6+vr////////////b29t7e3t4eHh0dHRwcHBqamphYWFXV1dJSUk5OTkiIiIKCgoAAAAAAABGRkYA + AADn5t4WFhQAAAALCwsjIyM6OjpLS0tYWFhiYmJra2twcHB2dnZ5eXl8fHx9fX1+fn6BgYGBgYGEhITs + 7Oz////////8/Py6urqDg4N/f3+AgICAgICRkZHk5OT////////9/f2fn597e3t4eHhycnJsbGxkZGRa + WlpNTU0+Pj4oKCgPDw8BAQEAAAAZGRkAAADGw7YHBgYAAAAODg4oKCg9PT1NTU1aWlpkZGRsbGxxcXF2 + dnZ6enp8fHx9fX2FhYXn5+fu7u7v7+/+/v7////////Hx8eAgICAgICAgICAgICAgICAgICPj4/39/f/ + ///////29vbt7e3s7Oy2trZtbW1mZmZcXFxQUFBBQUEtLS0TExMCAgIAAAAHBwejo6OfnJADAwMBAQER + ERErKytAQEBPT09cXFxlZWVtbW1ycnJ3d3d6enp8fHx+fn6IiIj6+vr////////////////8/PyNjY2A + gICAgICAgICAgICAgICAgICAgIDKysr////////////////////IyMhubm5nZ2deXl5SUlJDQ0MwMDAW + FhYDAwMAAAAAAACCgoKHhHkBAQECAgITExMtLS1BQUFRUVFdXV1mZmZubm5zc3N4eHh7e3t9fX1+fn6I + iIj6+vr////////////////w8PCCgoKAgICAgICAgICAgICAgICAgICAgICqqqr///////////////// + ///Jyclvb29oaGhfX19TU1NFRUUyMjIYGBgEBAQAAAAAAABvb2+Gg3gBAQECAgIUFBQtLS1CQkJRUVFd + XV1mZmZubm5zc3N4eHh7e3t9fX1+fn6IiIj6+vr////////////////z8/ODg4OAgICAgICAgICAgICA + gICAgICAgICvr6/////////////////////Jyclvb29oaGhfX19TU1NFRUUyMjIYGBgEBAQAAAAAAABt + bW2WlIoCAgIBAQETExMsLCxBQUFQUFBdXV1mZmZubm5zc3N3d3d7e3t9fX1+fn6IiIj6+vr///////// + ///////+/v6WlpaAgICAgICAgICAgICAgICAgICAgIDa2tr////////////////////IyMhubm5oaGhe + Xl5TU1NEREQxMTEYGBgDAwMAAAAAAAB2dnatq6YEBAQBAQEQEBAqKio/Pz9OTk5bW1tlZWVtbW1ycnJ3 + d3eHh4e8vLy4uLiDg4PBwcHIyMjOzs77+/v////////i4uKZmZmVlZWVlZWTk5ORkZGMjIylpaX9/f3/ + ///////m5ubDw8PCwsKcnJxubm5nZ2ddXV1RUVFCQkIvLy8VFRUCAgIAAAACAgKSkpKNjIoLCwsAAAAO + Dg4mJiY9PT1NTU1aWlpkZGRsbGx9fX1/f3+kpKT+/v739/eRkZGfn5+ZmZmYmJjc3Nz////////////j + 4+Onp6eampqampqdnZ3AwMD4+Pj////////9/f2bm5t5eXl2dnZycnJtbW1mZmZcXFxQUFBAQEAsLCwS + EhICAgIAAAAODg6QkJAAAAAqKikAAAAJCQkhISE4ODhJSUlXV1dhYWFpaWnW1tb29vb9/f339/f7+/v5 + +fn8/PzBwcGbm5ulpaX39/f////////////9/f3w8PDt7e339/f////////////////d3d2ZmZmKiop3 + d3dxcXFra2tjY2NZWVlMTEw8PDwmJiYODg4AAAAAAAAqKioAAAAAAABnZ2UAAAAFBQUbGxszMzNFRUVT + U1NeXl50dHTu7u7////S0tKgoKCmpqbu7u7+/v7h4eGcnJyurq77+/v///////////////////////// + ///////////////////f39+dnZ2dnZ2Xl5d3d3doaGhgYGBVVVVISEg3NzcgICAJCQkAAAAAAABiYmIA + AAAAAACZmZgICAcCAgIUFBQsLCxAQEBPT09aWlpkZGSbm5v8/Pypqamfn5+fn5/IyMj39/empqafn5/k + 5OT////////////////////////////////////////////////9/f20tLSfn5+fn5+cnJxvb29dXV1R + UVFDQ0MxMTEZGRkEBAQAAAADAwOIiIgAAAAAAAAAAABCQkIAAAANDQ0jIyM5OTlJSUlWVlZoaGjb29v+ + /v7AwMCjo6OkpKTh4eH9/f3Q0NCnp6fx8fH////////////////5+fnl5eXp6enq6ur///////////// + ///9/f3Nzc2jo6Ojo6Ojo6OXl5dZWVlMTEw9PT0oKCgREREBAQEAAAA0NDQAAAAAAAAAAAAAAACTk5ME + BAQFBQUZGRkwMDBCQkJPT09paWnw8PD+/v77+/vk5OTr6+v+/v7////Y2Ninp6erq6vNzc34+Pj///// + ///IyMinp6eoqKiqqqrx8fH////+/v7o6Oi4uLioqKioqKioqKioqKinp6dlZWVFRUU0NDQeHh4ICAgA + AAAEBASZmZkAAAAAAAAAAAAAAAAAAABLS0sBAQEPDw8lJSU6OjpISEhsbGzBwcHDw8PY2Nj////8/PzI + yMjOzs6zs7OsrKysrKysrKy0tLTh4eHr6+utra2srKysrKysrKzFxcXy8vLIyMitra2srKysrKysrKys + rKysrKytra1tbW09PT0pKSkUFBQCAgIAAABEREQAAAAAAAAAAAAAAAAAAAAAAACUlJQYGBgFBQUYGBgu + Li4/Pz9eXl6ysrKysrK/v7/v7+/q6uqzs7OysrKysrKysrKysrKysrKysrKysrKzs7OysrKysrKysrKy + srKysrKzs7OysrKysrKysrKysrKysrKysrKysrKzs7NdXV0yMjIdHR0ICAgAAAAYGBgAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACbm5sNDQ0MDAwgICAzMzNERESlpaW5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5 + ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmmpqY7Ozsk + JCQPDw8BAQEGBgaBgYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/f38HBwcSEhIlJSU3NzddXV25 + ubnAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA + wMDAwMDAwMDAwMDAwMC7u7tWVlYpKSkVFRUEBAQFBQWMjIwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAACCgoILCwsUFBQnJyc4ODheXl69vb3IyMjHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH + x8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fIyMi9vb1ZWVkqKioYGBgGBgYFBQWHh4cAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXl5caGhoVFRUnJyc3NzdLS0uXl5fKysrR + 0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHMzMyVlZVF + RUUqKioYGBgICAgQEBCBgYEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACRkZE6OjoVFRUkJCQ0NDQ/Pz9XV1eUlJTGxsbY2Nja2tra2tra2tra2tra2tra2tra2tra2tra2tra + 2tra2tra2trY2NjGxsaUlJRTU1M1NTUoKCgYGBgJCQkxMTGVlZUAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACDg4MfHx8eHh4tLS05OTlBQUFISEhdXV2BgYGn + p6fCwsLQ0NDX19fa2tra2trX19fQ0NDCwsKmpqaBgYFbW1tDQ0M6OjowMDAhISETExMSEhJ/f38AAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACj + o6Nubm4qKioiIiIuLi44ODg+Pj5ERERISEhLS0tOTk5RUVFTU1NTU1NQUFBNTU1JSUlEREQ/Pz85OTkw + MDAmJiYYGBghISF1dXWRkZEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACWlpZSUlIrKysqKioxMTE2NjY7Ozs+Pj4/Pz9A + QEA/Pz8+Pj48PDw4ODgyMjIsLCwjIyMjIyNISEiMjIydnZ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAACcnJyAgIBUVFQ9PT0xMTEtLS0sLCwrKysrKysuLi44ODhPT097e3udnZ0AAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACenp6goKCbm5ubm5ugoKCkpKQA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////8AAP// + /////wAA////////AAD///////8AAP//+D//8AAA//8AAf8AAAD/+AAAOAEAAP/gAAAAAwAA/4AAAAAH + AAD/AAAAAA8AAP4AAAAADwAA/AAAAAAfAAD4AAAAAD8AAPAAAAAAHwAA4AAAAAAPAADAAAAAAAcAAMAA + AAAABwAAgAAAAAADAACAAAAAAAMAAIAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAABAACAAAAAAAEAAIAA + AAAAAQAAwAAAAAADAADAAAAAAAMAAOAAAAAABwAA4AAAAAAPAADwAAAAAA8AAPgAAAAAHwAA/AAAAAA/ + AAD+AAAAAH8AAP8AAAAA/wAA/8AAAAP/AAD/4AAAB/8AAP/8AAAf/wAA//+AAf//AAD///gf//8AAP// + /////wAA////////AAAoAAAAIAAAAEAAAAABABgAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl5aTAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAu7m0t7SrnJmSgX93cG9nbWtleHdvhYJ6p6WdrqynAAAAAAAAAAAAAAAAAAAA + AAAAmZiRenhvSUhBHx4ZKyspAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuLeyp6WbVFNOLy8uLS0tNDQ1 + OTk5PDw8Ozs7ODg4MzMzKioqJSQkOTg0h4Z+o6KeoqCZWFZPFRUSAQEBAAAAAAAAKCgoAAAAAAAAAAAA + AAAAAAAAAAAAAAAAqqmmnJqQMTAtJCQkNTU1QUFBSUlJT09PUlJSU1NTU1NTUVFRTU1NSEhIPj4+MjIy + ICAgFRQUAwMCAAAAAAAAAAAAAAAAHR0dAAAAAAAAAAAAAAAAAAAAAAAAAAAAvLuzRkQ/GRoZMDAwQUFB + TExMVlZWXFxcYGBgY2NjZGRkZGRkYmJiX19fW1tbVFRUS0tLPT09KysrFBQUAgICAAAAAAAADg4OkJCQ + AAAAAAAAAAAAAAAAAAAAAAAAsrCnHh0bGxsbNDQ0RkZGVFRUXV1dZGRkaWlpbGxsbm5ub29vb29vbm5u + bGxsaGhoY2NjXFxcUVFRREREMDAwFhYWAgICAgICeXl5AAAAAAAAAAAAAAAAAAAAAAAAtrOqFBQSGBgY + NDQ0SEhIV1dXYWFhaWlpbm5ucnJydXV1dnZ2d3d3d3d3dnZ2dHR0cXFxbW1taGhoX19fVFRURUVFLy8v + EhISAgICYmFfAAAAAAAAAAAAAAAAAAAAt7avHx4cEBAQLi4uRkZGVlZWYmJia2trcXFxdXV1eHh4eXl5 + e3t7k5OTfHx8enp6eXl5jY2NeXl5cHBwaWlpYGBgVFRUQkJCKCgoCwsLBAQEioqJAAAAAAAAAAAAAAAA + VlROBgYGIyMjQEBAU1NTYWFha2trcnJydnZ2enp6e3t7j4+P29vb/v7+p6enfX19hYWF9vb27+/voKCg + cnJyaWlpX19fUFBQOzs7HBwcAgICFxcXAAAAAAAAAAAAs7CpBQQEEhISMzMzS0tLXFxcaGhocXFxdnZ2 + enp6fHx8jIyM+vr6////////+Pj47Ozs7u7u////////////srKyb29vZmZmWVlZR0dHLS0tDAwMAAAA + V1dXAAAAAAAAU1JMAwMDHx8fPj4+VFRUY2Njbm5udXV1eXl5fHx8fn5+f39/2dnZ//////////////// + ////////////9/f3g4ODdHR0bGxsYWFhUFBQOTk5GBgYAQEBEhISAAAAu7q1EhEQCAgIKioqRkZGWlpa + aGhocXFxd3d3e3t7fX19f39/gICA29vb/////v7+4uLiv7+/0tLS+vr6////9PT0g4ODdnZ2cHBwZWVl + V1dXQkJCIiIiBAQEAAAAgoKCs7KoAQEBDg4OMTExTExMXl5ea2trdHR0eXl5fHx8hYWFr6+vurq6/v7+ + ////u7u7gICAgICAgICAlpaW+fn5////z8/Pq6urjY2NaWlpW1tbSEhIKysrCAgIAAAAW1tbfnxyAAAA + EhISNjY2T09PYWFhbW1tdXV1enp6fX19lZWV////////////8vLygoKCgICAgICAgICAgICAyMjI//// + ////////w8PDa2trXl5eS0tLLy8vCwsLAAAANjY2aGZdAAAAFBQUODg4UVFRYmJibm5udnZ2e3t7fX19 + lZWV////////////5OTkf39/gICAgICAgICAgICAsLCw////////////w8PDbGxsX19fTU1NMTExDQ0N + AAAAKioqd3VuAAAAExMTNzc3UFBQYWFhbm5udXV1e3t7fX19lZWV////////////9vb2h4eHgoKCgYGB + gICAgICA09PT////////////w8PDa2trX19fTExMMTExDQ0NAAAALy8veXh1AAAAEBAQNDQ0TU1NX19f + bGxsd3d3nJyc6urqkJCQoKCgra2t/Pz8////2tranJycmJiYl5eXtra2/f39////wMDAj4+Pfn5+ampq + XFxcSUlJLS0tCQkJAAAATU1NkZGPCQkJCgoKLS0tSUlJXFxccnJy6enp8fHx7u7u8PDw5OTkm5ub0tLS + ////////9vb25OTk7u7u/v7+////9vb2m5ubgoKCcXFxZ2dnWVlZREREJiYmBgYGAAAAcnJyAAAANDQz + BQUFJCQkQkJCV1dXb29v6urqvb29np6e1tbW4ODgoaGh6+vr/////////////////////////////Pz8 + q6urnp6eioqKY2NjVFRUPT09HR0dAgICBwcHAAAAAAAAgYGBAgICFxcXOTk5UFBQb29v8/PzyMjIpaWl + 3t7e6urqr6+v9vb2////////9PT04uLi5+fn/////////Pz8x8fHo6Ojo6OjdXV1TExMMzMzERERAAAA + QUFBAAAAAAAAAAAALCwsCgoKKysrRUVFc3Nz6enp8PDw+fn57Ozs4ODgqqqqrq6u1tbW+/v7vb29qqqq + rKys8/Pz6enptra2qqqqqqqqqqqqlJSUQEBAJCQkBgYGBgYGhISEAAAAAAAAAAAAhoaGCgoKGBgYNzc3 + ZWVlsrKyw8PD6enpuLi4srKysrKysrKysrKyt7e3s7OzsrKysrKyt7e3s7OzsrKysrKysrKysrKykZGR + MTExEhISAQEBXV1dAAAAAAAAAAAAAAAAAAAAenp6CgoKIyMjPz8/qKiovLy8vLy8vLy8vLy8vLy8vLy8 + vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8urq6UVFRHR0dBAQEQEBAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcnJyERERJycnSUlJrq6uyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI + yMjIwMDAYmJiISEhCAgIPT09AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh4eHHBwcJiYmPT09enp6 + wMDA1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1tbWy8vLlpaWREREISEhDAwMX19fAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWVlZIiIiMjIyQkJCXl5eh4eHrKyswMDAycnJysrKxMTE + tLS0lJSUaGhoRERELi4uGhoaLCwsiYmJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAjY2NZ2dnMDAwMDAwOzs7QUFBRUVFR0dHR0dHRUVFQEBAOTk5Li4uJCQkREREj4+PAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjo6Oenp6T09P + SUlJQUFBQEBAQ0NDVFRUZWVlgoKCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA///////////////+/+AH4P8AAAH8AAAD+AAAA/AAAAfgAAAHwAAAA8AAAAOAAAABgAAAAQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAABgAAAAcAAAAHAAAAD4AAAB/AAAA/4AAAf/gAAP/8A + AP//4Af///////////8oAAAAEAAAACAAAAABABgAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAm5qY + jIuGeHZxbmxof356lJOPAAAAAAAAi4uHcnBsSEhDAAAAAAAAAAAAnZyYWVhUODg4R0dHUFBQUlJSTExM + PT09MC8uIiEfAQEBBgYGc3NzAAAAAAAAjYyHJiYlRUVFXFxcaGhobW1tbm5ua2trYmJiUFBQLi4uBgYG + VlZVAAAAAAAAkZGMGRkYREREYWFhcHBweHh4ioqKh4eHf39/jIyMaWlpUlJSJycnHx8fAAAAAAAAJycl + MTExW1tbcHBwenp6lZWV+vr67Ozs4+Pj/f39pKSkZWVlRUVFDw8PT09PjoyHDQ0NRUVFZ2dnd3d3fn5+ + kpKS/Pz81dXVzMzM/Pz8nJyccXFxVlZWIiIiFRUVT05IFBQUTk5ObGxsenp6pKSk+fn54ODggICAgICA + x8fH+/v7uLi4XV1dLi4uBQUFR0ZCFRUVT09PbW1tgICApqam9fX15OTkh4eHhISEzc3N9/f3s7OzXl5e + Ly8vBQUFZ2ZlDg4OSEhIgYGB2tra3Nzcr6+v/Pz85+fn4eHh/v7+rKysdXV1WFhYJiYmEBAQAAAAGxsb + Nzc3gICA0tLS29vbvr6++fn57Ozs5eXl/Pz8wcHBnJycTU1NExMTRUVFAAAAbW1tGxsbbGxsy8vLxsbG + s7OzuLi4uLi4tbW1vLy8srKyr6+vNzc3FBQUAAAAAAAAAAAAYmJiLCwslpaWxsbGyMjIyMjIyMjIyMjI + yMjIwcHBaGhoGxsbfX19AAAAAAAAAAAAAAAAfn5+QUFBUlJSiYmJqqqqsbGxoqKidXV1Ozs7TU1NAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAiIiIbW1tW1tbU1NTYmJienp6AAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAPgYAADgAAAAwAEAAIAB + AACAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAQAAwAEAAOAHAAD4HwAA//8AACgAAAAgAAAAQAAAAAEA + IAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJeWk/8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALu5tP+3tKv/nJmS/4F/d/9wb2f/bWtl/3h3 + b/+Fgnr/p6Wd/66sp/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACZmJH/enhv/0lIQf8fHhn/Kysp/wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuLey/6elm/9UU07/Ly8u/y0tLf80NDX/OTk5/zw8 + PP87Ozv/ODg4/zMzM/8qKir/JSQk/zk4NP+Hhn7/o6Ke/6Kgmf9YVk//FRUS/wEBAf8AAAD/AAAA/ygo + KP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqqmm/5yakP8xMC3/JCQk/zU1Nf9BQUH/SUlJ/09P + T/9SUlL/U1NT/1NTU/9RUVH/TU1N/0hISP8+Pj7/MjIy/yAgIP8VFBT/AwMC/wAAAP8AAAD/AAAA/wAA + AP8dHR3/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALy7s/9GRD//GRoZ/zAwMP9BQUH/TExM/1ZW + Vv9cXFz/YGBg/2NjY/9kZGT/ZGRk/2JiYv9fX1//W1tb/1RUVP9LS0v/PT09/ysrK/8UFBT/AgIC/wAA + AP8AAAD/Dg4O/5CQkP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACysKf/Hh0b/xsbG/80NDT/RkZG/1RU + VP9dXV3/ZGRk/2lpaf9sbGz/bm5u/29vb/9vb2//bm5u/2xsbP9oaGj/Y2Nj/1xcXP9RUVH/RERE/zAw + MP8WFhb/AgIC/wICAv95eXn/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtrOq/xQUEv8YGBj/NDQ0/0hI + SP9XV1f/YWFh/2lpaf9ubm7/cnJy/3V1df92dnb/d3d3/3d3d/92dnb/dHR0/3Fxcf9tbW3/aGho/19f + X/9UVFT/RUVF/y8vL/8SEhL/AgIC/2JhX/8AAAAAAAAAAAAAAAAAAAAAAAAAALe2r/8fHhz/EBAQ/y4u + Lv9GRkb/VlZW/2JiYv9ra2v/cXFx/3V1df94eHj/eXl5/3t7e/+Tk5P/fHx8/3p6ev95eXn/jY2N/3l5 + ef9wcHD/aWlp/2BgYP9UVFT/QkJC/ygoKP8LCwv/BAQE/4qKif8AAAAAAAAAAAAAAAAAAAAAVlRO/wYG + Bv8jIyP/QEBA/1NTU/9hYWH/a2tr/3Jycv92dnb/enp6/3t7e/+Pj4//29vb//7+/v+np6f/fX19/4WF + hf/29vb/7+/v/6CgoP9ycnL/aWlp/19fX/9QUFD/Ozs7/xwcHP8CAgL/FxcX/wAAAAAAAAAAAAAAALOw + qf8FBAT/EhIS/zMzM/9LS0v/XFxc/2hoaP9xcXH/dnZ2/3p6ev98fHz/jIyM//r6+v////////////j4 + +P/s7Oz/7u7u/////////////////7Kysv9vb2//ZmZm/1lZWf9HR0f/LS0t/wwMDP8AAAD/V1dX/wAA + AAAAAAAAU1JM/wMDA/8fHx//Pj4+/1RUVP9jY2P/bm5u/3V1df95eXn/fHx8/35+fv9/f3//2dnZ//// + ///////////////////////////////////39/f/g4OD/3R0dP9sbGz/YWFh/1BQUP85OTn/GBgY/wEB + Af8SEhL/AAAAALu6tf8SERD/CAgI/yoqKv9GRkb/Wlpa/2hoaP9xcXH/d3d3/3t7e/99fX3/f39//4CA + gP/b29v///////7+/v/i4uL/v7+//9LS0v/6+vr///////T09P+Dg4P/dnZ2/3BwcP9lZWX/V1dX/0JC + Qv8iIiL/BAQE/wAAAP+CgoL/s7Ko/wEBAf8ODg7/MTEx/0xMTP9eXl7/a2tr/3R0dP95eXn/fHx8/4WF + hf+vr6//urq6//7+/v//////u7u7/4CAgP+AgID/gICA/5aWlv/5+fn//////8/Pz/+rq6v/jY2N/2lp + af9bW1v/SEhI/ysrK/8ICAj/AAAA/1tbW/9+fHL/AAAA/xISEv82Njb/T09P/2FhYf9tbW3/dXV1/3p6 + ev99fX3/lZWV//////////////////Ly8v+CgoL/gICA/4CAgP+AgID/gICA/8jIyP////////////// + ///Dw8P/a2tr/15eXv9LS0v/Ly8v/wsLC/8AAAD/NjY2/2hmXf8AAAD/FBQU/zg4OP9RUVH/YmJi/25u + bv92dnb/e3t7/319ff+VlZX/////////////////5OTk/39/f/+AgID/gICA/4CAgP+AgID/sLCw//// + /////////////8PDw/9sbGz/X19f/01NTf8xMTH/DQ0N/wAAAP8qKir/d3Vu/wAAAP8TExP/Nzc3/1BQ + UP9hYWH/bm5u/3V1df97e3v/fX19/5WVlf/////////////////29vb/h4eH/4KCgv+BgYH/gICA/4CA + gP/T09P/////////////////w8PD/2tra/9fX1//TExM/zExMf8NDQ3/AAAA/y8vL/95eHX/AAAA/xAQ + EP80NDT/TU1N/19fX/9sbGz/d3d3/5ycnP/q6ur/kJCQ/6CgoP+tra3//Pz8///////a2tr/nJyc/5iY + mP+Xl5f/tra2//39/f//////wMDA/4+Pj/9+fn7/ampq/1xcXP9JSUn/LS0t/wkJCf8AAAD/TU1N/5GR + j/8JCQn/CgoK/y0tLf9JSUn/XFxc/3Jycv/p6en/8fHx/+7u7v/w8PD/5OTk/5ubm//S0tL///////// + ///29vb/5OTk/+7u7v/+/v7///////b29v+bm5v/goKC/3Fxcf9nZ2f/WVlZ/0RERP8mJib/BgYG/wAA + AP9ycnL/AAAAADQ0M/8FBQX/JCQk/0JCQv9XV1f/b29v/+rq6v+9vb3/np6e/9bW1v/g4OD/oaGh/+vr + 6////////////////////////////////////////Pz8/6urq/+enp7/ioqK/2NjY/9UVFT/PT09/x0d + Hf8CAgL/BwcH/wAAAAAAAAAAgYGB/wICAv8XFxf/OTk5/1BQUP9vb2//8/Pz/8jIyP+lpaX/3t7e/+rq + 6v+vr6//9vb2////////////9PT0/+Li4v/n5+f////////////8/Pz/x8fH/6Ojo/+jo6P/dXV1/0xM + TP8zMzP/ERER/wAAAP9BQUH/AAAAAAAAAAAAAAAALCws/woKCv8rKyv/RUVF/3Nzc//p6en/8PDw//n5 + +f/s7Oz/4ODg/6qqqv+urq7/1tbW//v7+/+9vb3/qqqq/6ysrP/z8/P/6enp/7a2tv+qqqr/qqqq/6qq + qv+UlJT/QEBA/yQkJP8GBgb/BgYG/4SEhP8AAAAAAAAAAAAAAACGhob/CgoK/xgYGP83Nzf/ZWVl/7Ky + sv/Dw8P/6enp/7i4uP+ysrL/srKy/7Kysv+ysrL/t7e3/7Ozs/+ysrL/srKy/7e3t/+zs7P/srKy/7Ky + sv+ysrL/srKy/5GRkf8xMTH/EhIS/wEBAf9dXV3/AAAAAAAAAAAAAAAAAAAAAAAAAAB6enr/CgoK/yMj + I/8/Pz//qKio/7y8vP+8vLz/vLy8/7y8vP+8vLz/vLy8/7y8vP+8vLz/vLy8/7y8vP+8vLz/vLy8/7y8 + vP+8vLz/vLy8/7y8vP+6urr/UVFR/x0dHf8EBAT/QEBA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABycnL/ERER/ycnJ/9JSUn/rq6u/8jIyP/IyMj/yMjI/8jIyP/IyMj/yMjI/8jIyP/IyMj/yMjI/8jI + yP/IyMj/yMjI/8jIyP/IyMj/wMDA/2JiYv8hISH/CAgI/z09Pf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACHh4f/HBwc/yYmJv89PT3/enp6/8DAwP/V1dX/1dXV/9XV1f/V1dX/1dXV/9XV + 1f/V1dX/1dXV/9XV1f/W1tb/y8vL/5aWlv9ERET/ISEh/wwMDP9fX1//AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWVlZ/yIiIv8yMjL/QkJC/15eXv+Hh4f/rKys/8DA + wP/Jycn/ysrK/8TExP+0tLT/lJSU/2hoaP9ERET/Li4u/xoaGv8sLCz/iYmJ/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjY2N/2dnZ/8wMDD/MDAw/zs7 + O/9BQUH/RUVF/0dHR/9HR0f/RUVF/0BAQP85OTn/Li4u/yQkJP9ERET/j4+P/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACOjo7/enp6/09PT/9JSUn/QUFB/0BAQP9DQ0P/VFRU/2VlZf+CgoL/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///////////////+/+AH4P8A + AAH8AAAD+AAAA/AAAAfgAAAHwAAAA8AAAAOAAAABgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACAAAABgAAAAcAAAAHAAAAD4AAAB/AAAA/4AAAf/gAAP/8AAP//4Af///////////8oAAAAEAAAACAA + AAABACAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC0s60mAAAAAJaT + jdFzcWvRZGNd0XZzbdGPjYmzAAAAAAAAAAB+fXdMXVtV0Ts6N9EAAAAAAAAAALy7swiUk46zU1JP90A/ + P/9ISEj/UVFR/1JSUv9LS0v/Pj4+/zc3Nf8jIiD/BQUF/woKCv8yMjJMAAAAALazqgiIhoC8LS0s/0RE + RP9cXFz/aGho/21tbf9ubm7/a2tr/2JiYv9QUFD/Li4u/wcHB/9RUVDZAAAAAAAAAACMi4WzHx8e/0RE + RP9hYWH/cHBw/3h4eP+NjY3/h4eH/4CAgP+Li4v/ampq/1JSUv8nJyf/IyMi9wAAAAAAAAAAKSgn9zAw + MP9bW1v/cHBw/3p6ev+VlZX/9fX1/+vr6//j4+P/+vr6/6SkpP9lZWX/RUVF/xEREf8yMjKzh4aB2xIR + Ef9ERET/Z2dn/3d3d/9/f3//mZmZ//f39//X19f/z8/P//n5+f+goKD/dHR0/1VVVf8iIiL/FRUV92Zl + Xv8VFRX/Tk5O/2xsbP96enr/n5+f//T09P/i4uL/goKC/4GBgf/Jycn/9/f3/7W1tf9dXV3/Li4u/w0N + Df9bWVT/FhYW/09PT/9tbW3/gYGB/6SkpP/y8vL/5eXl/4mJif+FhYX/z8/P//T09P+ysrL/XV1d/y8v + L/8MDAz/ZGRi2xEREf9HR0f/f39//9nZ2f/a2tr/tbW1//j4+P/o6Oj/4+Pj//v7+/+vr6//eHh4/1hY + WP8mJib/EhIS9wAAAAAcHBz3NjY2/4GBgf/Ozs7/2dnZ/8HBwf/19fX/7e3t/+Xl5f/5+fn/wcHB/5mZ + mf9PT0//FBQU/zQ0NNEAAAAAYGBgsx8fH/9paWn/zMzM/8fHx/+0tLT/u7u7/7m5uf+2trb/vr6+/7Oz + s/+qqqr/Ojo6/xkZGfdfX19DAAAAAHp6eghZWVm8MTEx/5SUlP/Dw8P/yMjI/8jIyP/IyMj/yMjI/8jI + yP+7u7v/ZmZm/yIiIvc7OztDAAAAAAAAAAAAAAAAh4eHCFBQUExCQkL3V1dX/4eHh/+srKz/tLS0/6Oj + o/9ycnL/RUVF/0VFRdl8fHxDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHt7e0xpaWnRSEhI0UJC + QtFQUFDRa2trs0xMTC6Pj48IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD0GAAAwAAAAIABAACAAQAAgAAAAAAA + AAAAAAAAAAAAAAAAAACAAAAAgAAAAIABAADAAwAA+AcAAP//AAAoAAAAMAAAAGAAAAABACAAAAAAAAAk + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AACjoqD/l5aP/6imn/+mpJv/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAL++u//S0Mn/1NHF/7azpv+YlYn/fXpx/2tpYP9hYFj/YV9X/2ln + Xv96d27/lZKG/7e0qP/Fw7v/xcTA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKGg + nf+rqJ3/iod5/0tJPf8fHhj/CgkG/wEBAf9LS0v/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKyrqP/S0Mb/rKmd/2poYf88Ozj/KSko/ygoKP8rKyv/Ly8v/zEx + Mf8yMjL/MTEx/y8vL/8sLCz/KCgo/yMjI/8iIyL/MzMv/2NhWP+ioJb/tLOs/wAAAAAAAAAAAAAAAKmn + n/+Vkof/TkxC/xoZFP8FBQT/AQAA/wAAAP8AAAD/AQEB/2BgYP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBwLz/zMm9/3t5b/8yMS7/ICAg/ygoKP8xMTH/ODg4/zw8 + PP9AQED/Q0ND/0RERP9FRUX/RERE/0NDQ/9BQUH/PT09/zk5Of8zMzP/Kysr/yAgIP8XFxf/JSQh/3Vy + af+sqqH/W1lP/xUUEf8CAgH/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/Xl5e/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAqain/8rIvP9wbWT/Gxoa/x0dHv8rKyv/NTU1/z09 + Pf9ERET/SUlJ/01NTf9QUFD/UlJS/1RUVP9UVFT/VFRU/1JSUv9RUVH/Tk5O/0pKSv9FRUX/Pz8//zc3 + N/8tLS3/ICAg/xMTE/8HBwf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9NTU3/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEw7z/mJWJ/yIhH/8WFhb/Jycn/zQ0 + NP8+Pj7/RUVF/0xMTP9SUlL/VlZW/1paWv9cXFz/Xl5e/19fX/9fX1//X19f/15eXv9dXV3/Wlpa/1dX + V/9SUlL/TU1N/0dHR/9AQED/NjY2/ygoKP8aGhr/CwsL/wEBAf8AAAD/AAAA/wAAAP8AAAD/AAAA/0VF + Rf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANPQxv9iYFj/DQ0M/xoa + Gv8rKyv/OTk5/0NDQ/9LS0v/U1NT/1hYWP9dXV3/YWFh/2RkZP9lZWX/Z2dn/2hoaP9oaGj/aGho/2dn + Z/9mZmb/ZGRk/2FhYf9eXl7/WVlZ/1RUVP9NTU3/RUVF/zs7O/8uLi7/Hh4e/w0NDf8BAQH/AAAA/wAA + AP8AAAD/Gxsb/5GRkf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzcu//zY1 + Mf8JCQn/Gxsb/y4uLv88PDz/RkZG/1BQUP9XV1f/XV1d/2JiYv9mZmb/aWlp/2xsbP9tbW3/bm5u/29v + b/9vb2//b29v/25ubv9tbW3/bGxs/2pqav9nZ2f/Y2Nj/19fX/9ZWVn/UVFR/0lJSf8+Pj7/MDAw/x4e + Hv8NDQ3/AQEB/wAAAP8HBwf/mpqa/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AADGxLf/Kikm/wgICP8ZGRn/Li4u/zw8PP9ISEj/U1NT/1paWv9gYGD/ZWVl/2lpaf9sbGz/b29v/3Fx + cf9ycnL/c3Nz/3R0dP90dHT/dHR0/3Nzc/9zc3P/cXFx/29vb/9ubm7/ampq/2ZmZv9hYWH/W1tb/1RU + VP9KSkr/Pz8//zAwMP8eHh7/CwsL/wAAAP8WFhT/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAANHOxP85ODP/BAQE/xYWFv8pKSn/Ozs7/0hISP9TU1P/W1tb/2JiYv9oaGj/bGxs/3Bw + cP9zc3P/dHR0/3Z2dv93d3f/eHh4/3h4eP94eHj/eHh4/3h4eP93d3f/dnZ2/3V1df9zc3P/cHBw/21t + bf9oaGj/Y2Nj/11dXf9VVVX/S0tL/z09Pf8uLi7/GRkZ/wYGBv8AAAD/Li4s/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAx8bA/1JRSv8BAQH/EBAQ/yUlJf84ODj/RkZG/1JSUv9bW1v/YmJi/2ho + aP9tbW3/cXFx/3R0dP92dnb/eHh4/3l5ef96enr/enp6/39/f/+Kior/e3t7/3p6ev96enr/eXl5/3t7 + e/+Hh4f/dHR0/3Fxcf9ubm7/aWlp/2NjY/9cXFz/VFRU/0lJSf87Ozv/KSkp/xQUFP8DAwP/AAAA/1hY + V/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACko6H/k5CG/wMDA/8ICAj/HBwc/zIyMv9CQkL/T09P/1lZ + Wf9iYmL/aGho/21tbf9ycnL/dXV1/3h4eP95eXn/e3t7/3x8fP9+fn7/paWl/+zs7P/y8vL/hISE/319 + ff99fX3/e3t7/7m5uf/6+vr/zMzM/4ODg/9ycnL/bm5u/2pqav9jY2P/XFxc/1FRUf9FRUX/NjY2/yEh + If8MDAz/AAAA/wMDA/+Ghob/AAAAAAAAAAAAAAAAAAAAAAAAAADPzMP/FxcV/wICAv8SEhL/KSkp/zw8 + PP9LS0v/VlZW/2BgYP9oaGj/bW1t/3Jycv91dXX/d3d3/3p6ev97e3v/fHx8/5SUlP/a2tr//v7+//// + ////////xsbG/4ODg/+EhIT/ioqK//X19f////////////T09P+zs7P/d3d3/25ubv9paWn/YmJi/1hY + WP9NTU3/Pz8//y0tLf8XFxf/AwMD/wAAAP8YGBj/AAAAAAAAAAAAAAAAAAAAALe3tP90cmn/AAAA/wgI + CP8dHR3/NDQ0/0VFRf9SUlL/XFxc/2VlZf9ra2v/cXFx/3R0dP93d3f/enp6/3x8fP99fX3/hISE//b2 + 9v///////////////////////f39//f39//4+Pj/9/f3////////////////////////////v7+//3Fx + cf9sbGz/ZmZm/15eXv9UVFT/R0dH/zc3N/8iIiL/CwsL/wAAAP8AAAD/YGBg/wAAAAAAAAAAAAAAAMPB + uP8YFxX/AQEB/w8PD/8nJyf/PDw8/0tLS/9XV1f/YWFh/2lpaf9vb2//dHR0/3d3d/96enr/fHx8/319 + ff9+fn7/fn5+/8jIyP////////////////////////////////////////////////////////////// + ///39/f/hYWF/3V1df9wcHD/ampq/2NjY/9ZWVn/TU1N/z8/P/8rKyv/FBQU/wICAv8AAAD/EhIS/wAA + AAAAAAAAAAAAAJORh/8BAQH/AwMD/xcXF/8vLy//QkJC/1BQUP9cXFz/ZWVl/2xsbP9ycnL/dnZ2/3l5 + ef97e3v/fX19/35+fv9/f3//f39//4uLi//29vb///////////////////////////////////////// + ///////////////////BwcH/eHh4/3Z2dv9ycnL/bW1t/2ZmZv9eXl7/U1NT/0VFRf8zMzP/HBwc/wUF + Bf8AAAD/AAAA/4iIiP8AAAAAAAAAAERCPf8AAAD/BwcH/x0dHf81NTX/RkZG/1RUVP9fX1//aGho/25u + bv90dHT/d3d3/3p6ev98fHz/fX19/35+fv9/f3//f39//6ioqP/9/f3/////////////////7e3t/8fH + x/+8vLz/2NjY//r6+v/////////////////b29v/e3t7/3h4eP90dHT/cHBw/2pqav9hYWH/V1dX/0lJ + Sf85OTn/IiIi/woKCv8AAAD/AAAA/0ZGRv8AAAAAAAAAABYWFP8AAAD/CwsL/yMjI/86Ojr/S0tL/1hY + WP9iYmL/a2tr/3BwcP92dnb/eXl5/3x8fP99fX3/fn5+/4GBgf+BgYH/hISE/+zs7P////////////z8 + /P+6urr/g4OD/39/f/+AgID/gICA/5GRkf/k5OT////////////9/f3/n5+f/3t7e/94eHj/cnJy/2xs + bP9kZGT/Wlpa/01NTf8+Pj7/KCgo/w8PD/8BAQH/AAAA/xkZGf8AAAAAAAAAAAcGBv8AAAD/Dg4O/ygo + KP89PT3/TU1N/1paWv9kZGT/bGxs/3Fxcf92dnb/enp6/3x8fP99fX3/hYWF/+fn5//u7u7/7+/v//7+ + /v///////////8fHx/+AgID/gICA/4CAgP+AgID/gICA/4CAgP+Pj4//9/f3////////////9vb2/+3t + 7f/s7Oz/tra2/21tbf9mZmb/XFxc/1BQUP9BQUH/LS0t/xMTE/8CAgL/AAAA/wcHB/+jo6P/AAAAAAMD + A/8BAQH/ERER/ysrK/9AQED/T09P/1xcXP9lZWX/bW1t/3Jycv93d3f/enp6/3x8fP9+fn7/iIiI//r6 + +v///////////////////////Pz8/42Njf+AgID/gICA/4CAgP+AgID/gICA/4CAgP+AgID/ysrK//// + ////////////////////////yMjI/25ubv9nZ2f/Xl5e/1JSUv9DQ0P/MDAw/xYWFv8DAwP/AAAA/wAA + AP+CgoL/AAAAAAEBAf8CAgL/ExMT/y0tLf9BQUH/UVFR/11dXf9mZmb/bm5u/3Nzc/94eHj/e3t7/319 + ff9+fn7/iIiI//r6+v//////////////////////8PDw/4KCgv+AgID/gICA/4CAgP+AgID/gICA/4CA + gP+AgID/qqqq////////////////////////////ycnJ/29vb/9oaGj/X19f/1NTU/9FRUX/MjIy/xgY + GP8EBAT/AAAA/wAAAP9vb2//AAAAAAEBAf8CAgL/FBQU/y0tLf9CQkL/UVFR/11dXf9mZmb/bm5u/3Nz + c/94eHj/e3t7/319ff9+fn7/iIiI//r6+v//////////////////////8/Pz/4ODg/+AgID/gICA/4CA + gP+AgID/gICA/4CAgP+AgID/r6+v////////////////////////////ycnJ/29vb/9oaGj/X19f/1NT + U/9FRUX/MjIy/xgYGP8EBAT/AAAA/wAAAP9tbW3/AAAAAAICAv8BAQH/ExMT/ywsLP9BQUH/UFBQ/11d + Xf9mZmb/bm5u/3Nzc/93d3f/e3t7/319ff9+fn7/iIiI//r6+v///////////////////////v7+/5aW + lv+AgID/gICA/4CAgP+AgID/gICA/4CAgP+AgID/2tra////////////////////////////yMjI/25u + bv9oaGj/Xl5e/1NTU/9ERET/MTEx/xgYGP8DAwP/AAAA/wAAAP92dnb/AAAAAAQEBP8BAQH/EBAQ/yoq + Kv8/Pz//Tk5O/1tbW/9lZWX/bW1t/3Jycv93d3f/h4eH/7y8vP+4uLj/g4OD/8HBwf/IyMj/zs7O//v7 + +////////////+Li4v+ZmZn/lZWV/5WVlf+Tk5P/kZGR/4yMjP+lpaX//f39////////////5ubm/8PD + w//CwsL/nJyc/25ubv9nZ2f/XV1d/1FRUf9CQkL/Ly8v/xUVFf8CAgL/AAAA/wICAv+SkpL/AAAAAAsL + C/8AAAD/Dg4O/yYmJv89PT3/TU1N/1paWv9kZGT/bGxs/319ff9/f3//pKSk//7+/v/39/f/kZGR/5+f + n/+ZmZn/mJiY/9zc3P/////////////////j4+P/p6en/5qamv+ampr/nZ2d/8DAwP/4+Pj///////// + ///9/f3/m5ub/3l5ef92dnb/cnJy/21tbf9mZmb/XFxc/1BQUP9AQED/LCws/xISEv8CAgL/AAAA/w4O + Dv+QkJD/AAAAACoqKf8AAAD/CQkJ/yEhIf84ODj/SUlJ/1dXV/9hYWH/aWlp/9bW1v/29vb//f39//f3 + 9//7+/v/+fn5//z8/P/BwcH/m5ub/6Wlpf/39/f//////////////////f39//Dw8P/t7e3/9/f3//// + ///////////////////d3d3/mZmZ/4qKiv93d3f/cXFx/2tra/9jY2P/WVlZ/0xMTP88PDz/JiYm/w4O + Dv8AAAD/AAAA/yoqKv8AAAAAAAAAAGdnZf8AAAD/BQUF/xsbG/8zMzP/RUVF/1NTU/9eXl7/dHR0/+7u + 7v//////0tLS/6CgoP+mpqb/7u7u//7+/v/h4eH/nJyc/66urv/7+/v///////////////////////// + ///////////////////////////////////f39//nZ2d/52dnf+Xl5f/d3d3/2hoaP9gYGD/VVVV/0hI + SP83Nzf/ICAg/wkJCf8AAAD/AAAA/2JiYv8AAAAAAAAAAJmZmP8ICAf/AgIC/xQUFP8sLCz/QEBA/09P + T/9aWlr/ZGRk/5ubm//8/Pz/qamp/5+fn/+fn5//yMjI//f39/+mpqb/n5+f/+Tk5P////////////// + ///////////////////////////////////////////////////9/f3/tLS0/5+fn/+fn5//nJyc/29v + b/9dXV3/UVFR/0NDQ/8xMTH/GRkZ/wQEBP8AAAD/AwMD/4iIiP8AAAAAAAAAAAAAAABCQkL/AAAA/w0N + Df8jIyP/OTk5/0lJSf9WVlb/aGho/9vb2//+/v7/wMDA/6Ojo/+kpKT/4eHh//39/f/Q0ND/p6en//Hx + 8f//////////////////////+fn5/+Xl5f/p6en/6urq///////////////////////9/f3/zc3N/6Oj + o/+jo6P/o6Oj/5eXl/9ZWVn/TExM/z09Pf8oKCj/ERER/wEBAf8AAAD/NDQ0/wAAAAAAAAAAAAAAAAAA + AACTk5P/BAQE/wUFBf8ZGRn/MDAw/0JCQv9PT0//aWlp//Dw8P/+/v7/+/v7/+Tk5P/r6+v//v7+//// + ///Y2Nj/p6en/6urq//Nzc3/+Pj4////////////yMjI/6enp/+oqKj/qqqq//Hx8f///////v7+/+jo + 6P+4uLj/qKio/6ioqP+oqKj/qKio/6enp/9lZWX/RUVF/zQ0NP8eHh7/CAgI/wAAAP8EBAT/mZmZ/wAA + AAAAAAAAAAAAAAAAAAAAAAAAS0tL/wEBAf8PDw//JSUl/zo6Ov9ISEj/bGxs/8HBwf/Dw8P/2NjY//// + ///8/Pz/yMjI/87Ozv+zs7P/rKys/6ysrP+srKz/tLS0/+Hh4f/r6+v/ra2t/6ysrP+srKz/rKys/8XF + xf/y8vL/yMjI/62trf+srKz/rKys/6ysrP+srKz/rKys/62trf9tbW3/PT09/ykpKf8UFBT/AgIC/wAA + AP9ERET/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlJSU/xgYGP8FBQX/GBgY/y4uLv8/Pz//Xl5e/7Ky + sv+ysrL/v7+//+/v7//q6ur/s7Oz/7Kysv+ysrL/srKy/7Kysv+ysrL/srKy/7Kysv+zs7P/srKy/7Ky + sv+ysrL/srKy/7Kysv+zs7P/srKy/7Kysv+ysrL/srKy/7Kysv+ysrL/srKy/7Ozs/9dXV3/MjIy/x0d + Hf8ICAj/AAAA/xgYGP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJubm/8NDQ3/DAwM/yAg + IP8zMzP/RERE/6Wlpf+5ubn/ubm5/7m5uf+5ubn/ubm5/7m5uf+5ubn/ubm5/7m5uf+5ubn/ubm5/7m5 + uf+5ubn/ubm5/7m5uf+5ubn/ubm5/7m5uf+5ubn/ubm5/7m5uf+5ubn/ubm5/7m5uf+5ubn/ubm5/6am + pv87Ozv/JCQk/w8PD/8BAQH/BgYG/4GBgf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAB/f3//BwcH/xISEv8lJSX/Nzc3/11dXf+5ubn/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DA + wP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DAwP/AwMD/wMDA/8DA + wP/AwMD/u7u7/1ZWVv8pKSn/FRUV/wQEBP8FBQX/jIyM/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAgoKC/wsLC/8UFBT/Jycn/zg4OP9eXl7/vb29/8jIyP/Hx8f/x8fH/8fH + x//Hx8f/x8fH/8fHx//Hx8f/x8fH/8fHx//Hx8f/x8fH/8fHx//Hx8f/x8fH/8fHx//Hx8f/x8fH/8fH + x//Hx8f/x8fH/8jIyP+9vb3/WVlZ/yoqKv8YGBj/BgYG/wUFBf+Hh4f/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJeXl/8aGhr/FRUV/ycnJ/83Nzf/S0tL/5eX + l//Kysr/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR0f/R0dH/0dHR/9HR + 0f/R0dH/0dHR/9HR0f/R0dH/zMzM/5WVlf9FRUX/Kioq/xgYGP8ICAj/EBAQ/4GBgf8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRkZH/Ojo6/xUV + Ff8kJCT/NDQ0/z8/P/9XV1f/lJSU/8bGxv/Y2Nj/2tra/9ra2v/a2tr/2tra/9ra2v/a2tr/2tra/9ra + 2v/a2tr/2tra/9ra2v/a2tr/2NjY/8bGxv+UlJT/U1NT/zU1Nf8oKCj/GBgY/wkJCf8xMTH/lZWV/wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIODg/8fHx//Hh4e/y0tLf85OTn/QUFB/0hISP9dXV3/gYGB/6enp//CwsL/0NDQ/9fX + 1//a2tr/2tra/9fX1//Q0ND/wsLC/6ampv+BgYH/W1tb/0NDQ/86Ojr/MDAw/yEhIf8TExP/EhIS/39/ + f/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjo6P/bm5u/yoqKv8iIiL/Li4u/zg4OP8+Pj7/RERE/0hI + SP9LS0v/Tk5O/1FRUf9TU1P/U1NT/1BQUP9NTU3/SUlJ/0RERP8/Pz//OTk5/zAwMP8mJib/GBgY/yEh + If91dXX/kZGR/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACWlpb/UlJS/ysr + K/8qKir/MTEx/zY2Nv87Ozv/Pj4+/z8/P/9AQED/Pz8//z4+Pv88PDz/ODg4/zIyMv8sLCz/IyMj/yMj + I/9ISEj/jIyM/52dnf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAACcnJz/gICA/1RUVP89PT3/MTEx/y0tLf8sLCz/Kysr/ysrK/8uLi7/ODg4/09P + T/97e3v/nZ2d/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////// + AAD///////8AAP///////wAA////////AAD///////AAAP//AAH/AAAA//gAADgBAAD/4AAAAAMAAP+A + AAAABwAA/wAAAAAPAAD+AAAAAA8AAPwAAAAAHwAA+AAAAAA/AADwAAAAAB8AAOAAAAAADwAAwAAAAAAH + AADAAAAAAAcAAIAAAAAAAwAAgAAAAAADAACAAAAAAAEAAIAAAAAAAQAAgAAAAAABAACAAAAAAAAAAIAA + AAAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAIAAAAAAAQAAgAAAAAAB + AACAAAAAAAEAAMAAAAAAAwAAwAAAAAADAADgAAAAAAcAAOAAAAAADwAA8AAAAAAPAAD4AAAAAB8AAPwA + AAAAPwAA/gAAAAB/AAD/AAAAAP8AAP/AAAAD/wAA/+AAAAf/AAD//AAAH/8AAP//gAH//wAA//////// + AAD///////8AAP///////wAA + + + \ No newline at end of file diff --git a/MoviconHub.App/packages.config b/MoviconHub.App/packages.config new file mode 100644 index 0000000..d6e1b3e --- /dev/null +++ b/MoviconHub.App/packages.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MoviconHub.App/websocket_config.json b/MoviconHub.App/websocket_config.json new file mode 100644 index 0000000..7feb56a --- /dev/null +++ b/MoviconHub.App/websocket_config.json @@ -0,0 +1,13 @@ +{ + "ServerAddress": "127.0.0.1", + "ServerPort": 1883, + "AutoReconnect": true, + "ReconnectInterval": 5000, + "HeartbeatInterval": 30000, + "ConnectionTimeout": 5000, + "Url": "/push/device/942010002", + "DeviceCode": "MOVICON_HUB_001", + "Cycle": 1, + "SecretKey": "your_secret_key_here" + +} diff --git a/MoviconHub.sln b/MoviconHub.sln new file mode 100644 index 0000000..f961771 --- /dev/null +++ b/MoviconHub.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35806.99 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoviconHub.App", "MoviconHub.App\MoviconHub.App.csproj", "{8888B1D2-AAD3-4BD5-B934-20C0E6E84DD9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoviconWebApi", "MoviconWebApi\MoviconWebApi.csproj", "{7FBEFF3B-9B7C-4F36-BECE-6DB7B6797DB1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8888B1D2-AAD3-4BD5-B934-20C0E6E84DD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8888B1D2-AAD3-4BD5-B934-20C0E6E84DD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8888B1D2-AAD3-4BD5-B934-20C0E6E84DD9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8888B1D2-AAD3-4BD5-B934-20C0E6E84DD9}.Release|Any CPU.Build.0 = Release|Any CPU + {7FBEFF3B-9B7C-4F36-BECE-6DB7B6797DB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FBEFF3B-9B7C-4F36-BECE-6DB7B6797DB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FBEFF3B-9B7C-4F36-BECE-6DB7B6797DB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FBEFF3B-9B7C-4F36-BECE-6DB7B6797DB1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {668D423C-9348-4F50-AF54-AFBCF5636E2B} + EndGlobalSection +EndGlobal diff --git a/MoviconWebApi/.config/dotnet-tools.json b/MoviconWebApi/.config/dotnet-tools.json new file mode 100644 index 0000000..837b189 --- /dev/null +++ b/MoviconWebApi/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "9.0.8", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/MoviconWebApi/API/ClearDataApi/Data.cs b/MoviconWebApi/API/ClearDataApi/Data.cs new file mode 100644 index 0000000..9f0107f --- /dev/null +++ b/MoviconWebApi/API/ClearDataApi/Data.cs @@ -0,0 +1,83 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.ClearDataApi +{ + public static class Data + { + internal static async Task?> GetClearData(ClearDataRequest request, IFreeSql freeSql) + { + try + { + // 构建查询 + var query = freeSql.Select(); + + // 根据设备编号过滤 + if (!string.IsNullOrWhiteSpace(request.DeviceCode)) + { + query = query.Where(x => x.DeviceCode == request.DeviceCode); + } + + // 根据开始时间过滤 + if (!string.IsNullOrWhiteSpace(request.StartTime) && DateTime.TryParse(request.StartTime, out DateTime startTime)) + { + query = query.Where(x => x.CreateTime >= startTime); + } + + // 根据结束时间过滤 + if (!string.IsNullOrWhiteSpace(request.EndTime) && DateTime.TryParse(request.EndTime, out DateTime endTime)) + { + query = query.Where(x => x.CreateTime <= endTime); + } + + // 按创建时间降序排序 + query = query.OrderByDescending(x => x.CreateTime); + + // 执行查询并映射到响应模型 + var result = await query.ToListAsync(x => new ClearDataResponse + { + DeviceCode = x.DeviceCode, + DeviceName = x.DeviceName, + program_process = x.program_process, + vehicle_model = x.vehicle_model, + locomotive_number = x.locomotive_number, + repair_process = x.repair_process, + component_name = x.component_name, + part_position = x.part_position, + part_num = x.part_num, + part_qrid = x.part_qrid, + AgentTank_Level = x.AgentTank_Level, + Test_CleaningAgentTankAdd=x.Test_CleaningAgentTankAdd, + Test_CleaningAgentTankHeat=x.Test_CleaningAgentTankHeat, + Test_FrameworkPerModelCleaningAgentUsage = x.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = x.Test_FrameworkPerModelWaterUsage, + WaterTank_Temp = x.WaterTank_Temp, + AgentTank_Temp = x.AgentTank_Temp, + SoakingTank1_Temp = x.SoakingTank1_Temp, + SoakingTank2_Temp = x.SoakingTank2_Temp, + Test_ElectricSurveillance=x.Test_ElectricSurveillance, + Test_FrameworkPerModelCleaningDuration=x.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkProgramProcess=x.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage=x.Test_FrameworkProgramProcessPercentage, + Test_SteamSurveillance= x.Test_SteamSurveillance, + Test_WaterTankAdd= x.Test_WaterTankAdd, + Test_WaterTankHeat= x.Test_WaterTankHeat, + WaterTank_Level=x.WaterTank_Level, + + CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return result; + } + catch (Exception ex) + { + // 记录异常日志(可以根据实际需求添加日志记录) + Console.WriteLine($"获取清洗数据失败:{ex.Message}"); + return null; + } + + } + + + + } +} diff --git a/MoviconWebApi/API/ClearDataApi/Endpoint.cs b/MoviconWebApi/API/ClearDataApi/Endpoint.cs new file mode 100644 index 0000000..ac83a14 --- /dev/null +++ b/MoviconWebApi/API/ClearDataApi/Endpoint.cs @@ -0,0 +1,100 @@ +using Azure; +using Azure.Core; +using FastEndpoints; +using MoviconWebApi.API.ClearDataQrApi; +using MoviconWebApi.Common; + +namespace MoviconWebApi.API.ClearDataApi +{ + /// + /// 获取清洗数据端点 + /// + public class Endpoint : Endpoint>> + { + private readonly IFreeSql _freeSql; + + /// + /// 构造函数,注入FreeSql实例 + /// + /// FreeSql实例 + public Endpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + // 配置路由,支持GET和POST两种方式 + //Post("/cleardata/list"); + Get("/cleardata/listbycode"); + + // 允许匿名访问(根据实际需求可以改为需要认证) + AllowAnonymous(); + + // 配置摘要信息(用于Swagger文档) + Summary(s => + { + s.Summary = "获取清洗数据列表"; + s.Description = "根据设备编号、开始时间、结束时间查询清洗数据"; + s.Response< ApiResponse>>(200, "成功返回清洗数据列表"); + s.Response(404, "未找到数据"); + s.Response(500, "服务器内部错误"); + }); + } + + public override async Task HandleAsync(ClearDataRequest request, CancellationToken ct) + { + try + { + // 调用Data层方法获取数据 + var dataList = await Data.GetClearData(request, _freeSql); + + if (dataList == null || dataList.Count == 0) + { + // 未找到数据,返回404 + //await Send.NotFoundAsync(ct); + //Response = dataList ?? new List(); + + //await Send.OkAsync(new List(), ct); + + // 没有数据 + Response = ApiResponse>.Success( + new List(), + "暂无数据" + ); + + } + else + { + //await Send.OkAsync(dataList, ct); + + // 现在的代码 + Response = ApiResponse>.Success( + dataList, + "查询成功" + ); + + } + } + catch (Exception ex) + { + // 记录错误日志 + Logger.LogError(ex, "获取清洗数据失败"); + + // 返回错误响应 + Response = ApiResponse>.Error( + "500", + "服务器内部错误" + ); + + //// 记录错误日志 + //Logger.LogError(ex, "获取清洗数据失败"); + + //// 返回500错误 + //await Send.ErrorsAsync(500, ct); + } + } + } + + +} diff --git a/MoviconWebApi/API/ClearDataApi/Mapper.cs b/MoviconWebApi/API/ClearDataApi/Mapper.cs new file mode 100644 index 0000000..4236720 --- /dev/null +++ b/MoviconWebApi/API/ClearDataApi/Mapper.cs @@ -0,0 +1,33 @@ +using Azure.Core; +using FastEndpoints; +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.ClearDataApi +{ + public class Mapper : Mapper + { + /// + /// 请求体映射为数据库实体 + /// + /// + /// + public override ClearData ToEntity(ClearDataRequest r) + { + return new ClearData() + { + AgentTank_Level = "", + }; + } + + ///// + ///// 数据库实体类映射为返回体,我这里返回体就是一个string,就没必要用 + ///// + ///// + ///// + //public override async Task FromEntityAsync(ClearData Model ) + //{ + // return await base.FromEntityAsync(Model,new CancellationToken()); + //} + + } +} diff --git a/MoviconWebApi/API/ClearDataApi/Models.cs b/MoviconWebApi/API/ClearDataApi/Models.cs new file mode 100644 index 0000000..451d3b0 --- /dev/null +++ b/MoviconWebApi/API/ClearDataApi/Models.cs @@ -0,0 +1,61 @@ +using Newtonsoft.Json; + +namespace MoviconWebApi.API.ClearDataApi +{ + public class ClearDataRequest + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + + /// + /// 开始时间 + /// + public string? StartTime { get; set; } + + /// + /// 结束时间 + /// + public string? EndTime { get; set; } + + } + + public class ClearDataResponse + { + public string? DeviceCode { get; set; } + public string? DeviceName { get; set; } + public string? program_process { get; set; } + public string? vehicle_model { get; set; } + public string? locomotive_number { get; set; } + public string? repair_process { get; set; } + public string? component_name { get; set; } + public string? part_position { get; set; } + public string? part_num { get; set; } + public string? part_qrid { get; set; } + public string? Test_FrameworkProgramProcessPercentage { get; set; } + public string? Test_FrameworkProgramProcess { get; set; } + public string? Test_FrameworkPerModelCleaningDuration { get; set; } + public string? Test_FrameworkPerModelCleaningAgentUsage { get; set; } + public string? Test_FrameworkPerModelWaterUsage { get; set; } + public string? WaterTank_Temp { get; set; } + public string? AgentTank_Temp { get; set; } + public string? WaterTank_Level { get; set; } + public string? AgentTank_Level { get; set; } + public string? SoakingTank1_Temp { get; set; } + public string? SoakingTank2_Temp { get; set; } + public string? Test_WaterTankHeat { get; set; } + public string? Test_WaterTankAdd { get; set; } + public string? Test_CleaningAgentTankHeat { get; set; } + public string? Test_CleaningAgentTankAdd { get; set; } + public string? Test_ElectricSurveillance { get; set; } + public string? Test_SteamSurveillance { get; set; } + + [JsonIgnore] + public string? CreateTime { get; set; } + + + } + + +} diff --git a/MoviconWebApi/API/ClearDataQrApi/Data.cs b/MoviconWebApi/API/ClearDataQrApi/Data.cs new file mode 100644 index 0000000..d0440e7 --- /dev/null +++ b/MoviconWebApi/API/ClearDataQrApi/Data.cs @@ -0,0 +1,293 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.ClearDataQrApi +{ + /// + /// 清洗数据二维码查询数据访问层 + /// + public static class Data + { + /// + /// 根据设备编号和部件二维码查询清洗数据 + /// + /// 查询请求参数 + /// FreeSql实例 + /// 清洗数据列表 + public static async Task> GetClearDataByQr( + ClearDataQrRequest request, + IFreeSql freeSql) + { + try + { + // 使用FreeSql的Fluent API构建查询 + var query = freeSql.Select(); + + // 添加设备编号条件 + if (!string.IsNullOrWhiteSpace(request.DeviceCode)) + { + query = query.Where(x => x.DeviceCode == request.DeviceCode); + } + + // 添加部件二维码条件 + if (!string.IsNullOrWhiteSpace(request.PartQRCode)) + { + query = query.Where(x => x.part_qrid == request.PartQRCode); + } + + // 按创建时间降序排序 + query = query.OrderByDescending(x => x.CreateTime); + + // 执行查询并映射到响应模型 + var result = await query.ToListAsync(x => new ClearDataQrResponse + { + DeviceCode = x.DeviceCode, + DeviceName = x.DeviceName, + program_process = x.program_process, + vehicle_model = x.vehicle_model, + locomotive_number = x.locomotive_number, + repair_process = x.repair_process, + component_name = x.component_name, + part_position = x.part_position, + part_num = x.part_num, + part_qrid = x.part_qrid, + AgentTank_Level = x.AgentTank_Level, + Test_CleaningAgentTankAdd = x.Test_CleaningAgentTankAdd, + Test_CleaningAgentTankHeat = x.Test_CleaningAgentTankHeat, + Test_FrameworkPerModelCleaningAgentUsage = x.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = x.Test_FrameworkPerModelWaterUsage, + WaterTank_Temp = x.WaterTank_Temp, + AgentTank_Temp = x.AgentTank_Temp, + SoakingTank1_Temp = x.SoakingTank1_Temp, + SoakingTank2_Temp = x.SoakingTank2_Temp, + Test_ElectricSurveillance = x.Test_ElectricSurveillance, + Test_FrameworkPerModelCleaningDuration = x.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkProgramProcess = x.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage = x.Test_FrameworkProgramProcessPercentage, + Test_SteamSurveillance = x.Test_SteamSurveillance, + Test_WaterTankAdd = x.Test_WaterTankAdd, + Test_WaterTankHeat = x.Test_WaterTankHeat, + WaterTank_Level = x.WaterTank_Level, + + CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return result; + } + catch (Exception ex) + { + // 记录异常(在生产环境中应使用适当的日志框架) + Console.WriteLine($"查询清洗数据时出错: {ex.Message}"); + throw; + } + } + + /// + /// 分页查询清洗数据 + /// + /// 分页查询请求参数 + /// FreeSql实例 + /// 分页数据和总数 + public static async Task<(List Data, long Total)> GetClearDataPagedByQr( + ClearDataQrPagedRequest request, + IFreeSql freeSql) + { + try + { + // 使用FreeSql的Fluent API构建查询 + var query = freeSql.Select(); + + // 添加设备编号条件 + if (!string.IsNullOrWhiteSpace(request.DeviceCode)) + { + query = query.Where(x => x.DeviceCode == request.DeviceCode); + } + + // 添加部件二维码条件 + if (!string.IsNullOrWhiteSpace(request.PartQRCode)) + { + query = query.Where(x => x.part_qrid == request.PartQRCode); + } + + // 按创建时间降序排序 + query = query.OrderByDescending(x => x.CreateTime); + + // 获取总数 + var total = await query.CountAsync(); + + // 分页查询并映射到响应模型 + var data = await query + .Page(request.PageIndex, request.PageSize) + .ToListAsync(x => new ClearDataQrResponse + { + DeviceCode = x.DeviceCode, + DeviceName = x.DeviceName, + program_process = x.program_process, + vehicle_model = x.vehicle_model, + locomotive_number = x.locomotive_number, + repair_process = x.repair_process, + component_name = x.component_name, + part_position = x.part_position, + part_num = x.part_num, + part_qrid = x.part_qrid, + AgentTank_Level = x.AgentTank_Level, + Test_CleaningAgentTankAdd = x.Test_CleaningAgentTankAdd, + Test_CleaningAgentTankHeat = x.Test_CleaningAgentTankHeat, + Test_FrameworkPerModelCleaningAgentUsage = x.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = x.Test_FrameworkPerModelWaterUsage, + WaterTank_Temp = x.WaterTank_Temp, + AgentTank_Temp = x.AgentTank_Temp, + SoakingTank1_Temp = x.SoakingTank1_Temp, + SoakingTank2_Temp = x.SoakingTank2_Temp, + Test_ElectricSurveillance = x.Test_ElectricSurveillance, + Test_FrameworkPerModelCleaningDuration = x.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkProgramProcess = x.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage = x.Test_FrameworkProgramProcessPercentage, + Test_SteamSurveillance = x.Test_SteamSurveillance, + Test_WaterTankAdd = x.Test_WaterTankAdd, + Test_WaterTankHeat = x.Test_WaterTankHeat, + WaterTank_Level = x.WaterTank_Level, + CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return (data, total); + } + catch (Exception ex) + { + // 记录异常(在生产环境中应使用适当的日志框架) + Console.WriteLine($"分页查询清洗数据时出错: {ex.Message}"); + throw; + } + } + + /// + /// 获取最新的清洗数据记录 + /// + /// 查询请求(可包含设备编号筛选) + /// FreeSql实例 + /// 最新的清洗数据记录,如果没有则返回null + public static async Task GetLatestClearData( + ClearDataQrRequest request, + IFreeSql freeSql) + { + try + { + // 使用FreeSql的Fluent API构建查询 + var query = freeSql.Select(); + + // 添加设备编号条件(如果提供) + if (!string.IsNullOrWhiteSpace(request.DeviceCode)) + { + query = query.Where(x => x.DeviceCode == request.DeviceCode); + } + + // 添加部件二维码条件(如果提供) + if (!string.IsNullOrWhiteSpace(request.PartQRCode)) + { + query = query.Where(x => x.part_qrid == request.PartQRCode); + } + + // 按创建时间降序排序,取第一条 + var result = await query + .OrderByDescending(x => x.CreateTime) + .Limit(1) + .FirstAsync(x => new ClearDataQrResponse + { + DeviceCode = x.DeviceCode, + DeviceName = x.DeviceName, + program_process = x.program_process, + vehicle_model = x.vehicle_model, + locomotive_number = x.locomotive_number, + repair_process = x.repair_process, + component_name = x.component_name, + part_position = x.part_position, + part_num = x.part_num, + part_qrid = x.part_qrid, + AgentTank_Level = x.AgentTank_Level, + Test_CleaningAgentTankAdd = x.Test_CleaningAgentTankAdd, + Test_CleaningAgentTankHeat = x.Test_CleaningAgentTankHeat, + Test_FrameworkPerModelCleaningAgentUsage = x.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = x.Test_FrameworkPerModelWaterUsage, + WaterTank_Temp = x.WaterTank_Temp, + AgentTank_Temp = x.AgentTank_Temp, + SoakingTank1_Temp = x.SoakingTank1_Temp, + SoakingTank2_Temp = x.SoakingTank2_Temp, + Test_ElectricSurveillance = x.Test_ElectricSurveillance, + Test_FrameworkPerModelCleaningDuration = x.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkProgramProcess = x.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage = x.Test_FrameworkProgramProcessPercentage, + Test_SteamSurveillance = x.Test_SteamSurveillance, + Test_WaterTankAdd = x.Test_WaterTankAdd, + Test_WaterTankHeat = x.Test_WaterTankHeat, + WaterTank_Level = x.WaterTank_Level, + CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return result; + } + catch (Exception ex) + { + // 记录异常(在生产环境中应使用适当的日志框架) + Console.WriteLine($"获取最新清洗数据时出错: {ex.Message}"); + throw; + } + } + + /// + /// 根据部件二维码获取所有相关的清洗记录 + /// + /// 部件二维码 + /// FreeSql实例 + /// 清洗数据列表 + public static async Task> GetClearDataByPartQRCode( + string partQRCode, + IFreeSql freeSql) + { + try + { + // 使用FreeSql的Fluent API构建查询 + var result = await freeSql.Select() + .Where(x => x.part_qrid == partQRCode) + .OrderByDescending(x => x.CreateTime) + .ToListAsync(x => new ClearDataQrResponse + { + DeviceCode = x.DeviceCode, + DeviceName = x.DeviceName, + program_process = x.program_process, + vehicle_model = x.vehicle_model, + locomotive_number = x.locomotive_number, + repair_process = x.repair_process, + component_name = x.component_name, + part_position = x.part_position, + part_num = x.part_num, + part_qrid = x.part_qrid, + AgentTank_Level = x.AgentTank_Level, + Test_CleaningAgentTankAdd = x.Test_CleaningAgentTankAdd, + Test_CleaningAgentTankHeat = x.Test_CleaningAgentTankHeat, + Test_FrameworkPerModelCleaningAgentUsage = x.Test_FrameworkPerModelCleaningAgentUsage, + Test_FrameworkPerModelWaterUsage = x.Test_FrameworkPerModelWaterUsage, + WaterTank_Temp = x.WaterTank_Temp, + AgentTank_Temp = x.AgentTank_Temp, + SoakingTank1_Temp = x.SoakingTank1_Temp, + SoakingTank2_Temp = x.SoakingTank2_Temp, + Test_ElectricSurveillance = x.Test_ElectricSurveillance, + Test_FrameworkPerModelCleaningDuration = x.Test_FrameworkPerModelCleaningDuration, + Test_FrameworkProgramProcess = x.Test_FrameworkProgramProcess, + Test_FrameworkProgramProcessPercentage = x.Test_FrameworkProgramProcessPercentage, + Test_SteamSurveillance = x.Test_SteamSurveillance, + Test_WaterTankAdd = x.Test_WaterTankAdd, + Test_WaterTankHeat = x.Test_WaterTankHeat, + WaterTank_Level = x.WaterTank_Level, + CreateTime = x.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return result; + } + catch (Exception ex) + { + // 记录异常(在生产环境中应使用适当的日志框架) + Console.WriteLine($"根据二维码查询清洗数据时出错: {ex.Message}"); + throw; + } + } + } +} diff --git a/MoviconWebApi/API/ClearDataQrApi/Endpoint.cs b/MoviconWebApi/API/ClearDataQrApi/Endpoint.cs new file mode 100644 index 0000000..10ca686 --- /dev/null +++ b/MoviconWebApi/API/ClearDataQrApi/Endpoint.cs @@ -0,0 +1,257 @@ +using Azure; +using FastEndpoints; +using Microsoft.Extensions.Logging; +using MoviconWebApi.Common; + +namespace MoviconWebApi.API.ClearDataQrApi +{ + /// + /// 根据设备编号和部件二维码查询清洗数据端点 + /// + public class GetByQrEndpoint : Endpoint>> + { + private readonly IFreeSql _freeSql; + + public GetByQrEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Get("/cleardata-qr/list"); + AllowAnonymous(); + + Summary(s => + { + s.Summary = "根据设备编号和部件二维码查询清洗数据"; + s.Description = "可以通过设备编号、部件二维码或两者组合查询清洗数据"; + s.Response>>(200, "成功返回清洗数据列表"); + s.Response(404, "未找到数据"); + s.Response(500, "服务器内部错误"); + }); + } + + public override async Task HandleAsync(ClearDataQrRequest request, CancellationToken ct) + { + try + { + var dataList = await Data.GetClearDataByQr(request, _freeSql); + + if (dataList == null || dataList.Count == 0) + { + Response = ApiResponse>.Success( + new List(), "暂无数据");// "暂无数据" + } + else + { + Response = ApiResponse>.Success( + dataList, "查询成功");// "查询成功" + } + } + catch (Exception ex) + { + //Logger.LogError(ex, "根据二维码查询清洗数据失败"); + //await Send.ErrorsAsync(500, ct); + + Logger.LogError(ex, "根据二维码查询清洗数据失败"); + Response = ApiResponse>.Error( + "500", + "服务器内部错误" + ); + + } + } + } + + + + + + + + + + + + /// + /// 分页查询清洗数据端点 + /// + public class GetPagedByQrEndpoint : Endpoint + { + private readonly IFreeSql _freeSql; + + public GetPagedByQrEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Post("/cleardata-qr/paged"); + AllowAnonymous(); + + Summary(s => + { + s.Summary = "分页查询清洗数据"; + s.Description = "根据设备编号和部件二维码分页查询清洗数据"; + s.Response(200, "成功返回分页数据"); + s.Response(500, "服务器内部错误"); + }); + } + + public override async Task HandleAsync(ClearDataQrPagedRequest request, CancellationToken ct) + { + try + { + // 验证分页参数 + if (request.PageIndex < 1) + request.PageIndex = 1; + if (request.PageSize < 1) + request.PageSize = 20; + if (request.PageSize > 100) + request.PageSize = 100; + + // 调用Data层方法,获取元组返回值 + var (data, total) = await Data.GetClearDataPagedByQr(request, _freeSql); + + // 构建响应对象 + Response = new ClearDataQrPagedResponse + { + Items = data, + TotalCount = total, + PageIndex = request.PageIndex, + PageSize = request.PageSize + }; + } + catch (Exception ex) + { + Logger.LogError(ex, "分页查询清洗数据失败"); + await Send.ErrorsAsync(500, ct); + } + } + } + + /// + /// 获取最新清洗数据端点 + /// + public class GetLatestByQrEndpoint : Endpoint + { + private readonly IFreeSql _freeSql; + + public GetLatestByQrEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Get("/cleardata-qr/latest"); + AllowAnonymous(); + + Summary(s => + { + s.Summary = "获取最新的清洗数据"; + s.Description = "根据设备编号和部件二维码获取最新的一条清洗数据"; + s.Response(200, "成功返回最新清洗数据"); + s.Response(404, "未找到数据"); + s.Response(500, "服务器内部错误"); + }); + } + + public override async Task HandleAsync(ClearDataQrRequest request, CancellationToken ct) + { + try + { + // 调用重构后的方法,直接传递request对象 + var latestData = await Data.GetLatestClearData(request, _freeSql); + + if (latestData == null) + { + //await Send.NotFoundAsync(ct); + Response = latestData ?? new ClearDataQrResponse(); + } + else + { + Response = latestData; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "获取最新清洗数据失败"); + await Send.ErrorsAsync(500, ct); + } + } + } + + /// + /// 根据部件二维码获取所有清洗记录端点 + /// + public class GetByPartQrCodeRequest + { + /// + /// 部件二维码(必填) + /// + public string PartQRCode { get; set; } = string.Empty; + } + + public class GetByPartQrCodeEndpoint : Endpoint> + { + private readonly IFreeSql _freeSql; + + public GetByPartQrCodeEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Get("/cleardata-qr/by-qrcode/{PartQRCode}"); + AllowAnonymous(); + + Summary(s => + { + s.Summary = "根据部件二维码获取所有清洗记录"; + s.Description = "仅根据部件二维码查询该部件的所有清洗历史记录"; + s.Response>(200, "成功返回清洗数据列表"); + s.Response(400, "请求参数错误"); + s.Response(404, "未找到数据"); + s.Response(500, "服务器内部错误"); + }); + } + + public override async Task HandleAsync(GetByPartQrCodeRequest request, CancellationToken ct) + { + try + { + // 验证参数 + if (string.IsNullOrWhiteSpace(request.PartQRCode)) + { + AddError("PartQRCode", "部件二维码不能为空"); + await Send.ErrorsAsync(400, ct); + return; + } + + // 调用重构后的方法 + var dataList = await Data.GetClearDataByPartQRCode(request.PartQRCode, _freeSql); + + if (dataList == null || dataList.Count == 0) + { + //await Send.NotFoundAsync(ct); + Response = dataList ?? new List(); + } + else + { + Response = dataList; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "根据部件二维码查询清洗数据失败"); + await Send.ErrorsAsync(500, ct); + } + } + } + + +} diff --git a/MoviconWebApi/API/ClearDataQrApi/Mapper.cs b/MoviconWebApi/API/ClearDataQrApi/Mapper.cs new file mode 100644 index 0000000..cd476d7 --- /dev/null +++ b/MoviconWebApi/API/ClearDataQrApi/Mapper.cs @@ -0,0 +1,99 @@ +namespace MoviconWebApi.API.ClearDataQrApi +{ + /// + /// 数据映射器 + /// + public static class Mapper + { + /// + /// 将数据库实体映射到响应模型 + /// + /// 数据库实体 + /// 响应模型 + public static ClearDataQrResponse ToResponse(dynamic entity) + { + if (entity == null) return null; + + var response = new ClearDataQrResponse + { + DeviceCode = entity.DeviceCode?.ToString(), + DeviceName = entity.DeviceName?.ToString(), + program_process = entity.program_process?.ToString(), + vehicle_model = entity.vehicle_model?.ToString(), + locomotive_number = entity.locomotive_number?.ToString(), + repair_process = entity.repair_process?.ToString(), + component_name = entity.component_name?.ToString(), + part_position = entity.part_position?.ToString(), + part_num = entity.part_num?.ToString(), + part_qrid = entity.part_qrid?.ToString(), + Test_FrameworkPerModelCleaningDuration = entity.Test_FrameworkPerModelCleaningDuration?.ToString(), + Test_FrameworkPerModelCleaningAgentUsage = entity.Test_FrameworkPerModelCleaningAgentUsage?.ToString(), + Test_FrameworkPerModelWaterUsage = entity.Test_FrameworkPerModelWaterUsage?.ToString(), + WaterTank_Temp = entity.WaterTank_Temp?.ToString(), + AgentTank_Temp = entity.AgentTank_Temp?.ToString(), + SoakingTank1_Temp = entity.SoakingTank1_Temp?.ToString(), + SoakingTank2_Temp = entity.SoakingTank2_Temp?.ToString() + }; + + // 处理时间字段 + if (entity.CreateTime != null) + { + if (entity.CreateTime is DateTime dt) + { + response.CreateTime = dt.ToString("yyyy-MM-dd HH:mm:ss"); + } + else + { + response.CreateTime = entity.CreateTime.ToString(); + } + } + + return response; + } + + /// + /// 格式化日期时间 + /// + /// 日期时间字符串 + /// 格式化后的字符串 + public static string FormatDateTime(string dateTimeString) + { + if (string.IsNullOrWhiteSpace(dateTimeString)) + return dateTimeString; + + if (DateTime.TryParse(dateTimeString, out DateTime dateTime)) + { + return dateTime.ToString("yyyy-MM-dd HH:mm:ss"); + } + + return dateTimeString; + } + + /// + /// 解析数值类型 + /// + /// 原始值 + /// 数值或null + public static double? ParseDouble(object value) + { + if (value == null) return null; + + if (value is double d) + return d; + + if (value is float f) + return f; + + if (value is decimal dec) + return (double)dec; + + if (value is int i) + return i; + + if (double.TryParse(value.ToString(), out double result)) + return result; + + return null; + } + } +} diff --git a/MoviconWebApi/API/ClearDataQrApi/Models.cs b/MoviconWebApi/API/ClearDataQrApi/Models.cs new file mode 100644 index 0000000..e55aec6 --- /dev/null +++ b/MoviconWebApi/API/ClearDataQrApi/Models.cs @@ -0,0 +1,109 @@ +namespace MoviconWebApi.API.ClearDataQrApi +{ + /// + /// 清洗数据二维码查询请求模型 + /// + public class ClearDataQrRequest + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + + /// + /// 部件二维码 + /// + public string? PartQRCode { get; set; } + } + + /// + /// 清洗数据响应模型 + /// + public class ClearDataQrResponse + { + public string? DeviceCode { get; set; } + public string? DeviceName { get; set; } + public string? program_process { get; set; } + public string? vehicle_model { get; set; } + public string? locomotive_number { get; set; } + public string? repair_process { get; set; } + public string? component_name { get; set; } + public string? part_position { get; set; } + public string? part_num { get; set; } + public string? part_qrid { get; set; } + public string? Test_FrameworkProgramProcessPercentage { get; set; } + public string? Test_FrameworkProgramProcess { get; set; } + public string? Test_FrameworkPerModelCleaningDuration { get; set; } + public string? Test_FrameworkPerModelCleaningAgentUsage { get; set; } + public string? Test_FrameworkPerModelWaterUsage { get; set; } + public string? WaterTank_Temp { get; set; } + public string? AgentTank_Temp { get; set; } + public string? WaterTank_Level { get; set; } + public string? AgentTank_Level { get; set; } + public string? SoakingTank1_Temp { get; set; } + public string? SoakingTank2_Temp { get; set; } + public string? Test_WaterTankHeat { get; set; } + public string? Test_WaterTankAdd { get; set; } + public string? Test_CleaningAgentTankHeat { get; set; } + public string? Test_CleaningAgentTankAdd { get; set; } + public string? Test_ElectricSurveillance { get; set; } + public string? Test_SteamSurveillance { get; set; } + + /// + /// 创建时间 + /// + public string? CreateTime { get; set; } + } + + + + /// + /// 分页查询请求模型 + /// + public class ClearDataQrPagedRequest : ClearDataQrRequest + { + /// + /// 页码(从1开始) + /// + public int PageIndex { get; set; } = 1; + + /// + /// 每页数据量 + /// + public int PageSize { get; set; } = 20; + } + + + + + /// + /// 分页响应模型 + /// + public class ClearDataQrPagedResponse + { + /// + /// 数据列表 + /// + public List Items { get; set; } = new List(); + + /// + /// 总记录数 + /// + public long TotalCount { get; set; } + + /// + /// 当前页码 + /// + public int PageIndex { get; set; } + + /// + /// 每页数量 + /// + public int PageSize { get; set; } + + /// + /// 总页数 + /// + public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)TotalCount / PageSize) : 0; + } +} diff --git a/MoviconWebApi/API/ClearStaticApi/Data.cs b/MoviconWebApi/API/ClearStaticApi/Data.cs new file mode 100644 index 0000000..2fa9a1b --- /dev/null +++ b/MoviconWebApi/API/ClearStaticApi/Data.cs @@ -0,0 +1,103 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.ClearStaticApi +{ + /// + /// 清洗统计数据访问类 + /// + public static class Data + { + /// + /// 获取清洗统计数据 + /// + /// 请求参数 + /// FreeSql实例 + /// 清洗统计响应 + internal static async Task GetClearStatic(ClearStaticRequest request, IFreeSql freeSql) + { + try + { + // 获取当前日期信息 + var today = DateTime.Today; + var firstDayOfMonth = new DateTime(today.Year, today.Month, 1); + var firstDayOfYear = new DateTime(today.Year, 1, 1); + + // 构建基础查询 + var query = freeSql.Select(); + // 构建基础查询 + //var queryDeviceState = freeSql.Select(); + + var CurRunClearData = freeSql.Select().Where(a => a.Test_PartsEquipmentStatus == "1").OrderByDescending(a => a.CreateTime).First(); + + // 根据设备编号过滤 + if (!string.IsNullOrWhiteSpace(request.DeviceCode)) + { + query = query.Where(x => x.DeviceCode == request.DeviceCode); + } + + // 计算累计作业数量 + var totalJobCount = await query.CountAsync(); + + // 计算本年度作业数 + var yearJobCount = await query + .Where(x => x.CreateTime >= firstDayOfYear) + .CountAsync(); + + // 计算本月作业数 + var monthJobCount = await query + .Where(x => x.CreateTime >= firstDayOfMonth) + .CountAsync(); + + // 计算今日作业数 + var todayJobCount = await query + .Where(x => x.CreateTime >= today) + .CountAsync(); + + // 计算累计作业时长 + // 注意:这里假设ClearData表中有一个字段表示清洗时长,可能需要根据实际情况调整 + var totalJobHours = await query + .SumAsync(x => Convert.ToDecimal(x.RunTime)) / 60; // 假设时长单位是秒,转换为小时 + + // 计算本年作业时长 + var yearJobHours = await query + .Where(x => x.CreateTime >= firstDayOfYear) + .SumAsync(x => Convert.ToDecimal(x.RunTime)) / 60; + + // 计算本月作业时长 + var monthJobHours = await query + .Where(x => x.CreateTime >= firstDayOfMonth) + .SumAsync(x => Convert.ToDecimal(x.RunTime)) / 60; + + // 计算今日作业时长 + var todayJobHours = await query + .Where(x => x.CreateTime >= today) + .SumAsync(x => Convert.ToDecimal(x.RunTime)) / 60; + + // 返回结果 + return new ClearStaticResponse + { + TotalJobCount = (int)totalJobCount, + YearJobCount = (int)yearJobCount, + MonthJobCount = (int)monthJobCount, + TodayJobCount = (int)todayJobCount, + TotalJobHours = Math.Round(totalJobHours, 2), + YearJobHours = Math.Round(yearJobHours, 2), + MonthJobHours = Math.Round(monthJobHours, 2), + TodayJobHours = Math.Round(todayJobHours, 2), + CurrentVehicleModel = CurRunClearData != null ? CurRunClearData.vehicle_model : "", + CurrentLocomotiveNumber = CurRunClearData != null ? CurRunClearData.locomotive_number : "", + CurrentRepairProcess = CurRunClearData != null ? CurRunClearData.repair_process : "", + CurrentWheelNumber = "", // 假设part_num字段存储车轮编号 + + UpdateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + }; + } + catch (Exception ex) + { + // 记录异常日志 + Console.WriteLine($"获取清洗统计数据失败:{ex.Message}"); + return null; + } + } + } +} diff --git a/MoviconWebApi/API/ClearStaticApi/Endpoint.cs b/MoviconWebApi/API/ClearStaticApi/Endpoint.cs new file mode 100644 index 0000000..f2157c9 --- /dev/null +++ b/MoviconWebApi/API/ClearStaticApi/Endpoint.cs @@ -0,0 +1,80 @@ +using Azure; +using FastEndpoints; +using Microsoft.Extensions.Logging; +using MoviconWebApi.Common; + +namespace MoviconWebApi.API.ClearStaticApi +{ + /// + /// 获取清洗统计数据端点 + /// + public class Endpoint : Endpoint> + { + private readonly IFreeSql _freeSql; + + /// + /// 构造函数,注入FreeSql实例 + /// + /// FreeSql实例 + public Endpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + // 配置路由,支持GET方式 + Get("/clearstatic/summary"); + + // 允许匿名访问(根据实际需求可以改为需要认证) + AllowAnonymous(); + + // 配置摘要信息(用于Swagger文档) + Summary(s => + { + s.Summary = "获取清洗统计数据"; + s.Description = "根据设备编号获取清洗统计数据,包括作业数量、作业时长等"; + s.Response>(200, "成功返回清洗统计数据"); + s.Response(404, "未找到数据"); + s.Response(500, "服务器内部错误"); + }); + } + + public override async Task HandleAsync(ClearStaticRequest request, CancellationToken ct) + { + try + { + // 调用Data层方法获取数据 + var staticData = await Data.GetClearStatic(request, _freeSql); + + if (staticData == null) + { + // 未找到数据,返回空数据但状态码200 + Response = ApiResponse.Success( + new ClearStaticResponse(), + "暂无数据" + ); + } + else + { + // 返回统计数据 + Response = ApiResponse.Success( + staticData, + "查询成功" + ); + } + } + catch (Exception ex) + { + // 记录错误日志 + Logger.LogError(ex, "获取清洗统计数据失败"); + + // 返回错误响应 + Response = ApiResponse.Error( + "500", + "服务器内部错误" + ); + } + } + } +} diff --git a/MoviconWebApi/API/ClearStaticApi/Mapper.cs b/MoviconWebApi/API/ClearStaticApi/Mapper.cs new file mode 100644 index 0000000..cf560b1 --- /dev/null +++ b/MoviconWebApi/API/ClearStaticApi/Mapper.cs @@ -0,0 +1,28 @@ +namespace MoviconWebApi.API.ClearStaticApi +{ + /// + /// 清洗统计数据映射工具类 + /// + public static class Mapper + { + /// + /// 格式化小数为两位小数的字符串 + /// + /// 原始值 + /// 格式化后的字符串 + public static string FormatDecimal(decimal value) + { + return value.ToString("0.00"); + } + + /// + /// 格式化日期时间为yyyy-MM-dd HH:mm:ss格式 + /// + /// 日期时间 + /// 格式化后的字符串 + public static string FormatDateTime(DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd HH:mm:ss"); + } + } +} diff --git a/MoviconWebApi/API/ClearStaticApi/Models.cs b/MoviconWebApi/API/ClearStaticApi/Models.cs new file mode 100644 index 0000000..f94332d --- /dev/null +++ b/MoviconWebApi/API/ClearStaticApi/Models.cs @@ -0,0 +1,84 @@ +namespace MoviconWebApi.API.ClearStaticApi +{ + /// + /// 清洗统计请求模型 + /// + public class ClearStaticRequest + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + } + + /// + /// 清洗统计响应模型 + /// + public class ClearStaticResponse + { + /// + /// 累计作业数量 + /// + public int TotalJobCount { get; set; } + + /// + /// 本年度作业数 + /// + public int YearJobCount { get; set; } + + /// + /// 本月作业数 + /// + public int MonthJobCount { get; set; } + + /// + /// 今日作业数 + /// + public int TodayJobCount { get; set; } + + /// + /// 累计作业时长(时) + /// + public decimal TotalJobHours { get; set; } + + /// + /// 本年作业时长(时) + /// + public decimal YearJobHours { get; set; } + + /// + /// 本月作业时长(时) + /// + public decimal MonthJobHours { get; set; } + + /// + /// 今日作业时长(时) + /// + public decimal TodayJobHours { get; set; } + + /// + /// 当前机型 + /// + public string? CurrentVehicleModel { get; set; } + + /// + /// 当前车号 + /// + public string? CurrentLocomotiveNumber { get; set; } + + /// + /// 当前修程 + /// + public string? CurrentRepairProcess { get; set; } + + /// + /// 车轮编号 + /// + public string? CurrentWheelNumber { get; set; } + + /// + /// 更新时间 + /// + public string? UpdateTime { get; set; } + } +} diff --git a/MoviconWebApi/API/DeviceAlarmApi/Data.cs b/MoviconWebApi/API/DeviceAlarmApi/Data.cs new file mode 100644 index 0000000..a63262e --- /dev/null +++ b/MoviconWebApi/API/DeviceAlarmApi/Data.cs @@ -0,0 +1,171 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.DeviceAlarmApi +{ + public static class DeviceAlarmData + { + /// + /// 获取设备报警列表 + /// + public static async Task> GetDeviceAlarmList( + IFreeSql freeSql, + DeviceAlarmRequest request) + { + try + { + var query = freeSql.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), + a => a.DeviceCode == request.DeviceCode) + .WhereIf(request.DeviceState > 0, + a => a.DeviceState == request.DeviceState); + + // 处理时间范围查询 + if (!string.IsNullOrWhiteSpace(request.StartTime) && DateTime.TryParse(request.StartTime, out var startTime)) + { + query = query.Where(a => a.StartTime >= startTime); + } + + if (!string.IsNullOrWhiteSpace(request.EndTime) && DateTime.TryParse(request.EndTime, out var endTime)) + { + query = query.Where(a => a.EndTime <= endTime); + } + + var data = await query + .OrderByDescending(a => a.StartTime) + .ToListAsync(a => new DeviceAlarmResponse + { + DeviceCode = a.DeviceCode, + DeviceName = a.DeviceName, + DeviceState = a.DeviceState, + AlarmMessage = a.AlarmMessage, + StartTime = a.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = a.EndTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return data ?? new List(); + } + catch (Exception ex) + { + throw new Exception($"获取设备报警数据失败: {ex.Message}", ex); + } + } + + /// + /// 获取设备报警分页数据 + /// + public static async Task<(List Items, long Total)> GetDeviceAlarmPagedList( + IFreeSql freeSql, + DeviceAlarmPagedRequest request) + { + try + { + var query = freeSql.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), + a => a.DeviceCode == request.DeviceCode) + .WhereIf(request.DeviceState > 0, + a => a.DeviceState == request.DeviceState); + + // 处理时间范围查询 + if (!string.IsNullOrWhiteSpace(request.StartTime) && DateTime.TryParse(request.StartTime, out var startTime)) + { + query = query.Where(a => a.StartTime >= startTime); + } + + if (!string.IsNullOrWhiteSpace(request.EndTime) && DateTime.TryParse(request.EndTime, out var endTime)) + { + query = query.Where(a => a.EndTime <= endTime); + } + + var total = await query.CountAsync(); + + var data = await query + .OrderByDescending(a => a.StartTime) + .Page(request.PageNumber, request.PageSize) + .ToListAsync(a => new DeviceAlarmResponse + { + DeviceCode = a.DeviceCode, + DeviceName = a.DeviceName, + DeviceState = a.DeviceState, + AlarmMessage = a.AlarmMessage, + StartTime = a.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = a.EndTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return (data ?? new List(), total); + } + catch (Exception ex) + { + throw new Exception($"获取设备报警分页数据失败: {ex.Message}", ex); + } + } + + /// + /// 获取最新的设备报警记录 + /// + public static async Task GetLatestDeviceAlarm( + IFreeSql freeSql, + DeviceAlarmRequest request) + { + try + { + var query = freeSql.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), + a => a.DeviceCode == request.DeviceCode) + .WhereIf(request.DeviceState > 0, + a => a.DeviceState == request.DeviceState); + + var data = await query + .OrderByDescending(a => a.StartTime) + .FirstAsync(a => new DeviceAlarmResponse + { + DeviceCode = a.DeviceCode, + DeviceName = a.DeviceName, + DeviceState = a.DeviceState, + AlarmMessage = a.AlarmMessage, + StartTime = a.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = a.EndTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return data; + } + catch (Exception ex) + { + throw new Exception($"获取最新设备报警数据失败: {ex.Message}", ex); + } + } + + /// + /// 获取指定设备的活动报警(EndTime为最大值表示未结束) + /// + public static async Task> GetActiveAlarms( + IFreeSql freeSql, + string? deviceCode) + { + try + { + var query = freeSql.Select() + .WhereIf(!string.IsNullOrWhiteSpace(deviceCode), + a => a.DeviceCode == deviceCode) + .Where(a => a.EndTime == DateTime.MaxValue || a.EndTime > DateTime.Now); + + var data = await query + .OrderByDescending(a => a.StartTime) + .ToListAsync(a => new DeviceAlarmResponse + { + DeviceCode = a.DeviceCode, + DeviceName = a.DeviceName, + DeviceState = a.DeviceState, + AlarmMessage = a.AlarmMessage, + StartTime = a.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = a.EndTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return data ?? new List(); + } + catch (Exception ex) + { + throw new Exception($"获取活动报警数据失败: {ex.Message}", ex); + } + } + } +} diff --git a/MoviconWebApi/API/DeviceAlarmApi/Endpoint.cs b/MoviconWebApi/API/DeviceAlarmApi/Endpoint.cs new file mode 100644 index 0000000..007e955 --- /dev/null +++ b/MoviconWebApi/API/DeviceAlarmApi/Endpoint.cs @@ -0,0 +1,174 @@ +using Azure; +using FastEndpoints; +using MoviconWebApi.Common; +using MoviconWebApi.Entities; +using System; + +namespace MoviconWebApi.API.DeviceAlarmApi +{ + + /// + /// 设备报警列表查询端点 + /// + public class GetDeviceAlarmListEndpoint : Endpoint>> + { + private readonly IFreeSql _freeSql; + + public GetDeviceAlarmListEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Get("/devicealarm/list"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "获取设备报警列表"; + s.Description = "根据设备编号、时间范围和设备状态查询设备报警"; + s.Response>>(200, "成功返回设备报警列表"); + }); + } + + public override async Task HandleAsync(DeviceAlarmRequest req, CancellationToken ct) + { + try + { + var data = await DeviceAlarmData.GetDeviceAlarmList(_freeSql, req); + Response = ApiResponse>.Success(data, "success"); + } + catch (Exception ex) + { + Response = ApiResponse>.Error("500", $"获取数据失败: {ex.Message}"); + } + } + } + + /// + /// 设备报警分页查询端点 + /// + public class GetDeviceAlarmPagedEndpoint : Endpoint> + { + private readonly IFreeSql _freeSql; + + public GetDeviceAlarmPagedEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Post("/devicealarm/paged"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "分页获取设备报警"; + s.Description = "根据设备编号、时间范围和设备状态分页查询设备报警"; + s.Response>(200, "成功返回设备报警分页数据"); + }); + } + + public override async Task HandleAsync(DeviceAlarmPagedRequest req, CancellationToken ct) + { + try + { + var (items, total) = await DeviceAlarmData.GetDeviceAlarmPagedList(_freeSql, req); + var pagedResponse = new DeviceAlarmPagedResponse + { + Items = items, + Total = total + }; + Response = ApiResponse.Success(pagedResponse, "success"); + } + catch (Exception ex) + { + Response = ApiResponse.Error("500", $"获取数据失败: {ex.Message}"); + } + } + } + + /// + /// 获取最新设备报警端点 + /// + public class GetLatestDeviceAlarmEndpoint : Endpoint> + { + private readonly IFreeSql _freeSql; + + public GetLatestDeviceAlarmEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Get("/devicealarm/latest"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "获取最新设备报警"; + s.Description = "根据设备编号获取最新的设备报警记录"; + s.Response>(200, "成功返回最新设备报警"); + }); + } + + public override async Task HandleAsync(DeviceAlarmRequest req, CancellationToken ct) + { + try + { + var data = await DeviceAlarmData.GetLatestDeviceAlarm(_freeSql, req); + if (data == null) + { + Response = ApiResponse.Error("404", "未找到设备报警记录"); + } + else + { + Response = ApiResponse.Success(data, "success"); + } + } + catch (Exception ex) + { + Response = ApiResponse.Error("500", $"获取数据失败: {ex.Message}"); + } + } + } + + /// + /// 获取活动报警端点 + /// + public class GetActiveAlarmsEndpoint : Endpoint>> + { + private readonly IFreeSql _freeSql; + + public GetActiveAlarmsEndpoint(IFreeSql freeSql) + { + _freeSql = freeSql; + } + + public override void Configure() + { + Get("/devicealarm/active"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "获取活动报警"; + s.Description = "获取指定设备的所有活动(未结束)报警"; + s.Response>>(200, "成功返回活动报警列表"); + }); + } + + public override async Task HandleAsync(DeviceAlarmRequest req, CancellationToken ct) + { + try + { + var data = await DeviceAlarmData.GetActiveAlarms(_freeSql, req.DeviceCode); + Response = ApiResponse>.Success(data, "success"); + } + catch (Exception ex) + { + Response = ApiResponse>.Error("500", $"获取数据失败: {ex.Message}"); + } + } + } + +} diff --git a/MoviconWebApi/API/DeviceAlarmApi/Mapper.cs b/MoviconWebApi/API/DeviceAlarmApi/Mapper.cs new file mode 100644 index 0000000..a1806cb --- /dev/null +++ b/MoviconWebApi/API/DeviceAlarmApi/Mapper.cs @@ -0,0 +1,42 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.DeviceAlarmApi +{ + /// + /// 设备报警数据映射器 + /// + public static class DeviceAlarmMapper + { + /// + /// 将实体映射到响应模型 + /// + public static DeviceAlarmResponse ToResponse(this DeviceAlarm entity) + { + return new DeviceAlarmResponse + { + DeviceCode = entity.DeviceCode, + DeviceName = entity.DeviceName, + DeviceState = entity.DeviceState, + AlarmMessage = entity.AlarmMessage, + StartTime = entity.StartTime.ToString("yyyy-MM-dd HH:mm:ss"), + EndTime = entity.EndTime.ToString("yyyy-MM-dd HH:mm:ss") + }; + } + + /// + /// 批量映射 + /// + public static List ToResponseList(this IEnumerable entities) + { + return entities.Select(e => e.ToResponse()).ToList(); + } + + /// + /// 判断报警是否活动 + /// + public static bool IsActive(this DeviceAlarm entity) + { + return entity.EndTime == DateTime.MaxValue || entity.EndTime > DateTime.Now; + } + } +} diff --git a/MoviconWebApi/API/DeviceAlarmApi/Models.cs b/MoviconWebApi/API/DeviceAlarmApi/Models.cs new file mode 100644 index 0000000..725eb7d --- /dev/null +++ b/MoviconWebApi/API/DeviceAlarmApi/Models.cs @@ -0,0 +1,96 @@ +namespace MoviconWebApi.API.DeviceAlarmApi +{ + /// + /// 设备报警查询请求 + /// + public class DeviceAlarmRequest + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + + /// + /// 开始时间 + /// + public string? StartTime { get; set; } + + /// + /// 结束时间 + /// + public string? EndTime { get; set; } + + /// + /// 设备状态(0表示返回所有状态记录) + /// + public int DeviceState { get; set; } = 0; + } + + /// + /// 设备报警分页查询请求 + /// + public class DeviceAlarmPagedRequest : DeviceAlarmRequest + { + /// + /// 页码 + /// + public int PageNumber { get; set; } = 1; + + /// + /// 每页大小 + /// + public int PageSize { get; set; } = 10; + } + + /// + /// 设备报警响应 + /// + public class DeviceAlarmResponse + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + + /// + /// 设备名称 + /// + public string? DeviceName { get; set; } + + /// + /// 设备状态 + /// + public int? DeviceState { get; set; } + + /// + /// 报警信息 + /// + public string? AlarmMessage { get; set; } + + /// + /// 开始时间 + /// + public string? StartTime { get; set; } + + /// + /// 结束时间 + /// + public string? EndTime { get; set; } + } + + /// + /// 设备报警分页响应 + /// + public class DeviceAlarmPagedResponse + { + /// + /// 数据列表 + /// + public List Items { get; set; } = new List(); + + /// + /// 总数 + /// + public long Total { get; set; } + } +} diff --git a/MoviconWebApi/API/DeviceStateApi/Data.cs b/MoviconWebApi/API/DeviceStateApi/Data.cs new file mode 100644 index 0000000..4c8c529 --- /dev/null +++ b/MoviconWebApi/API/DeviceStateApi/Data.cs @@ -0,0 +1,218 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.DeviceStateApi +{ + public static class Data + { + /// + /// 获取设备状态列表 + /// + public static async Task> GetDeviceStateListAsync( + IFreeSql db, + DeviceStateRequest request) + { + try + { + // 1) 校验时间范围 + var hasStart = DateTime.TryParse(request.StartTime, out var startTime); + var hasEnd = DateTime.TryParse(request.EndTime, out var endTime); + + if (!hasStart || !hasEnd) + { + // 未提供有效时间范围,返回空列表(遵循列表查询返回空集合不返回404的约定) + return new List(); + } + + if (endTime < startTime) + { + // 纠正时间顺序或直接返回空 + return new List(); + } + + var totalMinutes = (endTime - startTime).TotalMinutes; + if (totalMinutes <= 0) + { + return new List(); + } + + // 2) 基础查询(按设备、时间过滤) + var baseQuery = db.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), x => x.DeviceCode == request.DeviceCode) + .Where(x => x.CreateTime >= startTime && x.CreateTime <= endTime); + + // 3) 聚合统计:仅选择必要字段到内存做聚合,避免类型不兼容问题 + var lightList = await baseQuery.ToListAsync(a => new + { + a.PowerOnTime, + a.RunTime, + a.StandbyTime, + a.FaultTime, + a.ShutdownTime, + a.FaultNum, + a.JobNum + }); + + long totalPowerOn = lightList.Sum(x => (long)(x.PowerOnTime ?? 0)); + long totalRun = lightList.Sum(x => (long)(x.RunTime ?? 0)); + long totalStandby = lightList.Sum(x => (long)(x.StandbyTime ?? 0)); + long totalFault = lightList.Sum(x => (long)(x.FaultTime ?? 0)); + long totalShutdown = lightList.Sum(x => (long)(x.ShutdownTime ?? 0)); + long totalFaultCount = lightList.Sum(x => (long)(x.FaultNum ?? 0)); + long totalJobCount = lightList.Sum(x => (long)(x.JobNum ?? 0)); + + // 若该时间段内没有任何记录,返回空集合 + var hasAnyData = (totalPowerOn + totalRun + totalStandby + totalFault + totalShutdown + totalFaultCount + totalJobCount) > 0; + if (!hasAnyData) + { + return new List(); + } + + // 4) 计算使用率:默认按(开机+运行+待机+故障)/ 时间段总分钟 + var usedMinutes = totalPowerOn + totalRun + totalStandby + totalFault; // 排除关机 + double ratio = usedMinutes <= 0 ? 0 : Math.Min(100.0, Math.Max(0.0, usedMinutes * 100.0 / totalMinutes)); + var useRatioText = $"{Math.Round(ratio, 2)}%"; + + // 5) 获取设备名称(取时间段内最新一条的名称,避免跨设备统计导致名称混淆) + string? deviceName = null; + try + { + deviceName = await db.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), x => x.DeviceCode == request.DeviceCode) + .Where(x => x.CreateTime >= startTime && x.CreateTime <= endTime) + .OrderByDescending(x => x.CreateTime) + .FirstAsync(x => x.DeviceName); + } + catch + { + // 忽略设备名获取失败,不影响主逻辑 + } + + // 6) 组装单条汇总结果 + var summary = new DeviceStateResponse + { + DeviceCode = string.IsNullOrWhiteSpace(request.DeviceCode) ? null : request.DeviceCode, + DeviceName = deviceName, + PowerOnTime = (int)totalPowerOn, + RunTime = (int)totalRun, + StandbyTime = (int)totalStandby, + FaultTime = (int)totalFault, + ShutdownTime = (int)totalShutdown, + UseRatio = useRatioText, + FaultNum = (int)totalFaultCount, + JobNum = (int)totalJobCount, + CreateTime = endTime.ToString("yyyy-MM-dd HH:mm:ss") + }; + + return new List { summary }; + + } + catch (Exception ex) + { + throw new Exception($"查询设备状态数据失败: {ex.Message}", ex); + } + } + + /// + /// 分页获取设备状态 + /// + public static async Task<(List, long)> GetDeviceStatePagedAsync( + IFreeSql db, + DeviceStatePagedRequest request) + { + try + { + var query = db.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), + a => a.DeviceCode == request.DeviceCode); + + if (!string.IsNullOrWhiteSpace(request.StartTime) && + DateTime.TryParse(request.StartTime, out var startTime)) + { + query = query.Where(a => a.CreateTime >= startTime); + } + + if (!string.IsNullOrWhiteSpace(request.EndTime) && + DateTime.TryParse(request.EndTime, out var endTime)) + { + query = query.Where(a => a.CreateTime <= endTime); + } + + var total = await query.CountAsync(); + + var data = await query + .OrderByDescending(a => a.CreateTime) + .Page(request.PageIndex, request.PageSize) + .ToListAsync(a => new DeviceStateResponse + { + DeviceCode = a.DeviceCode, + DeviceName = a.DeviceName, + PowerOnTime = a.PowerOnTime, + RunTime = a.RunTime, + StandbyTime = a.StandbyTime, + FaultTime = a.FaultTime, + ShutdownTime = a.ShutdownTime, + UseRatio = a.UseRatio, + FaultNum = a.FaultNum, + JobNum = a.JobNum, + CreateTime = a.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return (data ?? new List(), total); + } + catch (Exception ex) + { + throw new Exception($"分页查询设备状态数据失败: {ex.Message}", ex); + } + } + + /// + /// 获取最新的设备状态记录 + /// + public static async Task GetLatestDeviceStateAsync( + IFreeSql db, + DeviceStateRequest request) + { + try + { + var query = db.Select() + .WhereIf(!string.IsNullOrWhiteSpace(request.DeviceCode), + a => a.DeviceCode == request.DeviceCode); + + if (!string.IsNullOrWhiteSpace(request.StartTime) && + DateTime.TryParse(request.StartTime, out var startTime)) + { + query = query.Where(a => a.CreateTime >= startTime); + } + + if (!string.IsNullOrWhiteSpace(request.EndTime) && + DateTime.TryParse(request.EndTime, out var endTime)) + { + query = query.Where(a => a.CreateTime <= endTime); + } + + var data = await query + .OrderByDescending(a => a.CreateTime) + .FirstAsync(a => new DeviceStateResponse + { + DeviceCode = a.DeviceCode, + DeviceName = a.DeviceName, + PowerOnTime = a.PowerOnTime, + RunTime = a.RunTime, + StandbyTime = a.StandbyTime, + FaultTime = a.FaultTime, + ShutdownTime = a.ShutdownTime, + UseRatio = a.UseRatio, + FaultNum = a.FaultNum, + JobNum = a.JobNum, + CreateTime = a.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }); + + return data; + } + catch (Exception ex) + { + throw new Exception($"查询最新设备状态数据失败: {ex.Message}", ex); + } + } + } +} diff --git a/MoviconWebApi/API/DeviceStateApi/Endpoint.cs b/MoviconWebApi/API/DeviceStateApi/Endpoint.cs new file mode 100644 index 0000000..c446c42 --- /dev/null +++ b/MoviconWebApi/API/DeviceStateApi/Endpoint.cs @@ -0,0 +1,142 @@ +using Azure; +using FastEndpoints; +using MoviconWebApi.Common; + +namespace MoviconWebApi.API.DeviceStateApi +{ + /// + /// 获取设备状态列表 + /// + public class GetListEndpoint : Endpoint>> + { + private readonly IFreeSql _db; + + public GetListEndpoint(IFreeSql db) + { + _db = db; + } + + public override void Configure() + { + Get("/devicestate/list"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "获取设备状态列表"; + s.Description = "根据设备编号和时间范围查询设备状态数据"; + s.Response>>(200, "成功返回设备状态数据列表"); + }); + } + + public override async Task HandleAsync(DeviceStateRequest req, CancellationToken ct) + { + try + { + var data = await Data.GetDeviceStateListAsync(_db, req); + Response = ApiResponse>.Success(data, "success"); + } + catch (Exception ex) + { + Response = ApiResponse>.Error( + "500", + $"查询失败: {ex.Message}", + new List()); + } + } + } + + /// + /// 分页获取设备状态 + /// + public class GetPagedEndpoint : Endpoint> + { + private readonly IFreeSql _db; + + public GetPagedEndpoint(IFreeSql db) + { + _db = db; + } + + public override void Configure() + { + Post("/devicestate/paged"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "分页获取设备状态"; + s.Description = "分页查询设备状态数据,支持设备编号和时间范围筛选"; + s.Response>(200, "成功返回分页数据"); + }); + } + + public override async Task HandleAsync(DeviceStatePagedRequest req, CancellationToken ct) + { + try + { + var (items, total) = await Data.GetDeviceStatePagedAsync(_db, req); + var pagedResponse = new DeviceStatePagedResponse + { + Items = items, + Total = total + }; + Response = ApiResponse.Success(pagedResponse, "success"); + } + catch (Exception ex) + { + Response = ApiResponse.Error( + "500", + $"查询失败: {ex.Message}", + new DeviceStatePagedResponse()); + } + } + } + + /// + /// 获取最新的设备状态记录 + /// + public class GetLatestEndpoint : Endpoint> + { + private readonly IFreeSql _db; + + public GetLatestEndpoint(IFreeSql db) + { + _db = db; + } + + public override void Configure() + { + Get("/devicestate/latest"); + AllowAnonymous(); + Summary(s => + { + s.Summary = "获取最新的设备状态记录"; + s.Description = "获取指定设备的最新状态记录"; + s.Response>(200, "成功返回最新记录"); + s.Response>(404, "未找到记录"); + }); + } + + public override async Task HandleAsync(DeviceStateRequest req, CancellationToken ct) + { + try + { + var data = await Data.GetLatestDeviceStateAsync(_db, req); + if (data == null) + { + Response = ApiResponse.Error("404", "未找到数据", null); + } + else + { + Response = ApiResponse.Success(data, "success"); + } + } + catch (Exception ex) + { + Response = ApiResponse.Error( + "500", + $"查询失败: {ex.Message}", + null); + } + } + } +} diff --git a/MoviconWebApi/API/DeviceStateApi/Mapper.cs b/MoviconWebApi/API/DeviceStateApi/Mapper.cs new file mode 100644 index 0000000..bb8a864 --- /dev/null +++ b/MoviconWebApi/API/DeviceStateApi/Mapper.cs @@ -0,0 +1,39 @@ +using MoviconWebApi.Entities; + +namespace MoviconWebApi.API.DeviceStateApi +{ + /// + /// 设备状态数据映射器 + /// + public static class Mapper + { + /// + /// 将实体映射到响应模型 + /// + public static DeviceStateResponse ToResponse(this DeviceState entity) + { + return new DeviceStateResponse + { + DeviceCode = entity.DeviceCode, + DeviceName = entity.DeviceName, + PowerOnTime = entity.PowerOnTime, + RunTime = entity.RunTime, + StandbyTime = entity.StandbyTime, + FaultTime = entity.FaultTime, + ShutdownTime = entity.ShutdownTime, + UseRatio = entity.UseRatio, + FaultNum = entity.FaultNum, + JobNum = entity.JobNum, + CreateTime = entity.CreateTime.ToString("yyyy-MM-dd HH:mm:ss") + }; + } + + /// + /// 将实体列表映射到响应模型列表 + /// + public static List ToResponseList(this List entities) + { + return entities?.Select(e => e.ToResponse()).ToList() ?? new List(); + } + } +} diff --git a/MoviconWebApi/API/DeviceStateApi/Models.cs b/MoviconWebApi/API/DeviceStateApi/Models.cs new file mode 100644 index 0000000..f777827 --- /dev/null +++ b/MoviconWebApi/API/DeviceStateApi/Models.cs @@ -0,0 +1,116 @@ +namespace MoviconWebApi.API.DeviceStateApi +{ + /// + /// 设备状态查询请求 + /// + public class DeviceStateRequest + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + + /// + /// 开始时间 + /// + public string? StartTime { get; set; } + + /// + /// 结束时间 + /// + public string? EndTime { get; set; } + } + + /// + /// 设备状态分页请求 + /// + public class DeviceStatePagedRequest : DeviceStateRequest + { + /// + /// 页码(从1开始) + /// + public int PageIndex { get; set; } = 1; + + /// + /// 每页数量 + /// + public int PageSize { get; set; } = 10; + } + + /// + /// 设备状态响应 + /// + public class DeviceStateResponse + { + /// + /// 设备编号 + /// + public string? DeviceCode { get; set; } + + /// + /// 设备名称 + /// + public string? DeviceName { get; set; } + + /// + /// 开机时长(分钟) + /// + public int? PowerOnTime { get; set; } + + /// + /// 运行时长(分钟) + /// + public int? RunTime { get; set; } + + /// + /// 待机时长(分钟) + /// + public int? StandbyTime { get; set; } + + /// + /// 故障时长(分钟) + /// + public int? FaultTime { get; set; } + + /// + /// 关机时长(分钟) + /// + public int? ShutdownTime { get; set; } + + /// + /// 使用率 + /// + public string? UseRatio { get; set; } + + /// + /// 故障次数 + /// + public int? FaultNum { get; set; } + + /// + /// 作业次数 + /// + public int? JobNum { get; set; } + + /// + /// 创建时间 + /// + public string? CreateTime { get; set; } + } + + /// + /// 设备状态分页响应 + /// + public class DeviceStatePagedResponse + { + /// + /// 数据列表 + /// + public List Items { get; set; } = new(); + + /// + /// 总记录数 + /// + public long Total { get; set; } + } +} diff --git a/MoviconWebApi/Common/ApiResponse.cs b/MoviconWebApi/Common/ApiResponse.cs new file mode 100644 index 0000000..dcb1b8f --- /dev/null +++ b/MoviconWebApi/Common/ApiResponse.cs @@ -0,0 +1,49 @@ +namespace MoviconWebApi.Common +{ + /// + /// 统一的API响应结构 + /// + public class ApiResponse + { + /// + /// 状态码 + /// + public string status { get; set; } = "200"; + + /// + /// 响应消息 + /// + public string msg { get; set; } = "success"; + + /// + /// 响应数据 + /// + public T? data { get; set; } + + /// + /// 创建成功响应 + /// + public static ApiResponse Success(T data, string msg = "success") + { + return new ApiResponse + { + status = "200", + msg = msg, + data = data + }; + } + + /// + /// 创建失败响应 + /// + public static ApiResponse Error(string status = "500", string msg = "error", T? data = default) + { + return new ApiResponse + { + status = status, + msg = msg, + data = data + }; + } + } +} diff --git a/MoviconWebApi/Entities/BaseEntity.cs b/MoviconWebApi/Entities/BaseEntity.cs new file mode 100644 index 0000000..ccf104a --- /dev/null +++ b/MoviconWebApi/Entities/BaseEntity.cs @@ -0,0 +1,23 @@ +using FreeSql.DataAnnotations; + +namespace MoviconWebApi.Entities +{ + /// + /// 基础实体类 + /// + public abstract class BaseEntity + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconWebApi/Entities/ClearData.cs b/MoviconWebApi/Entities/ClearData.cs new file mode 100644 index 0000000..28d3f1f --- /dev/null +++ b/MoviconWebApi/Entities/ClearData.cs @@ -0,0 +1,190 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; + +namespace MoviconWebApi.Entities +{ + /// + /// 清洗数据 + /// + [Table(Name = "ClearData")] + public class ClearData + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string? DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string? DeviceName { get; set; } + + /// + /// 程序进程 + /// + [Column(Name = "program_process", StringLength = 100, IsNullable = true)] + public string? program_process { get; set; } + + /// + /// 车型 + /// + [Column(Name = "vehicle_model", StringLength = 100, IsNullable = true)] + public string? vehicle_model { get; set; } + + /// + /// 下车号 + /// + [Column(Name = "locomotive_number", StringLength = 100, IsNullable = true)] + public string? locomotive_number { get; set; } + + /// + /// 修程 + /// + [Column(Name = "repair_process", StringLength = 100, IsNullable = true)] + public string? repair_process { get; set; } + + /// + /// 部件名称 + /// + [Column(Name = "component_name", StringLength = 100, IsNullable = true)] + public string? component_name { get; set; } + + /// + /// 位别 + /// + [Column(Name = "part_position", StringLength = 100, IsNullable = true)] + public string? part_position { get; set; } + + /// + /// 部件编号 + /// + [Column(Name = "part_num", StringLength = 100, IsNullable = true)] + public string? part_num { get; set; } + + /// + /// 部件二维码 + /// + [Column(Name = "part_qrid", StringLength = 100, IsNullable = true)] + public string? part_qrid { get; set; } + + /// + /// 程序进程百分比​ + /// + [Column(Name = "Test_FrameworkProgramProcessPercentage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkProgramProcessPercentage { get; set; } + + /// + /// 程序进程​ + /// + [Column(Name = "Test_FrameworkProgramProcess", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkProgramProcess { get; set; } + + + /// + /// 构架单个机型清洗时长 + /// + [Column(Name = "Test_FrameworkPerModelCleaningDuration", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelCleaningDuration { get; set; } + + /// + /// 构架单个机型用清洗剂量 + /// + [Column(Name = "Test_FrameworkPerModelCleaningAgentUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelCleaningAgentUsage { get; set; } + + /// + /// 构架单个机型用水量 + /// + [Column(Name = "Test_FrameworkPerModelWaterUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelWaterUsage { get; set; } + + /// + /// 水罐温度 + /// + [Column(Name = "WaterTank_Temp", StringLength = 100, IsNullable = true)] + public string? WaterTank_Temp { get; set; } + + /// + /// 清洗剂罐温度 + /// + [Column(Name = "AgentTank_Temp", StringLength = 100, IsNullable = true)] + public string? AgentTank_Temp { get; set; } + + /// + /// 水罐液位 + /// + [Column(Name = "WaterTank_Level", StringLength = 100, IsNullable = true)] + public string? WaterTank_Level { get; set; } + + + /// + /// 清洗剂罐液位 + /// + [Column(Name = "AgentTank_Level", StringLength = 100, IsNullable = true)] + public string? AgentTank_Level { get; set; } + + /// + /// 浸泡池1温度 + /// + [Column(Name = "SoakingTank1_Temp", StringLength = 100, IsNullable = true)] + public string? SoakingTank1_Temp { get; set; } + + /// + /// 浸泡池2温度 + /// + [Column(Name = "SoakingTank2_Temp", StringLength = 100, IsNullable = true)] + public string? SoakingTank2_Temp { get; set; } + + + /// + /// ​热水罐加热模式运行​​ + /// + [Column(Name = "Test_WaterTankHeat", StringLength = 100, IsNullable = true)] + public string? Test_WaterTankHeat { get; set; } + + + /// + /// ​热水罐补水模式运行​​ + /// + [Column(Name = "Test_WaterTankAdd", StringLength = 100, IsNullable = true)] + public string? Test_WaterTankAdd { get; set; } + + /// + /// ​清洗剂罐加热模式运行 ​​ + /// + [Column(Name = "Test_CleaningAgentTankHeat", StringLength = 100, IsNullable = true)] + public string? Test_CleaningAgentTankHeat { get; set; } + + /// + /// ​清洗剂罐补水模式运行​​ + /// + [Column(Name = "Test_CleaningAgentTankAdd", StringLength = 100, IsNullable = true)] + public string? Test_CleaningAgentTankAdd { get; set; } + + /// + /// ​电能监控​​ + /// + [Column(Name = "Test_ElectricSurveillance", StringLength = 100, IsNullable = true)] + public string? Test_ElectricSurveillance { get; set; } + + /// + /// ​蒸汽监控​​ + /// + [Column(Name = "Test_SteamSurveillance", StringLength = 100, IsNullable = true)] + public string? Test_SteamSurveillance { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconWebApi/Entities/ComponentsInfo.cs b/MoviconWebApi/Entities/ComponentsInfo.cs new file mode 100644 index 0000000..9068799 --- /dev/null +++ b/MoviconWebApi/Entities/ComponentsInfo.cs @@ -0,0 +1,76 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; + +namespace MoviconWebApi.Entities +{ + /// + /// 不论多部件还是单部件均采用数组,若为单部件则数组中仅有一个对象 + /// + [Table(Name = "ComponentsInfo")] + public class ComponentsInfo + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 车型 + /// + [JsonProperty("part_Vehicle_model")] + [Column(Name = "part_Vehicle_model", StringLength = 100, IsNullable = true)] + public string? part_Vehicle_model { get; set; } + + /// + /// 车号 + /// + [JsonProperty("part_locomotive_number")] + [Column(Name = "part_locomotive_number", StringLength = 100, IsNullable = true)] + public string? part_locomotive_number { get; set; } + + /// + /// 修程 + /// + [JsonProperty("part_repair_process")] + [Column(Name = "part_repair_process", StringLength = 100, IsNullable = true)] + public string? part_repair_process { get; set; } + + /// + /// 位别 + /// + [JsonProperty("part_position")] + [Column(Name = "part_position", StringLength = 100, IsNullable = true)] + public string? part_position { get; set; } + + /// + /// 部件名称 + /// + [JsonProperty("part_name")] + [Column(Name = "part_name", StringLength = 100, IsNullable = true)] + public string? part_name { get; set; } + + /// + /// 部件编号 + /// + [JsonProperty("part_num")] + [Column(Name = "part_num", StringLength = 100, IsNullable = true)] + public string? part_num { get; set; } + + /// + /// 二维码id + /// + [JsonProperty("part_qrid")] + [Column(Name = "part_qrid", StringLength = 100, IsNullable = true)] + public string? part_qrid { get; set; } + + + + /// + /// ///////////////////////////////////////////导航属性/////////////////////////////////////////////////////// + /// + + public long DataReocrdId { get; set; } + public DataReocrd? DataReocrd { get; set; } + } +} diff --git a/MoviconWebApi/Entities/CurRunClearState.cs b/MoviconWebApi/Entities/CurRunClearState.cs new file mode 100644 index 0000000..0545132 --- /dev/null +++ b/MoviconWebApi/Entities/CurRunClearState.cs @@ -0,0 +1,194 @@ +using FreeSql.DataAnnotations; + +namespace MoviconWebApi.Entities +{ + /// + /// 当前运行的清洗状态 + /// + [Table(Name = "CurRunClearState")] + public class CurRunClearState + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string? DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string? DeviceName { get; set; } + + /// + /// 程序进程 + /// + [Column(Name = "program_process", StringLength = 100, IsNullable = true)] + public string? program_process { get; set; } + + /// + /// 车型 + /// + [Column(Name = "vehicle_model", StringLength = 100, IsNullable = true)] + public string? vehicle_model { get; set; } + + /// + /// 下车号 + /// + [Column(Name = "locomotive_number", StringLength = 100, IsNullable = true)] + public string? locomotive_number { get; set; } + + /// + /// 修程 + /// + [Column(Name = "repair_process", StringLength = 100, IsNullable = true)] + public string? repair_process { get; set; } + + /// + /// 部件名称 + /// + [Column(Name = "component_name", StringLength = 100, IsNullable = true)] + public string? component_name { get; set; } + + /// + /// 位别 + /// + [Column(Name = "part_position", StringLength = 100, IsNullable = true)] + public string? part_position { get; set; } + + /// + /// 部件编号 + /// + [Column(Name = "part_num", StringLength = 100, IsNullable = true)] + public string? part_num { get; set; } + + /// + /// 部件二维码 + /// + [Column(Name = "part_qrid", StringLength = 100, IsNullable = true)] + public string? part_qrid { get; set; } + + /// + /// 程序进程百分比​ + /// + [Column(Name = "Test_FrameworkProgramProcessPercentage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkProgramProcessPercentage { get; set; } + + /// + /// 程序进程​ + /// + [Column(Name = "Test_FrameworkProgramProcess", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkProgramProcess { get; set; } + + /// + /// 零部件设备状态 + /// + [Column(Name = "Test_PartsEquipmentStatus", StringLength = 100, IsNullable = true)] + public string? Test_PartsEquipmentStatus { get; set; } + + /// + /// 构架单个机型清洗时长 + /// + [Column(Name = "Test_FrameworkPerModelCleaningDuration", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelCleaningDuration { get; set; } + + /// + /// 构架单个机型用清洗剂量 + /// + [Column(Name = "Test_FrameworkPerModelCleaningAgentUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelCleaningAgentUsage { get; set; } + + /// + /// 构架单个机型用水量 + /// + [Column(Name = "Test_FrameworkPerModelWaterUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelWaterUsage { get; set; } + + /// + /// 水罐温度 + /// + [Column(Name = "WaterTank_Temp", StringLength = 100, IsNullable = true)] + public string? WaterTank_Temp { get; set; } + + /// + /// 清洗剂罐温度 + /// + [Column(Name = "AgentTank_Temp", StringLength = 100, IsNullable = true)] + public string? AgentTank_Temp { get; set; } + + /// + /// 水罐液位 + /// + [Column(Name = "WaterTank_Level", StringLength = 100, IsNullable = true)] + public string? WaterTank_Level { get; set; } + + + /// + /// 清洗剂罐液位 + /// + [Column(Name = "AgentTank_Level", StringLength = 100, IsNullable = true)] + public string? AgentTank_Level { get; set; } + + /// + /// 浸泡池1温度 + /// + [Column(Name = "SoakingTank1_Temp", StringLength = 100, IsNullable = true)] + public string? SoakingTank1_Temp { get; set; } + + /// + /// 浸泡池2温度 + /// + [Column(Name = "SoakingTank2_Temp", StringLength = 100, IsNullable = true)] + public string? SoakingTank2_Temp { get; set; } + + + /// + /// ​热水罐加热模式运行​​ + /// + [Column(Name = "Test_WaterTankHeat", StringLength = 100, IsNullable = true)] + public string? Test_WaterTankHeat { get; set; } + + + /// + /// ​热水罐补水模式运行​​ + /// + [Column(Name = "Test_WaterTankAdd", StringLength = 100, IsNullable = true)] + public string? Test_WaterTankAdd { get; set; } + + /// + /// ​清洗剂罐加热模式运行 ​​ + /// + [Column(Name = "Test_CleaningAgentTankHeat", StringLength = 100, IsNullable = true)] + public string? Test_CleaningAgentTankHeat { get; set; } + + /// + /// ​清洗剂罐补水模式运行​​ + /// + [Column(Name = "Test_CleaningAgentTankAdd", StringLength = 100, IsNullable = true)] + public string? Test_CleaningAgentTankAdd { get; set; } + + /// + /// ​电能监控​​ + /// + [Column(Name = "Test_ElectricSurveillance", StringLength = 100, IsNullable = true)] + public string? Test_ElectricSurveillance { get; set; } + + /// + /// ​蒸汽监控​​ + /// + [Column(Name = "Test_SteamSurveillance", StringLength = 100, IsNullable = true)] + public string? Test_SteamSurveillance { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconWebApi/Entities/DataReocrd.cs b/MoviconWebApi/Entities/DataReocrd.cs new file mode 100644 index 0000000..f74f09c --- /dev/null +++ b/MoviconWebApi/Entities/DataReocrd.cs @@ -0,0 +1,77 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; + +namespace MoviconWebApi.Entities +{ + /// + /// 数据记录 + /// + [Table(Name = "DataReocrd")] + public class DataReocrd + { + /// + /// 主键 + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 设备编号 + /// + [JsonProperty("DeviceCode")] + [Column(Name = "Device_Code", StringLength = 100, IsNullable = true)] + public string Device_Code { get; set; } = ""; + + /// + /// 设备名称 + /// + [JsonProperty("DeviceName")] + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string Device_Name { get; set; } = ""; + + /// + /// 设备厂商 + /// + [JsonProperty("DeviceManufacturer")] + [Column(Name = "Device_Manufacturer", StringLength = 100, IsNullable = true)] + public string Device_Manufacturer { get; set; } = ""; + + /// + /// 设备状态, 代码含义自定 + /// + [JsonProperty("Status")] + [Column(Name = "Device_Status", StringLength = 100, IsNullable = true)] + public string Device_Status { get; set; } = ""; + + /// + /// 设备错误 + /// + [JsonProperty("FaultDetails")] + [Column(Name = "FaultDetails", StringLength = 100, IsNullable = true)] + public string? FaultDetails { get; set; } + + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + + /// + /// ///////////////////////////////////////////导航属性/////////////////////////////////////////////////////// + /// + + + /// + /// 部件信息列表 + /// + public List? ListComponentsInfo { get; set; } = new List(); + + + /// + /// 部件信息列表 + /// + public List? TestData { get; set; } = new List(); + + } +} diff --git a/MoviconWebApi/Entities/DeviceAlarm.cs b/MoviconWebApi/Entities/DeviceAlarm.cs new file mode 100644 index 0000000..53a7fad --- /dev/null +++ b/MoviconWebApi/Entities/DeviceAlarm.cs @@ -0,0 +1,57 @@ +using FreeSql.DataAnnotations; + +namespace MoviconWebApi.Entities +{ + /// + /// 设备报警 + /// + [Table(Name = "DeviceAlarm")] + public class DeviceAlarm + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string? DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string? DeviceName { get; set; } + + + /// + /// 设备状态 + /// + [Column(Name = "DeviceState", IsNullable = true)] + public int? DeviceState { get; set; } + + /// + /// 报警信息 + /// + [Column(Name = "AlarmMessage", StringLength = 180, IsNullable = true)] + public string? AlarmMessage { get; set; } + + + /// + /// 开始时间 + /// + [Column(CanUpdate = false)] + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + [Column(CanUpdate = false)] + public DateTime EndTime { get; set; } + + } +} diff --git a/MoviconWebApi/Entities/DeviceState.cs b/MoviconWebApi/Entities/DeviceState.cs new file mode 100644 index 0000000..c1d7d8e --- /dev/null +++ b/MoviconWebApi/Entities/DeviceState.cs @@ -0,0 +1,92 @@ +using FreeSql.DataAnnotations; + +namespace MoviconWebApi.Entities +{ + /// + /// 设备状态 + /// + [Table(Name = "DeviceState")] + public class DeviceState + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + + /// + /// 设备码 + /// + [Column(Name = "DeviceCode", StringLength = 100, IsNullable = true)] + public string? DeviceCode { get; set; } + + /// + /// 设备名称 + /// + [Column(Name = "DeviceName", StringLength = 100, IsNullable = true)] + public string? DeviceName { get; set; } + + + /// + /// 开机时长(status为1) + /// + [Column(Name = "PowerOnTime", IsNullable = true)] + public int? PowerOnTime { get; set; } + + /// + /// 运行时长(status为2) + /// + [Column(Name = "RunTime", IsNullable = true)] + public int? RunTime { get; set; } + + /// + /// 待机时长(status为3) + /// + [Column(Name = "StandbyTime", IsNullable = true)] + public int? StandbyTime { get; set; } + + /// + /// 故障时长(status为4) + /// + [Column(Name = "FaultTime", IsNullable = true)] + public int? FaultTime { get; set; } + + /// + /// 关机时长(status为5) + /// + [Column(Name = "ShutdownTime", IsNullable = true)] + public int? ShutdownTime { get; set; } + + ///// + ///// 使用率(PowerOnTime+ RunTime+ StandbyTime+ FaultTime)/( EndTime- StartTime) 从ts_sh_device_status_change_log中计算 + ///// + //[Column(Name = "UseRatio", IsNullable = true)] + //public int? UseRatio { get; set; } + + /// + /// 使用率(PowerOnTime+ RunTime+ StandbyTime+ FaultTime)/( EndTime- StartTime) 从ts_sh_device_status_change_log中计算 + /// + [Column(Name = "UseRatio", StringLength = 100, IsNullable = true)] + public string? UseRatio { get; set; } + + + /// + /// 故障次数 + /// + [Column(Name = "FaultNum", IsNullable = true)] + public int? FaultNum { get; set; } + + /// + /// 作业次数,建议从作业数据表中取数据 + /// + [Column(Name = "JobNum", IsNullable = true)] + public int? JobNum { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = false)] + public DateTime CreateTime { get; set; } + } +} diff --git a/MoviconWebApi/Entities/TestData.cs b/MoviconWebApi/Entities/TestData.cs new file mode 100644 index 0000000..6a6e72a --- /dev/null +++ b/MoviconWebApi/Entities/TestData.cs @@ -0,0 +1,195 @@ +using FreeSql.DataAnnotations; +using Newtonsoft.Json; + +namespace MoviconWebApi.Entities +{ + /// + /// 测试数据 + /// + [Table(Name = "TestData")] + public class TestData + { + /// + /// 主键ID + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 构架设备状态​ + /// + [JsonProperty("Test_FrameworkEquipmentStatus")] + [Column(Name = "Test_FrameworkEquipmentStatus", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkEquipmentStatus { get; set; } + + /// + /// 零部件设备状态​ + /// + [JsonProperty("Test_PartsEquipmentStatus")] + [Column(Name = "Test_PartsEquipmentStatus", StringLength = 100, IsNullable = true)] + public string? Test_PartsEquipmentStatus { get; set; } + + /// + /// ​构架累计用清洗剂量​ + /// + [JsonProperty("Test_FrameworkCumulativeCleaningAgentUsage")] + [Column(Name = "Test_FrameworkCumulativeCleaningAgentUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkCumulativeCleaningAgentUsage { get; set; } + + /// + /// 构架累计用水量​ + /// + [JsonProperty("Test_FrameworkCumulativeWaterUsage")] + [Column(Name = "Test_FrameworkCumulativeWaterUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkCumulativeWaterUsage { get; set; } + + /// + /// 构架累计清洗时长​ + /// + [JsonProperty("Test_FrameworkCumulativeCleaningDuration")] + [Column(Name = "Test_FrameworkCumulativeCleaningDuration", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkCumulativeCleaningDuration { get; set; } + + /// + /// 零部件累计用水量 + /// + [JsonProperty("Test_PartsCumulativeWaterUsage")] + [Column(Name = "Test_PartsCumulativeWaterUsage", StringLength = 100, IsNullable = true)] + public string? Test_PartsCumulativeWaterUsage { get; set; } + + /// + /// ​零部件累计清洗时长​ + /// + [JsonProperty("Test_PartsCumulativeCleaningDuration")] + [Column(Name = "Test_PartsCumulativeCleaningDuration", StringLength = 100, IsNullable = true)] + public string? Test_PartsCumulativeCleaningDuration { get; set; } + + + /// + /// 累计检修数量​ + /// + [JsonProperty("Test_CumulativeMaintenanceCount")] + [Column(Name = "Test_CumulativeMaintenanceCount", StringLength = 100, IsNullable = true)] + public string? Test_CumulativeMaintenanceCount { get; set; } + + + /// + /// ​机器人1状态​ + /// + [JsonProperty("Test_Robot1Status")] + [Column(Name = "Test_Robot1Status", StringLength = 100, IsNullable = true)] + public string? Test_Robot1Status { get; set; } + + + /// + /// 机器人2状态​ + /// + [JsonProperty("Test_Robot2Status")] + [Column(Name = "Test_Robot2Status", StringLength = 100, IsNullable = true)] + public string? Test_Robot2Status { get; set; } + + + /// + /// 构架清洗数量 + /// + [JsonProperty("Test_FrameworkCleaningCount")] + [Column(Name = "Test_FrameworkCleaningCount", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkCleaningCount { get; set; } + + + /// + /// 构架单个机型清洗时长​ + /// + [JsonProperty("Test_FrameworkPerModelCleaningDuration")] + [Column(Name = "Test_FrameworkPerModelCleaningDuration", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelCleaningDuration { get; set; } + + /// + /// ​​构架单个机型用清洗剂量​ + /// + [JsonProperty("Test_FrameworkPerModelCleaningAgentUsage")] + [Column(Name = "Test_FrameworkPerModelCleaningAgentUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelCleaningAgentUsage { get; set; } + + + /// + /// ​构架单个机型用水量​ + /// + [JsonProperty("Test_FrameworkPerModelWaterUsage")] + [Column(Name = "Test_FrameworkPerModelWaterUsage", StringLength = 100, IsNullable = true)] + public string? Test_FrameworkPerModelWaterUsage { get; set; } + + + /// + /// ​​零部件单次清洗时长​ + /// + [JsonProperty("Test_PartsSingleCleaningDuration")] + [Column(Name = "Test_PartsSingleCleaningDuration", StringLength = 100, IsNullable = true)] + public string? Test_PartsSingleCleaningDuration { get; set; } + + + /// + /// 零部件单次用水量​ + /// + [JsonProperty("Test_PartsSingleWaterUsage")] + [Column(Name = "Test_PartsSingleWaterUsage", StringLength = 100, IsNullable = true)] + public string? Test_PartsSingleWaterUsage { get; set; } + + + /// + /// ​水罐温度​ + /// + [JsonProperty("Test_WaterTankTemperature")] + [Column(Name = "Test_WaterTankTemperature", StringLength = 100, IsNullable = true)] + public string? Test_WaterTankTemperature { get; set; } + + + /// + /// ​​清洗剂罐温度​ + /// + [JsonProperty("Test_CleaningAgentTankTemperature")] + [Column(Name = "Test_CleaningAgentTankTemperature", StringLength = 100, IsNullable = true)] + public string? Test_CleaningAgentTankTemperature { get; set; } + + + /// + /// 浸泡池1温度​ + /// + [JsonProperty("Test_SoakingTank1Temperature")] + [Column(Name = "Test_SoakingTank1Temperature", StringLength = 100, IsNullable = true)] + public string? Test_SoakingTank1Temperature { get; set; } + + + /// + /// ​浸泡池2温度​ + /// + [JsonProperty("Test_SoakingTank2Temperature")] + [Column(Name = "Test_SoakingTank2Temperature", StringLength = 100, IsNullable = true)] + public string? Test_SoakingTank2Temperature { get; set; } + + + /// + /// ​水罐液位​​ + /// + [JsonProperty("Test_WaterTankLevel")] + [Column(Name = "Test_WaterTankLevel", StringLength = 100, IsNullable = true)] + public string? Test_WaterTankLevel { get; set; } + + /// + /// ​清洗剂罐液位​​ + /// + [JsonProperty("Test_CleaningAgentTankLevel")] + [Column(Name = "Test_CleaningAgentTankLevel", StringLength = 100, IsNullable = true)] + public string? Test_CleaningAgentTankLevel { get; set; } + + + + /// + /// ///////////////////////////////////////////导航属性/////////////////////////////////////////////////////// + /// + + public long DataReocrdId { get; set; } + public DataReocrd? DataReocrd { get; set; } + + } +} diff --git a/MoviconWebApi/MoviconWebApi.csproj b/MoviconWebApi/MoviconWebApi.csproj new file mode 100644 index 0000000..b5ef3bb --- /dev/null +++ b/MoviconWebApi/MoviconWebApi.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + LatestPatch + enable + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/MoviconWebApi/MoviconWebApi.http b/MoviconWebApi/MoviconWebApi.http new file mode 100644 index 0000000..c444d68 --- /dev/null +++ b/MoviconWebApi/MoviconWebApi.http @@ -0,0 +1,6 @@ +@MoviconWebApi_HostAddress = http://localhost:5055 + +GET {{MoviconWebApi_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/MoviconWebApi/Program.cs b/MoviconWebApi/Program.cs new file mode 100644 index 0000000..f01f59d --- /dev/null +++ b/MoviconWebApi/Program.cs @@ -0,0 +1,87 @@ + +using FastEndpoints; +using FastEndpoints.Security; +using FastEndpoints.Swagger; + +namespace MoviconWebApi +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + //builder.Services.AddAuthorization(); + + // FreeSql + var connectionString = builder.Configuration.GetConnectionString("SqlServer"); + var freeSql = new FreeSql.FreeSqlBuilder() + .UseConnectionString(FreeSql.DataType.SqlServer, connectionString) + .UseAutoSyncStructure(true) // Զͬʵṹݿ + .UseMonitorCommand(cmd => Console.WriteLine($"SQL{cmd.CommandText}")) // SQL䣨ã + .Build(); + // עFreeSqlΪ + builder.Services.AddSingleton(freeSql); + + // JWT֤ + builder.Services.AddAuthenticationJwtBearer(o => o.SigningKey = builder.Configuration["JwtSigningKey"]); + builder.Services.AddAuthorization(); + builder.Services.AddFastEndpoints(); + builder.Services.SwaggerDocument(o => + { + o.DocumentSettings = s => + { + s.Title = "Movicon Web API"; + s.Version = "v1"; + s.Description = "MoviconHub ݷAPI"; + }; + }); + + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + //builder.Services.AddEndpointsApiExplorer(); + //builder.Services.AddSwaggerGen(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + //ע⣺FastEndpointsԼSwaggerãҪʹԭUseSwaggerUseSwaggerUI + //if (app.Environment.IsDevelopment()) + //{ + // app.UseSwagger(); + // app.UseSwaggerUI(); + //} + + app.UseAuthentication() + .UseAuthorization() + .UseFastEndpoints(c => + { + c.Serializer.Options.PropertyNamingPolicy = null; // + c.Endpoints.RoutePrefix = "api"; // API·ǰ׺ + }) + .UseSwaggerGen(); + + var summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + //app.MapGet("/weatherforecast", (HttpContext httpContext) => + //{ + // var forecast = Enumerable.Range(1, 5).Select(index => + // new WeatherForecast + // { + // Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + // TemperatureC = Random.Shared.Next(-20, 55), + // Summary = summaries[Random.Shared.Next(summaries.Length)] + // }) + // .ToArray(); + // return forecast; + //}) + //.WithName("GetWeatherForecast") + //.WithOpenApi(); + + app.Run(); + } + } +} diff --git a/MoviconWebApi/Properties/launchSettings.json b/MoviconWebApi/Properties/launchSettings.json new file mode 100644 index 0000000..973ed1a --- /dev/null +++ b/MoviconWebApi/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15703", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5055", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/MoviconWebApi/WeatherForecast.cs b/MoviconWebApi/WeatherForecast.cs new file mode 100644 index 0000000..14440ab --- /dev/null +++ b/MoviconWebApi/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace MoviconWebApi +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/MoviconWebApi/appsettings.Development.json b/MoviconWebApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/MoviconWebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/MoviconWebApi/appsettings.json b/MoviconWebApi/appsettings.json new file mode 100644 index 0000000..8555a37 --- /dev/null +++ b/MoviconWebApi/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "SqlServer": "Server=CT-PC;Database=MoviconDb;User Id=sa;Password=12345678;Encrypt=False;TrustServerCertificate=True;" + }, + "JwtSigningKey": "your-super-secret-key-at-least-32-characters-long!", + "AllowedHosts": "*" +}