极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 26586|回复: 10

Arduino MEGA 2560 + Ethernet Shield,上传串口摄像头PTC06拍摄图片至JSP服务器

[复制链接]
发表于 2012-12-3 22:57:41 | 显示全部楼层 |阅读模式
本帖最后由 爱我吧 于 2012-12-3 23:08 编辑

前两天研究了一下串口摄像头PTC06,现在将PTC06拍摄的照片上传到服务器。
我的服务器端用的是JSP。
JSP实现文件上传还算简单了,只要做个JSP页面,放上个form,设置一下enctype和post,再加个type为file的input就可以上传了。
但是Arduino是没有form、input的之类的HTML标记的。那应该怎么实现上传呢?
其实,上传的过程,无非是客户端与服务器建立了一个通道,然后通过这个通道传送数据罢了。
那么,Arduino如何与服务器建立通道呢?
Ethernet Shield提供了EthernetClient类,可以使用这个类连接服务器。比如:
  1. //访问服务器资源的客户端
  2. EthernetClient client;
复制代码

下一步就是发送数据。数据可不是随便发送的,是有规则的。例如下面的JSP页面提交时,可以看到其发送的数据:

其发送的数据格式如上图下半部分所示。这个查看HTTP头的工具叫:ieHTTPHeaders。其实,只要把这个头复制一下,放到Arduino中就能使用了。例如:
  1.        
  2. if(client.connect(server, 8080)) //连接服务器8080端口
  3. {
  4.         Serial.println("Connect to Server");
  5.         boundary = "---------------------------" + String(millis(), HEX);//用当前的微秒构造一个boundary

  6.         contentLine[0] = boundary;
  7.         contentLine[1] = "Content-Disposition: form-data; name="field1"";//上传的第一个数据的名称
  8.         contentLine[2] = "";
  9.         contentLine[3] = field1_value;//上传的第一个数据的值
  10.         contentLine[4] = boundary;
  11.         contentLine[5] = "Content-Disposition: form-data; name="field2"";//上传的第二个数据的名称
  12.         contentLine[6] = "";
  13.         contentLine[7] = field_value;//上传的第二个数据的值
  14.         contentLine[8] = boundary;
  15.         contentLine[9] = "Content-Disposition: form-data; name="field3"";//上传的第三个数据的名称
  16.         contentLine[10] = "";
  17.         contentLine[11] = field3_value;//上传的第三个数据的值
  18.         contentLine[12] = boundary;
  19.                        
  20.         if(picture_send)//如果要上传图片
  21.         {
  22.                 contentLine[13] = "Content-Disposition: form-data; name="file"; filename="somefile.jpg"";//上传图片的名字随便起
  23.                 contentLine[14] = "Content-Type: image/pjpeg";//上传图片的类型,当然,如果上传的是其它类型的话,改为对应的type即可,则个可以在网上查
  24.         }
  25.         else
  26.         {
  27.                 contentLine[13] = "Content-Disposition: form-data; name="file"; filename=""";
  28.                 contentLine[14] = "Content-Type: application/octet-stream";
  29.         }
  30.                        
  31.         contentLine[15] = "";

  32.         contentLength = 0;//计算提交时内容的长度
  33.         for(uint8_t i=0;i<CONTENT_LENGTH;i++)
  34.         {
  35.                 contentLength += contentLine[i].length();
  36.         }
  37.         //额外加上图片的大小、结束符的长度和每一行的回车换行(一行2个字符)
  38.         contentLength += ((cinema_send)?pictureLength:0) + (boundary.length() + 2) + ((CONTENT_LENGTH+2) * 2);

  39.         client.println("POST /Security/servlet/InfoServlet HTTP/1.1");
  40.         client.println("Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, */*");
  41.         client.println("Referer: http://localhost:8080/Security/");
  42.         client.println("Accept-Language: zh-CN");
  43.         client.println("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)");
  44.         client.println("Content-Type: multipart/form-data; boundary=" + boundary);
  45.         client.println("Accept-Encoding: gzip, deflate");       
  46.         client.println("Host: 172.16.1.31");
  47.         client.println("Content-Length: " + String(contentLength, DEC));
  48.         client.println("Connection: Keep-Alive");
  49.         client.println("Cache-Control: no-cache");


  50.         //先将固定的信息输出出去
  51.         client.println();
  52.         for(uint8_t i=0;i<CONTENT_LENGTH;i++)
  53.         {
  54.                 client.println(contentLine[i]);
  55.         }

  56.         if(picture_send){
  57.                 //输出图片的信息
  58.                 while((readed = cinema->getBytes(buffer, CINEMA_BUFFER_LENGTH))>0)
  59.                 {//cinema是我自己封装的PTC06的类库
  60.                         client.write(buffer, readed);
  61.                 }
  62.         }
  63.         client.println();

  64.         //输出结束boundary
  65.         client.println(boundary + "--");
  66.         client.println();

  67.         Serial.println();
  68. }
  69. else
  70. {
  71.         //如果不能创建连接
  72.         client.stop();

  73.         Serial.println("Connect Faild");
  74. }
复制代码

之后,在Servlet中只要对request的InputStream对象进行处理就行了。但是,不能用getParameter直接处理的,需要根据boundary依次访问,获取参数的name和参数的value。如果是上传的文件的话,则需要将其bytes写入单独的文件。下面是Servlet的doPost方法的代码:
[pre lang="JSP" line="1"]
request.setCharacterEncoding("UTF-8");

// 用于存储值的HashMap
HashMap<String, String> data = new HashMap<String, String>();
// Content-Disposition对应的K-V
String name = "";//用于获取提交时参数的名字
String filename = "";//用户获取提交时文件名

String tmpLine;//每次读取到的行
int len;//对于文件,每次读取到的长度
byte buffer[] = new byte[1024];// 1Kb,对于文件,读取时的缓存

String contentType = request.getContentType();//获取提交(请求)的type,对应着Arduino中的那个“Content-Type”,用于获取boundary
int contentLength = request.getContentLength();//获取提交(请求)的长度

String boundary = contentType.substring(contentType.indexOf("boundary=") + "boundary=".length()).trim();//把Arduino提交的boundary拿出来
String endBoudary = boundary + "--";//这是结束的boundary

// 获取客户端的输入流
ServletInputStream sis = request.getInputStream();
OutputStream os = null;
StringBuilder sb = null;

while ((len = sis.readLine(buffer, 0, buffer.length)) != -1) {//每次读取一行
        tmpLine = new String(buffer, 0, len);
        if (tmpLine.trim().startsWith(boundary)) {// 这是一段数据的开始,也可能是请求结束
                // 完成上一个数据的读取
                if (os != null) {//如果上一个数据是文件,则关闭输出流
                        data.put(name, filename);//存储相应的数据
                        os.close();
                        os = null;
                }
                if (sb != null) {//如果是简单的文本数据
                        data.put(name, sb.toString());//存储相应的数据
                        sb = null;
                }

                if (!tmpLine.trim().equals(endBoudary)) {//如果是新的数据
                        // 接着把其下一行的数据读出来
                        len = sis.readLine(buffer, 0, buffer.length);
                        tmpLine = new String(buffer, 0, len);

                        int start = tmpLine.indexOf("name=\"") + "name=\"".length();
                        int end = tmpLine.indexOf("\"", start);
                        name = tmpLine.substring(start, end);//获取参数的名字

                        if (tmpLine.indexOf("filename=\"") != -1) {//如果包含filename,则把文件名一起拿出来
                                start = tmpLine.indexOf("filename=\"") + "filename=\"".length();
                                end = tmpLine.indexOf("\"", start);
                                filename = tmpLine.substring(start, end);
                        }

                        // 再度一行
                        len = sis.readLine(buffer, 0, buffer.length);
                        tmpLine = new String(buffer, 0, len);

                        // 如果包含Content-Type,说明需要准备上传文件,否则,使用StringBuilder缓存简单数据
                        if (tmpLine.startsWith("Content-Type")) {
                                if (!filename.isEmpty()) {//如果在提交数据时,没有指定上传的文件,则filename为空字符串。这里需要进行判断的
                                        Calendar c = Calendar.getInstance();//在服务器端构造以月、日为名字的目录结构,避免把所有的图片都放在一个文件夹下
                                        String dir = this.getServletContext().getRealPath("/picture");
                                        String year = String.valueOf(c.get(Calendar.YEAR));
                                        String month = String.valueOf(c.get(Calendar.MONTH) + 1);

                                        File d = new File(dir, year);
                                        if (!d.exists()) {
                                                d.mkdir();
                                        }
                                        d = new File(d, month);
                                        if (!d.exists()) {
                                                d.mkdir();
                                        }

                                        String fn = Toolkit.nextUUID() + filename.substring(filename.lastIndexOf("."));//这里在保存文件的时候,扩展名使用上传时文件的扩展名,即filename的扩展名
                                        filename = "picture" + "/" + year + "/" + month + "/" + fn;
                                        os = new FileOutputStream(new File(d, fn));
                                }

                                // 再读一个空行
                                sis.readLine(buffer, 0, buffer.length);
                        } else {
                                // 读出来的就是一个空行
                                sb = new StringBuilder();
                        }
                }
        } else {
                if (os != null) {
                        os.write(buffer, 0, len);
                }
                if (sb != null) {
                        sb.append(tmpLine);
                }
        }
}[/code]
好了,这样即可以实现Arduino上传图片至服务器了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复

使用道具 举报

发表于 2012-12-4 08:57:19 | 显示全部楼层
好东西,抓图速度呢?另希望将硬件和连接公布一下最好
回复 支持 反对

使用道具 举报

发表于 2012-12-4 10:00:46 | 显示全部楼层
做的不错,提供一下性能方面的参考数据?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-12-5 08:27:03 | 显示全部楼层
本帖最后由 爱我吧 于 2012-12-5 08:28 编辑

测试的数据(单位毫秒):
第一组:PTC06拍照后,将图片存储在SD卡上,一共12组数据
5743、7051、76174884、5396、5529、5407、5419、4983、6155、6948、5962。平均耗时:5924.5。

第二组:PTC06拍照后,直接读取图片数据,不存储在SD卡上,一共12组数据
6538、4707、5712、5152、4128、4861、5421、5664、5667、4550、4634、4428。平均耗时:5121.833。

第三组:PTC06拍照后,通过网络将图片上传至服务器,一共12组数据
6938、6549、73864548、6771、6108、6560、5809、5784、6678、6409、6772。平均耗时:6359.333。
回复 支持 反对

使用道具 举报

发表于 2012-12-5 08:54:29 | 显示全部楼层
5-6秒的样子,貌似实用价值不高啊,就是说只能定时5秒以上拍照,防个小偷有可能5秒已经走过去了
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-12-5 09:04:58 | 显示全部楼层
本帖最后由 爱我吧 于 2012-12-5 09:46 编辑


这是个串口摄像头嘛,本来速度就不是特别快。我是加了个人体热释感应和光线感应模块配合拍照的。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x
回复 支持 反对

使用道具 举报

发表于 2012-12-5 10:46:05 | 显示全部楼层
如果做一个低成本的上传Yeelink平台的图像传感器有意义的,比树莓派便宜多了,现在用树莓派干这个活还是有点偏贵。

http://blog.yeelink.net/?p=468

楼主有空可以看看我们用树莓派做的实现
回复 支持 反对

使用道具 举报

发表于 2012-12-5 11:00:41 | 显示全部楼层
本帖最后由 ttyp 于 2012-12-5 11:01 编辑

光传感器是什么作用?有人来了会导致光线变暗?而且还用2个?

真要防盗的,可以采用激光装窗户或门上,像电影里的那种。或者超声探距来测试物体在运动
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-12-5 11:56:24 | 显示全部楼层
ttyp 发表于 2012-12-5 11:00
光传感器是什么作用?有人来了会导致光线变暗?而且还用2个?

真要防盗的,可以采用激光装窗户或门上,像 ...

是要放在学生宿舍,每一个柜子里面放一个光线感应模块。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-12-5 11:58:04 | 显示全部楼层
erjiang 发表于 2012-12-5 10:46
如果做一个低成本的上传Yeelink平台的图像传感器有意义的,比树莓派便宜多了,现在用树莓派干这个活还是有点 ...

最近有这个企图,不过赶上精品课程的屏蔽,时间被强制退后了。
回复 支持 反对

使用道具 举报

发表于 2012-12-5 12:02:42 | 显示全部楼层
爱我吧 发表于 2012-12-5 11:56
是要放在学生宿舍,每一个柜子里面放一个光线感应模块。

哦,这个到不错,这样人体红外到可以省了啊,是每次打开都拍照么,这个可以做些优化的
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-5-5 04:57 , Processed in 0.044337 second(s), 20 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表