using Dapper; using EVCB_OCPP.TaskScheduler.Models; using Quartz; using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using System.Web; namespace EVCB_OCPP.TaskScheduler.Jobs { /// /// Update EVSE's online status /// [DisallowConcurrentExecution] public class CheckEVSEOnlineJob : IJob { private NLog.ILogger logger = NLog.LogManager.GetCurrentClassLogger(); private DateTime latestHeartbeatTime = DateTime.Now; private List updateData = new List(); private List insertData = new List(); private string mainDBConnectString = ConfigurationManager.ConnectionStrings["MainDBContext"].ToString(); private string onlineDBConnectString = ConfigurationManager.ConnectionStrings["OnlineLogDBContext"].ToString(); private string webConnectionString = ConfigurationManager.ConnectionStrings["WebDBContext"].ToString(); public CheckEVSEOnlineJob() { CreateEVSEOnlineRecordTable(); } //測試方式 // A: 假裝更新心跳包 // B: 一直開著程式 / 一段時間關程式(關掉時間點 online/offline) public async Task Execute(IJobExecutionContext context) { await Console.Out.WriteLineAsync(this.ToString() + " :Starting........"); List _EVSEs = GetEVSEs(); foreach (var evse in _EVSEs) { latestHeartbeatTime = evse.HeartbeatUpdatedOn; if (IsOnlineNow(evse)) { if (evse.Online) { if (latestHeartbeatTime.Minute % 10 == 0) { //on-on //現在是整點,找上一筆填入end time and insert start time // var _pickDate = currentTime.AddHours(-1); var _pickDate = latestHeartbeatTime; bool isCrossDay = false; int _startHour = _pickDate.AddHours(-6).Hour; //往回巡視六個小時前紀錄 //跨日處理 if (_startHour > _pickDate.Hour) { isCrossDay = true; } var _picks = GetOnlineRecords(_pickDate, evse.CustomerId.ToString(), evse.ChargeBoxId, isCrossDay ? 0 : _startHour, _pickDate.Hour); if (isCrossDay) { _picks.AddRange(GetOnlineRecords(_pickDate.AddHours(-6), evse.CustomerId.ToString(), evse.ChargeBoxId, _startHour, 23)); } _picks = _picks.OrderBy(x => x.OnlineTime).ToList(); bool _isIgnore = true; for (int i = 0; i < _picks.Count; i++) { if (_picks[i].HourIndex != evse.HeartbeatUpdatedOn.Hour && _picks[i].OfflineTime == DefaultSetting.DefaultNullTime) { _picks[i].OfflineTime = new DateTime(_picks[i].OnlineTime.AddHours(1).Year, _picks[i].OnlineTime.AddHours(1).Month, _picks[i].OnlineTime.AddHours(1).Day, _picks[i].OnlineTime.AddHours(1).Hour, 0, 0); _picks[i].TotalMinute = (int)_picks[i].OfflineTime.Subtract(_picks[i].OnlineTime).TotalMinutes; updateData.Add(_picks[i]); var checkTime = _picks[i].OfflineTime; while ((int)latestHeartbeatTime.Subtract(checkTime).TotalHours > 0) { var _existedCount = _picks.Where(x => x.HourIndex == checkTime.Hour).ToList().Count; if (_existedCount == 0) { insertData.Add(new EVSEOnlineRecord() { CustomerId = evse.CustomerId, StationId = "0", HourIndex = checkTime.Hour, ChargeBoxId = evse.ChargeBoxId, OnlineTime = new DateTime(checkTime.Year, checkTime.Month, checkTime.Day, checkTime.Hour, 0, 0), OfflineTime = new DateTime(checkTime.AddHours(1).Year, checkTime.AddHours(1).Month, checkTime.AddHours(1).Day, checkTime.AddHours(1).Hour, 0, 0), TotalMinute = 60 }); checkTime = checkTime.AddHours(1); } } } } _isIgnore = _picks.Where(x => x.HourIndex == latestHeartbeatTime.Hour).ToList().Count == 0 ? false : true; if (!_isIgnore) { insertData.Add(new EVSEOnlineRecord() { CustomerId = evse.CustomerId, StationId = "0", HourIndex = evse.HeartbeatUpdatedOn.Hour, ChargeBoxId = evse.ChargeBoxId, OnlineTime = new DateTime(latestHeartbeatTime.Year, latestHeartbeatTime.Month, latestHeartbeatTime.Day, latestHeartbeatTime.Hour, 0, 0), OfflineTime = DefaultSetting.DefaultNullTime }); } } } else { //off - on UpdateEVSECurrentStatus(evse.CustomerId.ToString(), evse.ChargeBoxId, true, DefaultSetting.DefaultNullTime); insertData.Add(new EVSEOnlineRecord() { CustomerId = evse.CustomerId, StationId = "0", HourIndex = evse.HeartbeatUpdatedOn.Hour, ChargeBoxId = evse.ChargeBoxId, OnlineTime = evse.HeartbeatUpdatedOn, }); await NotifyOnlineChanged(evse.CustomerId.ToString(), evse.Id.ToString(), evse.ChargeBoxId, true); } } else { if (evse.Online) { //on -off UpdateEVSECurrentStatus(evse.CustomerId.ToString(), evse.ChargeBoxId, false, evse.HeartbeatUpdatedOn); var _pickDate = evse.HeartbeatUpdatedOn.Date; var _picks = GetOnlineRecords(_pickDate, evse.CustomerId.ToString(), evse.ChargeBoxId, _pickDate.Hour, evse.HeartbeatUpdatedOn.Hour).Where(x => x.OfflineTime == DefaultSetting.DefaultNullTime); foreach (var item in _picks) { if (evse.HeartbeatUpdatedOn.Hour - item.OnlineTime.Hour > 0) { item.OfflineTime = new DateTime(item.OnlineTime.AddHours(1).Year, item.OnlineTime.AddHours(1).Month, item.OnlineTime.AddHours(1).Day, item.OnlineTime.AddHours(1).Hour, 0, 0); item.TotalMinute = (int)item.OfflineTime.Subtract(item.OnlineTime).TotalMinutes; updateData.Add(item); var checkTime = item.OfflineTime; while (evse.HeartbeatUpdatedOn.Hour - checkTime.Hour >= 0) { insertData.Add(new EVSEOnlineRecord() { CustomerId = evse.CustomerId, StationId = "0", HourIndex = checkTime.Hour, ChargeBoxId = evse.ChargeBoxId, OnlineTime = checkTime, OfflineTime = evse.HeartbeatUpdatedOn.Hour == checkTime.Hour ? evse.HeartbeatUpdatedOn : new DateTime(checkTime.AddHours(1).Year, checkTime.AddHours(1).Month, checkTime.AddHours(1).Day, checkTime.AddHours(1).Hour, 0, 0) }); checkTime = evse.HeartbeatUpdatedOn.Hour == checkTime.Hour ? insertData.LastOrDefault().OfflineTime.AddHours(1) : insertData.LastOrDefault().OfflineTime; } } else { item.OfflineTime = evse.HeartbeatUpdatedOn; item.TotalMinute = (int)item.OfflineTime.Subtract(item.OnlineTime).TotalMinutes; updateData.Add(item); } } await NotifyOnlineChanged(evse.CustomerId.ToString(), evse.Id.ToString(), evse.ChargeBoxId, false); } else { //off-off do nothing } } if ((insertData.Count + updateData.Count) % 100 == 0) { UpdateOnlineRecords(updateData, insertData); ClearCache(); } } UpdateOnlineRecords(updateData, insertData); await Console.Out.WriteLineAsync(this.ToString() + " :Finished........"); } async private Task NotifyOnlineChanged(string customerId, string machineId, string chargeBoxId, bool turnOn) { string stationName = await GetStationName(machineId, chargeBoxId); string errorMsg = string.Empty; List sendlist = ConfigurationManager.AppSettings["OnlineChanged_Receivers"].Split(',').ToList(); foreach (var item in sendlist) { string message = string.Format("[{0}{1}]\r\n{2} \r\n時間:{3}\r\n ", stationName, turnOn ? "電樁上線通知" : "電樁斷線通知",chargeBoxId, DateTime.UtcNow.AddHours(8).ToString("yyyy/MM/dd HH:mm:ss") ); SendMessage(item, message, out errorMsg); } } private int SendMessage(string phoneNumber, string message, out string errorMsg) { int result = 0; try { errorMsg = string.Empty; HttpClient client = new HttpClient(); StringBuilder url = new StringBuilder("http://smexpress.mitake.com.tw:7002/SpSendUtf?"); url.Append("username=").Append(HttpUtility.UrlEncode("83196607SMS", Encoding.UTF8)); url.Append("&password=").Append(HttpUtility.UrlEncode("83196607SMS@nhoa.tcc", Encoding.UTF8)); url.Append("&encoding=UTF8"); url.Append("&dstaddr=").Append(phoneNumber); url.Append("&smbody=").Append(HttpUtility.UrlEncode(message, Encoding.UTF8).Replace("+", "%20")); url.Append("&CharsetURL=").Append("utf-8"); Console.WriteLine(url.ToString()); var id = client.GetStringAsync(url.ToString()).Result; if (!SendResult(id)) { result = -1; errorMsg = id; } } catch (Exception ex) { result = -1; errorMsg = ex.Message; } return result; } private bool SendResult(string response) { bool result = false; if (response.Contains("msgid=")) { result = true; } return result; } async private Task GetStationName(string machineId, string chargeBoxId) { string stationName = string.Empty; try { var parameters = new DynamicParameters(); parameters.Add("@MachineId", machineId, DbType.String, ParameterDirection.Input); string strSql = "SELECT [Name] as StationName FROM[StationMachine] left join[dbo].[Station]" + " on[StationMachine].StationId = Station.[Id] where StationMachine.MachineId=@MachineId ; "; using (SqlConnection conn = new SqlConnection(webConnectionString)) { var result = await conn.QueryAsync(strSql, parameters); stationName = result.FirstOrDefault(); } } catch (Exception ex) { } return stationName; } private List GetEVSEs() { List result = new List(); try { using (var dbConn = new SqlConnection(mainDBConnectString)) { dbConn.Open(); result = dbConn.Query("SELECT CustomerId,Id,ChargeBoxId,Online,HeartbeatUpdatedOn FROM [dbo].[Machine]").ToList(); } } catch (Exception ex) { logger.Error("Query Data Error " + ex.ToString()); } return result; } private void UpdateEVSECurrentStatus(string customerId, string ChargeBoxId, bool turnOn, DateTime offlineTime) { try { string sqlString = string.Format("UPDATE[dbo].[Machine] SET Online=@Online {0} WHERE chargeBoxId=@chargeBoxId and customerId=@customerId", turnOn ? "" : " ,OfflineOn=@OfflineOn"); using (var dbConn = new SqlConnection(mainDBConnectString)) { dbConn.Open(); var parameters = new DynamicParameters(); parameters.Add("@Online", turnOn, System.Data.DbType.Boolean); parameters.Add("@chargeBoxId", ChargeBoxId, System.Data.DbType.String); parameters.Add("@customerId", customerId, System.Data.DbType.String); if (!turnOn) { parameters.Add("@OfflineOn", offlineTime, System.Data.DbType.DateTime); } dbConn.Execute(sqlString, parameters); } } catch (Exception ex) { logger.Error("Update Data Error " + ex.ToString()); } } private void UpdateOnlineRecords(List updateItems, List insertItems) { List records = new List(); try { for (int i = 0; i < updateItems.Count; i++) { string sqlString = string.Format("UPDATE [dbo].[EVSEOnlineRecord_{0}] SET OfflineTime=@OfflineTime , TotalMinute=@TotalMinute WHERE customerId=@customerId and chargeBoxId=@chargeBoxId and " + "OnlineTime=@OnlineTime", updateItems[i].OnlineTime.Date.ToString("yyMMdd")); using (var dbConn = new SqlConnection(onlineDBConnectString)) { dbConn.Open(); dbConn.Execute(sqlString, updateItems[i]); } } for (int i = 0; i < insertItems.Count; i++) { string sqlString = string.Format("INSERT INTO [dbo].[EVSEOnlineRecord_{0}] (\"CustomerId\",\"StationId\",\"ChargeBoxId\",\"HourIndex\",\"TotalMinute\",\"OnlineTime\",\"OfflineTime\")" + "VALUES(@CustomerId,@StationId, @ChargeBoxId,@HourIndex,@TotalMinute,@OnlineTime,@OfflineTime); ", insertItems[i].OnlineTime.Date.ToString("yyMMdd")); using (var dbConn = new SqlConnection(onlineDBConnectString)) { dbConn.Open(); dbConn.Execute(sqlString, insertItems[i]); } } } catch (Exception ex) { logger.Error("Update Data Error " + ex.ToString()); } } private List GetOnlineRecords(DateTime pickDate, string customerId, string chargeBoxId, int startHourCondition, int stopHourCondition) { List records = new List(); try { string sqlString = string.Format("SELECT * FROM [dbo].[EVSEOnlineRecord_{0}] WHERE customerId=@customerId and chargeBoxId=@chargeBoxId and " + "HourIndex >= @startHourCondition and HourIndex <= @stopHourCondition and OfflineTime=@OfflineTime", pickDate.ToString("yyMMdd")); using (var dbConn = new SqlConnection(onlineDBConnectString)) { dbConn.Open(); var parameters = new DynamicParameters(); parameters.Add("@customerId", new Guid(customerId), System.Data.DbType.Guid); parameters.Add("@chargeBoxId", chargeBoxId, System.Data.DbType.String); parameters.Add("@startHourCondition", startHourCondition, System.Data.DbType.Int32); parameters.Add("@stopHourCondition", stopHourCondition, System.Data.DbType.Int32); parameters.Add("@OfflineTime", DefaultSetting.DefaultNullTime, System.Data.DbType.Date); records = dbConn.Query(sqlString, parameters).ToList(); } } catch (Exception ex) { logger.Error("Query Data Error " + ex.ToString()); } return records; } private void CreateEVSEOnlineRecordTable() { try { string sqlString = string.Format("SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME ='EVSEOnlineRecord_{0}'", latestHeartbeatTime.ToString("yyMMdd")); using (var dbConn = new SqlConnection(onlineDBConnectString)) { bool exists = dbConn.ExecuteScalar(sqlString); if (!exists) { dbConn.Execute(string.Format(@" CREATE TABLE [dbo].[EVSEOnlineRecord_{0}] ( [CustomerId] [UNIQUEIDENTIFIER] NOT NULL, [StationId] [nvarchar](36) NOT NULL, [ChargeBoxId] [nvarchar](36) NOT NULL, [HourIndex] [int] NOT NULL, [TotalMinute] [int] NOT NULL, [OnlineTime] [datetime] NOT NULL, [OfflineTime] [datetime] NOT NULL ) ON [PRIMARY] ", latestHeartbeatTime.ToString("yyMMdd"))); } } } catch (Exception ex) { logger.Error("Query Data Error " + ex.ToString()); } } private void ClearCache() { if (updateData != null) { updateData.Clear(); } if (insertData != null) { insertData.Clear(); } } private bool IsOnlineNow(EVSECurrentStatus currentEVSE) { bool isOnline = false; var checkTime = DateTime.Now.AddSeconds(-120); if (currentEVSE.HeartbeatUpdatedOn > checkTime) { isOnline = true; } return isOnline; } } }