我的大创之旅
来源:互联网 发布:浙江大学城市学院知乎 编辑:程序博客网 时间:2024/04/29 14:12
0
2014年四月份的一天,老上司用一顿饭把我引出来,告诉我,她要参加大创,有一个好的Idea,现在就差一只程序猿了。作为一个典型的计院宅男,我当初并不明白也不看重。但是饭已经吃了,嘴已经软了。我还是答应了。
开始的设想是一个关于家庭成员互动的APP。我们拿着这个不值钱的想法去找‘投资人’侯老师,她很认真地听了一番,然后很礼貌地说你们这个想法简直bull shit要想在大创上拿名次简直是梦话。
我们重新构思了几个方案,后来,我们的注意力集中到了水幕身上。这个想法看起来展示性强了很多,简单地说就是一个水幕版本的“别踩白块”,和正版的区别是显示的屏幕变成了水幕而已。我们七嘴八舌,又意淫出了各种奇奇怪怪的想法。
我信誓旦旦地保证这个程序我绝对可以写得出来,其实我自个也没有什么底。因为要想控制水幕就得有电磁阀,这玩意得用单片机控制。可我并没有怎么学过这玩意。
1
我回去看了些书,发现比我预想的还要麻烦不少。单片机上限制多,想法要实现有难度。拖延了两天,写出了这个程序的初版。
#include<REG52.H>#include<INTRINS.H>//本例采用89C52, 晶振为11.0592MHZ #define GPIO_KEY P0 //独立键盘用P1口 即为以后的输入#define GPIO_LED P1 //led使用P0口 led模拟喷嘴 和以后的喷嘴一一对应#define GPIO_NUM P2 ////这里的n是持续时间,本身别踩白块的某个喷头持续时间是一定的,但是为了以后的多样性,将其设为变量unsigned char last_time=250; unsigned char left_time=0;unsigned char cout=0;unsigned char cjudge=0;//代判定的数组下标比输出数组下标小1bit success=0;//成功标志位初始化为0bit dead_flag=0;//定义我们要读取的数组 因为类似于P1,GPIP_KEY的数据类型可以转化为unsighed char,一组输出既可以用这样的一个unsigned char 表示unsigned char code block_map[]={ //12组输出 //0x00,//开始位为0 0x01,0x08,0x04,0x08, 0x01,0x04,0x02,0x01, 0x02,0x04,0x01,0x08, 0x01,0x02,0x04,0x08, 0x02,0x04,0x02,0x01, 0x02,0x04,0x01,0x08, 0x01,0x02,0x04,0x08, 0x01,0x04,0x02,0x01, 0x02,0x04,0x01,0x08, 0x01,0x02,0x04,0x08, 0x02,0x04,0x02,0x01, 0x02,0x04,0x01,0x08, 0x00};unsigned char code DIG_CODE[16]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71};//0~F段码 00111111 00000110 01011010 01001111 //0:0011_1111 unsigned char n=3000;//unsigned char code block_hold_map[]={};//12组输出,每组输出的持续时间unsigned char keyscan();void judge(unsigned char);void Delay10ms(unsigned int c); //这个中断用于控制一组输出的持续时间void int0() interrupt 1 //采用中断0 控制节拍 { TH0=0xd8; TL0=0xef;//相隔10000us n--; if(n==0){ cout++; cjudge++;//到了时间则输出更新 if(success==0){ dead_flag=1;//时间已到而未判定成功,游戏结束 } else{ success=0;//如果判定成功,则将标志位重新置0,准备下一周期的判定 } }} //毫秒延时的子程序void delay(unsigned char a) //毫秒延时{ unsigned char b=1000; while(--a){ b=1000; while(--b); }; //采用while(--a) 不要采用while(a--);} //主程序入口void main(){ unsigned char temp; unsigned char keyValue; //计时器初始化 TMOD&=0x0f; TMOD|=0x01; TH0=0xd8;TL0=0xef; IE=0x82; TR0=1;play: while(1){ //dead_flag为1代表游戏失败 或者 游戏还未开始 if(dead_flag==1){ GPIO_LED=0x0f; delay(250);delay(250);delay(250);delay(250); GPIO_LED=0x00; delay(250);delay(250);delay(250);delay(250); } //游戏正在进行 else{ temp=block_map[cout]; if(temp==0x00){//碰到了结束符,则竖起死亡flag,游戏结束 //在这里可以添加的功能是显示最终成绩 cout=1; cjudge=1; } else{//在正常输出的阶段 TR0=1; GPIO_LED=temp; while(n!=0){ keyValue= keyscan(); judge(keyValue); } TR0=0; n=3000; } } } } unsigned char keyscan(){ unsigned char keyValue = 0 , i; //保存键值 //--检测按键1--// if (GPIO_KEY != 0xFF) //检测按键K1是否按下 { keyValue = GPIO_KEY; //i = 0; //while ((i<50) && (GPIO_KEY != 0xFF)) //检测按键是否松开 延时50ms或者松开了按键之后退出 //{ // Delay10ms(1); // i++; //} } else{ keyValue=0xFF; } return keyValue; //将读取到键值的值返回}void judge(unsigned char keyValue){ if(keyValue==0xFF){ Delay10ms(1); return; } else if(keyValue==~block_map[cout]){ success=1; GPIO_NUM=~DIG_CODE[cout]; } else{ success=0; }}void Delay10ms(unsigned int c) //误差 0us{ unsigned char a, b; //--c已经在传递过来的时候已经赋值了,所以在for语句第一句就不用赋值了--// for (;c>0;c--) { for (b=38;b>0;b--) { for (a=130;a>0;a--); } } }
代码是在52单片机上跑的,其实程序挺简单,如果我老老实实地画程序流程图,就是小一天的工作量。麻烦就是在单片机的定时器和输入检测。
由于还没有实物,我们就只能用单片机去给负责检查的老师看。老师当时的表情是很嫌弃的。。
不管怎样,这说明了我们还是干活的,所以项目也得以成活了。看起来我接下来的活也比较轻松了,毕竟主体的逻辑应该不会大改,无非是让单片机输出音乐和添加别的一些功能了。但是突然有一天,老上司跟我说董啊,我们不能这么玩了,因为我们的核心部件,控制水流的电磁阀,单价实在是太高了。以我们要求的精度,可能一百元以上才能满足需求。这还是最低报价。而要形成水幕,没有三四十个估计够呛。
2
转眼到了十一,我回家的时候。老上司给我打了个电话,跟我说,嗨嗨嗨董我有一个新的Idea,又跟我说她在某某展会上看到了‘可爱的可以弹奏的苹果’,我们也可以实现啊。
那好吧,首先要给单片机加上侦测到触摸的功能是吧。。这个难了我们一段时间,但是后来在另一种单片机Arduino上找到了解决方案,简单地说,是在单片机几个模拟输入口上每一个都连出来一个大电阻,电阻另一端接5V电压。这样在正常情况下这个端口上的电压是最大的,如果人手触碰了输入口的话,电压会有一个不太明显的降幅。这是可以检测出来的。
另外一个额外的收获是,Arduino的接口够丰富,我们甚至可以自制一个简单的读卡器来播放一首歌。官方库在这里。
这时候我们的想法已经变成了一个检测人输入-去播放音符的钢琴了。前面写的代码只能扔到垃圾堆里了。。于是我磨了两天,又写了这一版。
#include<SimpleSDAudio.h>int InData1 = 0, InData2 = 0, InData3 = 0, InData4 = 0, InData5 = 0, InData0 = 0; //触摸输入值暂存int TouchSensitivity = 30; //触摸灵敏度。0~1023,越大越不灵敏char AudioFileName[16]; int noteDuration=1000;// Create static buffer #define BIGBUFSIZE (2*512) // bigger than 2*512 is often only possible on Arduino megas!uint8_t bigbuf[BIGBUFSIZE];void setup(){ Serial.begin(9600); for(int i = A0; i <= A5; i++) { pinMode(i, INPUT); //A0~A5端设置为输入 //digitalWrite(i, HIGH); //并且上拉 } TIMSK0 &= !(1 << TOIE0); SdPlay.setWorkBuffer(bigbuf, BIGBUFSIZE); SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO); }void loop(){ //读取所有引脚电压值,并且由于上拉电阻原因, //默认所有引脚为最高电平1023,通过触摸拉低引脚电平。 //所以数值由1024-analogRead(A0); InData0 = 1024 - analogRead(A0); InData1 = 1024 - analogRead(A1); InData2 = 1024 - analogRead(A2); InData3 = 1024 - analogRead(A3); InData4 = 1024 - analogRead(A4); InData5 = 1024 - analogRead(A5); //按照各种可能触发键盘事件 Serial.print("InData0="); Serial.println(InData0); Serial.print("InData1="); Serial.println(InData1); Serial.print("InData2="); Serial.println(InData2); Serial.print("InData3="); Serial.println(InData3); Serial.print("InData4="); Serial.println(InData4); Serial.print("InData5="); Serial.println(InData5); if(InData0 >= TouchSensitivity) { Serial.print("0"); SdPlay.setFile("1.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { SdPlay.worker(); } } if(InData1 >= TouchSensitivity) { Serial.print("1"); SdPlay.setFile("2.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { SdPlay.worker(); } } if(InData2 >= TouchSensitivity) { Serial.print("2"); SdPlay.setFile("3.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { SdPlay.worker(); } } if(InData3 >= TouchSensitivity) { Serial.print("3"); SdPlay.setFile("4.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { SdPlay.worker(); } } if(InData4 >= TouchSensitivity) { Serial.print("4"); SdPlay.setFile("5.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { SdPlay.worker(); }} if(InData5 >= TouchSensitivity) { Serial.println("5"); SdPlay.setFile("6.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { SdPlay.worker(); } }}
补充说明一下,这几个AFM文件是经过转换的音乐文件,对应相应的音调。
3
这时候在Arduino上运行效果已经有了。但是有一些比较棘手的问题没有解决:比如,如果人手一直摸着这根线的话,这个音乐会被重复播放。。这里还是缺一个逻辑检测人手的放开。还有一点需要注意的是,Arduino兼容板的端口稳定性并不怎么好,各个端口的检测值就不一样。。而且经常出现波动。
于是又对第一版进行修改,边改边试,这里多说一句:好恶心。。因为有些时候你检查了很多遍代码,可是后来发现是稳定性问题。。
代码有些地方很冗余,是需要甄别、批判的
#include<SimpleSDAudio.h>int InData1 = 0, InData2 = 0, InData3 = 0, InData4 = 0, InData5 = 0, InData0 = 0; //touch value int flag0=1,flag1=1,flag2=1,flag3=1,flag4=1,flag5=1;//int empty[5]={0,0,0,0,0};int TouchSensitivity = 23; //0~1023char AudioFileName[16];//follow state is going to int sub=0;int a0[3]={0}; int test=0; const int test_time=100; const int empty_counter=10; int flags=0; const int flag_counters=10;// Create static buffer #define BIGBUFSIZE (2*512) // bigger than 2*512 is often only possible on Arduino megas!uint8_t bigbuf[BIGBUFSIZE];/*int i=23;int j=28;int testother(int cur){ i=23; j=28; while(i<cur){ if(analogRead(i)<1000) { return 0; } i++; } while(j>cur){ if(analogRead(j)<1000) { return 0; } j--; } return 1;} */void setup(){ Serial.begin(9600); for(int i = A0; i <= A5; i++) { pinMode(i, INPUT); //A0~A5端设置为输入 //digitalWrite(i, HIGH); //并且上拉 } TIMSK0 &= !(1 << TOIE0); SdPlay.setWorkBuffer(bigbuf, BIGBUFSIZE); if(! SdPlay.init(SSDA_MODE_FULLRATE | SSDA_MODE_MONO)){ Serial.println(SdPlay.getLastError()); } else Serial.println("initial complete"); }void loop(){ //读取所有引脚电压值,并且由于上拉电阻原因, //默认所有引脚为最高电平1023,通过触摸拉低引脚电平。 //所以数值由1024-analogRead(A0); InData0 = 1024 - analogRead(A0); InData1 = 1024 - analogRead(A1); InData2 = 1024 - analogRead(A2); InData3 = 1024 - analogRead(A3); InData4 = 1024 - analogRead(A4); InData5 = 1024 - analogRead(A5); //按照各种可能触发键盘事件 Serial.print("InData0="); Serial.println(InData0); Serial.print("InData1="); Serial.println(InData1); Serial.print("InData2="); Serial.println(InData2); Serial.print("InData3="); Serial.println(InData3); Serial.print("InData4="); Serial.println(InData4); Serial.print("InData5="); Serial.println(InData5); /*if the system detector that there is no signal during one time,then it give the everyone right*/ if(InData0<30&&InData1<30&&InData2<30&&InData3<30&&InData4<30&&InData5<30) { flags++; if(flags==flag_counters){ flags=0; flag0=flag1=flag2=flag3=flag4=flag5=1; Serial.println("no keys on"); } } else{ flags=0; } Serial.print("flags="); Serial.println(flags); if(InData0 >= TouchSensitivity) { Serial.print("0"); if(flag0){ flag0=0; SdPlay.setFile("1.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { test++; if(test==test_time){ test=0; if(!(analogRead(A1)>1000&&analogRead(A2)>1000&&analogRead(A3)>1000&&analogRead(A4)>1000&&analogRead(A5)>1000)){ flag0=1; Serial.println("other key!"); break; } if(analogRead(A0)>1020){ empty[0]++; if(empty[0]==empty_counter){ empty[0]=0; flag0=1; Serial.println("you have put away your hand!"); break; } } else{ empty[0]=0; } } SdPlay.worker(); } } } if(InData1 >= TouchSensitivity) { Serial.print("1"); if(flag1){ flag1=0; SdPlay.setFile("2.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { test++; if(test==test_time){ test=0; if(!(analogRead(A0)>1000&&analogRead(A2)>1000&&analogRead(A3)>1000&&analogRead(A4)>1000&&analogRead(A5)>1000)){ flag1=1; break; } if(analogRead(A1)>1020){ empty[1]++; if(empty[1]==empty_counter){ empty[1]=0; flag1=1; Serial.println("you have put away your hand from 1"); break; } } else{ empty[1]=0; } //if you move your hand from this key away during the note is played,then make flag true } SdPlay.worker(); } } } if(InData2 >= TouchSensitivity) { Serial.print("2"); if(flag2){ flag2=0; SdPlay.setFile("3.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { test++; if(test==test_time){ test=0; if(!(analogRead(A1)>1000&&analogRead(A0)>1000&&analogRead(A3)>1000&&analogRead(A4)>1000&&analogRead(A5)>1000)){ flag2=1; break; } if(analogRead(A2)>1020){ empty[2]++; if(empty[2]==empty_counter){ empty[2]=0; flag2=1; Serial.println("you have put away your hand!"); break; } } else{ empty[2]=0; } /* if(analogRead(A2)>1000){ flag2=1; } else if(analogRead(A2)<1000&&flag2){ break; } */ } SdPlay.worker(); } } } if(InData3 >= TouchSensitivity) { Serial.print("3"); if(flag3){ flag3=0; SdPlay.setFile("4.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { test++; if(test==test_time){ test=0; if(!(analogRead(A1)>1000&&analogRead(A2)>1000&&analogRead(A0)>1000&&analogRead(A4)>1000&&analogRead(A5)>1000)){ flag3=1; break; } if(analogRead(A3)>1020){ empty[3]++; if(empty[3]==empty_counter){ empty[3]=0; flag3=1; Serial.println("you have put away your hand!"); break; } } else{ empty[3]=0; } /* if(analogRead(A3)>1000){ flag3=1; } else if(analogRead(A3)<1000&&flag3){ break; }*/ } SdPlay.worker(); } } } if(InData4 >= TouchSensitivity) { Serial.print("4"); if(flag4){ flag4=0; SdPlay.setFile("5.AFM"); SdPlay.play(); while(!SdPlay.isStopped()) { test++; if(test==test_time){ test=0; if(!(analogRead(A1)>990&&analogRead(A2)>990&&analogRead(A3)>990&&analogRead(A0)>990&&analogRead(A5)>990)){ flag4=1; break; } if(analogRead(A4)>1020){ empty[4]++; if(empty[4]==empty_counter){ empty[4]=0; flag4=1; Serial.println("you have put away your hand!"); break; } } else{ empty[4]=0; } /*if(analogRead(A4)>1000){ flag4=1; } else if(analogRead(A4)<1000&&flag4){ break; }*/ } SdPlay.worker(); } }} if(InData5 >= TouchSensitivity) { Serial.println("5"); SdPlay.setFile("6.AFM"); SdPlay.play(); if(flag5){ flag5=0; while(!SdPlay.isStopped()) { test++; if(test==test_time){ test=0; if(!(analogRead(A1)>1000&&analogRead(A2)>1000&&analogRead(A3)>1000&&analogRead(A4)>1000&&analogRead(A0)>1000)){ flag5=1; break; } if(analogRead(A5)>1020){ empty[5]++; if(empty[5]==empty_counter){ empty[5]=0; flag5=1; Serial.println("you have put away your hand!"); break; } } else{ empty[5]=0; } /*if(analogRead(A5)>1000){ flag5=1; } else if(analogRead(A5)<1000&&flag5){ break; }*/ } SdPlay.worker(); } } } }
简单地说,新增加的部分就是在播放的期间检测别的按键的值,如果有的话,就跳出当前音乐的播放。
当初还出了一件特变尴尬的事情,我们在寒假之前其实就已经把这个Demo做出来了,但是后来在寒假管理人员清理的时候被当作垃圾处理掉了。。谁让我们当初给老师检查之后就开开心心跑了,没有把它锁柜子里。Sigh。。
4
有一天老上司又跟我打电话,说董啊,这个东西也许我们还得再改改。我说咋了,她说这东西弹奏的时候后一个音符会打乱第二个音符,这个并不是我们想要的效果。还有啊,候妈我们提了一些建议,比如说:PM2.5上传?温湿度上传?哎呀,反正都要有的啦。。
做乙方好苦啊哭。。。
这些东西想要搞,那就得能联网对吧。。而且还得过学校的网关。WTF。真的不是在为难单片机么。
把wifi模块接上其实就已经是一件令人蛋碎的过程。我们选用的wifi模块是esp8266,我还记得当初用的这个驱动库
照着说明书接了之后,还是有问题。后来换了好几个库,都不好用。后来转回来,把原文中提到的两个端口2和3试探性地换成了别的口,居然成了。。。
网关的问题也解决了,我去寻找了一下校园网登陆的脚本,发现基本原理其实是向网关发送一个POST请求。于是我又去下了一个WireShark,然后截到了这个请求
登陆网关所发送的数据POST / HTTP/1.1Host: 10.3.8.211Connection: keep-aliveContent-Length: 47Cache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Origin: http://10.3.8.211User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 BIDUBrowser/6.x Safari/537.36Content-Type: application/x-www-form-urlencodedReferer: http://10.3.8.211/Accept-Encoding: gzip,deflateAccept-Language: zh-CN,zh;q=0.8Cookie: myusername=2012211289; pwd=260077; username=2012211289; smartdot=260077DDDDD=2012211289&upass=260077&savePWD=0&0MKKey=
当初做的wireshark笔记也贴一下,满足一下我的虚荣心嘛~
【wireshark 过滤规则】1.例子:ip.src eq 192.168.1.107 or ip.dst eq 192.168.1.107或者ip.addr eq 192.168.1.107 // 都能显示来源IP和目标IP2.端 口例子:tcp.port eq 80 // 不管端口是来源的还是目标的都显示tcp.port == 80tcp.port eq 2722tcp.port eq 80 or udp.port eq 80tcp.dstport == 80 // 只显tcp协议的目标端口80tcp.srcport == 80 // 只显tcp协议的来源端口803.协议tcp udp arp icmp http ftp dns ip6.http 模式过滤例子:http.request.method == "GET"http.request.method == "POST"http.request.uri == "/img/logo-edu.gif"http contains "GET"http contains "HTTP/1."// GET包http.request.method == "GET" && http contains "Host: "http.request.method == "GET" && http contains "User-Agent: "// POST包http.request.method == "POST" && http contains "Host: "http.request.method == "POST" && http contains "User-Agent: "// 响应包http contains "HTTP/1.1 200 OK" && http contains "Content-Type: "http contains "HTTP/1.0 200 OK" && http contains "Content-Type: "一 定包含如下Content-Type:例子:udp.length == 26 这个长度是指udp本身固定长度8加上udp下面那块数据包之和tcp.len >= 7 指的是ip数据包(tcp下面那块数据),不包括tcp本身ip.len == 94 除了以太网头固定长度14,其它都算是ip.len,即从ip本身到最后frame.len == 119 整个数据包长度,从eth开始到最后eth ---> ip or arp ---> tcp or udp ---> data
当时我的内心激动不已,最难的问题终于解决了!诶,等等,还有“哎呀,这个音符播放时会打断上一个的播放啊”这个问题呢。。我的第一想法是用线程去实现,但是尝试了一下网上少有的可怜的Arduino线程库之后发现并不行得通。
于是我做打死想去修改别人的库,就是前面提到的那个。我的想法是,既然第二个音符会打断第一个音符,是不是可以把第一个音符断点的后半部分加到第二个音符文件要播放的缓存区里面。。
我还专门去查了一下音乐的相关知识和WAV的文件格式(AFM其实就是它的单片机版本),欣喜地发现靠四则运算应该是可以解决的。
后来尝试的时候发现库的主体是用汇编写的。而且基本没有什么注释。。。。那个时候我们已经上过微机原理与接口这门课,汇编我也会一些。可是后来发现自己想尝试去修改还是太难了。
这几天其实是人最不好的日子,最后的检查就要到了,我们做的基本上和三个月前没有什么该进。而很多组都已经做得比较成熟了。我的心里也很动摇。
5
事情还是发生了转机,在我们那天在大创基地里面焊线的时候,我看到了一个盒子,上面写着Raspberry,出于好奇,我就搜索了这个玩意。。结果发现。。这个卡片大小的“单片机”,居然跑的是Liunx!
这意味着这个东西是一台完整的拥有操作系统的计算机,而且,我可以使用Python!
这里说明一点,其实之前我对Python并无多少认识,只是见识过一些方便好用的Python脚本。比如我之前提到的在网上看到的网关登录,就是一个鲜明、好看的Python实例。
那一天晚上我们正好开会,我忍不住把这个发现告诉了我的老上司。老上司心情也不好,随意说了一句,那你动手去做它啊。我怔了一下,心里又有些犹豫。我们现在的虽然有些简陋,但是已经做了大半了,真的是要重新推翻重来么,而且,时间在这里摆着,来得及么。
我回去仔细想了想,觉着剩下的那些任务用Arduino也不是不能做,但是可靠性堪忧。很有可能一个小问题就把我们拖死在上面了。衡量了一下之后,我决定还是把之前的代码都推翻,在树莓派上重新用Python来写。
第二天我就钻到了大创底下,借了基本树莓派的书,用了小半天时间把它跑起来。边玩边赞叹,HDMI,3.5mm,USBhub,这些东西一个都不少。接上显示器你根本想象不来它的本体其实只有身份证那么大。
我的想法是,获取输入的那一部分工作依然由Arduino来做,然后通过Serial将消息传给树莓派,由树莓派实现播放文件的工作。Arduino简化后的代码我就不贴出来了,看看树莓派的。
import serialimport pygames=serial.Serial('/dev/ttyUSB0',9600)pygame.mixer.init()t=[]t1=pygame.mixer.Sound("/home/pi/ogg/1.ogg")t2=pygame.mixer.Sound("/home/pi/ogg/2.ogg")t3=pygame.mixer.Sound("/home/pi/ogg/3.ogg")t4=pygame.mixer.Sound("/home/pi/ogg/4.ogg")t5=pygame.mixer.Sound("/home/pi/ogg/5.ogg")t6=pygame.mixer.Sound("/home/pi/ogg/6.ogg")t7=pygame.mixer.Sound("/home/pi/ogg/7.ogg")t.append(t1);t.append(t2);t.append(t3);t.append(t4);t.append(t5);t.append(t6);t.append(t7);while 1: c=s.read() print c t[int(c)].play()
有没有发现。非常简单!其中比较重要的一个类是pygame,这里有它的文档。
测试了一下,情况很不错。我们都挺开心的。老上司说我们现在的任务是把水加上。我们觉得实现也应该没有什么问题,现在的输入模式是人手碰导线。我们之前测试过,如果把水浇在导线头上,用手去碰水,也是可以响应的!
结果还是发现了一个灰常灰常致命的问题!
问题就出现在水泵上!
因为水泵工作时和人一样本身就是接地的,水泵在工作中其实就会扮演了人的角色。。。这还输入?都用不着输入了,8个输入口全部都响应了。
我们还尝试想设计各种模型,基本的想法是让水在中间断流。。但是后来的结论是,除非做到绝对的严密性,否则不可能。
【留图】
那一次的验收也非常不开心。。唉不说了。
6
现在怎么办?我的心基本上是崩溃的。。。大家都快哭了。
费劲了这么多心思,到最后却要放弃了么?
我们纠结了很久,有人提出了折衷的方案,就是光传感。原理就是检测到物体在某个范围之内就触发低电平。
我其实心里是不情愿的,因为这个违背了和水的“直接交互”。我和云鹏心里已经萌生退意了,我建议,干脆弃了得了,当时确实有很多人已经放弃了项目。可是老上司不同意。
考虑到我们的水竖琴大小问题,我没有继续使用Arduino来做传感部分了,而是直接把光传感器接到了树莓派上(这其实是导致了一些小问题的,后面再说)。
import RPi.GPIO as gpioimport timeimport pygameMAX_TIME=5000gpio.setwarnings(False)gpio.setmode(gpio.BOARD)time.sleep(1)pin=[]flag=[]dur=[]pin.append(7)pin.append(11)pin.append(12)pin.append(13)pin.append(15)pin.append(16)pin.append(18)pin.append(22)pygame.mixer.init()t1=pygame.mixer.Sound("/home/pi/ogg/1.ogg")t2=pygame.mixer.Sound("/home/pi/ogg/2.ogg")t3=pygame.mixer.Sound("/home/pi/ogg/3.ogg")t4=pygame.mixer.Sound("/home/pi/ogg/4.ogg")t5=pygame.mixer.Sound("/home/pi/ogg/5.ogg")t6=pygame.mixer.Sound("/home/pi/ogg/6.ogg")t7=pygame.mixer.Sound("/home/pi/ogg/7.ogg")t8=pygame.mixer.Sound("/home/pi/ogg/8.ogg")t=[]t.append(t1)t.append(t2)t.append(t3)t.append(t4)t.append(t5)t.append(t6)t.append(t7)t.append(t8)for i in range(0,7): flag.append(0) dur.append(0) gpio.setup(pin[i],gpio.IN)print('makey2 has started')while 1: for i in range(0,7): if gpio.input(pin[i])==0: if flag[i]==0: t[i].play() flag[i]=1 print(pin[i]) else: if flag[i]==1: dur[i]+=1 if dur[i]==MAX_TIME: flag[i]=0 dur[i]=0
注意,上一个方案树莓派和Arduino的结合中,树莓派只负责了播放器音乐的部分,由于线程的独立性,我们已经不用担心第二个音符打断第一个音符这个问题了。最后一个循环检测中有额外的逻辑,是为了让一段时间之内音乐只能奏响一次。
7
接下来,我还有数据上传和网站的工作要做。
树莓派上的代码
# -*- coding: utf-8 -*-import socketimport jsonimport RPi.GPIO as gpioimport timepin=11gpio.setwarnings(False)gpio.setmode(gpio.BOARD)time.sleep(1)data=[]def delay(i): #20*i usdelay a=0 for j in range(i): a+1j=0#start workwhile 1: time.sleep(0.05) gpio.setup(pin,gpio.OUT) gpio.output(pin,gpio.HIGH) time.sleep(0.1) gpio.output(pin,gpio.LOW) time.sleep(0.05) gpio.output(pin,gpio.HIGH) i=1 i=1 #wait to response gpio.setup(pin,gpio.IN) while gpio.input(pin)==1: continue while gpio.input(pin)==0: continue while gpio.input(pin)==1: continue #get data while j<40: k=0 while gpio.input(pin)==0: continue while gpio.input(pin)==1: k+=1 if k>100:break if k<5: data.append(0) else: data.append(1) j+=1 print "Sensor is working" #get temperature humidity_bit=data[0:8] humidity_point_bit=data[8:16] temperature_bit=data[16:24] temperature_point_bit=data[24:32] check_bit=data[32:40] humidity=0 humidity_point=0 temperature=0 temperature_point=0 check=0 for i in range(8): humidity+=humidity_bit[i]*2**(7-i) humidity_point+=humidity_point_bit[i]*2**(7-i) temperature+=temperature_bit[i]*2**(7-i) temperature_point+=temperature_point_bit[i]*2**(7-i) check+=check_bit[i]*2**(7-i) tmp=humidity+humidity_point+temperature+temperature_point if check==tmp: print "temperature is ", temperature,"wet is ",humidity,"%" # timestamp=time.strftime('%Y-%m-%d-%H-%M-%S',time.localtime(time.time())) obj={'temperature':temperature,'humidity':humidity} encodejson=json.dumps(obj) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('10.110.211.207', 10009)) time.sleep(2) sock.send(encodejson) sock.close() else: print "something is worong the humidity,humidity_point,temperature,temperature_point,check is",humidity,humidity_point,temperature,temperature_point,check
网站的部分
还好我之前自学过一点BootStrap和php,这个并没有太难倒我。
最后实现的网站在这里。(现在没有新的数据上传上去,显示会有些不正常)
信息接受部分
<?php //这个版本可以保存收集到的信息 在data.txt底下 //确保在连接客户端时不会超时 //连接数据库 include_once '/conn/conn.php'; set_time_limit(0); date_default_timezone_set('PRC'); $ip = '23.226.230.106'; $port = 10011; /* +------------------------------- * @socket通信整个过程 +------------------------------- * @socket_create * @socket_bind * @socket_listen * @socket_accept * @socket_read * @socket_write * @socket_close +-------------------------------- */ /*---------------- 以下操作都是手册上的 -------------------*/ if(($sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) { echo "socket_create() cause:".socket_strerror($sock)."\n"; } if(($ret = socket_bind($sock,$ip,$port)) < 0) { echo "socket_bind() cause:".socket_strerror($ret)."\n"; } if(($ret = socket_listen($sock,4)) < 0) { echo "socket_listen() cause:".socket_strerror($ret)."\n"; } do { if (($msgsock = socket_accept($sock)) < 0) { echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n"; break; } else { //发到客户端 $msg ="I am server\n"; socket_write($msgsock, $msg, strlen($msg)); $buf = socket_read($msgsock,8192); $talkback = "$buf"; echo $talkback; // $datafile='data.txt' // $fp=fopen($datafile,a); // fwrite($fp,$talkback); // fclose($fp); // //将talkback中的值写入到数据库中去 // $data=explode(" ",$talkback); // print_r($data); $data=json_decode($talkback); var_dump($data); $currentday=date("Y-m-d-H-i-s"); $query="insert into pi values ('$currentday',$data->temperature,$data->humidity,0);"; $result=mysql_query($query); var_dump($result); } //echo $buf; socket_close($msgsock); } while (1); socket_close($sock); ?>
conn.php
<?php $con = mysql_connect("localhost","root","");if (!$con) { die('Could not connect: ' . mysql_error()); } mysql_select_db("data",$con);?>
前端
<!DOCTYPE html><html><head> <meta charset="utf-8"> <!-- Bootstrap --> <link href="css/bootstrap.css" rel="stylesheet"> <!--viewport--> <meta name="viewport" content="width=device-width, initial-scale=1,height=device-height"> <!--shit jianmingzihaoyanghuo--> <link rel="stylesheet" href="css/shit.css"> <!--Chartjs--></head><script>var jsdata=new Array();var jsdate=new Array();var date="";var temp=0;//---3-26----var humidity;var temperature;//---end of 3-26</script><?php date_default_timezone_set('PRC'); $currentday=date("Y-m-d"); // echo '当前日期是'.$currentday; $yesterday_time=strtotime("-8 days"); $yesterday=date("Y-m-d",$yesterday_time); // echo '昨天的日期是'.$yesterday; include_once 'conn/conn.php'; $query="select * from pmdata where date >'".$yesterday."' order by date;"; // echo $query; $result=mysql_query($query); while($row =mysql_fetch_array($result,MYSQL_ASSOC)){?><script> temp=<?php echo $row['pm']?>; date="<?php echo $row['date']?>"; jsdata.push(temp); jsdate.push(date);</script><?php } ?><?php $query2="select max(date),temperature,humidity from pi;"; $result2=mysql_query($query2); $row2=mysql_fetch_array($result2,MYSQL_ASSOC);?><script> humidity=<?php echo $row2['humidity'] ?>; temperature=<?php echo $row2['temperature'] ?>;</script><!-- //数据图表 --><!-- <div style="width: 50%"> <canvas id="canvas" height="450" width="600"></canvas></div> --><!-- //新的数据图表 本周的天气 --> <!-- <h1>你好,世界!</h1> --> <div id="carousel-example-generic" class="carousel slide" data-ride="carousel" id="slide"> <!-- Indicators --> <ol class="carousel-indicators"> <li data-target="#carousel-example-generic" data-slide-to="0" class="active"></li> <li data-target="#carousel-example-generic" data-slide-to="1"></li> <li data-target="#carousel-example-generic" data-slide-to="2"></li> </ol> <!-- Wrapper for slides --> <div class="carousel-inner" role="listbox" id="slide"> <div class="item active slide1" id="slide1"> <div class="canvas_container"> <canvas id="canvas" height="50%" width="100%"></canvas> </div> <div class="carousel-caption"> <h2 font="40px">近一周数据</h2> <!-- <canvas style="z-index:5" id="canvas" height="450" width="600"></canvas> --> </div> </div> <div class="item slide2" > <div class="container pic_container"> <h1 id="todaydata"> </h1> </div> <div class="carousel-caption"> <h2>今日最新数据</h2> </div> </div> <div class="item slide3" > <div class="container pic_container"> <h1 id="temperature"></h1> <h1 id ="humidity"></h1> </div> <div class="carousel-caption"> <h2>目前室内温度 湿度</h2> </div> </div> <!-- Controls --> <a class="left carousel-control" href="#carousel-example-generic" role="button" data-slide="prev"> <span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> <span class="sr-only">Previous</span> </a> <a class="right carousel-control" href="#carousel-example-generic" role="button" data-slide="next"> <span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span> <span class="sr-only">Next</span> </a></div> <script src="js/Chart.js"></script> <script src="js/myChart.js"></script> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="http://cdn.bootcss.com/jquery/1.11.2/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="js/bootstrap.min.js"></script></body></html>
Chart.js是一个Bootstrap推荐的图表库。
myChart.js
var randomScalingFactor = function(){ return Math.round(Math.random()*100)}; var lineChartData = { labels:jsdate, datasets : [ { fillColor : "rgba(220,220,220,0.5)", strokeColor : "rgba(220,220,220,0.8)", highlightFill: "rgba(220,220,220,0.75)", highlightStroke: "rgba(220,220,220,1)", data:jsdata } ] } // lineChartData.labels=jsdate; // lineChartData.datasets.data=jsdata; var afunction =function(){ var ctx = document.getElementById("canvas").getContext("2d"); window.myBar = new Chart(ctx).Line(lineChartData, { responsive : true }); } var bfunction=function(){ var todaypm=jsdata[jsdata.length-1]; document.getElementById("todaydata").innerHTML=todaypm+"\npm"; } var cfunction=function(){ document.getElementById("temperature").innerHTML=temperature+"℃"; document.getElementById("humidity").innerHTML=humidity+"%"; } // var dfunction=function(){ // document.getElementById("slide1").height=window.screen.height; // document.getElementById("slide2").height=window.screen.height; // document.getElementById("slide3").height=window.screen.height; // } window.onload = function(){ afunction(); bfunction(); cfunction(); // dfunction(); }
- 我的大创之旅
- 你我的大数据之旅
- 我的大数据学习之路
- 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!
- 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!
- 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!
- 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!
- 我的Android进阶之旅:经典的大牛博客推荐
- 我的Android进阶之旅:经典的大牛博客推荐
- 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!
- 我的Android进阶之旅:经典的大牛博客推荐
- 我的Android进阶之旅------>经典的大牛博客推荐(排名不分先后)!!
- 《我与大数据的365天》 ——(1 ) 【开启探索数据之旅】
- 世界之大,竟没有我立足的地方
- 我的大数据之路(一)
- 我的hadoop大数据之路(一)
- 我的hadoop大数据之路(二)
- 我的大数据之路(三):HDFS文件系统
- STC DATAFLASH 模拟EEPROM
- java的类模板,对象,实例内存地址分析
- 手斧Linux – 从LFS到Funtoo (77)
- 手斧Linux – 从LFS到Funtoo (78)
- 手斧Linux – 从LFS到Funtoo (79)
- 我的大创之旅
- 手斧Linux – 从LFS到Funtoo (80)
- 手斧Linux – 从LFS到Funtoo (81)
- 手斧Linux – 从LFS到Funtoo (82)
- 手斧Linux – 从LFS到Funtoo (83)
- 手斧Linux – 从LFS到Funtoo (84)
- 手斧Linux – 从LFS到Funtoo (85)
- 手斧Linux – 从LFS到Funtoo (86)
- 手斧Linux – 从LFS到Funtoo (87)