zoukankan      html  css  js  c++  java
  • 微信硬件H5面板开发(一) ---- 调用openApi

     微信硬件平台是微信推出连接物与人,物与物的IOT解决方案。也就是说可以通过微信控制各种智能设备。比如一些蓝牙设备、空调、电视等等。

     

    我本身不懂硬件(虽然是电子信息专业),硬件是北航的两个研究生在弄,小团队里我负责开发H5自定义面板,刚开始看官方文档各种迷糊,对于jssdk、jsapi、Airkiss、openApi、直连SDK都不知道该用哪个做,官方论坛问问题基本上没结果,加了几个微信硬件群问问题,发现好些开发者和我一样,同一个问题,发到几个群里问,画面好心酸。给wxthings发邮件问,能回复就不错了,往往还是只言片语。吐槽了这么多,还是得去解决问题,毕竟设备能搭上微信是一大卖点,最近摸索出来一些东西,于是有了此文。

     一、接入流程

     

     也就是说,首先你得有一个公众号,然后开通设备功能、添加产品(也就是你的智能设备)。这些过程官方文档比较清楚,我就不讲了。接入方案我们选择的是微信硬件云标准接入方案。

    设置面板

    而我要说的H5面板开发,指的就是在微信中打开的一个H5控制页面,它如何和微信硬件云通信,如何读取和设置设备的状态。在添加产品的过程中有一栏设置面板

    如果选择标准面板,微信官方给出了三类标准面板:

     

     分别是空调、开关和灯,如果是自定义,则输入地址即可。如果是标准面板,你是不需要服务器,但如果是自定义的面板,你就需要有自己的服务器,不然你无法处理微信云发过来的消息

    启用服务器配置

    在设置服务器地址的时候要注意,你必须按照它要求方式处理响应了,你才能启用成功。

     你点击启用的时候,微信云会发过来一个签名、一个时间戳、一个随机数和一个随机字符串,验证之后,返回那个随机字符串,微信云收到你返回的随机字符串了,就能启用成功。比如,如果你定义的地址是http://www.xxx.com/device/ReceiveWXMsg,那么先把代码上传服务器,然后再点击启用,微信云会向这个地址post数据。 每一次微信向服务器发送数据时,都会先发这验证(也就是说如果不校验就返回会有安全问题)

      public string ReceiveWXMsg()
            {
                var signature = Request.QueryString["signature"];
                var timestamp = Request.QueryString["timestamp"];
                var echostr = Request.QueryString["echostr"];
                var nonce = Request.QueryString["nonce"];
                Logger.Debug("signature:" + signature);
                Logger.Debug("timestamp:" + timestamp);
                Logger.Debug("nonce:" + nonce);
                Logger.Debug("echostr:" + echostr);
                //验证 
                return echostr;
            }
    View Code

    直接返回就行,不要加个json什么的。这个地址是干嘛的呢,往下看。

    二、通信方式

    Wifi设备和蓝牙设备是不同的,蓝牙使用Airsync协议和微信通信,而wifi设备的话,我们在自己的服务器上调用微信提供的openApi获取或设置设备状态,微信服务器就会把数据以json格式post到我们上面设置的那个地址。硬件方面wifi设备可以使用微信提供的直连SDK来控制设备。

    添加完设备,设置好服务器,你还需要硬件的同学打开设备,帮定你的微信。从微信的设置-->设备-->选择设备-->打开面板。你就可以看到设备并进行控制了。

    三、调用openApi

    说了这么多前提工作,终于进入调用api环节。可以先看一下openApi的官方文档:http://iot.weixin.qq.com/wiki/doc/hardwarecloud/openapi.pdf

    文档里面主要讲了三个方法,一个是查询设备状态,一个是设置设备状态,一个是接受设备状态变化的消息,然后是一些错误信息等。但观察api就会发现我们还需要两个重要的参数,一个是access_token,一个是用户的openid。还说明一点,网页是Asp.net mvc。

    1.获取access_token

    官方有一个接口调试页面:http://mp.weixin.qq.com/debug/ ,获取access_token需要appid和secret。

    而这两个值,是在公众号后台的基本配置中查看,secret是个很重要的参数,所以请保密好。

    查看密钥还需要扫二维码得到管理员的确认...  拿到这两个参数了,我们就可以生成token了。注意返回的json是一个token字符串再加一个超时时间。定义一个TokenResult:

    public class TokenResult
        {
            public string access_token { get; set; }
            public int expires_in { get; set; }
        }

    要注意的一点是,token两小时后会过期。所以在我们的代码里面需要检查是否超时,然后自动重新获取。

     public const string AccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";

     getAccessToken方法定义在一个服务类中,比如WxDeviceService

    using SendHelp= Senparc.Weixin.CommonAPIs.CommonJsonSend;
    public TokenResult GetAccessToken()
            {
                var url = string.Format(WxDeviceConfig.AccessTokenUrl, WxDeviceConfig.AppId, WxDeviceConfig.APPSECRET);
                var res = SendHelp.Send<TokenResult>(null, url, null, CommonJsonSendType.GET);
                return res;
            }

    使用Senparc.Weixin框架封装好的api来处理请求。

    2.获取openid

    这个参数在查询和设置设备状态的时候会用到,对应user参数名。获取openid需要三个参数,access_token已经有了,然后需要device_type和device_id

     public const string GetOpenid ="https://api.weixin.qq.com/device/get_openid?access_token={0}&device_type={1}&device_id={2}";

    而type和id是前端页面传过来的,当用户微信中打开控制面板的时候,微信会自动将这两个参数追加到url后面。而这个url的返回值结构是这样:

    {"open_id":["oxa1otw5sk-Azgd8mx1bmBqoM2_E","oxa1ot8-j9j5bYUJJyAexe9d41_Y","oxa1ot5QTdxn0xNQ0DmYzoN0tUp1"],"resp_msg":{"ret_code":0,"error_info":"ok"}}

    openid部分是一个数组,实际使用的是第三个(我目前也不知道前面两个id是干啥的),定义一个openidResult:

     public class OpenIdResult
        {
            private List<string> _openId;
    
            public List<string> open_id
            {
                get { return _openId??(_openId=new List<string>()); }
                set { _openId = value; }
            }
    
            public resp_msg resp_msg { get; set; }
    
            public string GetOpenId()
            {
           // 将数组转换成string
    return ""; } }

    service中:

      public string GetOpenId(string accessToken,string deviceType,string deviceId)
            {
                    var url = string.Format(WxDeviceConfig.GetOpenid, accessToken, deviceType, deviceId);
                    var res = SendHelp.Send<OpenIdResult>(accessToken, url, null, CommonJsonSendType.GET);
                    return res.GetOpenId();
            }

    如果你的devicetype和deviceId错误,微信会返回一个不太恰当的错误json:

    对比错误代码列表,我开始以为微信服务器出错了。其实是我参数填错了。

     

    遇到的错误,将不止官方文档公布的这几个。

    3.查询设备状态

    查询设备和上面的这两个方法略有不同,因为流程是这的,我们的服务器先像向微信服务器请求,微信接受到请求后马上返回一个确认json,然后微信服务器会马上把数据post我们前面设置的那个地址上。

    请求url:

       public const string GetDeviceStatusUrl="https://api.weixin.qq.com/hardware/mydevice/platform/get_device_status?access_token={0}";

    从官方文档可以看到,查询设备还需要另外一个重要的参数,它包含device_type和device_id,services,user,data。

     

    需要说明一下的就是services,意思是指设备的能力项,也就是你要查询设备的哪些属性,这个在设置设备的时候一样用到。完成的产品能力定义请看:http://iot.weixin.qq.com/wiki/doc/intro/%E4%BA%A7%E5%93%81%E8%83%BD%E5%8A%9B%E5%AE%9A%E4%B9%89%E6%8C%87%E5%BC%95%20V1.2.pdf 

    因此定义一个RequestData对象以及设备对应的能力项

     public class RequestData
        {
            public string device_type { get; set; }
            public string device_id { get; set; }
            public string user { get; set; }
            public Service services { get; set; }
            public object data { get; set; }
        }
     public class Service
        {
            public lightbulb lightbulb { get; set; }
    
            public air_conditioner air_conditioner { get; set; }
    
            public power_switch power_switch { get; set; }
    
            public operation_status operation_status { get; set; }
        }

    service包括两个部分,一个是能力部分,好比上面这个service,就包含了三种能力,灯、空调以及开关(这只是测试,不是真正产品的能力)。和一个操作状态。操作状态就是指这个设备是否开着或者关闭了。而每一个能力,又包括两部分,拿灯来说:

        public class lightbulb
        {
            public int alpha { get; set; }
            public lightbulb_value_range value_range { get; set; }
        }
    
        public class lightbulb_value_range
        {
            public string alpha { get; set; }
        }

    灯有一个亮度值,和一个范围属性。范围值中包含了最大和最小值以及单位值。

    "lightbulb":{"alpha":10,"value_range":{"alpha":"0|100|1"}},"

    这表示灯的亮度是10,返回是0到100,每次可以调节1个单位。

    发送查询请求后,微信返回一个json,定义对象为下:

     public class OpenApiResult
        {
            public int error_code { get; set; }
            public string error_msg { get; set; }
    
            public string msg_id { get; set; }
        }

     WxDeviceService中:

       public OpenApiResult RequestDeviceStatus(string accessToken, RequestData data)
            {
                var url = string.Format(WxDeviceConfig.GetDeviceStatusUrl, accessToken);
                return SendHelp.Send<OpenApiResult>(accessToken, url, data);
            }

    4.接受消息

    那么问题来了,如何接受post过来的数据,以及如何存储呢。微信post的数据格式如下:

    定义了一个WxResponseData对象:

        public class WxResponseData
        {
            public int asy_error_code { get; set; }
    
            public string asy_error_msg { get; set; }
    
            public string create_time { get; set; }
    
            public string msg_id { get; set; }
    
            /// <summary>
            /// notify 说明是设备变更
            /// set_resp 说明是设置设备
            /// get_resp 说明获取设备信息
            /// </summary>
            public string msg_type { get; set; }
    
            public string device_type { get; set; }
            public string device_id { get; set; }
            public object data { get; set; }
    
            public Service services { get; set; }
    
            public string user { get; set; }
        }
    View Code

    msg_type代表着不同类型的消息, notify 说明是设备变更,set_resp 说明是设置设备 get_resp 说明获取设备信息。在WxDeviceService中增加GetDeviceStatus方法:

       public T GetWxResponse<T>(HttpRequestBase request)
            {
                Stream postData = request.InputStream;
                StreamReader sRead = new StreamReader(postData);
                string postContent = sRead.ReadToEnd();
                if (!string.IsNullOrEmpty(postContent))
                {
                    Logger.Debug("收到数据:"+postContent);
                }
                try
                {
                    return JsonConvert.DeserializeObject<T>(postContent);
                }
                catch (Exception e)
                {
                    Logger.Debug(e.Message);
                    throw;
                }
            }
    
            public WxResponseData GetDeviceStatus(HttpRequestBase request)
            {
                return GetWxResponse<WxResponseData>(request);
            }

    需要先读取请求中的json字符串然后转换成C#对象。然后在最初启用的ReceiveWXMsg方法中随时准备接受消息:

      public string ReceiveWXMsg()
            {
                var signature = Request.QueryString["signature"];
                var timestamp = Request.QueryString["timestamp"];
                var echostr = Request.QueryString["echostr"];
                var nonce = Request.QueryString["nonce"];
                Logger.Debug("signature:" + signature);
                Logger.Debug("timestamp:" + timestamp);
                Logger.Debug("nonce:" + nonce);
                Logger.Debug("echostr:" + echostr);
                try
                {
                    var userdata = getUserWxData();
                    var data = wxDeviceService.GetDeviceStatus(Request);
                    userdata.ResponseData = data;
                    setUserWxData(userdata);
                }
                catch (Exception e)
                {
                    Logger.Debug(e.Message);
                }
                return echostr;
            }

    因为读取到的数据需要及时呈现给页面,所以这里选用了缓存来存储设备信息以及用户相关信息。

    UserWxData:

    public class UserWxData
        {
            private WxResponseData _responseData;
    
            public UserWxData()
            {
                CreateTime = DateTime.Now;
            }
            public DateTime CreateTime { get; set; }
            public TokenResult AccessToken { get; set; }
    
            public WxResponseData ResponseData
            {
                get { return _responseData??(_responseData=new WxResponseData()); }
                set { _responseData = value; }
            }
    
            public string OpenId { get; set; }
        }
    View Code
      private UserWxData getUserWxData()
            {
                var target = _cacheManager.Get<UserWxData>(userKey) ?? new UserWxData();
                return target;
            }
    
            private string userKey
            {
                get
                {
                    return Session.SessionID;
                }
            }
    
            private void setUserWxData(UserWxData data)
            {
                _cacheManager.Set(userKey, data, 7200);
            }
    View Code

    缓存是Nop中的MemoryCacheManager:

    using System;
    using System.Collections.Generic;
    using System.Runtime.Caching;
    using System.Text.RegularExpressions;
    
    namespace Niqiu.Core.Domain.Common
    {
        /// <summary>
        /// Represents a manager for caching between HTTP requests (long term caching)
        /// </summary>
        public partial class MemoryCacheManager : ICacheManager
        {
            protected ObjectCache Cache
            {
                get
                {
                    return MemoryCache.Default;
                }
            }
            
            /// <summary>
            /// Gets or sets the value associated with the specified key.
            /// </summary>
            /// <typeparam name="T">Type</typeparam>
            /// <param name="key">The key of the value to get.</param>
            /// <returns>The value associated with the specified key.</returns>
            public virtual T Get<T>(string key)
            {
                return (T)Cache[key];
            }
    
            /// <summary>
            /// Adds the specified key and object to the cache.
            /// </summary>
            /// <param name="key">key</param>
            /// <param name="data">Data</param>
            /// <param name="cacheTime">Cache time</param>
            public virtual void Set(string key, object data, int cacheTime)
            {
                if (data == null)
                    return;
    
                var policy = new CacheItemPolicy {AbsoluteExpiration = DateTime.Now + TimeSpan.FromMinutes(cacheTime)};
                Cache.Add(new CacheItem(key, data), policy);
            }
    
            /// <summary>
            /// Gets a value indicating whether the value associated with the specified key is cached
            /// </summary>
            /// <param name="key">key</param>
            /// <returns>Result</returns>
            public virtual bool IsSet(string key)
            {
                return (Cache.Contains(key));
            }
    
            /// <summary>
            /// Removes the value with the specified key from the cache
            /// </summary>
            /// <param name="key">/key</param>
            public virtual void Remove(string key)
            {
                Cache.Remove(key);
            }
    
            /// <summary>
            /// Removes items by pattern
            /// </summary>
            /// <param name="pattern">pattern</param>
            public virtual void RemoveByPattern(string pattern)
            {
                var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
                var keysToRemove = new List<String>();
    
                foreach (var item in Cache)
                    if (regex.IsMatch(item.Key))
                        keysToRemove.Add(item.Key);
    
                foreach (string key in keysToRemove)
                {
                    Remove(key);
                }
            }
    
            /// <summary>
            /// Clear all cache data
            /// </summary>
            public virtual void Clear()
            {
                foreach (var item in Cache)
                    Remove(item.Key);
            }
        }
    }
    View Code

    而为什么不是PerRequestCacheManager呢,想一想~

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;
    using System.Web;
    
    namespace Niqiu.Core.Domain.Common
    {
        /// <summary>
        /// Represents a manager for caching during an HTTP request (short term caching)
        /// </summary>
        public partial class PerRequestCacheManager : ICacheManager
        {
    
            /// <summary>
            /// Ctor
            /// </summary>
            /// <param name="context">Context</param>
            //public PerRequestCacheManager(HttpContextBase context)
            //{
            //    this._context = context;
            //}
            
            /// <summary>
            /// Creates a new instance of the NopRequestCache class
            /// </summary>
            protected virtual IDictionary GetItems()
            {
                if (_context != null)
                    return _context.Items;
    
                return null;
            }
    
            //不用注入
            private HttpContextBase _context
            {
                get { return new HttpContextWrapper(HttpContext.Current); }
            }
    
            /// <summary>
            /// Gets or sets the value associated with the specified key.
            /// </summary>
            /// <typeparam name="T">Type</typeparam>
            /// <param name="key">The key of the value to get.</param>
            /// <returns>The value associated with the specified key.</returns>
            public virtual T Get<T>(string key)
            {
                var items = GetItems();
                if (items == null)
                    return default(T);
    
                return (T)items[key];
            }
    
            /// <summary>
            /// Adds the specified key and object to the cache.
            /// </summary>
            /// <param name="key">key</param>
            /// <param name="data">Data</param>
            /// <param name="cacheTime">Cache time</param>
            public virtual void Set(string key, object data, int cacheTime)
            {
                var items = GetItems();
                if (items == null)
                    return;
    
                if (data != null)
                {
                    if (items.Contains(key))
                        items[key] = data;
                    else
                        items.Add(key, data);
                }
            }
    
            /// <summary>
            /// Gets a value indicating whether the value associated with the specified key is cached
            /// </summary>
            /// <param name="key">key</param>
            /// <returns>Result</returns>
            public virtual bool IsSet(string key)
            {
                var items = GetItems();
                if (items == null)
                    return false;
                
                return (items[key] != null);
            }
    
            /// <summary>
            /// Removes the value with the specified key from the cache
            /// </summary>
            /// <param name="key">/key</param>
            public virtual void Remove(string key)
            {
                var items = GetItems();
                if (items == null)
                    return;
    
                items.Remove(key);
            }
    
            /// <summary>
            /// Removes items by pattern
            /// </summary>
            /// <param name="pattern">pattern</param>
            public virtual void RemoveByPattern(string pattern)
            {
                var items = GetItems();
                if (items == null)
                    return;
    
                var enumerator = items.GetEnumerator();
                var regex = new Regex(pattern, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase);
                var keysToRemove = new List<String>();
                while (enumerator.MoveNext())
                {
                    if (regex.IsMatch(enumerator.Key.ToString()))
                    {
                        keysToRemove.Add(enumerator.Key.ToString());
                    }
                }
    
                foreach (string key in keysToRemove)
                {
                    items.Remove(key);
                }
            }
    
            /// <summary>
            /// Clear all cache data
            /// </summary>
            public virtual void Clear()
            {
                var items = GetItems();
                if (items == null)
                    return;
    
                var enumerator = items.GetEnumerator();
                var keysToRemove = new List<String>();
                while (enumerator.MoveNext())
                {
                    keysToRemove.Add(enumerator.Key.ToString());
                }
    
                foreach (string key in keysToRemove)
                {
                    items.Remove(key);
                }
            }
        }
    }
    View Code

    5.设置设备状态

    有了前面几步,这里也好说了。url:

     public const string SetDeviceUrl ="https://api.weixin.qq.com/hardware/mydevice/platform/ctrl_device?access_token={0}";

    设置设备的参数和请求是一样的,

     public OpenApiResult SetDevice(string accessToken, RequestData data)
            {
                var url = string.Format(WxDeviceConfig.SetDeviceUrl, accessToken);
                return SendHelp.Send<OpenApiResult>(accessToken, url, data);
            }
    View Code

    调用openApi基本上就这样了,如有不完善的地方还请指正。 这个方法有权限的问题,可以用查询方法代替,同样可以改变设备状态。不知道这api是个什么鬼。

    四、常见错误

     

    如果硬件通信没有开启这个能力,去查询的会报这个错误。

    刚开始看到"device not login"实在没明白什么意思,文档里也没说明这个错误。设备还需要什么登录?原来是硬件同学没有连接设备... ORZ

    如果你的requestData结构不对,特别是附加的那个Data参数只能是字符串,不要写成空对象{},就会出现这个错误。

    token超时

    同一个设备同时被操作。

    小结:以上是硬件面板开发过程中遇到的种种“经验”总结。限于篇幅这一节只讲了openapi的调用。下一节将如何用seajs构建h5控制面板。敬请关注!

  • 相关阅读:
    HDU 1813 Escape from Tetris
    BZOJ 2276 Temperature
    BZOJ 4499 线性函数
    BZOJ 3131 淘金
    HDU 5738 Eureka
    POJ 2409 Let it Bead
    POJ 1286 Necklace of Beads
    POJ 1696 Space Ant
    Fox And Jumping
    Recover the String
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/5728017.html
Copyright © 2011-2022 走看看