22227浏览
查看: 22227|回复: 44

[项目] 小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入

[复制链接]
去年这会儿,老夫在Arduino中文社区首发《小题大做之远程LED控制》长文(https://www.arduino.cn/thread-8463-1-1.html)。据说,栏目主办者准备了WIZnet网络板、T恤、马克杯等奖品。老夫心不黑,冲着马克杯去的,于是乎就发了一篇。现如今该文点击达到35165,但是连马克杯的照片也没给我。这个骗纸。

今年4月间,就把该文转到咱地府萝卜头来(https://mc.dfrobot.com.cn/thread-10590-1-1.html),蒙编辑老爷厚爱,侥幸列入上半年度编辑推荐奖(此处当有掌声)。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

看看2015年已临近岁末,又老一岁。借宝地汇报一下老夫一年来的学习成果,风格仍延续小题大做、技术跨界、干湿货通吃的模式。

此文篇幅较长、技术储备较广、创作时断时续,故而体虚肾亏胸闷气急的读者,千万服药后阅读或遵医嘱。

此项目采集办公室空气质量(包括:PM1、PM2.5、PM10),温湿度、照明和噪声,并可通过微信公众号实时查询7个数据。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图2


终端实物参考下图。原先照片在家里拍的,如今已安装在老夫办公桌上了。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图3

以下篇幅,陆续展开全文。

kevinzhang19701  高级技匠
 楼主|

发表于 2015-12-16 12:30:00

刹那ZF清英 发表于 2015-12-16 08:59
哇哦对代码啥的一窍不通的我竟然把前辈的文章看完了————不对,是翻完了。果然还是要回家吃 ...

您的鱼菜共生炒鸡棒:D:D:handshake
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-26 09:09:57

本帖最后由 kevinzhang19701 于 2015-12-7 16:41 编辑

4. 项目拓扑简图和说明

4.1拓扑简图参考下图:
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

4.2 数据流向及其他说明(重要):

4.2.1 数据流相关细节

老夫有一台性能不错的Dell Optiplex 9010台式机放在公司,充当本项目服务器。在公司路由器上做了port forward,将公司闲置的公网IP地址116.236.162.186的80端口映射到这台台式机的某个高段端口号上。这里要注意,使用固定IP地址和80端口,不是数据采集和提交的必要条件,而是微信公众号后台开发的必要条件。如果您缺乏这两个条件的话,那么微信查询功能可能将无法实现。

4.2.2 传感器数据流向

Arduino终端设备每隔5秒收集7个传感器参数,而后通过W5100网络板向位于116.236.162.186的服务器(以下简称“本项目服务器”)提交这些参数。本项目服务器上的一个Java Servlet(mypm.class)会监听这个http请求,抓取7个参数后写入MySQL数据库表,并在服务器终端上每5秒显示7个数据,供后台参考。

4.2.3 微信数据流向

加入本项目关联的微信公众号(鑫山测试),可以输入“/??”(英文字符)这串字符进行数据查询。这个字符串会首先流入腾讯微信服务器,然后,腾讯服务器会判断这个公众号已是开发者,会将字符串打包成XML格式后,传向开发者自己的服务器(即本项目服务器)。本项目服务器上的一个Servlet(wxtest.class)会解析XML字符串,然后从数据库内查询最新的一条数据,再打包成XML格式,返回给腾讯服务器。最后,由腾讯服务器向微信App传递这7个参数。

==

老夫写到这里,大家应该没有疑问吧?如果没有疑问的话,我就接着说说代码和写代码中遇到的几个问题,希望得到读者们和大神们的指点。:lol

回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-12-7 21:17:22

本帖最后由 kevinzhang19701 于 2015-12-8 11:12 编辑

15. 完整的wxtest.class源代码

此为本项目核心代码之三,技术涉及非常广泛,如果您有疑问,欢迎跟帖咨询。
  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3. import java.io.InputStream;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import java.util.Arrays;
  10. import org.apache.commons.codec.digest.DigestUtils;
  11. import org.apache.commons.io.IOUtils;
  12. import org.dom4j.*;
  13. import org.dom4j.io.*;
  14. import java.sql.Connection;
  15. import java.sql.DriverManager;
  16. import java.sql.SQLException;
  17. import java.sql.Statement;
  18. import java.sql.ResultSet;
  19. @WebServlet(urlPatterns = {"/wxtest"})
  20. public class wxtest extends HttpServlet
  21. {
  22.     protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  23.             throws ServletException, IOException
  24.     {
  25.         response.setContentType("text/html;charset=UTF-8");
  26.         request.setCharacterEncoding("UTF-8");
  27.         response.setCharacterEncoding("UTF-8");
  28.         PrintWriter out = response.getWriter();
  29.                
  30.         String myVal01 = ""; //7个变量对应7个参数
  31.         String myVal02 = "";
  32.         String myVal03 = "";
  33.         String myVal04 = "";
  34.         String myVal05 = "";
  35.         String myVal06 = "";
  36.         String myVal07 = "";
  37.         
  38.         Connection conn = null;  // 涉及数据库打交道的变量
  39.         Statement stmt = null;
  40.         ResultSet rs1 = null;
  41.         int i = 0;
  42.         String mySQL = "SELECT * FROM t01 ORDER BY tid DESC LIMIT 1;"; // 从数据表里读取最后那条记录的SQL语句
  43.         try
  44.         {
  45.             conn = DriverManager.getConnection("jdbc:mysql://localhost/mypm?" +
  46.                                    "user=root&password=15186okia");
  47.             stmt = conn.createStatement();
  48.             rs1 = stmt.executeQuery(mySQL);
  49.             
  50.             while(rs1.next())
  51.             {
  52.                 myVal01 = rs1.getString(2); //分别将获得的数据写入7个变量
  53.                 myVal02 = rs1.getString(3);
  54.                 myVal03 = rs1.getString(4);
  55.                 myVal04 = rs1.getString(5);
  56.                 myVal05 = rs1.getString(6);
  57.                 myVal06 = rs1.getString(7);
  58.                 myVal07 = rs1.getString(8);
  59.             }
  60.         }
  61.         catch (SQLException ex)
  62.         {
  63.             System.out.println("Error in connection: " + ex.toString());
  64.             System.out.println("SQLException: " + ex.getMessage());
  65.             System.out.println("SQLState: " + ex.getSQLState());
  66.             System.out.println("VendorError: " + ex.getErrorCode());
  67.         }
  68.         
  69.         InputStream  postIn = request.getInputStream(); //从输入流获取微信服务器传过来的整个字符串
  70.         
  71.         try
  72.         {
  73.             SAXReader reader = new SAXReader();
  74.             Document myDocument = reader.read(postIn); // 转成dom4j可以处理的格式
  75.             Element root = myDocument.getRootElement(); // 从XML字符串内获取全部元素,组成一棵树
  76.             Element myToUserName = root.element("ToUserName"); // 分别获取需要的值
  77.             Element myFromUserName = root.element("FromUserName");
  78.             Element myCreateTime = root.element("CreateTime");
  79.             Element myMsgType = root.element("MsgType");
  80.             Element myContent = root.element("Content");
  81.             Element myMsgId = root.element("MsgId");
  82.             String sToUserName = myToUserName.getText(); // 转成String类型
  83.             String sFromUserName = myFromUserName.getText();
  84.             String sCreateTime = myCreateTime.getText();
  85.             String sMsgType = myMsgType.getText();
  86.             String sContent = myContent.getText();
  87.             String sMsgId = myMsgId.getText();
  88.             
  89.             if (sMsgType.equals("text") && sContent.equals("/??")) // 用户发来的是文本消息且是查询数据关键词吗?
  90.             {
  91.                 long lCreateTime = System.currentTimeMillis()/1000L; // 消息创建的时间
  92.                 String mySendContent = "";
  93.                 // 以下手工封装回复文本消息的XML字符串
  94.                 mySendContent = "PM1:" + myVal01 + "ug/m3\nPM2.5:" + myVal02 + "ug/m3\nPM10:" + myVal03 + "ug/m3\n温度:" + myVal04 +
  95.                         "℃\n湿度:" + myVal05 + "%\n亮度:" + myVal06 + "\n噪声:" + myVal07;
  96.                 String xmlString01 = "<xml>";
  97.                 String xmlString02 = "<ToUserName><![CDATA[" + sFromUserName + "]]></ToUserName>";
  98.                 String xmlString03 = "<FromUserName><![CDATA[" + sToUserName + "]]></FromUserName>";
  99.                 String xmlString04 = "<CreateTime><![CDATA[" + lCreateTime + "]]></CreateTime>";
  100.                 String xmlString05 = "<MsgType><![CDATA[text]]></MsgType>";
  101.                 String xmlString06 = "<Content><![CDATA[" + mySendContent + "]]></Content>";
  102.                 String xmlString07 = "</xml>";
  103.                 String xmlAll = xmlString01 + xmlString02 + xmlString03 + xmlString04 + xmlString05 + xmlString06
  104.                     + xmlString07;
  105.                 System.out.println(xmlAll);
  106.                 out.print(xmlAll); // 回复给微信服务器,由其转发给用户手机
  107.                 out.close();
  108.             }
  109.             System.out.println("== Done ==");  
  110.         }
  111.         catch (DocumentException ex)
  112.         {
  113.             System.out.println("dom4j Document: " + ex.toString());
  114.         }
  115. }
复制代码
正常情况下,正确反馈应该是下面酱紫滴:
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-25 16:14:58

首先,感谢Ash发表的《穹顶之下的挣扎-DIY空气质量监测+净化器》(https://mc.dfrobot.com.cn/thread-11400-1-1.html)一文,让老夫这个矮子可以站在巨人的肩膀上。同时,老夫也有一点私心,想深入了解一下那些个年轻人神魂颠倒的微信公众号到底是啥玩意儿。

从Arduino中文社区、极客工坊和地府萝卜头的过往帖子来看,结合微信公众号应用方面的内容较少,要么是借助Yeelink、Xively等第三方物联网平台实现的;要么是像《分分钟用微信连接一切硬件,实现自定义的微信控制》(https://www.arduino.cn/thread-12536-1-1.html)这样的在关键部位打马赛克的;或者是利用微信测试账号实现的,均不满老夫的意。因此,老夫花了不少时间研究了一下,现在个人申请的无认证的订阅号,也能方便实现。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图12

1. 硬件准备阶段

先罗列一下采购的装备(以下零部件均采购自地府萝卜头商城,非广告帖,管理猿高抬贵手)

1.1 DFRduino UNO控制器(78元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

1.2 DFRobot Ethernet W5100网络板(98元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图2
1.3 IO扩展板 V7.1(55元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图3
1.4 DHT22温湿度传感器模块(65元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图4

1.5 MIC声音传感器(40元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图5

1.6 LX1972光传感器(30元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图6

1.7 数字蜂鸣器模块(15元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图7

1.8  食人鱼 红色LED(10元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图8

1.9 多孔原型钣金板(圆形)(60元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图9

1.10 PM2.5激光粉尘传感器(215元)
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图10

这里我要吐槽几点:

(a) IO扩展板插入W5100网络板后,有一边的针脚会压到网络板的按钮。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图13

(b) 粉尘传感器有两个螺眼,但是没有配套的螺丝、螺母。老夫从淘宝上淘了M2.3、M2.0的试试,不过都装得挺别扭。地府有同好建议我用热胶粘一下,老夫还特意去买了一枝。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图11

结果发现作为底座的圆盘和粉尘传感器外壳都是金属,粘不住。最后,不得不用上万能的双面胶。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图15

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图14

(c) 地府商城缺少配套的缠绕管。老夫是个懒惰的人,当然希望在一个地方能把东西都买齐,像下面这样把线缆整理得漂亮一点。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图16

现在我只能偷懒,用线扎了一下。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图17
回复

使用道具 举报

hnyzcj  版主

发表于 2015-11-25 18:45:53

我的PM2.5也快了,正在紧张打印中
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-25 20:10:41

本帖最后由 kevinzhang19701 于 2015-11-29 19:50 编辑

2. 软件准备阶段

2.1 Arduino IDE 1.0.6

有朋友问为什么这次没有采用最新的1.6.6版本,主要是因为DHT22模块使用了DHT22库文件,这个库文件只能在早期版本内编译。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

2.2 NetBeans IDE 8.0.2

这个IDE是用来写Java Servlet的,实现三大功能:

(1) 获取Arduino终端发来的http请求(携带7个参数),然后将这7个参数写入MySQL数据库;

(2) 解析微信服务器送来的xml字符串,而后查询数据库最新一条数据,返回XML字符串给微信服务器(这一部分我会在后面谈微信公众平台的时候详细解释)。老夫早年留/学期间,使用的是Forte for Java,因此这一路就延续下来,没走Eclipes那条路。

(3) 在成为微信公众平台开发者时,需要过一个门槛,写一个简单的Servlet进行验证(这个后面再提)。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图2

2.3 MySQL 5.6 + MySQL Workbench 6.2 CE

数据库的目的是将Arduino终端传递过来的7个参数(每隔5秒请求一次),保存入数据库表内。

这些数据就是某些砖家叫兽所称的“大数据”;P。导出到Excel的话,可以很方便地做成曲线图、直方图、馅饼图等,便于观察某些特定的趋势。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图3

注意下载最新的ConnectorJ(数据库与Servlet的连接桥)。

48小时PM2.5走势(有两处因烧录而中断,峰值陡峭的地方就是)。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图9

48小时室内声音走势。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图10

2.4 Apache Tomcat 8.0.29

Tomcat是Servlet的容器,用来监听服务器80端口进来的http请求,可参考前一篇《小题大做之远程LED控制》。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图11


3. 终端设备细节图

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图4

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图5

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图6

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图7

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图8

回复

使用道具 举报

hnyzcj  版主

发表于 2015-11-25 20:50:36

超级长
回复

使用道具 举报

大连林海  初级技神

发表于 2015-11-25 21:36:38

帖子应该还没有写完吧 会不会再继续呢
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-26 12:27:45

本帖最后由 kevinzhang19701 于 2015-11-29 19:54 编辑

5. 代码和写代码中的问题

5.1 粉尘传感器

看了粉尘传感器Demo代码,虽然照着抄了抄还能用,但是,里面有两处老夫其实不明白。

一处是这里的“,5”是什么意思?
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

另一处是这里,为什么用millis()作延迟,而不是delay()?我更换为delay()后试过,PM数据突然全变成了5位数!!
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图2

5.2 DHT22温湿度模块

地府Wiki内分别有两个Demo,一个用dht库文件,另一个用DHT22库文件。本想走捷径用dht库(代码少),下载链接在:
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图3

不过用这个库文件编译时,遇到一些未定义的变量问题(见下图红字)。所以,最后还是采用了DHT22库,对demo略作了修改。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图4

5.3 模拟声音传感器

从地府商城采购的这枚声音传感器,上代码后,观察到的输出数字很小:dizzy:。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图5

即便老夫将声源位置靠得很近,输出的数字也才个位数,偶尔才有很小的两位数。不像下列官方图例那样能达到60甚至更大,总觉得很奇怪,不明觉厉:dizzy:。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图6

回复

使用道具 举报

丄帝De咗臂  高级技匠

发表于 2015-11-26 12:44:01

very good
nice
干得漂亮
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-26 20:57:04

本帖最后由 kevinzhang19701 于 2015-11-29 19:58 编辑

6. Arduino终端设备代码

这部分是本项目核心之一。

代码不足百行,为了视觉上便于区分功能,我用空白行做分隔。部分直接采用了demo代码,有些没有照搬而是尽可能简化。

再一次证明老夫极其偷懒。真所谓偷懒是促进人类科技发展的原动力。;P

  1. #include "Arduino.h"
  2. #include "serialReadPMValue.h"
  3. #include <DHT22.h>
  4. #include <stdio.h>
  5. #include <SPI.h>
  6. #include <Ethernet.h>
  7. // 数字PIN 3 --> LED 红
  8. // 数字PIN 7 --> DHT22温湿度
  9. // 数字PIN 9 --> 蜂鸣器
  10. // 模拟PIN 0 --> 模拟声音
  11. // 模拟PIN 3 --> LX1972环境光
  12. uint16_t PM01Value  = 0;  // PM1.0
  13. uint16_t PM2_5Value = 0;  // PM2.5
  14. uint16_t PM10Value  = 0;  // PM10
  15. #define receiveDatIndex 32
  16. uint8_t receiveDat[receiveDatIndex];
  17. #define DHT22_PIN 7
  18. DHT22 myDHT22(DHT22_PIN);
  19. int valLight = 0;  // 光线Value
  20. int valSound = 0;  // 声音Value
  21. byte mac[]     = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
  22. IPAddress server(116, 236, 162, 186);   // 项目服务器公网IP
  23. IPAddress      ip(192, 168, 1, 76);   // 内网IP(该终端设备的内网IP)
  24. EthernetClient client;
  25. String myTempPM01  = "";
  26. String myTempPM25  = "";
  27. String myTempPM10  = "";
  28. String myTempLight = "";
  29. String myTempSound = "";
  30. String myTempTemp  = "";
  31. String myTempHumi  = "";
  32. void setup()
  33. {
  34.   Serial.begin(9600);
  35.   Ethernet.begin(mac, ip);
  36.   pinMode(3, OUTPUT);
  37.   pinMode(9, OUTPUT);
  38. }
  39. void loop()
  40. {
  41.   int length   = serialRead(Serial, receiveDat, receiveDatIndex, 5);
  42.   int checkSum = checkValue(receiveDat, receiveDatIndex);
  43.   if(length&&checkSum)
  44.   {
  45.     PM01Value  = transmitPM01(receiveDat);   //取PM1.0
  46.     PM2_5Value = transmitPM2_5(receiveDat);  //取PM2.5
  47.     PM10Value  = transmitPM10(receiveDat);   //取PM10
  48.   }
  49.   static unsigned long OledTimer = millis();
  50.   if (millis() - OledTimer >= 5000)
  51.   {
  52.     OledTimer=millis();
  53.    
  54.     cDHT22();
  55.    
  56.     valLight = analogRead(3);   // 取光线Value
  57.     valSound = analogRead(0);   // 取声音Value
  58.    
  59.     digitalWrite(3, HIGH);  // LED点亮
  60.     digitalWrite(9, HIGH);  // 蜂鸣器鸣叫
  61.     Serial.println("");
  62.     myTempPM01  = String(PM01Value);   // 将int转换成String
  63.     myTempPM25  = String(PM2_5Value);
  64.     myTempPM10  = String(PM10Value);
  65.     myTempLight = String(valLight);
  66.     myTempSound = String(valSound);
  67.    
  68.     Serial.println("PM01:" + myTempPM01 + "ug/m3, PM25:" + myTempPM25 + "ug/m3, PM10:" + myTempPM10 + "ug/m3");
  69.     Serial.println("Temperature:" + myTempTemp + "C, Humidity:" + myTempHumi + "%");
  70.     Serial.println("Light:" + myTempLight + ", Sound:" + myTempSound);
  71.    
  72.     if (client.connect(server, 80))   // 提交的项目服务器80端口
  73.     {
  74.       Serial.println("Connected");
  75.       // HTTP请求,携带7个参数
  76.       client.println("POST http://116.236.162.186/servlet/mypm?unoVar01=" + myTempPM01 + "&unoVar02=" + myTempPM25 + "&unoVar03=" + myTempPM10 + "&unoVar04=" + myTempTemp + "&unoVar05=" + myTempHumi + "&unoVar06=" + myTempLight + "&unoVar07=" + myTempSound + " HTTP/1.1");
  77.       client.println("Host:116.236.162.186");
  78.       client.println();
  79.     }
  80.     else
  81.     {
  82.       Serial.println("connection failed");
  83.     }
  84.     Serial.println();
  85.     client.stop();
  86.   }
  87.   
  88.   digitalWrite(3, LOW);
  89.   digitalWrite(9, LOW);
  90. }
  91. void cDHT22()
  92. {
  93.     DHT22_ERROR_t errorCode;
  94.     errorCode = myDHT22.readData();
  95.     if (errorCode == DHT_ERROR_NONE)
  96.     {
  97.       myTempTemp = String(myDHT22.getTemperatureC());  //取温度
  98.       myTempHumi = String(myDHT22.getHumidity());  //取湿度
  99.    }
  100. }
复制代码


回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-27 14:16:08

本帖最后由 kevinzhang19701 于 2015-11-29 20:05 编辑

7. 关于微信公众平台

这次的项目要求通过微信公众号查询实时数据,以适应现如今如火如荼地拿微信当做客户端的潮流。真是顺之者昌、逆之者亡。老夫所供职的金融行业早就将微信作为一个非常重要的拓展领域投入了很多的人力、物力和财力。目的很明确,就是吸睛+客户粘着度。当然,也有一些企业是看上她的免费,至少在显性成本的投入上,远低于App的开发和推广。

老夫觊觎这东西很久了,但是问起周边的后生小子,却往往语焉不详,就知道个群发文章。为此,老夫聊发少年狂,红袖添香夜读书......

微信这句slogan设计得好,为不少个人用户提供了迈出创业第一步的便利,外加还免费。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

一些概念还是要区分的,比如:微信、微信公众平台、微信公众号、微信个人号、微信群。

微信是个统称,包含能提供的所有服务。这些服务就包含了那些个“号”、“群”和“平台”。一旦注册了微信,就产生一个个人号。个人号之间可以建群,群内发言,所有人都能看到。微信为了提高私密度,并且规避滥发内容的问题,搞出了一个“公众号”(腾讯解释,他们的目的是为了客户服务而非信息发布),关注这个公众号的人与号主之间是一对一对话关系,其他人看不到。号主可以群发内容,但是受到一定的限制以防止滥发广告(短信的下场)。

那么,在哪里制作、分发、管理内容呢?就在公众平台上。

综上所述,老夫得先注册一个微信公众号,然后就能用上公众平台。用上公众平台,后台就能数据对接了。

7.1 到底有哪些个微信公众号,以及他们的权限呢?

小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图2
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图3


上面表格已经说明清楚了,我就不啰嗦。作为普通个人,只能申请非认证的订阅号

7.2 只要8步,即可申请一个订阅号

7.2.1. 打开 http://mp.weixin.qq.com

7.2.2. 填写个人信息,完成邮箱激活。

7.2.3. 选择“订阅号”。注意:一旦注册,类型是无法变更的。

7.2.4. 选择正确的主体类型:个人。

7.2.5. 扫描银行卡关联二维码,完成身份验证。

微信以前是用身份证来验证的,5.0以后就换成银行卡验证。一举两得,既完成身份验证,顺便再绑定一张银行卡。打通微信支付、抢红包等应用渠道。身份证号仍有用,一般是一个身份证号可以注册5个公众号。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图5

7.2.6. 接着完成手机验证。后续公众号的很多修改动作,都要手机确认。

7.2.7. 填写公众号信息。注意,公众号名称一旦设定,无法更改。

7.2.8. 注册完成,公众号安全助手会发来祝贺。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图4





回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-28 15:17:43

本帖最后由 kevinzhang19701 于 2015-11-29 20:19 编辑

8. 编辑模式和开发模式

登录自己的微信公众号后台后,可以看到各个功能菜单。主要分成两部分:在“设置”以上的这些是编辑相关功能;设置下面的“开发者中心”只针对开发者。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

微信公众号后台有两种模式,即“编辑模式”和“开发模式”。

普通用户(指那些没有code能力或者有能力但是没有匹配软硬件条件的用户)使用的是编辑模式,通过后台所提供的“群发”、“回复”、“自定义菜单”等等功能,可以直接完成公众号的编辑、修改、发送和menu美化。这一模式为普通用户带来了极大的便利,使得阿姨妈妈老爹大叔型号的用户均能轻而易举滴使用微信公众号发布内容。

对于世界上还有那部分喜欢与机器和代码打交道的用户来说,或者是需要后台与业务系统直接对接的用户来说,只有“开发模式”才能满足。因此,第一步要成为开发者。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图2

这里需要说明一下:一旦老夫成为开发者,那么微信服务器将直接把用户提交的数据转给“本项目服务器“,她自己就什么都不管了。有朋友曾经问过,为什么他以前在编辑模式下做的自动回复、自定义菜单等功能,进入了开发模式后,全部消失了。原因就是这个,编辑模式与开发模式势不两立,一旦选择开发模式,编辑模式下的功能就没了,反之亦然。目前,目测微信团队没有混合模式这一说。

8.1 成为开发者的门槛

开发者自然有开发者的优越感和自尊心,不能便宜了那些个没开发能力的用户过来凑热闹,因此,微信团队在让用户成为”开发者“前设置了一些门槛:

8.1.1 需要一个公网IP地址。家里的宽带就没戏了。

8.1.2 需要开放80端口(即http端口)。家里的宽带也没戏了(还记得老夫的前一篇《 小题大做之远程LED控制》中解释的原因吗)。

8.1.3 需要证明给微信团队看,你有这个开发能力且服务器是有效的。

那么,怎么证明给微信团队看,老夫有这个开发能力和拥有有效服务器呢?这个在《公众平台开发者文档接入指南》(http://mp.weixin.qq.com/wiki/16/ ... 3e808014375b74.html)内有详细描述(顺便赞一下,微信公众平台的文档编写得很好。市面上出版的很多书,写得还没有文档好)。

首先,接受成为开发者的条款,如此这般,老夫就拥有了一个AppID(应用ID,在每个公众号内是唯一的,可以用她来标识用户)和一个AppSecret(应用密钥,打死也不能说哦)。听上去好高大上,目测貌似iOS App的开发场景;P。

接下来,需要提供项目服务器的URL(这个URL是老夫用来接收微信消息[就是用户回复的字符串]和事件的接口[就是自定义菜单点击动作]的URL),这就把老夫的公网IP和80端口都给验证了。注意,必须以http开头,https不行。老夫的这个URL就是:

http://116.236.162.186/servlet/wxtest

第三,是Token,任意填写。用作生成signature(该Token会和接口URL中包含的Token进行比对,从而验证用户来源的安全性)。老夫用网上的Password Generator软件生成了一个,为:

CO1drluxieGlUvoafl9THLawr2aspiAB。

第四,是EncodingAESKey(消息加解密密钥),老夫偷懒,点击”随机生成“让微信后台自动生成了一个:

vHVO2KR1rYlmuSvJhizvUWLJRgl9hyA7Mf4CgSxu79j。

第五,消息加密方式,老夫直接选明文。烦死了,这点破数据还加啥密:lol。

以上五项均搞妥之后,就进入了验证服务器有效性阶段,是否能成为开发者,在此一举。

8.2 验证服务器有效性
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图3

当老夫点击公众平台开发者中心”启用“后,微信服务器会向本项目服务器提交一个http请求,并携带4个参数,分别是:signature、timestamp、nonce、echostr。然后,需要开发者完成:

8.2.1 将token、timestamp、nonce三个参数进行字典序排序

8.2.2 将三个参数字符串拼接成一个字符串进行sha1加密

8.2.3 完成加密后的字符串与signature比对,如果相同,则表明http请求来自于微信。原样返回微信服务器echostr参数的内容,则接入生效,成为开发者成功,否则接入失败,无法成为开发者。

上述这几个任务,看看挺low的,不过,已经难倒不少想成为开发者的用户了;P。你躺枪了吗?:P
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-28 17:55:58

本帖最后由 kevinzhang19701 于 2015-12-7 17:01 编辑

8.2.4 服务器端官方PHP样例

如果你懂PHP,很幸运,微信官方语言也是PHP。所以,他们提供了样例:
  1. private function checkSignature()
  2. {
  3.         $signature = $_GET["signature"];
  4.         $timestamp = $_GET["timestamp"];
  5.         $nonce = $_GET["nonce"];        
  6.                         
  7.         $token = TOKEN;
  8.         $tmpArr = array($token, $timestamp, $nonce);
  9.         sort($tmpArr, SORT_STRING);
  10.         $tmpStr = implode( $tmpArr );
  11.         $tmpStr = sha1( $tmpStr );
  12.         
  13.         if( $tmpStr == $signature ){
  14.                 return true;
  15.         }else{
  16.                 return false;
  17.         }
  18. }
复制代码
注意,上面的代码只是一个代码片段,在真实开发环境下,头尾还要我们自己拼装上。这个核心代码就是完成有效性验证的样例。

很可惜的是,老夫不懂PHP:Q。所以,不得不用Java重写了一个Serlvet(wxtest.class),完成相同的功能。下面是全部代码:
  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3. import java.io.InputStream;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9. import java.util.Arrays;  // 数据用来进行字符串排序
  10. import org.apache.commons.codec.digest.DigestUtils;  // 使用Apahce Commons Codec项目,完成SHA1加密
  11. @WebServlet(urlPatterns = {"/wxtest"})
  12. public class wxtest extends HttpServlet
  13. {
  14.     protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  15.             throws ServletException, IOException
  16.     {
  17.         response.setContentType("text/html;charset=UTF-8");
  18.         request.setCharacterEncoding("UTF-8");
  19.         response.setCharacterEncoding("UTF-8");
  20.         PrintWriter out = response.getWriter();
  21.         
  22.         String myToken      = "CO1drluxieGlUvoafl9THLawr2aspiAB";
  23.         String mySignature  = request.getParameter("signature");  // 抓取微信服务器送来的4个参数
  24.         String myTimestamp  = request.getParameter("timestamp");
  25.         String myNonce      = request.getParameter("nonce");
  26.         String myEchostr    = request.getParameter("echostr");
  27.         String encodeString = "";
  28.         String finalString  = "";
  29.         
  30.         if(mySignature==null||myTimestamp==null||myNonce==null||myEchostr==null)
  31.         {
  32.             System.out.println("There is at least one null in pararmeters.");
  33.         }
  34.         else
  35.         {
  36.             String[] myString = new String[3];
  37.             myString[0] = myToken;
  38.             myString[1] = myTimestamp;
  39.             myString[2] = myNonce;
  40.             
  41.             Arrays.sort(myString);  // 通过数组,3个字符串按字典序排序
  42.             encodeString = myString[0] + myString[1] + myString[2];
  43.             finalString  = DigestUtils.sha1Hex(encodeString);  // 完成SHA1加密
  44.         }
  45.         
  46.         if (mySignature.equals(finalString))  // 加密后的字符串与微信传过来的signature字符串比较
  47.         {
  48.             System.out.println("same...");
  49.             out.print(myEchostr);  // 两者相同,则证明请求来自微信服务器,原样返回Echostr字符串
  50.             out.close();
  51.             System.out.println("Sent ok ...");
  52.         }
  53.         else
  54.         {
  55.             System.out.println("different...");
  56.         }
  57.     }
  58. }
复制代码
好消息:服务器有效性验证,只需要进行一次。一旦通过验证后,该公众号毋需验证了,哪怕你在编辑模式和开发模式无数次切换。

回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-29 10:27:54

本帖最后由 kevinzhang19701 于 2015-11-30 12:35 编辑

9. Tomcat和她的目录说明
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1
Tomcat的内容,请参考老夫上一篇《小题大做之远程LED控制》长文(http://www.arduino.cn/thread-8463-1-1.html)。这里强调一下重要的目录:

9.1 /lib目录

这个项目,lib目录存放MySQL与Servlet的沟通桥梁ConnectorJ。如果没有这个driver,那么我们将无法保存数据到数据库,也无法查询数据库内的记录。

9.2 /ROOT/WEB-INF/classes目录

这个目录存放我们的3个Servlet,即:

(a) wxtest.class(服务器有效性验证)。

(b) mypm.class(获取Arduino终端设备提交的数据,写入数据库)。

(b) wxtest.class(判断从微信服务器传入的字符串是否是用户回复的"/??",如是,则从数据库查询最新一条记录,转发回微信服务器)。这个与(a)保持同名,是为了与提交给微信验证的URL保持一致。



回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-30 12:17:13

本帖最后由 kevinzhang19701 于 2015-11-30 13:14 编辑

10. MySQL数据库

我们使用开源的MySQL存储数据。多年来,MySQL已经做了大量的改进,用于生产系统毫无违和感。配合WorkBench图形化界面,可以不用输入命令来维护整个数据库。
小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1

这里建了一个简单的库mypm,里面建了一个数据表t01,数据表结构和字段类型,请参考上图。

再次强调,Servlet与MySQL的连接桥梁是ConnectorJ。所以,一定不要忘记在Tomcat的lib目录,存放这个驱动。下载链接:

http://dev.mysql.com/downloads/connector/j/




回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-30 12:31:41

本帖最后由 kevinzhang19701 于 2015-11-30 12:49 编辑

11. 第二个Servlet,mypm.class

这是核心代码之二,主要实现:

1. 监听http请求,抓取7个传递来的参数。

2. 写入t01数据表。

  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.annotation.WebServlet;
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. // MySQL connection package
  9. import java.sql.Connection;
  10. import java.sql.DriverManager;
  11. import java.sql.SQLException;
  12. import java.sql.Statement;
  13. import java.sql.ResultSet;
  14. @WebServlet(urlPatterns = {"/mypm"})
  15. public class mypm extends HttpServlet
  16. {
  17.     protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  18.             throws ServletException, IOException
  19.     {
  20.         response.setContentType("text/html;charset=UTF-8");
  21.         request.setCharacterEncoding("UTF-8");
  22.         response.setCharacterEncoding("UTF-8");
  23.         
  24.         String myPm01 = request.getParameter("unoVar01");  // 抓取7个参数
  25.         String myPm25 = request.getParameter("unoVar02");
  26.         String myPm10 = request.getParameter("unoVar03");
  27.         String myTemp = request.getParameter("unoVar04");
  28.         String myHumi = request.getParameter("unoVar05");
  29.         String myLight = request.getParameter("unoVar06");
  30.         String mySound = request.getParameter("unoVar07");
  31.         
  32.         Connection conn = null;
  33.         Statement stmt = null;
  34.         ResultSet rs1 = null;
  35.         int i;
  36.         String mySQL = "INSERT INTO t01 (tpm01, tpm25, tpm10, ttemp, thumi, tlight, tsound) VALUES('" +
  37.                 myPm01       + "','" +
  38.                 myPm25       + "','" +
  39.                 myPm10       + "','" +
  40.                 myTemp       + "','" +
  41.                 myHumi       + "','" +
  42.                 myLight      + "','" +
  43.                 mySound      + "')";
  44.         try
  45.         {
  46.             conn = DriverManager.getConnection("jdbc:mysql://localhost/mypm?" +
  47.                                    "user=root&password=15186okia");
  48.             stmt = conn.createStatement();
  49.             i = stmt.executeUpdate(mySQL);  // 写入数据表
  50.             System.out.println("inserting...");
  51.         }
  52.         catch (SQLException ex)
  53.         {
  54.             System.out.println("Error in connection: " + ex.toString());
  55.             System.out.println("SQLException: " + ex.getMessage());
  56.             System.out.println("SQLState: " + ex.getSQLState());
  57.             System.out.println("VendorError: " + ex.getErrorCode());
  58.         }
  59.         
  60.         System.out.println("Written...");
  61.         System.out.println("PM 1: " + myPm01);
  62.         System.out.println("PM2.5:" + myPm25);
  63.         System.out.println("PM10: " + myPm10);
  64.         System.out.println("Temperature:" + myTemp + "C");
  65.         System.out.println("Humidity: " + myHumi + "%");
  66.         System.out.println("Light: " + myLight);
  67.         System.out.println("Sound: " + mySound);
  68.         System.out.println("==");
  69.     }
  70. }
复制代码



回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-11-30 14:47:47

本帖最后由 kevinzhang19701 于 2015-11-30 15:22 编辑

12. 关于XML

12.1 什么是XML

  • XML 指可扩展标记语言(EXtensible Markup Language)
  • XML 是一种标记语言,很类似 HTML
  • XML 的设计宗旨是传输数据,而非显示数据
  • XML 标签没有被预定义。您需要自行定义标签。
  • XML 被设计为具有自我描述性。
  • XML 是 W3C 的推荐标准

12.2 为什么使用XML

大家都知道Web Service吧,一种分布式传输数据的方式。考虑到分布式设备使用不同的系统,他们要相互传输数据有一定难度,后来大家发现传输文本类型的数据比较简单,因此就使用XML技术,将需要传输的数据打包成XML格式。
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-12-2 20:05:19

今天在仙台,休假中
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-12-4 15:12:52

本帖最后由 kevinzhang19701 于 2015-12-4 20:11 编辑

休假不忘发帖小题大做系列II(PM2.5微信控)炒鸡长文体虚慎入图1



回复

使用道具 举报

luna  初级技神

发表于 2015-12-4 17:01:56

坐等完整版啊~~前几天北京雾霾,还想找你的文章发一下呢~~T T 可惜你没有更完~我可等着下次雾霾微信公众号发这篇呢
回复

使用道具 举报

kevinzhang19701  高级技匠
 楼主|

发表于 2015-12-4 20:13:14

luna 发表于 2015-12-4 17:01
坐等完整版啊~~前几天北京雾霾,还想找你的文章发一下呢~~T T 可惜你没有更完~我可等着下次雾霾微信公众号 ...

不好意思,休假去日本一周。回来后会尽快写完整:lol
回复

使用道具 举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail