diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/SensorHub.Servers/AepDeviceCommand.cs b/SensorHub.Servers/AepDeviceCommand.cs new file mode 100644 index 0000000..09153d4 --- /dev/null +++ b/SensorHub.Servers/AepDeviceCommand.cs @@ -0,0 +1,150 @@ +using AepSdk.Apis.Core; +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AepSdk.Apis +{ + class AepDeviceCommand + { + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommand(string body) + { + string path = "/aep_device_command/command"; + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190712225145"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + Console.WriteLine(response); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数productId: 类型long, 参数不可以为空 + // 描述:产品ID,必填 + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID,必填 + //参数startTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数endTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数pageNow: 类型long, 参数可以为空 + // 描述:当前页数 + //参数pageSize: 类型long, 参数可以为空 + // 描述:每页记录数,最大40 + public static string QueryCommandList(string appKey, string appSecret, string MasterKey, string productId, string deviceId, string startTime = "", string endTime = "", string pageNow = "", string pageSize = "") + { + string path = "/aep_device_command/commands"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + param.Add("startTime", startTime); + param.Add("endTime", endTime); + param.Add("pageNow", pageNow); + param.Add("pageSize", pageSize); + + string version = "20200814163736"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数commandId: 类型String, 参数不可以为空 + // 描述:创建指令成功响应中返回的id, + //参数productId: 类型long, 参数不可以为空 + // 描述: + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID + public static string QueryCommand(string appKey, string appSecret, string MasterKey, string commandId, string productId, string deviceId) + { + string path = "/aep_device_command/command"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("commandId", commandId); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + + string version = "20190712225241"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述: + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CancelCommand(string appKey, string appSecret, string MasterKey, string body) + { + string path = "/aep_device_command/cancelCommand"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190615023142"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "PUT"); + if (response != null) + return response; + return null; + } + + //参数MasterKey: 类型String, 参数可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommandLwm2mProfile(string body) + { + string path = "/aep_device_command_lwm_profile/commandLwm2mProfile"; + + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20191231141545"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + if (response != null) + return response; + return null; + } + + } +} diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/SensorHub.Servers/AepDeviceCommand.cs b/SensorHub.Servers/AepDeviceCommand.cs new file mode 100644 index 0000000..09153d4 --- /dev/null +++ b/SensorHub.Servers/AepDeviceCommand.cs @@ -0,0 +1,150 @@ +using AepSdk.Apis.Core; +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AepSdk.Apis +{ + class AepDeviceCommand + { + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommand(string body) + { + string path = "/aep_device_command/command"; + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190712225145"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + Console.WriteLine(response); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数productId: 类型long, 参数不可以为空 + // 描述:产品ID,必填 + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID,必填 + //参数startTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数endTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数pageNow: 类型long, 参数可以为空 + // 描述:当前页数 + //参数pageSize: 类型long, 参数可以为空 + // 描述:每页记录数,最大40 + public static string QueryCommandList(string appKey, string appSecret, string MasterKey, string productId, string deviceId, string startTime = "", string endTime = "", string pageNow = "", string pageSize = "") + { + string path = "/aep_device_command/commands"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + param.Add("startTime", startTime); + param.Add("endTime", endTime); + param.Add("pageNow", pageNow); + param.Add("pageSize", pageSize); + + string version = "20200814163736"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数commandId: 类型String, 参数不可以为空 + // 描述:创建指令成功响应中返回的id, + //参数productId: 类型long, 参数不可以为空 + // 描述: + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID + public static string QueryCommand(string appKey, string appSecret, string MasterKey, string commandId, string productId, string deviceId) + { + string path = "/aep_device_command/command"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("commandId", commandId); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + + string version = "20190712225241"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述: + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CancelCommand(string appKey, string appSecret, string MasterKey, string body) + { + string path = "/aep_device_command/cancelCommand"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190615023142"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "PUT"); + if (response != null) + return response; + return null; + } + + //参数MasterKey: 类型String, 参数可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommandLwm2mProfile(string body) + { + string path = "/aep_device_command_lwm_profile/commandLwm2mProfile"; + + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20191231141545"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + if (response != null) + return response; + return null; + } + + } +} diff --git a/SensorHub.Servers/AepSdkCore.cs b/SensorHub.Servers/AepSdkCore.cs new file mode 100644 index 0000000..574cdcc --- /dev/null +++ b/SensorHub.Servers/AepSdkCore.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AepSdk.Apis.Core +{ + class AepHttpRequest + { + static long offset = 0; + static long lastGetOffsetTime = 0; + static readonly string baseUrl = ConfigurationManager.AppSettings["BaseUrl"]; + static readonly string timeUrl = "https://ag-api.ctwing.cn/echo"; + + + /// + /// 获取时间偏移量 + /// + /// 时间偏移量 + public static long GetTimeOffset() + { + long start = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + WebHeaderCollection head = null; + string response = SendHttpRequest(timeUrl, null, "application/json; charset=UTF-8", "GET", null, 5, out head); + long end = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (response != null) + { + long timeAg = Convert.ToInt64(head["x-ag-timestamp"]); + return timeAg - (end + start) / 2; + } + + return 0; + } + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method) + { + WebHeaderCollection head; + return SendAepHttpRequest(path, headers, param, body, version, application, key, method, out head); + } + + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求结果的head出参 + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method, out WebHeaderCollection head) + { + + string paramString = ""; + + if (param != null) + { + foreach (KeyValuePair kvp in param) + { + paramString += kvp.Key + "=" + kvp.Value + "&"; + } + } + + if (paramString.Length > 0) + { + paramString = paramString.Remove(paramString.Length - 1); + } + + // Console.WriteLine("paramString = " + paramString); + + + + Dictionary paramTmp = new Dictionary(); + if (headers != null) + paramTmp = paramTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + if (param != null) + paramTmp = paramTmp.Concat(param).ToDictionary(k => k.Key, v => v.Value); + + long curentTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (curentTime - lastGetOffsetTime > 300 * 1000) //300秒调用一次 + { + offset = GetTimeOffset(); + lastGetOffsetTime = curentTime; + } + long timestamp = curentTime + offset; + + Dictionary headersTmp = new Dictionary(); + headersTmp.Add("application", application); + headersTmp.Add("timestamp", "" + timestamp); + headersTmp.Add("version", version); + //headersTmp.Add("Content-Type", "application/json; charset=UTF-8"); + //headersTmp.Add("Date", dataString); + headersTmp.Add("signature", Sign(paramTmp, timestamp, application, key, body)); + if (headers != null) + { + headersTmp = headersTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + } + + string url = baseUrl + path; + if (paramString.Length > 0) + { + url += "?" + paramString; + } + // Console.WriteLine("url = " + url); + string result = SendHttpRequest(url, headersTmp, "application/json; charset=UTF-8", method, body, 35, out head); + return result; + } + + + + /// + /// 签名算法 + /// + /// api接口参数 + /// 时间戳,毫秒级 + /// App Key + /// App secret + /// body + /// + public static string Sign(Dictionary param, long timestamp, string application, string secret, string body) + { + // 连接系统参数 + string temp = "application:" + application + "\n"; + temp += "timestamp:" + timestamp + "\n"; + + // 连接请求参数 + if (param != null) + { + var dicNew = param.OrderBy(x => x.Key, new ComparerString()).ToDictionary(x => x.Key, y => y.Value); + + foreach (KeyValuePair kvp in dicNew) + { + temp += kvp.Key + ":" + (kvp.Value == null ? "" : kvp.Value) + "\n"; + } + } + + + // 得到需要签名的字符串 + if (body != null && body.Length > 0) + { + temp += body + "\n"; + } + // Console.WriteLine("Sign string: " + temp); + + // hmac-sha1编码 + var hmacsha1 = new HMACSHA1(); + hmacsha1.Key = Encoding.UTF8.GetBytes(secret); + byte[] dataBuffer = Encoding.UTF8.GetBytes(temp); + byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer); + + + // base64编码 + string encode = Convert.ToBase64String(hashBytes); + + return encode; + } + + /// + /// 处理http请求 + /// + /// 请求的url地址 + /// 协议标头 + /// 请求的内容类型 + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求的数据流 + /// 请求的超时时间(秒) + /// http POST成功后返回的数据,失败抛异常 + public static string SendHttpRequest(string url, Dictionary headers, string contentType, string method, string dataStream, int timeout, out WebHeaderCollection head) + { + System.GC.Collect();//垃圾回收,回收没有正常关闭的http链接 + HttpWebRequest request = null; + HttpWebResponse response = null; + Stream reqStream = null; + try + { + //设置最大链接数 + ServicePointManager.DefaultConnectionLimit = 200; + //设置https验证方式 + if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + ServicePointManager.ServerCertificateValidationCallback = + new RemoteCertificateValidationCallback(CertificateValidation); + } + request = (HttpWebRequest)WebRequest.Create(url); + + //HttpWebRequest 相关属性 + request.Method = method; + request.Timeout = timeout * 1000; + request.ContentType = contentType; + if (headers != null) + { + //配置协议标头 + foreach (KeyValuePair kvp in headers) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + byte[] data = null; + if (dataStream != null) + { + data = System.Text.Encoding.UTF8.GetBytes(dataStream); + request.ContentLength = data.Length; + } + + if (data != null) + { + //写入数据 + reqStream = request.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + } + + head = null; + //返回数据 + response = (HttpWebResponse)request.GetResponse(); + if (response != null) + { + head = response.Headers; + Stream stream = response.GetResponseStream(); + StreamReader sr = new StreamReader(stream, Encoding.UTF8); + string result = sr.ReadToEnd(); + sr.Close(); + //关闭连接和流 + response.Close(); + return result; + } + else + { + head = null; + return String.Empty; + } + + + } + //处理多线程模式下线程中止 + //catch (System.Threading.ThreadAbortException e) + //{ + // System.Threading.Thread.ResetAbort(); + //} + catch (WebException e) + { + head = null; + response = (HttpWebResponse)e.Response; + if (response != null) + { + head = response.Headers; + } + throw e; + } + catch (Exception e) + { + throw new HttpServiceException(e.ToString()); + } + finally + { + if (request != null) + { + request.Abort(); + } + } + } + + /* 忽略证书认证错误 + * .NET的SSL通信过程中,使用的证书可能存在各种问题 + * 此方法可以跳过服务器证书验证,完成正常通信。*/ + private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) + { + // 认证正常,没有错误 + return true; + } + } + + class HttpServiceException : Exception + { + public HttpServiceException(string msg) : base(msg) + { + + } + } + + public class ComparerString : IComparer + { + public int Compare(String x, String y) + { + return string.CompareOrdinal(x, y); + } + } +} diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/SensorHub.Servers/AepDeviceCommand.cs b/SensorHub.Servers/AepDeviceCommand.cs new file mode 100644 index 0000000..09153d4 --- /dev/null +++ b/SensorHub.Servers/AepDeviceCommand.cs @@ -0,0 +1,150 @@ +using AepSdk.Apis.Core; +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AepSdk.Apis +{ + class AepDeviceCommand + { + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommand(string body) + { + string path = "/aep_device_command/command"; + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190712225145"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + Console.WriteLine(response); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数productId: 类型long, 参数不可以为空 + // 描述:产品ID,必填 + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID,必填 + //参数startTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数endTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数pageNow: 类型long, 参数可以为空 + // 描述:当前页数 + //参数pageSize: 类型long, 参数可以为空 + // 描述:每页记录数,最大40 + public static string QueryCommandList(string appKey, string appSecret, string MasterKey, string productId, string deviceId, string startTime = "", string endTime = "", string pageNow = "", string pageSize = "") + { + string path = "/aep_device_command/commands"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + param.Add("startTime", startTime); + param.Add("endTime", endTime); + param.Add("pageNow", pageNow); + param.Add("pageSize", pageSize); + + string version = "20200814163736"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数commandId: 类型String, 参数不可以为空 + // 描述:创建指令成功响应中返回的id, + //参数productId: 类型long, 参数不可以为空 + // 描述: + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID + public static string QueryCommand(string appKey, string appSecret, string MasterKey, string commandId, string productId, string deviceId) + { + string path = "/aep_device_command/command"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("commandId", commandId); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + + string version = "20190712225241"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述: + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CancelCommand(string appKey, string appSecret, string MasterKey, string body) + { + string path = "/aep_device_command/cancelCommand"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190615023142"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "PUT"); + if (response != null) + return response; + return null; + } + + //参数MasterKey: 类型String, 参数可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommandLwm2mProfile(string body) + { + string path = "/aep_device_command_lwm_profile/commandLwm2mProfile"; + + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20191231141545"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + if (response != null) + return response; + return null; + } + + } +} diff --git a/SensorHub.Servers/AepSdkCore.cs b/SensorHub.Servers/AepSdkCore.cs new file mode 100644 index 0000000..574cdcc --- /dev/null +++ b/SensorHub.Servers/AepSdkCore.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AepSdk.Apis.Core +{ + class AepHttpRequest + { + static long offset = 0; + static long lastGetOffsetTime = 0; + static readonly string baseUrl = ConfigurationManager.AppSettings["BaseUrl"]; + static readonly string timeUrl = "https://ag-api.ctwing.cn/echo"; + + + /// + /// 获取时间偏移量 + /// + /// 时间偏移量 + public static long GetTimeOffset() + { + long start = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + WebHeaderCollection head = null; + string response = SendHttpRequest(timeUrl, null, "application/json; charset=UTF-8", "GET", null, 5, out head); + long end = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (response != null) + { + long timeAg = Convert.ToInt64(head["x-ag-timestamp"]); + return timeAg - (end + start) / 2; + } + + return 0; + } + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method) + { + WebHeaderCollection head; + return SendAepHttpRequest(path, headers, param, body, version, application, key, method, out head); + } + + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求结果的head出参 + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method, out WebHeaderCollection head) + { + + string paramString = ""; + + if (param != null) + { + foreach (KeyValuePair kvp in param) + { + paramString += kvp.Key + "=" + kvp.Value + "&"; + } + } + + if (paramString.Length > 0) + { + paramString = paramString.Remove(paramString.Length - 1); + } + + // Console.WriteLine("paramString = " + paramString); + + + + Dictionary paramTmp = new Dictionary(); + if (headers != null) + paramTmp = paramTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + if (param != null) + paramTmp = paramTmp.Concat(param).ToDictionary(k => k.Key, v => v.Value); + + long curentTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (curentTime - lastGetOffsetTime > 300 * 1000) //300秒调用一次 + { + offset = GetTimeOffset(); + lastGetOffsetTime = curentTime; + } + long timestamp = curentTime + offset; + + Dictionary headersTmp = new Dictionary(); + headersTmp.Add("application", application); + headersTmp.Add("timestamp", "" + timestamp); + headersTmp.Add("version", version); + //headersTmp.Add("Content-Type", "application/json; charset=UTF-8"); + //headersTmp.Add("Date", dataString); + headersTmp.Add("signature", Sign(paramTmp, timestamp, application, key, body)); + if (headers != null) + { + headersTmp = headersTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + } + + string url = baseUrl + path; + if (paramString.Length > 0) + { + url += "?" + paramString; + } + // Console.WriteLine("url = " + url); + string result = SendHttpRequest(url, headersTmp, "application/json; charset=UTF-8", method, body, 35, out head); + return result; + } + + + + /// + /// 签名算法 + /// + /// api接口参数 + /// 时间戳,毫秒级 + /// App Key + /// App secret + /// body + /// + public static string Sign(Dictionary param, long timestamp, string application, string secret, string body) + { + // 连接系统参数 + string temp = "application:" + application + "\n"; + temp += "timestamp:" + timestamp + "\n"; + + // 连接请求参数 + if (param != null) + { + var dicNew = param.OrderBy(x => x.Key, new ComparerString()).ToDictionary(x => x.Key, y => y.Value); + + foreach (KeyValuePair kvp in dicNew) + { + temp += kvp.Key + ":" + (kvp.Value == null ? "" : kvp.Value) + "\n"; + } + } + + + // 得到需要签名的字符串 + if (body != null && body.Length > 0) + { + temp += body + "\n"; + } + // Console.WriteLine("Sign string: " + temp); + + // hmac-sha1编码 + var hmacsha1 = new HMACSHA1(); + hmacsha1.Key = Encoding.UTF8.GetBytes(secret); + byte[] dataBuffer = Encoding.UTF8.GetBytes(temp); + byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer); + + + // base64编码 + string encode = Convert.ToBase64String(hashBytes); + + return encode; + } + + /// + /// 处理http请求 + /// + /// 请求的url地址 + /// 协议标头 + /// 请求的内容类型 + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求的数据流 + /// 请求的超时时间(秒) + /// http POST成功后返回的数据,失败抛异常 + public static string SendHttpRequest(string url, Dictionary headers, string contentType, string method, string dataStream, int timeout, out WebHeaderCollection head) + { + System.GC.Collect();//垃圾回收,回收没有正常关闭的http链接 + HttpWebRequest request = null; + HttpWebResponse response = null; + Stream reqStream = null; + try + { + //设置最大链接数 + ServicePointManager.DefaultConnectionLimit = 200; + //设置https验证方式 + if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + ServicePointManager.ServerCertificateValidationCallback = + new RemoteCertificateValidationCallback(CertificateValidation); + } + request = (HttpWebRequest)WebRequest.Create(url); + + //HttpWebRequest 相关属性 + request.Method = method; + request.Timeout = timeout * 1000; + request.ContentType = contentType; + if (headers != null) + { + //配置协议标头 + foreach (KeyValuePair kvp in headers) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + byte[] data = null; + if (dataStream != null) + { + data = System.Text.Encoding.UTF8.GetBytes(dataStream); + request.ContentLength = data.Length; + } + + if (data != null) + { + //写入数据 + reqStream = request.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + } + + head = null; + //返回数据 + response = (HttpWebResponse)request.GetResponse(); + if (response != null) + { + head = response.Headers; + Stream stream = response.GetResponseStream(); + StreamReader sr = new StreamReader(stream, Encoding.UTF8); + string result = sr.ReadToEnd(); + sr.Close(); + //关闭连接和流 + response.Close(); + return result; + } + else + { + head = null; + return String.Empty; + } + + + } + //处理多线程模式下线程中止 + //catch (System.Threading.ThreadAbortException e) + //{ + // System.Threading.Thread.ResetAbort(); + //} + catch (WebException e) + { + head = null; + response = (HttpWebResponse)e.Response; + if (response != null) + { + head = response.Headers; + } + throw e; + } + catch (Exception e) + { + throw new HttpServiceException(e.ToString()); + } + finally + { + if (request != null) + { + request.Abort(); + } + } + } + + /* 忽略证书认证错误 + * .NET的SSL通信过程中,使用的证书可能存在各种问题 + * 此方法可以跳过服务器证书验证,完成正常通信。*/ + private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) + { + // 认证正常,没有错误 + return true; + } + } + + class HttpServiceException : Exception + { + public HttpServiceException(string msg) : base(msg) + { + + } + } + + public class ComparerString : IComparer + { + public int Compare(String x, String y) + { + return string.CompareOrdinal(x, y); + } + } +} diff --git a/SensorHub.Servers/Common.cs b/SensorHub.Servers/Common.cs index 93b4e97..ea35a63 100644 --- a/SensorHub.Servers/Common.cs +++ b/SensorHub.Servers/Common.cs @@ -15,6 +15,8 @@ using Microsoft.Win32; using System.Threading; using SensorHub.Servers.SM4; +using AepSdk.Apis; +using Newtonsoft.Json.Linq; namespace SensorHub.Servers { @@ -121,7 +123,7 @@ byte[] btConfig = sysTimeConfig.getConfig(new byte[0]); btConfig.CopyTo(config, 16); - session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "")); + session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "") + " / source=" + source); if (source == "4G") { @@ -174,8 +176,9 @@ else { String strBase64Value = Convert.ToBase64String(afcrc); - int ret = SendNACommand(session, strBase64Value, source); - if (ret != 201) + // int ret = SendNACommand(session, strBase64Value, source); + int ret = SendAepCommand(session, strBase64Value, source); // 新AEP平台指令 + if (ret != 0) { session.Logger.Info("电信平台下发配置信息失败,返回的Http状态码:" + ret); } @@ -282,6 +285,28 @@ return result; } + public static int SendAepCommand(CasicSession session, String strValue, String deviceId) + { + string operatorId = ConfigurationManager.AppSettings["operatorId"]; + Int32 productId = Int32.Parse(ConfigurationManager.AppSettings["productId"]); + + JObject body = new JObject(); + JObject command = new JObject(); + JObject paras = new JObject(); + paras.Add("Value", strValue); // 透传的指令内容 加密后的base64字符串 参数名根据profile文件来确定 + command.Add("paras", paras); + command.Add("serviceId", "Config"); // 对应profile中的服务ID + command.Add("method", "Config"); // 对应profile中的能力 + + body.Add("command", command); + body.Add("deviceId", deviceId); + body.Add("operator", operatorId); // 对应profile中的服务ID + body.Add("productId", productId); // 对应profile中的能力 + + string ret = AepDeviceCommand.CreateCommandLwm2mProfile(JsonConvert.SerializeObject(body)); + return Int16.Parse(JObject.Parse(ret)["code"].ToString()); + } + public static void sender433Config(CasicSession session, string devCode, byte[] btPdu, byte routeflag) { if (routeflag == 0xFF || btPdu[0] == 0xFF)//通信方式为FF时表示不存在,不下发 @@ -570,8 +595,7 @@ public static void kafkaSetResponseProduce(CasicSession session, String devCode, String devName) { - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); KafkaUtils.produce(KafkaUtils.TOPIC, message); @@ -781,9 +805,11 @@ } } - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + // 设置成功的响应写入kafka + kafkaSetResponseProduce(session, devCode, devName); + /* + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); if (Common.SendMessage(message)) { session.Logger.Info("往第三方发送数据:" + message); @@ -791,7 +817,7 @@ else { session.Logger.Info("未连接上第三方服务器"); - } + }*/ } //public static void sendSetResponse(CasicSession session, String devCode, String devName) @@ -860,7 +886,7 @@ { if (routeFlag == "03") //GPRS,3G网络,电信平台 { - senderGPRSConfig(session, devCode, btPdu, source); + senderSM4Config(session, devCode, btPdu, source); } else if (routeFlag == "01") //433 { diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/SensorHub.Servers/AepDeviceCommand.cs b/SensorHub.Servers/AepDeviceCommand.cs new file mode 100644 index 0000000..09153d4 --- /dev/null +++ b/SensorHub.Servers/AepDeviceCommand.cs @@ -0,0 +1,150 @@ +using AepSdk.Apis.Core; +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AepSdk.Apis +{ + class AepDeviceCommand + { + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommand(string body) + { + string path = "/aep_device_command/command"; + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190712225145"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + Console.WriteLine(response); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数productId: 类型long, 参数不可以为空 + // 描述:产品ID,必填 + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID,必填 + //参数startTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数endTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数pageNow: 类型long, 参数可以为空 + // 描述:当前页数 + //参数pageSize: 类型long, 参数可以为空 + // 描述:每页记录数,最大40 + public static string QueryCommandList(string appKey, string appSecret, string MasterKey, string productId, string deviceId, string startTime = "", string endTime = "", string pageNow = "", string pageSize = "") + { + string path = "/aep_device_command/commands"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + param.Add("startTime", startTime); + param.Add("endTime", endTime); + param.Add("pageNow", pageNow); + param.Add("pageSize", pageSize); + + string version = "20200814163736"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数commandId: 类型String, 参数不可以为空 + // 描述:创建指令成功响应中返回的id, + //参数productId: 类型long, 参数不可以为空 + // 描述: + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID + public static string QueryCommand(string appKey, string appSecret, string MasterKey, string commandId, string productId, string deviceId) + { + string path = "/aep_device_command/command"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("commandId", commandId); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + + string version = "20190712225241"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述: + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CancelCommand(string appKey, string appSecret, string MasterKey, string body) + { + string path = "/aep_device_command/cancelCommand"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190615023142"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "PUT"); + if (response != null) + return response; + return null; + } + + //参数MasterKey: 类型String, 参数可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommandLwm2mProfile(string body) + { + string path = "/aep_device_command_lwm_profile/commandLwm2mProfile"; + + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20191231141545"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + if (response != null) + return response; + return null; + } + + } +} diff --git a/SensorHub.Servers/AepSdkCore.cs b/SensorHub.Servers/AepSdkCore.cs new file mode 100644 index 0000000..574cdcc --- /dev/null +++ b/SensorHub.Servers/AepSdkCore.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AepSdk.Apis.Core +{ + class AepHttpRequest + { + static long offset = 0; + static long lastGetOffsetTime = 0; + static readonly string baseUrl = ConfigurationManager.AppSettings["BaseUrl"]; + static readonly string timeUrl = "https://ag-api.ctwing.cn/echo"; + + + /// + /// 获取时间偏移量 + /// + /// 时间偏移量 + public static long GetTimeOffset() + { + long start = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + WebHeaderCollection head = null; + string response = SendHttpRequest(timeUrl, null, "application/json; charset=UTF-8", "GET", null, 5, out head); + long end = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (response != null) + { + long timeAg = Convert.ToInt64(head["x-ag-timestamp"]); + return timeAg - (end + start) / 2; + } + + return 0; + } + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method) + { + WebHeaderCollection head; + return SendAepHttpRequest(path, headers, param, body, version, application, key, method, out head); + } + + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求结果的head出参 + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method, out WebHeaderCollection head) + { + + string paramString = ""; + + if (param != null) + { + foreach (KeyValuePair kvp in param) + { + paramString += kvp.Key + "=" + kvp.Value + "&"; + } + } + + if (paramString.Length > 0) + { + paramString = paramString.Remove(paramString.Length - 1); + } + + // Console.WriteLine("paramString = " + paramString); + + + + Dictionary paramTmp = new Dictionary(); + if (headers != null) + paramTmp = paramTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + if (param != null) + paramTmp = paramTmp.Concat(param).ToDictionary(k => k.Key, v => v.Value); + + long curentTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (curentTime - lastGetOffsetTime > 300 * 1000) //300秒调用一次 + { + offset = GetTimeOffset(); + lastGetOffsetTime = curentTime; + } + long timestamp = curentTime + offset; + + Dictionary headersTmp = new Dictionary(); + headersTmp.Add("application", application); + headersTmp.Add("timestamp", "" + timestamp); + headersTmp.Add("version", version); + //headersTmp.Add("Content-Type", "application/json; charset=UTF-8"); + //headersTmp.Add("Date", dataString); + headersTmp.Add("signature", Sign(paramTmp, timestamp, application, key, body)); + if (headers != null) + { + headersTmp = headersTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + } + + string url = baseUrl + path; + if (paramString.Length > 0) + { + url += "?" + paramString; + } + // Console.WriteLine("url = " + url); + string result = SendHttpRequest(url, headersTmp, "application/json; charset=UTF-8", method, body, 35, out head); + return result; + } + + + + /// + /// 签名算法 + /// + /// api接口参数 + /// 时间戳,毫秒级 + /// App Key + /// App secret + /// body + /// + public static string Sign(Dictionary param, long timestamp, string application, string secret, string body) + { + // 连接系统参数 + string temp = "application:" + application + "\n"; + temp += "timestamp:" + timestamp + "\n"; + + // 连接请求参数 + if (param != null) + { + var dicNew = param.OrderBy(x => x.Key, new ComparerString()).ToDictionary(x => x.Key, y => y.Value); + + foreach (KeyValuePair kvp in dicNew) + { + temp += kvp.Key + ":" + (kvp.Value == null ? "" : kvp.Value) + "\n"; + } + } + + + // 得到需要签名的字符串 + if (body != null && body.Length > 0) + { + temp += body + "\n"; + } + // Console.WriteLine("Sign string: " + temp); + + // hmac-sha1编码 + var hmacsha1 = new HMACSHA1(); + hmacsha1.Key = Encoding.UTF8.GetBytes(secret); + byte[] dataBuffer = Encoding.UTF8.GetBytes(temp); + byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer); + + + // base64编码 + string encode = Convert.ToBase64String(hashBytes); + + return encode; + } + + /// + /// 处理http请求 + /// + /// 请求的url地址 + /// 协议标头 + /// 请求的内容类型 + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求的数据流 + /// 请求的超时时间(秒) + /// http POST成功后返回的数据,失败抛异常 + public static string SendHttpRequest(string url, Dictionary headers, string contentType, string method, string dataStream, int timeout, out WebHeaderCollection head) + { + System.GC.Collect();//垃圾回收,回收没有正常关闭的http链接 + HttpWebRequest request = null; + HttpWebResponse response = null; + Stream reqStream = null; + try + { + //设置最大链接数 + ServicePointManager.DefaultConnectionLimit = 200; + //设置https验证方式 + if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + ServicePointManager.ServerCertificateValidationCallback = + new RemoteCertificateValidationCallback(CertificateValidation); + } + request = (HttpWebRequest)WebRequest.Create(url); + + //HttpWebRequest 相关属性 + request.Method = method; + request.Timeout = timeout * 1000; + request.ContentType = contentType; + if (headers != null) + { + //配置协议标头 + foreach (KeyValuePair kvp in headers) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + byte[] data = null; + if (dataStream != null) + { + data = System.Text.Encoding.UTF8.GetBytes(dataStream); + request.ContentLength = data.Length; + } + + if (data != null) + { + //写入数据 + reqStream = request.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + } + + head = null; + //返回数据 + response = (HttpWebResponse)request.GetResponse(); + if (response != null) + { + head = response.Headers; + Stream stream = response.GetResponseStream(); + StreamReader sr = new StreamReader(stream, Encoding.UTF8); + string result = sr.ReadToEnd(); + sr.Close(); + //关闭连接和流 + response.Close(); + return result; + } + else + { + head = null; + return String.Empty; + } + + + } + //处理多线程模式下线程中止 + //catch (System.Threading.ThreadAbortException e) + //{ + // System.Threading.Thread.ResetAbort(); + //} + catch (WebException e) + { + head = null; + response = (HttpWebResponse)e.Response; + if (response != null) + { + head = response.Headers; + } + throw e; + } + catch (Exception e) + { + throw new HttpServiceException(e.ToString()); + } + finally + { + if (request != null) + { + request.Abort(); + } + } + } + + /* 忽略证书认证错误 + * .NET的SSL通信过程中,使用的证书可能存在各种问题 + * 此方法可以跳过服务器证书验证,完成正常通信。*/ + private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) + { + // 认证正常,没有错误 + return true; + } + } + + class HttpServiceException : Exception + { + public HttpServiceException(string msg) : base(msg) + { + + } + } + + public class ComparerString : IComparer + { + public int Compare(String x, String y) + { + return string.CompareOrdinal(x, y); + } + } +} diff --git a/SensorHub.Servers/Common.cs b/SensorHub.Servers/Common.cs index 93b4e97..ea35a63 100644 --- a/SensorHub.Servers/Common.cs +++ b/SensorHub.Servers/Common.cs @@ -15,6 +15,8 @@ using Microsoft.Win32; using System.Threading; using SensorHub.Servers.SM4; +using AepSdk.Apis; +using Newtonsoft.Json.Linq; namespace SensorHub.Servers { @@ -121,7 +123,7 @@ byte[] btConfig = sysTimeConfig.getConfig(new byte[0]); btConfig.CopyTo(config, 16); - session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "")); + session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "") + " / source=" + source); if (source == "4G") { @@ -174,8 +176,9 @@ else { String strBase64Value = Convert.ToBase64String(afcrc); - int ret = SendNACommand(session, strBase64Value, source); - if (ret != 201) + // int ret = SendNACommand(session, strBase64Value, source); + int ret = SendAepCommand(session, strBase64Value, source); // 新AEP平台指令 + if (ret != 0) { session.Logger.Info("电信平台下发配置信息失败,返回的Http状态码:" + ret); } @@ -282,6 +285,28 @@ return result; } + public static int SendAepCommand(CasicSession session, String strValue, String deviceId) + { + string operatorId = ConfigurationManager.AppSettings["operatorId"]; + Int32 productId = Int32.Parse(ConfigurationManager.AppSettings["productId"]); + + JObject body = new JObject(); + JObject command = new JObject(); + JObject paras = new JObject(); + paras.Add("Value", strValue); // 透传的指令内容 加密后的base64字符串 参数名根据profile文件来确定 + command.Add("paras", paras); + command.Add("serviceId", "Config"); // 对应profile中的服务ID + command.Add("method", "Config"); // 对应profile中的能力 + + body.Add("command", command); + body.Add("deviceId", deviceId); + body.Add("operator", operatorId); // 对应profile中的服务ID + body.Add("productId", productId); // 对应profile中的能力 + + string ret = AepDeviceCommand.CreateCommandLwm2mProfile(JsonConvert.SerializeObject(body)); + return Int16.Parse(JObject.Parse(ret)["code"].ToString()); + } + public static void sender433Config(CasicSession session, string devCode, byte[] btPdu, byte routeflag) { if (routeflag == 0xFF || btPdu[0] == 0xFF)//通信方式为FF时表示不存在,不下发 @@ -570,8 +595,7 @@ public static void kafkaSetResponseProduce(CasicSession session, String devCode, String devName) { - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); KafkaUtils.produce(KafkaUtils.TOPIC, message); @@ -781,9 +805,11 @@ } } - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + // 设置成功的响应写入kafka + kafkaSetResponseProduce(session, devCode, devName); + /* + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); if (Common.SendMessage(message)) { session.Logger.Info("往第三方发送数据:" + message); @@ -791,7 +817,7 @@ else { session.Logger.Info("未连接上第三方服务器"); - } + }*/ } //public static void sendSetResponse(CasicSession session, String devCode, String devName) @@ -860,7 +886,7 @@ { if (routeFlag == "03") //GPRS,3G网络,电信平台 { - senderGPRSConfig(session, devCode, btPdu, source); + senderSM4Config(session, devCode, btPdu, source); } else if (routeFlag == "01") //433 { diff --git a/SensorHub.Servers/SensorHub.Servers.csproj b/SensorHub.Servers/SensorHub.Servers.csproj index b6236fa..4789683 100644 --- a/SensorHub.Servers/SensorHub.Servers.csproj +++ b/SensorHub.Servers/SensorHub.Servers.csproj @@ -127,6 +127,8 @@ + + diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/SensorHub.Servers/AepDeviceCommand.cs b/SensorHub.Servers/AepDeviceCommand.cs new file mode 100644 index 0000000..09153d4 --- /dev/null +++ b/SensorHub.Servers/AepDeviceCommand.cs @@ -0,0 +1,150 @@ +using AepSdk.Apis.Core; +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AepSdk.Apis +{ + class AepDeviceCommand + { + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommand(string body) + { + string path = "/aep_device_command/command"; + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190712225145"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + Console.WriteLine(response); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数productId: 类型long, 参数不可以为空 + // 描述:产品ID,必填 + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID,必填 + //参数startTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数endTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数pageNow: 类型long, 参数可以为空 + // 描述:当前页数 + //参数pageSize: 类型long, 参数可以为空 + // 描述:每页记录数,最大40 + public static string QueryCommandList(string appKey, string appSecret, string MasterKey, string productId, string deviceId, string startTime = "", string endTime = "", string pageNow = "", string pageSize = "") + { + string path = "/aep_device_command/commands"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + param.Add("startTime", startTime); + param.Add("endTime", endTime); + param.Add("pageNow", pageNow); + param.Add("pageSize", pageSize); + + string version = "20200814163736"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数commandId: 类型String, 参数不可以为空 + // 描述:创建指令成功响应中返回的id, + //参数productId: 类型long, 参数不可以为空 + // 描述: + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID + public static string QueryCommand(string appKey, string appSecret, string MasterKey, string commandId, string productId, string deviceId) + { + string path = "/aep_device_command/command"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("commandId", commandId); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + + string version = "20190712225241"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述: + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CancelCommand(string appKey, string appSecret, string MasterKey, string body) + { + string path = "/aep_device_command/cancelCommand"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190615023142"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "PUT"); + if (response != null) + return response; + return null; + } + + //参数MasterKey: 类型String, 参数可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommandLwm2mProfile(string body) + { + string path = "/aep_device_command_lwm_profile/commandLwm2mProfile"; + + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20191231141545"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + if (response != null) + return response; + return null; + } + + } +} diff --git a/SensorHub.Servers/AepSdkCore.cs b/SensorHub.Servers/AepSdkCore.cs new file mode 100644 index 0000000..574cdcc --- /dev/null +++ b/SensorHub.Servers/AepSdkCore.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AepSdk.Apis.Core +{ + class AepHttpRequest + { + static long offset = 0; + static long lastGetOffsetTime = 0; + static readonly string baseUrl = ConfigurationManager.AppSettings["BaseUrl"]; + static readonly string timeUrl = "https://ag-api.ctwing.cn/echo"; + + + /// + /// 获取时间偏移量 + /// + /// 时间偏移量 + public static long GetTimeOffset() + { + long start = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + WebHeaderCollection head = null; + string response = SendHttpRequest(timeUrl, null, "application/json; charset=UTF-8", "GET", null, 5, out head); + long end = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (response != null) + { + long timeAg = Convert.ToInt64(head["x-ag-timestamp"]); + return timeAg - (end + start) / 2; + } + + return 0; + } + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method) + { + WebHeaderCollection head; + return SendAepHttpRequest(path, headers, param, body, version, application, key, method, out head); + } + + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求结果的head出参 + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method, out WebHeaderCollection head) + { + + string paramString = ""; + + if (param != null) + { + foreach (KeyValuePair kvp in param) + { + paramString += kvp.Key + "=" + kvp.Value + "&"; + } + } + + if (paramString.Length > 0) + { + paramString = paramString.Remove(paramString.Length - 1); + } + + // Console.WriteLine("paramString = " + paramString); + + + + Dictionary paramTmp = new Dictionary(); + if (headers != null) + paramTmp = paramTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + if (param != null) + paramTmp = paramTmp.Concat(param).ToDictionary(k => k.Key, v => v.Value); + + long curentTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (curentTime - lastGetOffsetTime > 300 * 1000) //300秒调用一次 + { + offset = GetTimeOffset(); + lastGetOffsetTime = curentTime; + } + long timestamp = curentTime + offset; + + Dictionary headersTmp = new Dictionary(); + headersTmp.Add("application", application); + headersTmp.Add("timestamp", "" + timestamp); + headersTmp.Add("version", version); + //headersTmp.Add("Content-Type", "application/json; charset=UTF-8"); + //headersTmp.Add("Date", dataString); + headersTmp.Add("signature", Sign(paramTmp, timestamp, application, key, body)); + if (headers != null) + { + headersTmp = headersTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + } + + string url = baseUrl + path; + if (paramString.Length > 0) + { + url += "?" + paramString; + } + // Console.WriteLine("url = " + url); + string result = SendHttpRequest(url, headersTmp, "application/json; charset=UTF-8", method, body, 35, out head); + return result; + } + + + + /// + /// 签名算法 + /// + /// api接口参数 + /// 时间戳,毫秒级 + /// App Key + /// App secret + /// body + /// + public static string Sign(Dictionary param, long timestamp, string application, string secret, string body) + { + // 连接系统参数 + string temp = "application:" + application + "\n"; + temp += "timestamp:" + timestamp + "\n"; + + // 连接请求参数 + if (param != null) + { + var dicNew = param.OrderBy(x => x.Key, new ComparerString()).ToDictionary(x => x.Key, y => y.Value); + + foreach (KeyValuePair kvp in dicNew) + { + temp += kvp.Key + ":" + (kvp.Value == null ? "" : kvp.Value) + "\n"; + } + } + + + // 得到需要签名的字符串 + if (body != null && body.Length > 0) + { + temp += body + "\n"; + } + // Console.WriteLine("Sign string: " + temp); + + // hmac-sha1编码 + var hmacsha1 = new HMACSHA1(); + hmacsha1.Key = Encoding.UTF8.GetBytes(secret); + byte[] dataBuffer = Encoding.UTF8.GetBytes(temp); + byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer); + + + // base64编码 + string encode = Convert.ToBase64String(hashBytes); + + return encode; + } + + /// + /// 处理http请求 + /// + /// 请求的url地址 + /// 协议标头 + /// 请求的内容类型 + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求的数据流 + /// 请求的超时时间(秒) + /// http POST成功后返回的数据,失败抛异常 + public static string SendHttpRequest(string url, Dictionary headers, string contentType, string method, string dataStream, int timeout, out WebHeaderCollection head) + { + System.GC.Collect();//垃圾回收,回收没有正常关闭的http链接 + HttpWebRequest request = null; + HttpWebResponse response = null; + Stream reqStream = null; + try + { + //设置最大链接数 + ServicePointManager.DefaultConnectionLimit = 200; + //设置https验证方式 + if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + ServicePointManager.ServerCertificateValidationCallback = + new RemoteCertificateValidationCallback(CertificateValidation); + } + request = (HttpWebRequest)WebRequest.Create(url); + + //HttpWebRequest 相关属性 + request.Method = method; + request.Timeout = timeout * 1000; + request.ContentType = contentType; + if (headers != null) + { + //配置协议标头 + foreach (KeyValuePair kvp in headers) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + byte[] data = null; + if (dataStream != null) + { + data = System.Text.Encoding.UTF8.GetBytes(dataStream); + request.ContentLength = data.Length; + } + + if (data != null) + { + //写入数据 + reqStream = request.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + } + + head = null; + //返回数据 + response = (HttpWebResponse)request.GetResponse(); + if (response != null) + { + head = response.Headers; + Stream stream = response.GetResponseStream(); + StreamReader sr = new StreamReader(stream, Encoding.UTF8); + string result = sr.ReadToEnd(); + sr.Close(); + //关闭连接和流 + response.Close(); + return result; + } + else + { + head = null; + return String.Empty; + } + + + } + //处理多线程模式下线程中止 + //catch (System.Threading.ThreadAbortException e) + //{ + // System.Threading.Thread.ResetAbort(); + //} + catch (WebException e) + { + head = null; + response = (HttpWebResponse)e.Response; + if (response != null) + { + head = response.Headers; + } + throw e; + } + catch (Exception e) + { + throw new HttpServiceException(e.ToString()); + } + finally + { + if (request != null) + { + request.Abort(); + } + } + } + + /* 忽略证书认证错误 + * .NET的SSL通信过程中,使用的证书可能存在各种问题 + * 此方法可以跳过服务器证书验证,完成正常通信。*/ + private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) + { + // 认证正常,没有错误 + return true; + } + } + + class HttpServiceException : Exception + { + public HttpServiceException(string msg) : base(msg) + { + + } + } + + public class ComparerString : IComparer + { + public int Compare(String x, String y) + { + return string.CompareOrdinal(x, y); + } + } +} diff --git a/SensorHub.Servers/Common.cs b/SensorHub.Servers/Common.cs index 93b4e97..ea35a63 100644 --- a/SensorHub.Servers/Common.cs +++ b/SensorHub.Servers/Common.cs @@ -15,6 +15,8 @@ using Microsoft.Win32; using System.Threading; using SensorHub.Servers.SM4; +using AepSdk.Apis; +using Newtonsoft.Json.Linq; namespace SensorHub.Servers { @@ -121,7 +123,7 @@ byte[] btConfig = sysTimeConfig.getConfig(new byte[0]); btConfig.CopyTo(config, 16); - session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "")); + session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "") + " / source=" + source); if (source == "4G") { @@ -174,8 +176,9 @@ else { String strBase64Value = Convert.ToBase64String(afcrc); - int ret = SendNACommand(session, strBase64Value, source); - if (ret != 201) + // int ret = SendNACommand(session, strBase64Value, source); + int ret = SendAepCommand(session, strBase64Value, source); // 新AEP平台指令 + if (ret != 0) { session.Logger.Info("电信平台下发配置信息失败,返回的Http状态码:" + ret); } @@ -282,6 +285,28 @@ return result; } + public static int SendAepCommand(CasicSession session, String strValue, String deviceId) + { + string operatorId = ConfigurationManager.AppSettings["operatorId"]; + Int32 productId = Int32.Parse(ConfigurationManager.AppSettings["productId"]); + + JObject body = new JObject(); + JObject command = new JObject(); + JObject paras = new JObject(); + paras.Add("Value", strValue); // 透传的指令内容 加密后的base64字符串 参数名根据profile文件来确定 + command.Add("paras", paras); + command.Add("serviceId", "Config"); // 对应profile中的服务ID + command.Add("method", "Config"); // 对应profile中的能力 + + body.Add("command", command); + body.Add("deviceId", deviceId); + body.Add("operator", operatorId); // 对应profile中的服务ID + body.Add("productId", productId); // 对应profile中的能力 + + string ret = AepDeviceCommand.CreateCommandLwm2mProfile(JsonConvert.SerializeObject(body)); + return Int16.Parse(JObject.Parse(ret)["code"].ToString()); + } + public static void sender433Config(CasicSession session, string devCode, byte[] btPdu, byte routeflag) { if (routeflag == 0xFF || btPdu[0] == 0xFF)//通信方式为FF时表示不存在,不下发 @@ -570,8 +595,7 @@ public static void kafkaSetResponseProduce(CasicSession session, String devCode, String devName) { - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); KafkaUtils.produce(KafkaUtils.TOPIC, message); @@ -781,9 +805,11 @@ } } - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + // 设置成功的响应写入kafka + kafkaSetResponseProduce(session, devCode, devName); + /* + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); if (Common.SendMessage(message)) { session.Logger.Info("往第三方发送数据:" + message); @@ -791,7 +817,7 @@ else { session.Logger.Info("未连接上第三方服务器"); - } + }*/ } //public static void sendSetResponse(CasicSession session, String devCode, String devName) @@ -860,7 +886,7 @@ { if (routeFlag == "03") //GPRS,3G网络,电信平台 { - senderGPRSConfig(session, devCode, btPdu, source); + senderSM4Config(session, devCode, btPdu, source); } else if (routeFlag == "01") //433 { diff --git a/SensorHub.Servers/SensorHub.Servers.csproj b/SensorHub.Servers/SensorHub.Servers.csproj index b6236fa..4789683 100644 --- a/SensorHub.Servers/SensorHub.Servers.csproj +++ b/SensorHub.Servers/SensorHub.Servers.csproj @@ -127,6 +127,8 @@ + + diff --git a/SensorHub.Servers/TelecomReceiveFilter.cs b/SensorHub.Servers/TelecomReceiveFilter.cs index 616139d..10ca642 100644 --- a/SensorHub.Servers/TelecomReceiveFilter.cs +++ b/SensorHub.Servers/TelecomReceiveFilter.cs @@ -18,10 +18,10 @@ public class TelecomReceiveFilter : BeginEndMarkReceiveFilter { //开始和结束标记也可以是两个或两个以上的字节 + // 改成电信AEP平台后传入的data有变化 通过java接口进行转发 增加了##作为帧头 **作为帧尾 private readonly static byte[] BeginMark = new byte[] { (byte)'P', (byte)'O', (byte)'S', (byte)'T' }; - private readonly static byte[] EndMark = new byte[] { (byte)'}', (byte)'}' }; + private readonly static byte[] EndMark = new byte[] { (byte)'*', (byte)'*' }; private static MemoryCache memoryCache = new MemoryCache("Telecom"); - //private static MemoryCache sm4MemoryCache = new MemoryCache("SM4"); private readonly object SyncObj = new object(); public TelecomReceiveFilter() @@ -34,11 +34,13 @@ { //TODO: 通过解析到的数据来构造请求实例,并返回 String dataJson = System.Text.Encoding.Default.GetString(readBuffer); - dataJson = dataJson.Substring(dataJson.LastIndexOf('\n') + 1); + int startIdx = dataJson.IndexOf("##"); + int endIdx = dataJson.IndexOf("**"); + dataJson = dataJson.Substring(dataJson.IndexOf("##") + 2, endIdx - startIdx - 2); JObject jo = (JObject)JsonConvert.DeserializeObject(dataJson); String telecomDeviceId = jo["deviceId"].ToString();//电信平台设备编号 - String Value = jo["service"]["data"]["Value"].ToString();//{"Value":"ow=="} + String Value = jo["payload"]["serviceData"]["Value"].ToString();//{"Value":"ow=="} String result = ""; byte[] src = Convert.FromBase64String(Value); diff --git a/.gitignore b/.gitignore index 1a67bd4..63e6a86 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,11 @@ /SensorHub.WasteGas/obj /SensorHub.WaterMeter/obj /SensorHub.Well/obj +/SensorHub.Well/bin +/SensorHub.WellPlus/obj +/SensorHub.WellPlus/bin +/SensorHub.HydrogenSulfide/obj +/SensorHub.HydrogenSulfide/bin +/SensorHub.Tube/obj +/SensorHub.Tube/bin /TestClass/obj \ No newline at end of file diff --git a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs index 8691d2f..2bc32cf 100644 --- a/SensorHub.HydrogenSulfide/HydrogenSulfide.cs +++ b/SensorHub.HydrogenSulfide/HydrogenSulfide.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "HydrogenSulfide"); + Common.sendSetResponse(session, devCode, devName); return; } diff --git a/SensorHub.Methane/Methane.cs b/SensorHub.Methane/Methane.cs index 8513d89..aee6906 100644 --- a/SensorHub.Methane/Methane.cs +++ b/SensorHub.Methane/Methane.cs @@ -52,7 +52,7 @@ //判断是返回的设置确认数据帧, 回复第三方 if (operType == "SetResponse") { - Common.sendSetResponse(session, devCode, "Methane"); + Common.sendSetResponse(session, devCode, devName); return; } @@ -191,8 +191,8 @@ } } - Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); - // Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + // Common.sendMessage(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); + Common.kafkaProduce(session, devName, devCode, cell, pci, rsrp, snr, eventList, datasList, startupList); if (softwareVersion != "" || size != 0 || offset != 0)//进入远程升级流程 { diff --git a/SensorHub.Servers/AepDeviceCommand.cs b/SensorHub.Servers/AepDeviceCommand.cs new file mode 100644 index 0000000..09153d4 --- /dev/null +++ b/SensorHub.Servers/AepDeviceCommand.cs @@ -0,0 +1,150 @@ +using AepSdk.Apis.Core; +using System; +using System.Collections.Generic; +using System.Configuration; + +namespace AepSdk.Apis +{ + class AepDeviceCommand + { + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommand(string body) + { + string path = "/aep_device_command/command"; + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190712225145"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + Console.WriteLine(response); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数productId: 类型long, 参数不可以为空 + // 描述:产品ID,必填 + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID,必填 + //参数startTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数endTime: 类型String, 参数可以为空 + // 描述:日期格式,年月日时分秒,例如:20200801120130 + //参数pageNow: 类型long, 参数可以为空 + // 描述:当前页数 + //参数pageSize: 类型long, 参数可以为空 + // 描述:每页记录数,最大40 + public static string QueryCommandList(string appKey, string appSecret, string MasterKey, string productId, string deviceId, string startTime = "", string endTime = "", string pageNow = "", string pageSize = "") + { + string path = "/aep_device_command/commands"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + param.Add("startTime", startTime); + param.Add("endTime", endTime); + param.Add("pageNow", pageNow); + param.Add("pageSize", pageSize); + + string version = "20200814163736"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数commandId: 类型String, 参数不可以为空 + // 描述:创建指令成功响应中返回的id, + //参数productId: 类型long, 参数不可以为空 + // 描述: + //参数deviceId: 类型String, 参数不可以为空 + // 描述:设备ID + public static string QueryCommand(string appKey, string appSecret, string MasterKey, string commandId, string productId, string deviceId) + { + string path = "/aep_device_command/command"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = new Dictionary(); + param.Add("commandId", commandId); + param.Add("productId", productId); + param.Add("deviceId", deviceId); + + string version = "20190712225241"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, null, version, application, key, "GET"); + if (response != null) + return response; + return null; + } + //参数MasterKey: 类型String, 参数不可以为空 + // 描述: + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CancelCommand(string appKey, string appSecret, string MasterKey, string body) + { + string path = "/aep_device_command/cancelCommand"; + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20190615023142"; + + string application = appKey; + string key = appSecret; + + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "PUT"); + if (response != null) + return response; + return null; + } + + //参数MasterKey: 类型String, 参数可以为空 + // 描述:MasterKey在该设备所属产品的概况中可以查看 + //参数body: 类型json, 参数不可以为空 + // 描述:body,具体参考平台api说明 + public static string CreateCommandLwm2mProfile(string body) + { + string path = "/aep_device_command_lwm_profile/commandLwm2mProfile"; + + string application = ConfigurationManager.AppSettings["AppKey"]; + string key = ConfigurationManager.AppSettings["AppSecret"]; + string MasterKey = ConfigurationManager.AppSettings["MasterKey"]; + + Dictionary headers = new Dictionary(); + headers.Add("MasterKey", MasterKey); + + Dictionary param = null; + string version = "20191231141545"; + + string response = AepHttpRequest.SendAepHttpRequest(path, headers, param, body, version, application, key, "POST"); + if (response != null) + return response; + return null; + } + + } +} diff --git a/SensorHub.Servers/AepSdkCore.cs b/SensorHub.Servers/AepSdkCore.cs new file mode 100644 index 0000000..574cdcc --- /dev/null +++ b/SensorHub.Servers/AepSdkCore.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace AepSdk.Apis.Core +{ + class AepHttpRequest + { + static long offset = 0; + static long lastGetOffsetTime = 0; + static readonly string baseUrl = ConfigurationManager.AppSettings["BaseUrl"]; + static readonly string timeUrl = "https://ag-api.ctwing.cn/echo"; + + + /// + /// 获取时间偏移量 + /// + /// 时间偏移量 + public static long GetTimeOffset() + { + long start = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + WebHeaderCollection head = null; + string response = SendHttpRequest(timeUrl, null, "application/json; charset=UTF-8", "GET", null, 5, out head); + long end = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (response != null) + { + long timeAg = Convert.ToInt64(head["x-ag-timestamp"]); + return timeAg - (end + start) / 2; + } + + return 0; + } + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method) + { + WebHeaderCollection head; + return SendAepHttpRequest(path, headers, param, body, version, application, key, method, out head); + } + + + /// + /// 发送api请求到aep + /// + /// api接口路径 + /// 请求head + /// 参数 + /// body,如果为get等没有body请求,填null + /// api接口版本,在文档中查询 + /// App Key + /// App Secret + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求结果的head出参 + /// + public static string SendAepHttpRequest(string path, Dictionary headers, Dictionary param, string body, string version, string application, string key, string method, out WebHeaderCollection head) + { + + string paramString = ""; + + if (param != null) + { + foreach (KeyValuePair kvp in param) + { + paramString += kvp.Key + "=" + kvp.Value + "&"; + } + } + + if (paramString.Length > 0) + { + paramString = paramString.Remove(paramString.Length - 1); + } + + // Console.WriteLine("paramString = " + paramString); + + + + Dictionary paramTmp = new Dictionary(); + if (headers != null) + paramTmp = paramTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + if (param != null) + paramTmp = paramTmp.Concat(param).ToDictionary(k => k.Key, v => v.Value); + + long curentTime = (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000; + if (curentTime - lastGetOffsetTime > 300 * 1000) //300秒调用一次 + { + offset = GetTimeOffset(); + lastGetOffsetTime = curentTime; + } + long timestamp = curentTime + offset; + + Dictionary headersTmp = new Dictionary(); + headersTmp.Add("application", application); + headersTmp.Add("timestamp", "" + timestamp); + headersTmp.Add("version", version); + //headersTmp.Add("Content-Type", "application/json; charset=UTF-8"); + //headersTmp.Add("Date", dataString); + headersTmp.Add("signature", Sign(paramTmp, timestamp, application, key, body)); + if (headers != null) + { + headersTmp = headersTmp.Concat(headers).ToDictionary(k => k.Key, v => v.Value); + } + + string url = baseUrl + path; + if (paramString.Length > 0) + { + url += "?" + paramString; + } + // Console.WriteLine("url = " + url); + string result = SendHttpRequest(url, headersTmp, "application/json; charset=UTF-8", method, body, 35, out head); + return result; + } + + + + /// + /// 签名算法 + /// + /// api接口参数 + /// 时间戳,毫秒级 + /// App Key + /// App secret + /// body + /// + public static string Sign(Dictionary param, long timestamp, string application, string secret, string body) + { + // 连接系统参数 + string temp = "application:" + application + "\n"; + temp += "timestamp:" + timestamp + "\n"; + + // 连接请求参数 + if (param != null) + { + var dicNew = param.OrderBy(x => x.Key, new ComparerString()).ToDictionary(x => x.Key, y => y.Value); + + foreach (KeyValuePair kvp in dicNew) + { + temp += kvp.Key + ":" + (kvp.Value == null ? "" : kvp.Value) + "\n"; + } + } + + + // 得到需要签名的字符串 + if (body != null && body.Length > 0) + { + temp += body + "\n"; + } + // Console.WriteLine("Sign string: " + temp); + + // hmac-sha1编码 + var hmacsha1 = new HMACSHA1(); + hmacsha1.Key = Encoding.UTF8.GetBytes(secret); + byte[] dataBuffer = Encoding.UTF8.GetBytes(temp); + byte[] hashBytes = hmacsha1.ComputeHash(dataBuffer); + + + // base64编码 + string encode = Convert.ToBase64String(hashBytes); + + return encode; + } + + /// + /// 处理http请求 + /// + /// 请求的url地址 + /// 协议标头 + /// 请求的内容类型 + /// 请求的类型,GET、POST、PUT、DELETE + /// 请求的数据流 + /// 请求的超时时间(秒) + /// http POST成功后返回的数据,失败抛异常 + public static string SendHttpRequest(string url, Dictionary headers, string contentType, string method, string dataStream, int timeout, out WebHeaderCollection head) + { + System.GC.Collect();//垃圾回收,回收没有正常关闭的http链接 + HttpWebRequest request = null; + HttpWebResponse response = null; + Stream reqStream = null; + try + { + //设置最大链接数 + ServicePointManager.DefaultConnectionLimit = 200; + //设置https验证方式 + if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) + { + ServicePointManager.ServerCertificateValidationCallback = + new RemoteCertificateValidationCallback(CertificateValidation); + } + request = (HttpWebRequest)WebRequest.Create(url); + + //HttpWebRequest 相关属性 + request.Method = method; + request.Timeout = timeout * 1000; + request.ContentType = contentType; + if (headers != null) + { + //配置协议标头 + foreach (KeyValuePair kvp in headers) + { + request.Headers.Add(kvp.Key, kvp.Value); + } + } + + byte[] data = null; + if (dataStream != null) + { + data = System.Text.Encoding.UTF8.GetBytes(dataStream); + request.ContentLength = data.Length; + } + + if (data != null) + { + //写入数据 + reqStream = request.GetRequestStream(); + reqStream.Write(data, 0, data.Length); + reqStream.Close(); + } + + head = null; + //返回数据 + response = (HttpWebResponse)request.GetResponse(); + if (response != null) + { + head = response.Headers; + Stream stream = response.GetResponseStream(); + StreamReader sr = new StreamReader(stream, Encoding.UTF8); + string result = sr.ReadToEnd(); + sr.Close(); + //关闭连接和流 + response.Close(); + return result; + } + else + { + head = null; + return String.Empty; + } + + + } + //处理多线程模式下线程中止 + //catch (System.Threading.ThreadAbortException e) + //{ + // System.Threading.Thread.ResetAbort(); + //} + catch (WebException e) + { + head = null; + response = (HttpWebResponse)e.Response; + if (response != null) + { + head = response.Headers; + } + throw e; + } + catch (Exception e) + { + throw new HttpServiceException(e.ToString()); + } + finally + { + if (request != null) + { + request.Abort(); + } + } + } + + /* 忽略证书认证错误 + * .NET的SSL通信过程中,使用的证书可能存在各种问题 + * 此方法可以跳过服务器证书验证,完成正常通信。*/ + private static bool CertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) + { + // 认证正常,没有错误 + return true; + } + } + + class HttpServiceException : Exception + { + public HttpServiceException(string msg) : base(msg) + { + + } + } + + public class ComparerString : IComparer + { + public int Compare(String x, String y) + { + return string.CompareOrdinal(x, y); + } + } +} diff --git a/SensorHub.Servers/Common.cs b/SensorHub.Servers/Common.cs index 93b4e97..ea35a63 100644 --- a/SensorHub.Servers/Common.cs +++ b/SensorHub.Servers/Common.cs @@ -15,6 +15,8 @@ using Microsoft.Win32; using System.Threading; using SensorHub.Servers.SM4; +using AepSdk.Apis; +using Newtonsoft.Json.Linq; namespace SensorHub.Servers { @@ -121,7 +123,7 @@ byte[] btConfig = sysTimeConfig.getConfig(new byte[0]); btConfig.CopyTo(config, 16); - session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "")); + session.Logger.Info("下发回复配置信息:" + BitConverter.ToString(config).Replace("-", "") + " / source=" + source); if (source == "4G") { @@ -174,8 +176,9 @@ else { String strBase64Value = Convert.ToBase64String(afcrc); - int ret = SendNACommand(session, strBase64Value, source); - if (ret != 201) + // int ret = SendNACommand(session, strBase64Value, source); + int ret = SendAepCommand(session, strBase64Value, source); // 新AEP平台指令 + if (ret != 0) { session.Logger.Info("电信平台下发配置信息失败,返回的Http状态码:" + ret); } @@ -282,6 +285,28 @@ return result; } + public static int SendAepCommand(CasicSession session, String strValue, String deviceId) + { + string operatorId = ConfigurationManager.AppSettings["operatorId"]; + Int32 productId = Int32.Parse(ConfigurationManager.AppSettings["productId"]); + + JObject body = new JObject(); + JObject command = new JObject(); + JObject paras = new JObject(); + paras.Add("Value", strValue); // 透传的指令内容 加密后的base64字符串 参数名根据profile文件来确定 + command.Add("paras", paras); + command.Add("serviceId", "Config"); // 对应profile中的服务ID + command.Add("method", "Config"); // 对应profile中的能力 + + body.Add("command", command); + body.Add("deviceId", deviceId); + body.Add("operator", operatorId); // 对应profile中的服务ID + body.Add("productId", productId); // 对应profile中的能力 + + string ret = AepDeviceCommand.CreateCommandLwm2mProfile(JsonConvert.SerializeObject(body)); + return Int16.Parse(JObject.Parse(ret)["code"].ToString()); + } + public static void sender433Config(CasicSession session, string devCode, byte[] btPdu, byte routeflag) { if (routeflag == 0xFF || btPdu[0] == 0xFF)//通信方式为FF时表示不存在,不下发 @@ -570,8 +595,7 @@ public static void kafkaSetResponseProduce(CasicSession session, String devCode, String devName) { - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); KafkaUtils.produce(KafkaUtils.TOPIC, message); @@ -781,9 +805,11 @@ } } - String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, - new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); + // 设置成功的响应写入kafka + kafkaSetResponseProduce(session, devCode, devName); + /* + String message = JsonConvert.SerializeObject(new Json("SetResponse", devName, devCode, new JsonBody(devName + "ConfigSuccess"), getTimeStamp())); if (Common.SendMessage(message)) { session.Logger.Info("往第三方发送数据:" + message); @@ -791,7 +817,7 @@ else { session.Logger.Info("未连接上第三方服务器"); - } + }*/ } //public static void sendSetResponse(CasicSession session, String devCode, String devName) @@ -860,7 +886,7 @@ { if (routeFlag == "03") //GPRS,3G网络,电信平台 { - senderGPRSConfig(session, devCode, btPdu, source); + senderSM4Config(session, devCode, btPdu, source); } else if (routeFlag == "01") //433 { diff --git a/SensorHub.Servers/SensorHub.Servers.csproj b/SensorHub.Servers/SensorHub.Servers.csproj index b6236fa..4789683 100644 --- a/SensorHub.Servers/SensorHub.Servers.csproj +++ b/SensorHub.Servers/SensorHub.Servers.csproj @@ -127,6 +127,8 @@ + + diff --git a/SensorHub.Servers/TelecomReceiveFilter.cs b/SensorHub.Servers/TelecomReceiveFilter.cs index 616139d..10ca642 100644 --- a/SensorHub.Servers/TelecomReceiveFilter.cs +++ b/SensorHub.Servers/TelecomReceiveFilter.cs @@ -18,10 +18,10 @@ public class TelecomReceiveFilter : BeginEndMarkReceiveFilter { //开始和结束标记也可以是两个或两个以上的字节 + // 改成电信AEP平台后传入的data有变化 通过java接口进行转发 增加了##作为帧头 **作为帧尾 private readonly static byte[] BeginMark = new byte[] { (byte)'P', (byte)'O', (byte)'S', (byte)'T' }; - private readonly static byte[] EndMark = new byte[] { (byte)'}', (byte)'}' }; + private readonly static byte[] EndMark = new byte[] { (byte)'*', (byte)'*' }; private static MemoryCache memoryCache = new MemoryCache("Telecom"); - //private static MemoryCache sm4MemoryCache = new MemoryCache("SM4"); private readonly object SyncObj = new object(); public TelecomReceiveFilter() @@ -34,11 +34,13 @@ { //TODO: 通过解析到的数据来构造请求实例,并返回 String dataJson = System.Text.Encoding.Default.GetString(readBuffer); - dataJson = dataJson.Substring(dataJson.LastIndexOf('\n') + 1); + int startIdx = dataJson.IndexOf("##"); + int endIdx = dataJson.IndexOf("**"); + dataJson = dataJson.Substring(dataJson.IndexOf("##") + 2, endIdx - startIdx - 2); JObject jo = (JObject)JsonConvert.DeserializeObject(dataJson); String telecomDeviceId = jo["deviceId"].ToString();//电信平台设备编号 - String Value = jo["service"]["data"]["Value"].ToString();//{"Value":"ow=="} + String Value = jo["payload"]["serviceData"]["Value"].ToString();//{"Value":"ow=="} String result = ""; byte[] src = Convert.FromBase64String(Value); diff --git a/TestClass/App.config b/TestClass/App.config index 2757ed8..f803952 100644 --- a/TestClass/App.config +++ b/TestClass/App.config @@ -115,7 +115,14 @@ + + + + + + +