Home  >  Article  >  WeChat Applet  >  WeChat H5 development calls openApi

WeChat H5 development calls openApi

Y2J
Y2JOriginal
2017-05-09 09:54:414218browse

WeChat Hardware Platform is an IOT solution launched by WeChat to connect things and people, and things and things. In other words, various smart devices can be controlled through WeChat. For example, some Bluetooth devices, air conditioners, TVs, etc.

I don’t know much about hardware (although I am majoring in electronic information). The hardware is being worked on by two graduate students at Beihang University. In a small team, I am responsible for developing H5 custom panels. Just now I started to read the official documents and was confused. I didn’t know which one to use for jssdk, jsapi, Airkiss, openApi, and direct SDK. Asking questions on the official forum basically had no results. , I joined several WeChat hardware groups to ask questions, and found that many developers were like me. I posted the same question to several groups, and the picture was so sad. If you send an email to wxthings and ask, it would be nice if you can get a reply, which is often just a few words. After complaining so much, I still have to solve the problem. After all, the ability of the device to connect to WeChat is a big selling point. I recently figured out something, so I wrote this article.

1. Access process

In other words, you must first have a public account, then activate device functions and add products (that is, your smart device). The official documents on these processes are relatively clear, so I won’t go into details. The access solution we chose is the WeChat Hardware Cloud standard access solution.

Setting Panel

The H5 panel development I want to talk about refers to an H5 control page opened in WeChat, how it communicates with the WeChat hardware cloud, and how to read and set the device status. In the process of adding products, there is a setting panel

If you choose the standard panel, WeChat officially provides three types of standard panels:

are air conditioners, switches and lights respectively. If it is customized, just enter the address. If it is a standard panel, you do not need a server, but if it is a customized panel,

you need to have your own server, otherwise you will not be able to process messages sent from WeChat Cloud.

Enable server configuration

When setting the server address, please note that you must process the response according to its requirements before you can enable it successfully.

When you click Enable, WeChat Cloud will send a signature, a

timestamp, a random number and a random string , After verification, return the random string . Once WeChat Cloud receives the random string returned by you, it can be activated successfully. For example, if the address you define is http://www.xxx.com/device/ReceiveWXMsg, then upload the code to the server first, and then click Enable, WeChat Cloud will post data to this address. Every time WeChat sends data to the server, this verification will be sent first (that is, if it is returned without verification, there will be security issues).

##

  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

Just return it directly, don’t add a

json

or something. What is this address for? Look down. 2. Communication methods

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

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

三、调用openApi

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

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

1.获取access_token

官方有一个接口调试页面: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:

   List<> List<> {  _openId??(_openId= List<> { _openId = resp_msg resp_msg { ;

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:

      GetDeviceStatusUrl=

从官方文档可以看到,查询设备还需要另外一个重要的参数,它包含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:

    SetDeviceUrl =

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

 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超时

同一个设备同时被操作。

【相关推荐】

1. 微信公众号平台源码下载

2. 微信投票源码下载

The above is the detailed content of WeChat H5 development calls openApi. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn