1023
nodyang@aliyun.com 1 week ago
parent 7ec06bb4ea
commit fef79eadb9

@ -102,6 +102,7 @@
<Compile Include="DbCommandInterceptor.cs" />
<Compile Include="DbFactory.cs" />
<Compile Include="Dto\UserDto.cs" />
<Compile Include="Entity\AlarmData.cs" />
<Compile Include="Entity\Function.cs" />
<Compile Include="Entity\Log.cs" />
<Compile Include="Entity\Point.cs" />
@ -111,6 +112,7 @@
<Compile Include="Entity\User.cs" />
<Compile Include="MappingHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Service\AlarmdDataService.cs" />
<Compile Include="Service\PointLogService.cs" />
<Compile Include="Service\PointService.cs" />
<Compile Include="Service\UpdateLogService.cs" />

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DB.Entity
{
public class AlarmData: BaseChimsDb
{
public string Address { get; set; }
public string PlcAb { get; set; }
public int No { get; set; }
public string Msg { get; set; }
}
public class AlarmDataMapper : SystemEntityTypeBuilder<AlarmData>
{
public AlarmDataMapper() : base("AlarmData")
{
}
}
}

@ -0,0 +1,34 @@
using DB.Entity;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NewLife.Caching;
using Tool.Model;
namespace DB.Service
{
public class AlarmDataService
{
public List<AlarmData> GetList()
{
string key = CacheKeyManager.AramlDataList;
ICache cache = Cache.Default;
if (cache.ContainsKey(key))
{
return cache.Get<List<AlarmData>>(key);
}
using (var dbContext = DbFactory.GetContext)
{
var entity= dbContext.Query<AlarmData>()
.ToList();
cache.Set(key, entity, TimeSpan.FromMinutes(5));
return entity;
}
}
}
}

@ -36,18 +36,17 @@ namespace RfidWeb
DbInfo.Init(typeof(UserInfo).Assembly);
var rfidSetting = RfidSetting.Current;
InitializeComponent();
if (this.FormBorderStyle == FormBorderStyle.None)
{
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.WindowState = FormWindowState.Normal;
}
else
{
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;
}
//if (this.FormBorderStyle == FormBorderStyle.None)
//{
// this.FormBorderStyle = FormBorderStyle.FixedSingle;
// this.WindowState = FormWindowState.Normal;
//}
//else
//{
// this.FormBorderStyle = FormBorderStyle.None;
// this.WindowState = FormWindowState.Maximized;
//}
Init();
}
@ -64,7 +63,7 @@ namespace RfidWeb
logNet.WriteInfo("nihao");
this.panContent.Controls.Clear();
this.panContent.Controls.Add(new UserMain());
this.panContent.Controls.Add(new UserAlarmShow());
}

@ -28,18 +28,148 @@
/// </summary>
private void InitializeComponent()
{
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.panel1 = new System.Windows.Forms.Panel();
this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel();
this.dataGridView1 = new System.Windows.Forms.DataGridView();
this.al = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Msg = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridView2 = new System.Windows.Forms.DataGridView();
this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.tableLayoutPanel1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.dataGridView2)).BeginInit();
this.SuspendLayout();
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.Controls.Add(this.dataGridView2, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.dataGridView1, 0, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 659);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 1;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(910, 276);
this.tableLayoutPanel1.TabIndex = 0;
//
// panel1
//
this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(910, 320);
this.panel1.TabIndex = 1;
//
// flowLayoutPanel1
//
this.flowLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 320);
this.flowLayoutPanel1.Name = "flowLayoutPanel1";
this.flowLayoutPanel1.Size = new System.Drawing.Size(910, 339);
this.flowLayoutPanel1.TabIndex = 2;
//
// dataGridView1
//
this.dataGridView1.AllowUserToAddRows = false;
this.dataGridView1.AllowUserToDeleteRows = false;
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.al,
this.Msg});
this.dataGridView1.Dock = System.Windows.Forms.DockStyle.Fill;
this.dataGridView1.Location = new System.Drawing.Point(3, 3);
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.ReadOnly = true;
dataGridViewCellStyle1.BackColor = System.Drawing.Color.Red;
dataGridViewCellStyle1.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
dataGridViewCellStyle1.ForeColor = System.Drawing.Color.White;
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.Color.Red;
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.Color.White;
this.dataGridView1.RowsDefaultCellStyle = dataGridViewCellStyle1;
this.dataGridView1.RowTemplate.Height = 23;
this.dataGridView1.Size = new System.Drawing.Size(449, 270);
this.dataGridView1.TabIndex = 0;
//
// al
//
this.al.DataPropertyName = "AlartTime";
this.al.HeaderText = "警告时间";
this.al.Name = "al";
this.al.ReadOnly = true;
this.al.Width = 150;
//
// Msg
//
this.Msg.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.Msg.DataPropertyName = "Msg";
this.Msg.HeaderText = "警告内容";
this.Msg.Name = "Msg";
this.Msg.ReadOnly = true;
//
// dataGridView2
//
this.dataGridView2.AllowUserToAddRows = false;
this.dataGridView2.AllowUserToDeleteRows = false;
this.dataGridView2.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView2.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.dataGridViewTextBoxColumn1,
this.dataGridViewTextBoxColumn2});
this.dataGridView2.Dock = System.Windows.Forms.DockStyle.Fill;
this.dataGridView2.Location = new System.Drawing.Point(458, 3);
this.dataGridView2.Name = "dataGridView2";
this.dataGridView2.ReadOnly = true;
this.dataGridView2.RowTemplate.Height = 23;
this.dataGridView2.Size = new System.Drawing.Size(449, 270);
this.dataGridView2.TabIndex = 1;
//
// dataGridViewTextBoxColumn1
//
this.dataGridViewTextBoxColumn1.DataPropertyName = "AlartTime";
this.dataGridViewTextBoxColumn1.HeaderText = "警告时间";
this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1";
this.dataGridViewTextBoxColumn1.ReadOnly = true;
this.dataGridViewTextBoxColumn1.Width = 150;
//
// dataGridViewTextBoxColumn2
//
this.dataGridViewTextBoxColumn2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.dataGridViewTextBoxColumn2.DataPropertyName = "Msg";
this.dataGridViewTextBoxColumn2.HeaderText = "警告内容";
this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2";
this.dataGridViewTextBoxColumn2.ReadOnly = true;
//
// UserAlarmShow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flowLayoutPanel1);
this.Controls.Add(this.panel1);
this.Controls.Add(this.tableLayoutPanel1);
this.Name = "UserAlarmShow";
this.Size = new System.Drawing.Size(910, 935);
this.tableLayoutPanel1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
((System.ComponentModel.ISupportInitialize)(this.dataGridView2)).EndInit();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1;
private System.Windows.Forms.DataGridView dataGridView1;
private System.Windows.Forms.DataGridViewTextBoxColumn al;
private System.Windows.Forms.DataGridViewTextBoxColumn Msg;
private System.Windows.Forms.DataGridView dataGridView2;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1;
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2;
}
}

@ -1,4 +1,13 @@
using System;
using DB.Entity;
using HZH_Controls.Controls;
using HZH_Controls.Forms;
using NewLife.Caching;
using NewLife.Reflection;
using NewLife.Threading;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
@ -7,14 +16,210 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using DB.Service;
using Tool;
using Tool.Model;
namespace RfidWeb.Frm
{
public partial class UserAlarmShow : UserControl
{
private TimerX _timer;
private HimAlarmManager himAlarmManager = new HimAlarmManager();
public UserAlarmShow()
{
InitializeComponent();
_timer = new TimerX(TimeState, null, 1000, 1200);
}
void TimeState(Object state)
{
var dataTime = DateTime.Now;
List< GridViewData > lsList=new List< GridViewData >();
var list = himAlarmManager.GetList();
foreach (var keyValuePair in list)
{
lsList.Add(new GridViewData()
{
AlartTime = keyValuePair.Value.ToString("MM-dd HH:mm:ss"),
Msg = keyValuePair.Key
});
}
this.Invoke(() =>
{
int currentFirstVisibleRowIndex = dataGridView1.FirstDisplayedScrollingRowIndex;
this.dataGridView1.DataSource=lsList;
dataGridView1.FirstDisplayedScrollingRowIndex = currentFirstVisibleRowIndex;
});
// ThreadPoolX.QueueUserWorkItem(UpdateLog);
}
public class HimAlarmManager
{
private HmiChAlarm hmiChAlarm = new HmiChAlarm();
private HmiCqAlarm hmiCq = new HmiCqAlarm();
EsStop esStop=new EsStop();
AlarmDataService alarmDataService = new AlarmDataService();
public Dictionary<string, DateTime> GetList()
{
List<AlarmData> list = alarmDataService.GetList();
ICache cache = Cache.Default;
Dictionary<string, DateTime> dictionary = cache[CacheKeyManager.AramlList] as Dictionary<string, DateTime>
?? new Dictionary<string, DateTime>();
var a = hmiChAlarm.GetPlcDb();
var dicCha = list.Where(x => x.PlcAb == "A" && x.Address == "CH_alarm"
&& !string.IsNullOrEmpty(x.Msg))
.ToDictionary(x => x.No, x => x.Msg);
for (int i = 0; i < 31; i++)
{
var obj = a.GetValue("A" + i);
if (obj != null)
{
if (dicCha.TryGetValue(i, out var value))
{
if (Convert.ToBoolean(obj))
{
if (!dictionary.ContainsKey(value))
{
dictionary[value] = DateTime.Now;
}
}
else
{
dictionary.Remove(value);
}
}
}
}
var dicChb = list.Where(x => x.PlcAb == "B" && x.Address == "CH_alarm"
&& !string.IsNullOrEmpty(x.Msg))
.ToDictionary(x => x.No, x => x.Msg);
for (int i = 0; i < 31; i++)
{
var obj = a.GetValue("B" + i);
if (obj != null)
{
if (dicChb.TryGetValue(i, out var value))
{
if (Convert.ToBoolean(obj))
{
if (!dictionary.ContainsKey(value))
{
dictionary[value] = DateTime.Now;
}
}
else
{
dictionary.Remove(value);
}
}
}
}
a = hmiCq.GetPlcDb();
var dicCqA = list.Where(x => x.PlcAb == "A" && x.Address == "CQ_alarm"
&& !string.IsNullOrEmpty(x.Msg))
.ToDictionary(x => x.No, x => x.Msg);
for (int i = 0; i < 31; i++)
{
var obj = a.GetValue("A" + i);
if (obj != null)
{
if (dicCqA.TryGetValue(i, out var value))
{
if (Convert.ToBoolean(obj))
{
if (!dictionary.ContainsKey(value))
{
dictionary[value] = DateTime.Now;
}
}
else
{
dictionary.Remove(value);
}
}
}
}
var dicCqB = list.Where(x => x.PlcAb == "B" && x.Address == "CQ_alarm"
&& !string.IsNullOrEmpty(x.Msg))
.ToDictionary(x => x.No, x => x.Msg);
for (int i = 0; i < 31; i++)
{
var obj = a.GetValue("B" + i);
if (obj != null)
{
if (dicCqB.TryGetValue(i, out var value))
{
if (Convert.ToBoolean(obj))
{
if (!dictionary.ContainsKey(value))
{
dictionary[value] = DateTime.Now;
}
}
else
{
dictionary.Remove(value);
}
}
}
}
var listEs= list.Where(x => x.PlcAb == "" && x.Address == "estop_temporary_storage"
&& !string.IsNullOrEmpty(x.Msg))
.ToDictionary(x => x.No, x => x.Msg);
var bools = esStop.GetDb();
for (int i = 0; i < 31; i++)
{
var obj = bools[i];
if (listEs.TryGetValue(i, out var value))
{
if (Convert.ToBoolean(obj))
{
if (!dictionary.ContainsKey(value))
{
dictionary[value] = DateTime.Now;
}
}
else
{
dictionary.Remove(value);
}
}
}
cache.Set(CacheKeyManager.AramlList, dictionary, TimeSpan.FromMinutes(10));
return dictionary;
}
}
}
}

@ -117,4 +117,16 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="dataGridViewTextBoxColumn1.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="dataGridViewTextBoxColumn2.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="al.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Msg.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

@ -8,6 +8,7 @@ using Chloe.PostgreSQL.DDL;
using Chloe.RDBMS.DDL;
using Tool;
using DB.Service;
using Tool.Model;
namespace RfidWeb
{
@ -26,7 +27,28 @@ namespace RfidWeb
DbInfo.Init(typeof(UserInfo).Assembly);
var rfidSetting = RfidSetting.Current;
var dbContext = DbFactory.GetContext;
new PostgreSQLTableGenerator(dbContext).CreateTables(TableCreateMode.CreateIfNotExists);
//new PostgreSQLTableGenerator(dbContext).CreateTables(TableCreateMode.CreateIfNotExists);
AlarmData data = new AlarmData();
data.LastModifyDate=DateTime.Now;
data.CreateDate=DateTime.Now;
data.LastModifyUserId = "";
data.LastModifyUserName = "";
data.CreateUserId = "";
data.CreateUserName = "";
for (int i = 0; i < 31; i++)
{
data.ID = GetId;
data.Address = "estop_temporary_storage";
data.No = i;
data.PlcAb = "";
data.Msg = "";
dbContext.Insert(data);
}
//dbContext.Insert(new Role()
//{

@ -19,8 +19,8 @@ namespace RfidWeb
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new FromSQl());
Application.Run(new FormMain());
// Application.Run(new FromSQl());
Application.Run(new FormMain());
}
}
}

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tool
{
public class GridViewData
{
public string AlartTime { get; set; }
public string Msg { get; set; }
}
}

@ -0,0 +1,118 @@
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using System.Threading.Tasks;
//namespace Tool.Model
//{
// public static class AlarmMsgFactory
// {
// public static Dictionary<int, string> CHA
// {
// get
// {
// Dictionary<int, string> dictionary = new Dictionary<int, string>
// {
// [0] = "盘料取料1级气缸动作超时",
// [1] = "卷料取料1级气缸动作超时",
// [2] = "卷料取料横移气缸动作超时"
// };
// return dictionary;
// }
// }
// public static Dictionary<int, string> CHB
// {
// get
// {
// Dictionary<int, string> dictionary = new Dictionary<int, string>
// {
// [0] = "盘料取料标签掉落异常",
// [1] = "盘料取料传感器异常",
// [2] = "",
// [3] = "料取料标签掉落异常",
// [4] = "卷料取料传感器异常卷",
// [5] = "芯片料带检测缺失",
// [6] = "层合下层胶料检测缺失",
// [7] = "层合下层胶料断开",
// [8] = "层合下层胶料跑偏",
// [9] = "层合上层胶料检测缺失",
// [10] = "层合上层胶料断开",
// [11] = "层合上层胶料跑偏",
// [12] = "供料电机动作超时",
// [13] = "层合电机动作超时",
// [14] = "芯片抓取失败",
// [15] = "",
// [16] = "",
// [17] = "",
// [18] = "",
// [19] = "",
// [20] = "",
// };
// return dictionary;
// }
// }
// public static Dictionary<int, string> CQA
// {
// get
// {
// Dictionary<int, string> dictionary = new Dictionary<int, string>
// {
// [0] = "裁刀1动作超时",
// [1] = "裁刀2动作超时",
// [2] = "裁刀1收料机构不在原点位"
// };
// return dictionary;
// }
// }
// public static Dictionary<int, string> CQB
// {
// get
// {
// Dictionary<int, string> dictionary = new Dictionary<int, string>
// {
// [0] = "芯片1读取失败",
// [1] = "芯片2读取失败",
// [2] = "废料芯片残留",
// [3] = "裁切1放料异常",
// [4] = "裁切2放料异常",
// [5] = "收料1前进感应超时",
// [6] = "收料2前进感应超时",
// [7] = "收料1底膜缺料",
// [8] = "收料2底膜缺料",
// [9] = "裁切胶料检测缺失",
// [10] = "",
// [11] = "",
// [12] = "",
// [13] = "",
// [14] = "",
// [15] = "",
// [16] = "",
// [17] = "",
// [18] = "",
// [19] = "",
// [20] = "",
// };
// return dictionary;
// }
// }
// }
//}

@ -23,18 +23,18 @@ namespace Tool.Model
public PlcBoolModel GetDb()
public PlcBoolModel GetPlcDb()
{
return Reload();
}
public UInt32 GetA(PlcBoolModel model)
public UInt32 SetA(PlcBoolModel model)
{
return GetValue(model, "A");
}
public UInt32 GetB(PlcBoolModel model)
public UInt32 SetB(PlcBoolModel model)
{
return GetValue(model,"B");

@ -3,7 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using HslCommunication;
using NewLife.Caching;
using NewLife.Reflection;
namespace Tool.Model
{
@ -28,4 +32,46 @@ namespace Tool.Model
{
}
}
public class HmiChAlarm : BoolModelFactory
{
public HmiChAlarm() : base("CH_alarm_A", "CH_alarm_B")
{
}
}
public class HmiCqAlarm : BoolModelFactory
{
public HmiCqAlarm() : base("CQ_alarm_A", "CQ_alarm_B")
{
}
}
public class EsStop
{
public List<bool> GetDb()
{
bool[] bitArray = new bool[32];
var readUInt32 = PlcConnect.Instance.ReadUInt32("estop_temporary_storage");
var bo = readUInt32.IsSuccess;
var context = readUInt32.Content;
if (bo)
{
for (int i = 0; i < 31; i++)
{
bitArray[i] = context.GetBoolByIndex(i);
}
}
return bitArray.ToList();
}
}
}

@ -64,5 +64,9 @@ namespace Tool.Model
public static readonly string User = "HmiUser";
public static readonly string AesPwd = "nodyang";
public static readonly string AramlList = "AramlList";
public static readonly string AramlDataList = "AramlDataList";
}
}

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@ -83,5 +84,8 @@ namespace Tool.Model
public bool B30 { get; set; }
}
}

@ -63,9 +63,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DataTypeEnum.cs" />
<Compile Include="GridViewData.cs" />
<Compile Include="HmiHelp.cs" />
<Compile Include="HttpUtil.cs" />
<Compile Include="ILogNetFactory.cs" />
<Compile Include="Model\AlarmMsgFactory.cs" />
<Compile Include="Model\BoolModelFactory.cs" />
<Compile Include="Model\HmiChModel.cs" />
<Compile Include="Model\HmiPoint.cs" />

Loading…
Cancel
Save