再Centos7上安装python3

第一步:安装相关依赖包和编译环境

$>yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel gcc

(注意:这一步很重要,如果不安装相关依赖包,在使用pip安装python包时会报找不到SSL错误!)

第二步:下载python3.8.0安装包

$>wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tar.xz

第三步:解压安装包并创建安装目录

$>xz -d Python-3.8.0.tar.xz

$>tar -xvf Python-3.8.0.tar

$>mkdir /usr/local/python3.8.0

第四步:编译安装

$>cd Python-3.8.0

$>./configure –with-ssl –prefix=/usr/local/python3.8.0

(注意:prefix后面配置第三步中创建的路径,且等号两边不能有空格,不然会报错)

$>make && make install

第五步:创建python3.6.5软链接

$>ln -s /usr/local/python3.8.0/bin/python3.8 /usr/bin/python3

$>ln -s /usr/local/python3.8.0/bin/pip3.8 /usr/bin/pip3

$>pip3 install –upgrade pip(升级pip3)

第六步:修改python2.7.5软链接(这一步可有可无)

$>mv /usr/bin/python /usr/bin/python2

第七步:验证,使用python3进入python3.8.0命令行

$>python3

Centos7系统编译安装PHP7.4

最近买了个盒子,拆开一看是用了一家已经倒闭的某公司的产品,拆了板子重新烧录了Centos7系统装了宝塔再加了个新壳子,首先应该肯定的是卖家会做生意,不敢苟同的是里面一堆坑要踩,当然想买这种板子的人都是爱折腾的,估计卖家也是吃准了这一点。我一个centos新手(拿到这个盒子之前从来没摸过centos)被折腾的够呛,本来买盒子就一个目的,架一个个人博客(当然选wordpress),结果盒子自带的php版本太低,wordpress直接警告,随后按卖家文档升级,于是坑一个接一个。。。。。说说多了,最后总结,这玩意只能编译安装,步骤如下:
安装依赖包
yum install libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl-devel libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel gmp gmp-devel libmcrypt libmcrypt-devel readline readline-devel libxslt libxslt-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel ncurses curl gdbm-devel db4-devel libXpm-devel libX11-devel gd-devel gmp-devel expat-devel xmlrpc-c xmlrpc-c-devel libicu-devel libmcrypt-devel libmemcached-devel
下载安装包
wget https://www.php.net/distributions/php-7.4.10.tar.gz
解压
tar -xf php-7.4.10.tar.gz
进入目录
cd php-7.4.10
安装配置
./configure \
--prefix=/usr/local/php7.1.33 \
--enable-fpm \
--enable-inline-optimization \
--disable-debug \
--disable-rpath \
--enable-shared \
--enable-soap \
--with-libxml-dir \
--with-xmlrpc \
--with-openssl \
--with-mcrypt \
--with-mhash \
--with-pcre-regex \
--with-sqlite3 \
--with-zlib \
--enable-bcmath \
--with-iconv \
--with-bz2 \
--enable-calendar \
--with-curl \
--with-cdb \
--enable-dom \
--enable-exif \
--enable-fileinfo \
--enable-filter \
--with-pcre-dir \
--enable-ftp \
--with-gd \
--with-openssl-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib-dir \
--with-freetype-dir \
--enable-gd-native-ttf \
--enable-gd-jis-conv \
--with-gettext \
--with-gmp \
--with-mhash \
--enable-json \
--enable-mbstring \
--enable-mbregex \
--enable-mbregex-backtrack \
--with-libmbfl \
--with-onig \
--enable-pdo \
--with-mysqli=mysqlnd \
--with-pdo-mysql=mysqlnd \
--with-zlib-dir \
--with-pdo-sqlite \
--with-readline \
--enable-session \
--enable-shmop \
--enable-simplexml \
--enable-sockets \
--enable-sysvmsg \
--enable-sysvsem \
--enable-sysvshm \
--enable-wddx \
--with-libxml-dir \
--with-xsl \
--enable-zip \
--enable-mysqlnd-compression-support \
--with-pear \
--enable-opcache
编译安装
make && make install

Centos8 编译安装 PHP 8.1.0 – 简书 (jianshu.com)

如果是源码安装,先运行./configure,生成makefile,再执行make,即可正常运行

Centos8 编译安装 PHP 8.1.0

安装工具包

yum -y install libtool automake libzip-devel epel-release libxml2 libxml2-devel openssl openssl-devel curl-devel libjpeg-devel libpng-devel freetype-devel libmcrypt-devel uuid libuuid-devel gcc bzip2 bzip2-devel gmp-devel  readline-devel libxslt-devel autoconf bison gcc gcc-c++ sqlite-devel cmake

下载安装 oniguruma

wget -c https://github.com/kkos/oniguruma/archive/refs/tags/v6.9.7.1.tar.gz
tar -zxvf v6.9.7.1.tar.gz
cd oniguruma-6.9.7.1
./autogen.sh && ./configure --prefix=/usr
make && make install

下载安装 php 8.1.0

wget -c  https://www.php.net/distributions/php-8.1.0.tar.gz
tar -zxvf php-8.1.0.tar.gz
cd php-8.1.0
./configure \
--prefix=/opt/php/8.1.0 \
--with-config-file-path=/opt/php/8.1.0/etc \
--with-config-file-scan-dir=/opt/php/8.1.0/etc/conf.d \
--enable-fpm \
--enable-soap \
--with-openssl \
--with-openssl-dir \
--with-zlib \
--with-iconv \
--with-bz2 \
--enable-gd \
--with-jpeg \
--with-freetype \
--with-curl \
--enable-dom \
--with-xml \
--with-zip \
--enable-mbstring \
--enable-pdo \
--with-pdo-mysql \
--with-zlib-dir \
--enable-session \
--enable-shmop \
--enable-simplexml \
--enable-sockets \
--enable-sysvmsg \
--enable-sysvsem \
--enable-sysvshm \
--with-xsl \
--enable-mysqlnd \
--with-mysqli \
--without-pear \
--disable-short-tags
make && make install
Installing shared extensions:     /opt/php/8.1.0/lib/php/extensions/no-debug-non-zts-20210902/
Installing PHP CLI binary:        /opt/php/8.1.0/bin/
Installing PHP CLI man page:      /opt/php/8.1.0/php/man/man1/
Installing PHP FPM binary:        /opt/php/8.1.0/sbin/
Installing PHP FPM defconfig:     /opt/php/8.1.0/etc/
Installing PHP FPM man page:      /opt/php/8.1.0/php/man/man8/
Installing PHP FPM status page:   /opt/php/8.1.0/php/php/fpm/
Installing phpdbg binary:         /opt/php/8.1.0/bin/
Installing phpdbg man page:       /opt/php/8.1.0/php/man/man1/
Installing PHP CGI binary:        /opt/php/8.1.0/bin/
Installing PHP CGI man page:      /opt/php/8.1.0/php/man/man1/
Installing build environment:     /opt/php/8.1.0/lib/php/build/
Installing header files:          /opt/php/8.1.0/include/php/
Installing helper programs:       /opt/php/8.1.0/bin/
  program: phpize
  program: php-config
Installing man pages:             /opt/php/8.1.0/php/man/man1/
  page: phpize.1
  page: php-config.1
/home/centos/php-8.1.0/build/shtool install -c ext/phar/phar.phar /opt/php/8.1.0/bin/phar.phar
ln -s -f phar.phar /opt/php/8.1.0/bin/phar
Installing PDO headers:           /opt/php/8.1.0/include/php/ext/pdo/

启动和后续配件可以参考这里
Centos7 编译 PHP7.4.16 – 简书 (jianshu.com)


增加扩展

进入刚才的解压目录

#安装GD库示例
cd php-8.1.0/ext/gd
/opt/php/8.1.0/bin/phpize
Configuring for:
PHP Api Version:         20210902
Zend Module Api No:      20210902
Zend Extension Api No:   420210902
./configure --with-php-config=/opt/php/8.1.0/bin/php-config
make && make install

完成后记得在 php.ini 里加入 extension=gd.so

PHP8 主要不向后兼容的变更

  • 字符串与数字的比较
    数字与非数字形式的字符串之间的非严格比较现在将首先将数字转为字符串,然后比较这两个字符串。
    数字与数字形式的字符串之间的比较仍然像之前那样进行。

请注意,这意味着 0 == “not-a-number” 现在将被认为是 false 。

ComparisonBeforeAfter
0 == “0”truetrue
0 == “0.0”truetrue
0 == “foo”truefalse
0 == “”truefalse
42 == ” 42″truetrue
42 == “42foo”truefalse
  • match 现在是一个保留字。
  • 断言(Assertion)失败现在默认抛出异常。如果想要改回之前的行为,可以在 INI 设置中设置 assert.exception=0
  • 与类名相同的方法名将不再被当做构造方法。应该使用__construct() 来取代它。
  • 不再允许通过静态调用的方式去调用非静态方法。因此is_callable()在检查一个类名与非静态方法 时将返回失败(应当检查一个类的实例)。
  • (real)(unset) 转换已被移除

PHP8 主要新特性

命名参数

新增命名参数的功能。

注解(Attributes)

新增注解的功能。

构造器属性提升(Constructor Property Promotion)

新增构造器属性提升功能 在构造函数中声明类的属性)。

联合类型

新增 联合类型

Match 表达式

新增 match 表达式

Nullsafe 运算符

新增Nullsafe 运算符(?->)。

其他新特性

  • 新增 WeakMap 类。
  • 新增 ValueError 类。
  • 现在,只要类型兼容,任意数量的函数参数都可以用一个可变参数替换。 例如允许编写下面的代码:

<?php class A {
         public function method(int $many, string $parameters, $here) {}
    }
    class B extends A {
         public function method(...$everything) {}
    } ?>
  • static (“后期静态绑定”中) 可以作为返回类型:

    <?php class Test {
         public function create(): static {
              return new static();
         }
    } ?>
  • 现在可以通过 $object::class 获取类名,返回的结果和 get_class($object) 一致。
  • newinstanceof 可用于任何表达式, 用法为 new (expression)(...$args)$obj instanceof (expression)
  • 添加对一些变量语法一致性的修复,例如现在能够编写 Foo::BAR::$baz
  • 添加 Stringable interface, 当一个类定义 __toString() 方法后会自动实现该接口。
  • Trait 可以定义私有抽象方法(abstract private method)。 类必须实现 trait 定义的该方法。
  • 可作为表达式使用 throw。 使得可以编写以下用法:

    <?php
    $fn = fn() => throw new Exception('Exception in arrow function'); $user = $session->user ?? throw new Exception('Must have user');
  • 参数列表中的末尾逗号为可选。

    <?php function functionWithLongSignature(
        Type1 $parameter1,
        Type2 $parameter2, // <-- 这个逗号也被允许了 ) {
    }
  • 现在允许 catch (Exception) 一个 exception 而无需捕获到变量中。
  • 支持 mixed 类型。

开启树莓派看门狗

树莓派内核默认没有启用看门狗功能,当内核挂死时将进入“死机”状态或kgdb调试状态,并不会自动重启系统。本文为树莓派开启看门狗功能并通过内核线程周期性喂狗,当出现系统崩溃时会自动重启Linux系统。

加载看门狗模块

sudo modprobe bcm2835_wdt

/etc/modules里添加信息

bcm2835_wdt

启用硬件看门狗

sudo su
echo 'dtparam=watchdog=on' >> /boot/config.txt
reboot

安装看门狗系统服务

sudo apt-get update
sudo apt-get install watchdog

如果安装过程报错,请看我的这篇文章对你是否有帮助
看门狗安装报错解决办法

配置看门狗服务

sudo su
echo 'watchdog-device = /dev/watchdog' >> /etc/watchdog.conf
echo 'watchdog-timeout = 15' >> /etc/watchdog.conf
echo 'max-load-1 = 24' >> /etc/watchdog.conf

启用服务

su pi
sudo systemctl enable watchdog
sudo systemctl start watchdog
sudo systemctl status watchdog

如果启用正常,会得到以下结果
在这里插入图片描述
如果要对此进行测试,可以尝试在shell上运行fork炸弹,模拟系统卡死情况:

sudo bash -c ':(){ :|:& };:'

https://diode.io/raspberry%20pi/running-forever-with-the-raspberry-pi-hardware-watchdog-20202/


启用bcm2835_wdt模块:

sudo modprobe bcm2835_wdt
在/etc/modules文件末尾添加一行:(sudo nano /etc/modules Linux下nano编辑器的使用 )

bcm2835_wdt
安装watchdog模块

sudo apt-get install watchdog
编辑/etc/watchdog.conf文件进行编辑配置看门狗: (sudo /etc/watchdog.conf Linux下nano编辑器的使用 )

#ping                   = 172.31.14.1
#ping                   = 172.26.1.255
#interface              = eth0
#file                   = /var/log/messages
#change                 = 1407
# 如果要使能,请去掉行前的注释标号,设置其中一个的值为0来禁用
# 这些值应设定为在正常使用期间不会重启你的设备的值
# (如果你的机器真的死机了,平均载入进程量应该超过25)
#max-load-m=n表示在m分钟内开超过n个进程的时候重启
max-load-1              = 24
#max-load-5             = 18
#max-load-15            = 12
# 注意这是内存分页的数量
# 请检查你的机器的分页大小以获取真实的大小
#最小剩余内存
#min-memory             = 1
#最小可分配内存
#allocatable-memory     = 1
#repair-binary          = /usr/sbin/repair
#repair-timeout         =
#test-binary            =
#test-timeout           =
#选择看门狗设备,这里用默认值
watchdog-device = /dev/watchdog
# 已经编译进二进制文件的默认值
#这个文件里面的内容就是CPU温度(摄氏温标)的一千倍
temperature-device      = /sys/class/thermal/thermal_zone0/temp
#设定为重置温度的一千倍
max-temperature = 75000
# 已经编译进二进制文件的默认值
#admin                  = root
#interval               = 1
#logtick                = 1
#log-dir                = /var/log/watchdog
# 这极大地减少了在你的机器真正加载完成前看门狗不被列入进程表的几率
realtime                = yes
priority                = 1
# 使能下面这行来检查rsyslogd是否仍然在运行
#pidfile                = /var/run/rsyslogd.pid

使看门狗服务开机自动启动:

sudo systemctl enable watchdog.service


测试看门狗:

sudo bash -c ':(){ :|:& };:'

所谓看门狗就是检测功能性软件是否还在运转的软件或硬件模块。当它检测到软件没有在工作时,就会重启机器,使得软件在重启之后能够继续工作。它的检测方式很简单,就让功能性软件定时的发信号给它(所谓的喂狗),一旦一定时间内没有接收到信号,那么就认为软件已经停止运行了,系统就需要重启了。在树莓派的硬件中就有看门狗的功能。下面我们就来玩玩这条狗。

教程目的:

用脚本的方式来喂狗,并试验当脚本停止喂狗后,树莓派会重启。(网上大量流传着下载watchdog这个软件来进行喂狗工作,本教程是自己写shell来喂狗的。当然,也是很简单的。)

教程器材及软件:

树莓派的板子。
SD卡(已经有镜像刷入)。
电源线及USB充电器。
U盘或USB硬盘
putty和psftp。
有DHCP的网线。
步骤:

先打开内核模块中的看门狗功能:
sudo modprobe bcm2708_wdog
(如果,你要在树莓派启动的时候,就启动看门狗功能。那么你需要在/etc/modules文件中加入,一行:bcm2708_wdog)
开启这个之后,你就可以在/dev目录下看到一个叫watchdog的文件。
执行完上面的命令,狗还不需要喂。但是一旦,你打开了这个文件(所谓,打开一种是c语言里的fopen,另一种就是等价的在shell中使用读写该文件的命令),就需要开始喂狗。喂狗的方式就是输入任何字符后数字除了V以外。此时,哪怕你关闭了文件,狗还是需要喂的。知道你输入V,才可以停止喂狗。有了这样子的原理,写一个喂狗的脚本就容易了。

!/bin/bash

while((1))
do
sleep 3
echo “Feed the dog”
echo “Feed the dog!” > /dev/watchdog
done
然后,运行这个脚本一段时间,一直喂狗,然后中断他。(Ctrl+C)
过了一会,树莓派就重启了。

树莓派 config.txt 文件解析

由于树莓派并没有传统意义上的BIOS, 所以现在各种系统配置参数通常被存在”config.txt”这个文本文件中.

树莓派的config.txt文件会在ARM内核初始化之前被GPU读取.

这个文件存在引导分区上的.对于Linux, 路径通常是/boot/config.txt, 如果是Windows (或者OS X) 它会被识别为SD卡中可访问部分的一个普通文件.

如果想要编辑配置文件, 请查看介绍编辑树莓派配置文件.

你可以使用下列命令去获取当前激活的设置:

- 列出指定的配置参数.
- 例如: vcgencmd get_config arm_freq
vcgencmd get_config 
- 列出所有已设置的整形配置参数(非零)
vcgencmd get_config int
- 列出所有已设置的字符型配置参数(非零)
vcgencmd get_config str

文件格式

当值是整形时格式为”属性=值”. 每行只指定一个参数. 注释使用’#’井号作为一行开头.

注意: 在新版的树莓派里每行都有#注释, 要想使用该行参数只需移除#.

下面是示例文件
# Set stdv mode to PAL (as used in Europe) sdtv_mode=2
# Force the monitor to HDMI mode so that sound will be sent over HDMI cable
hdmi_drive=2
# Set monitor mode to DMT
hdmi_group=2
# Set monitor resolution to 1024x768 XGA 60Hz (HDMI_DMT_XGA_60)
hdmi_mode=16
# Make display smaller to stop text spilling off the screen
overscan_left=20
overscan_right=12
overscan_top=10
overscan_bottom=10

这是另一个示例文件, 包含了各种功能的扩展文档.

内存

disable_l2cache 禁止ARM访问GPU的二级缓存. 相应的需要在内核中关闭二级缓存. 默认为0
gpu_mem GPU内存以兆为单位. 设置ARM和GPU之间的内存分配. ARM会获得剩余所有内存. 最小设为16. 默认为64
gpu_mem_256 对于有256MB内存的树莓派的GPU内存设置. 512MB的派请忽略. 会覆盖gpu_mem. 最大设为192. 默认不设置
gpu_mem_512 对于有512MB内存的树莓派的GPU内存设置. 256MB的派请忽略.  会覆盖gpu_mem. 最大设为448. 默认不设置
disable_pvt 禁止每500毫秒调整一次RAM的刷新率 (RAM温度测量).

CMA – 动态内存分配

自2012年11月19号, 固件和内核开始支持CMA, 这意味运行时可以动态管理ARM和GPU之间的内存分配. 这儿有相关config.txt示例.
cma_lwm 当GPU可用内存低于cma_lwm所设值, 将会向ARM请求一些内存.
cma_hwm 当GPU可用内存高于cma_hwm所设值, 将会向ARM释放一些内存.
要启用CMA,下面的参数需要添加到cmdline.txt文件里:
coherent_pool=6M smsc95xx.turbo_mode=N

视频

视频模式选项
sdtv_mode 为复合信号输出设置视频制式(默认为0)
sdtv_mode=0    NTSC
sdtv_mode=1    日本版NTSC – 无基座
sdtv_mode=2    PAL
sdtv_mode=3    巴西版PAL – 副载波为525/60而不是625/50

sdtv_aspect 为复合信号输出设置宽高比(默认为1)
sdtv_aspect=1  4:3
sdtv_aspect=2  14:9
sdtv_aspect=3  16:9

sdtv_disable_colourburst 禁止复合信号输出彩色副载波群. 图片会显示为单色, 但是可能会更清晰
sdtv_disable_colourburst=1  禁止输出彩色副载波群
hdmi_safe 使用”安全模式”的设置去尝试用HDMI最大兼容性启动. 这和下面的组合是一个意思: hdmi_force_hotplug=1, config_hdmi_boost=4, hdmi_group=2, hdmi_mode=4, disable_overscan=0
hdmi_safe=1
hdmi_ignore_edid 如果你的显示器是天朝产的垃圾货, 允许系统忽略EDID显示数据
hdmi_ignore_edid=0xa5000080
hdmi_edid_file 当设为1时, 将会从edid.dat文件中读取EDID数据,而不是从显示器.
hdmi_edid_file=1
hdmi_force_edid_audio 伪装成支持所有音频格式播放, 即便报告不支持也允许通过DTS/AC3.
hdmi_force_edid_audio=1
hdmi_force_edid_3d 伪装成全部CEA模式都支持3D, 即便EDID并不支持.
hdmi_force_edid_3d=1
avoid_edid_fuzzy_match 禁止去模糊匹配EDID中描述的模式. 即便遮蔽错误, 也选用匹配分辨率和最接近帧率的标准模式.
avoid_edid_fuzzy_match=1
hdmi_ignore_cec_init 不发送初始化激活源消息. 避免在重启时使(启用CEC)TV结束待机并切换频道.
hdmi_ignore_cec_init=1
hdmi_ignore_cec 伪装成TV不支持CEC. 将不会支持任何CEC功能.
hdmi_ignore_cec=1
hdmi_force_hotplug 伪装成HDMI热插拔信号被检测到, 出现HDMI显示器被接入
hdmi_force_hotplug=1 即便没有检测到HDMI显示器也要使用HDMI模式
hdmi_ignore_hotplug 伪装成HDMI热插拔信号没有被检测到, 出现HDMI显示器未接入
hdmi_ignore_hotplug=1 即便检测到HDMI显示器也要使用混合模式
hdmi_pixel_encoding 强制像素编码模式. 默认情况下会使用EDID请求的模式, 所以不需要修改.
hdmi_pixel_encoding=0 default       (limited for CEA, full for DMT)
hdmi_pixel_encoding=1 RGB limited   (16-235)
hdmi_pixel_encoding=2 RGB full      ( 0-255)
hdmi_pixel_encoding=3 YCbCr limited (16-235)
hdmi_pixel_encoding=4 YCbCr limited ( 0-255)

hdmi_drive 选择HDMI还是DVI模式
hdmi_drive=1 DVI模式 (没声音)
hdmi_drive=2 HDMI模式 (如果支持并已启用将有声音输出)

hdmi_group 设置HDMI类型
不指定组, 或者设为0, 将会使用EDID报告的首选组.
hdmi_group=1   CEA
hdmi_group=2   DMT

hdmi_mode 设置在CEA或DMT格式下的屏幕分辨率
当hdmi_group=1 (CEA)时,下列值有效
hdmi_mode=1    VGA
hdmi_mode=2    480p  60Hz
hdmi_mode=3    480p  60Hz  H
hdmi_mode=4    720p  60Hz
hdmi_mode=5    1080i 60Hz
hdmi_mode=6    480i  60Hz
hdmi_mode=7    480i  60Hz  H
hdmi_mode=8    240p  60Hz
hdmi_mode=9    240p  60Hz  H
hdmi_mode=10   480i  60Hz  4x
hdmi_mode=11   480i  60Hz  4x H
hdmi_mode=12   240p  60Hz  4x
hdmi_mode=13   240p  60Hz  4x H
hdmi_mode=14   480p  60Hz  2x
hdmi_mode=15   480p  60Hz  2x H
hdmi_mode=16   1080p 60Hz
hdmi_mode=17   576p  50Hz
hdmi_mode=18   576p  50Hz  H
hdmi_mode=19   720p  50Hz
hdmi_mode=20   1080i 50Hz
hdmi_mode=21   576i  50Hz
hdmi_mode=22   576i  50Hz  H
hdmi_mode=23   288p  50Hz
hdmi_mode=24   288p  50Hz  H
hdmi_mode=25   576i  50Hz  4x
hdmi_mode=26   576i  50Hz  4x H
hdmi_mode=27   288p  50Hz  4x
hdmi_mode=28   288p  50Hz  4x H
hdmi_mode=29   576p  50Hz  2x
hdmi_mode=30   576p  50Hz  2x H
hdmi_mode=31   1080p 50Hz
hdmi_mode=32   1080p 24Hz
hdmi_mode=33   1080p 25Hz
hdmi_mode=34   1080p 30Hz
hdmi_mode=35   480p  60Hz  4x
hdmi_mode=36   480p  60Hz  4xH
hdmi_mode=37   576p  50Hz  4x
hdmi_mode=38   576p  50Hz  4x H
hdmi_mode=39   1080i 50Hz  reduced blanking
hdmi_mode=40   1080i 100Hz
hdmi_mode=41   720p  100Hz
hdmi_mode=42   576p  100Hz
hdmi_mode=43   576p  100Hz H
hdmi_mode=44   576i  100Hz
hdmi_mode=45   576i  100Hz H
hdmi_mode=46   1080i 120Hz
hdmi_mode=47   720p  120Hz
hdmi_mode=48   480p  120Hz
hdmi_mode=49   480p  120Hz H
hdmi_mode=50   480i  120Hz
hdmi_mode=51   480i  120Hz H
hdmi_mode=52   576p  200Hz
hdmi_mode=53   576p  200Hz H
hdmi_mode=54   576i  200Hz
hdmi_mode=55   576i  200Hz H
hdmi_mode=56   480p  240Hz
hdmi_mode=57   480p  240Hz H
hdmi_mode=58   480i  240Hz
hdmi_mode=59   480i  240Hz H
H表示16:9比例(正常是4:3).
2x表示双倍像素(即更高的像素时脉, 每个像素重复两次)
4x表示四倍像素(即更高的像素时脉, 每个像素重复四次)

当hdmi_group=2 (DMT)时,下列值有效
警告: 根据这篇帖子所述
像素时脉是有限制的, 最高支持的模式是1920x1200 @60Hz with reduced blanking.
hdmi_mode=1    640x350   85Hz
hdmi_mode=2    640x400   85Hz
hdmi_mode=3    720x400   85Hz
hdmi_mode=4    640x480   60Hz
hdmi_mode=5    640x480   72Hz
hdmi_mode=6    640x480   75Hz
hdmi_mode=7    640x480   85Hz
hdmi_mode=8    800x600   56Hz
hdmi_mode=9    800x600   60Hz
hdmi_mode=10   800x600   72Hz
hdmi_mode=11   800x600   75Hz
hdmi_mode=12   800x600   85Hz
hdmi_mode=13   800x600   120Hz
hdmi_mode=14   848x480   60Hz
hdmi_mode=15   1024x768  43Hz  DO NOT USE
hdmi_mode=16   1024x768  60Hz
hdmi_mode=17   1024x768  70Hz
hdmi_mode=18   1024x768  75Hz
hdmi_mode=19   1024x768  85Hz
hdmi_mode=20   1024x768  120Hz
hdmi_mode=21   1152x864  75Hz
hdmi_mode=22   1280x768        reduced blanking
hdmi_mode=23   1280x768  60Hz
hdmi_mode=24   1280x768  75Hz
hdmi_mode=25   1280x768  85Hz
hdmi_mode=26   1280x768  120Hz reduced blanking
hdmi_mode=27   1280x800        reduced blanking
hdmi_mode=28   1280x800  60Hz
hdmi_mode=29   1280x800  75Hz
hdmi_mode=30   1280x800  85Hz
hdmi_mode=31   1280x800  120Hz reduced blanking
hdmi_mode=32   1280x960  60Hz
hdmi_mode=33   1280x960  85Hz
hdmi_mode=34   1280x960  120Hz reduced blanking
hdmi_mode=35   1280x1024 60Hz
hdmi_mode=36   1280x1024 75Hz
hdmi_mode=37   1280x1024 85Hz
hdmi_mode=38   1280x1024 120Hz reduced blanking
hdmi_mode=39   1360x768  60Hz
hdmi_mode=40   1360x768  120Hz reduced blanking
hdmi_mode=41   1400x1050       reduced blanking
hdmi_mode=42   1400x1050 60Hz
hdmi_mode=43   1400x1050 75Hz
hdmi_mode=44   1400x1050 85Hz
hdmi_mode=45   1400x1050 120Hz reduced blanking
hdmi_mode=46   1440x900        reduced blanking
hdmi_mode=47   1440x900  60Hz
hdmi_mode=48   1440x900  75Hz
hdmi_mode=49   1440x900  85Hz
hdmi_mode=50   1440x900  120Hz reduced blanking
hdmi_mode=51   1600x1200 60Hz
hdmi_mode=52   1600x1200 65Hz
hdmi_mode=53   1600x1200 70Hz
hdmi_mode=54   1600x1200 75Hz
hdmi_mode=55   1600x1200 85Hz
hdmi_mode=56   1600x1200 120Hz reduced blanking
hdmi_mode=57   1680x1050       reduced blanking
hdmi_mode=58   1680x1050 60Hz
hdmi_mode=59   1680x1050 75Hz
hdmi_mode=60   1680x1050 85Hz
hdmi_mode=61   1680x1050 120Hz reduced blanking
hdmi_mode=62   1792x1344 60Hz
hdmi_mode=63   1792x1344 75Hz
hdmi_mode=64   1792x1344 120Hz reduced blanking
hdmi_mode=65   1856x1392 60Hz
hdmi_mode=66   1856x1392 75Hz
hdmi_mode=67   1856x1392 120Hz reduced blanking
hdmi_mode=68   1920x1200       reduced blanking
hdmi_mode=69   1920x1200 60Hz
hdmi_mode=70   1920x1200 75Hz
hdmi_mode=71   1920x1200 85Hz
hdmi_mode=72   1920x1200 120Hz reduced blanking
hdmi_mode=73   1920x1440 60Hz
hdmi_mode=74   1920x1440 75Hz
hdmi_mode=75   1920x1440 120Hz reduced blanking
hdmi_mode=76   2560x1600       reduced blanking
hdmi_mode=77   2560x1600 60Hz
hdmi_mode=78   2560x1600 75Hz
hdmi_mode=79   2560x1600 85Hz
hdmi_mode=80   2560x1600 120Hz reduced blanking
hdmi_mode=81   1366x768  60Hz
hdmi_mode=82   1080p     60Hz
hdmi_mode=83   1600x900        reduced blanking
hdmi_mode=84   2048x1152       reduced blanking
hdmi_mode=85   720p      60Hz
hdmi_mode=86   1366x768        reduced blanking

overscan_left 左侧跳过像素数
overscan_right 右侧跳过像素数
overscan_top 顶部跳过像素数
overscan_bottom 底部跳过像素数
framebuffer_width 控制台framebuffer宽度, 以像素为单位. 默认是显示器宽度减去超出扫描.
framebuffer_height 控制台framebuffer高度, 以像素为单位. 默认是显示器高度减去超出扫描.
framebuffer_depth 控制台framebuffer深度, 以位为单位. 默认是16位. 8位也是有效的, 但是默认RGB调色板会导致屏幕不可读. 24位效果更好 ,但是2012年6月15号发现有显示混乱问题. 32位没有混乱问题, 但是需要设置framebuffer_ignore_alpha=1, 并在2012年6月15号发现颜色显示错误.
framebuffer_ignore_alpha 设为1将禁用alpha通道. 仅对32位有效.
test_mode 允许在启动时做声音与图像测试.
disable_overscan 设为1将禁用超出扫描.
config_hdmi_boost 设置HDMI接口的信号强度. 默认为0. 如果出现HDMI干扰问题可以试试设为4. 最大为7.
display_rotate 顺时针旋转屏幕显示 (默认为0) 或者翻转显示.
display_rotate=0        正常
display_rotate=1        90度
display_rotate=2        180度
display_rotate=3        270度
display_rotate=0x10000  水平翻转
display_rotate=0x20000  垂直翻转

注意: 旋转90度或者270度额外需要GPU内存, 所以在GPU只分配到16M的时候旋转会无效. 可能的原因:
Crashes my RPI before Linux boots if set to “1” — REW 20120913.

哪些值对我的显示器有效?

你的HDMI显示器可能只支持一部分设置. 想要找出支持哪些设置, 可以使用下面的方法.
把输出格式设为VGA 60Hz (hdmi_group=1 hdmi_mode=1) 然后启动树莓派
输入下列命令可以获取CEA支持模式的列表
/opt/vc/bin/tvservice -m CEA
输入下列命令可以获取DMT支持模式的列表
/opt/vc/bin/tvservice -m DMT
输入下列命令可以获取当前设置状态
/opt/vc/bin/tvservice -s
输入下列命令可以从显示器获取更多详细信息
/opt/vc/bin/tvservice -d edid.dat /opt/vc/bin/edidparser edid.dat
使用默认HDMI模式去排除问题时, edid.dat文件同样会提供信息

许可的解码器

你可以购买绑定树莓派CPU序列号的证书来使用额外的硬件解码器.
decode_MPG2 可开启MPEG-2硬解的序列号.
decode_MPG2=0x12345678
decode_WVC1 可开启VC-1硬解的序列号.
decode_WVC1=0x12345678
可在多台树莓派间共享SD卡的序列号. 同时最多8个证书.
decode_XXXX=0x12345678,0xabcdabcd,0x87654321,...

启动

disable_commandline_tags 在启动内核前, 通过改写ATAGS (0x100处的内存)来阻止start.elf
cmdline (string) 命令行参数. 可用来代替cmdline.txt文件
kernel (string) 加载指定名称的内核镜像文件启动内核. 默认为”kernel.img”
kernel_address 加载kernel.img文件地址
kernel_old (bool) 为1时, 从0x0处加载内核
ramfsfile (string) 要的加载的ramfs文件
ramfsaddr 要加载的ramfs文件地址
initramfs (string address) 要加载的ramfs文件及其地址 (就是把ramfsfile+ramfsaddr合并为一项).
注意: 这项使用与其他项不同的语法 – 不要在这用”=”号. 正确示例:
initramfs initramf.gz 0x00800000
device_tree_address 加载device_tree的地址
init_uart_baud 初始化uart波特率. 默认为115200
init_uart_clock 初始化uart时序. 默认为3000000 (3Mhz)
init_emmc_clock 初始化emmc时序. 默认为100000000 (100MHz)
boot_delay 在加载内核前在start.elf等待指定秒. 总延迟=1000 * boot_delay + boot_delay_ms. 默认为1
boot_delay_ms 在加载内核前在start.elf等待指定毫秒. 默认为0
avoid_safe_mode 如果设为1, 将不以安全模式启动. 默认为0

超频

注意: 设置任何参数来超频树莓派都会在芯片中永久的储存一个保修位, 用于检测你的树莓派是否超频过. 如果设备超频过保修就无效了. 自2012年9月19号,你可以自由超频而不影响保修了.
最新的内核有一个默认开启”ondemand”调速器的cpu频率内核驱动. 未开启超频并不会有任何影响. 一旦你开超频, ARM频率将随处理器负载而变化. 只有在调速器需要时才会使用非默认值. 你可以使用*_min配置选项来调整最低值, 或者使用force_turbo=1来禁用动态超频.

当芯片温度达到85°C运行时会关闭超频及超压, 直到冷却. 即使在25°C环境温度下使用最高设置, 也不要让温度达到极限.

超频选项

参数    说明
arm_freq    ARM频率,以MHz为单位. 默认为700
gpu_freq    同时设置core_freq, h264_freq, isp_freq, v3d_freq. 默认为250
core_freq    GPU处理器核心频率,以MHz为单位. 由于GPU要驱动二级缓存, 对ARM性能会造成影响. 默认为 250
h264_freq    视频硬解模块频率,以MHz为单位. 默认为250
isp_freq    图像传感器管道模块频率,以MHz为单位. 默认为250
v3d_freq    3D模块频率,以MHz为单位. 默认为250
avoid_pwm_pll    不要把锁相环用在PWM音频. 这会略微降低模拟音频的效果. 空闲的锁相环允许从剩余GPU独立设置core_freq, 这将会比超频有更多权限. 默认为0
sdram_freq    SDRAM频率,以MHz为单位.默认为400
over_voltage    ARM/GPU核心电压调节. [-16,8]用0.025V步进等同于[0.8V,1.4V]. 默认为0 (1.2V). 只有在指定 force_turbo或current_limit_override时 (会设置保修位), 才允许数值在6以上
over_voltage_sdram    同时设置over_voltage_sdram_c, over_voltage_sdram_i, over_voltage_sdram_p
over_voltage_sdram_c    SDRAM控制器电压调节. [-16,8]用0.025V步进等同于[0.8V,1.4V]. 默认为0 (1.2V)
over_voltage_sdram_i    SDRAM I/O电压调节. [-16,8]用0.025V步进等同于[0.8V,1.4V]. 默认为0 (1.2V)
over_voltage_sdram_p    SDRAM phy电压调节. [-16,8]用0.025V步进等同于[0.8V,1.4V]. 默认为0 (1.2V)
force_turbo    关闭动态CPU频率驱动及下面的最小设置. 开启h264/v3d/isp超频. 默认为0. 会设置保修位.
initial_turbo    在启动时以指定秒数 (上限为60) 或者以CPU频率来开启急速模式. 如果已经超频, 能对SD卡错误问题有改善. 默认为0
arm_freq_min    设置动态时序的最小arm_freq. 默认为700
core_freq_min    设置动态时序的最小core_freq. 默认为250
sdram_freq_min    设置动态时序的最小sdram_freq. 默认为400
over_voltage_min    设置动态时序的最小over_voltage. 默认为0
temp_limit    过热保护. 当芯片达到指定温度就把时序和电源切换会默认值. 把此值设高于默认值将影响保修. 默认为85
current_limit_override    当设为”0x5A000020″时, 禁止SMPS限流保护. 在超频过高无法重启时设置此项会有所帮助. 会设置保修位.

force_turbo模式
force_turbo=0
开启对ARM核心,GPU核心和SDRAM的动态时序及电压. 在忙的时候ARM频率会提高到”arm_freq”并在闲的时候降低到”arm_freq_min”. “core_freq”, “sdram_freq”和”over_voltage”的行为都一样. “over_voltage”最高为6 (1.35V). h264/v3d/isp部分的非默认值将被忽略.
force_turbo=1
关闭动态时序, 因此所有频率和电压会保持高值. h264/v3d/isp GPU部分的超频也会开启, 等同于设置”over_voltage”为8 (1.4V). 

时序关系

GPU核心, h264, v3d和isp共享一个锁相环, 因此需要相关联的频率. ARM, SDRAM和GPU有各自独有的锁相环, 因此可以设为没有关联的频率.

当设了”avoid_pwm_pll=1″下列设置就没必要了.
pll_freq = floor(2400 / (2 * core_freq)) * (2 * core_freq)
gpu_freq = pll_freq / [偶数]

有效的gpu_freq会自动四舍五到到最接近的整型偶数, 所以请求core_freq为500, gpu_freq为300,算一下2000/300 = 6.666 => 6 ,结果就是333.33MHz.

已测试过的超频设置

下表显示了一些成功的超频尝试, 这些可以指导你进行超频. 这些设置不一定能在每台树莓派上都成功, 并且会缩短高通芯片的寿命.

arm_freq    gpu_freq    core_freq    h264_freq    isp_freq    v3d_freq    sdram_freq    over_voltage    over_voltage_sdram
800
900    275                    500
900        450                450
930    350                    500
1000        500                500    6
1050                            6
1150        500                600    8
这是一个表明Hynix产的RAM在超频上表现不如三星产的RAM的报告.

超频时SD卡使用

设置SD卡: http://elinux.org/RPi_Easy_SD_Card_Setup
超频时使用6速或10速的SD卡(SHDC/SHDX)会导致在一些天后树莓派读取SD卡文件系统不稳定.
不管是ext4 , NTFS 或其他格式都一样.
不管是哪家SD卡生产商都一样.
不管是哪个版本的树莓派都一样.
这与SD卡容量无关 – 实际验证出现在16G或更大的SD卡上.
! 关键是你何时让树莓派功率不足,也就是低于树莓派的基本设置需求 !
popcornmix发表在https://github.com/raspberrypi/linux/issues/280:
“超频会导致SD卡错误.这情况往往是与板子相关(就是说有些树莓派超频后SD卡没事,有些不行).
我认为通常都是core_freq导致的SD卡问题(和arm_freq,sdram_freq比)”
在2013年4月写这个提示的时候在树莓派官方论坛上一共有137个有关于SD的问题, 绝大部分与超频有关.
如果你使用6速或10速SD卡, 还想要树莓派稳定运行:  不要尝试超频,否则很可能会丢失数据

监测温度及电压

要检测树莓派的温度, 看: /sys/class/thermal/thermal_zone0/temp
要检测树莓派当前的频率, 看: /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
要检测树莓派电源装置的电压, 你需要一个万用电表, 接上电源测试点, 或者扩展头.

通常来说要保持核心温度低于70度, 电压高于4.8V. (另外请注意, 不要用那种便宜的USB电源, 那基本上是4.2V的, 这是因为那本来就是为充3.7V锂电池设计的, 根本无法为树莓派提供稳定的5V电压). 此外, 用散热片也是个好主意, 尤其是你把树莓派装到了壳子里. 一个合适的散热器是自带不干胶栅格状的 14x14x10 mm 散热片.

超频稳定性测试

大多数超频问题立马就会出现启动问题, 但还是会随时间而出现文件系统问题. 这是一个对系统,特别是SD卡进行压力测试的脚本. 如果脚本执行完成, dmesg中不提示任何错误, 你做的超频设置可能会比较稳定.

如果系统崩溃了, 在重启时按住shift键, 这会临时性关闭所有超频. 同样, 注意SD卡问题通常由core_freq造成,不要在raspi-config预设的高速(950 MHz)和超速(1 GHz)里来个大跳越(从250 MHz飞到500 MHz).
#!/bin/bash
#Simple stress test for system. If it survives this, it's probably stable.
#Free software, GPL2+

echo “Testing overclock stability…”

#Max out the CPU in the background (one core). Heats it up, loads the power-supply.
nice yes >/dev/null &

#Read the entire SD card 10x. Tests RAM and I/O
for i in `seq 1 10`; do echo reading: $i; sudo dd if=/dev/mmcblk0 of=/dev/null bs=4M; done

#Writes 512 MB test file,  10x.
for i in `seq 1 10`; do echo writing: $i; dd if=/dev/zero of=deleteme.dat bs=1M count=512; sync; done

#Clean up
killall yes
rm deleteme.dat

#Print summary. Anything nasty will appear in dmesg.
echo -n “CPU freq: ” ; cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
echo -n “CPU temp: ” ; cat /sys/class/thermal/thermal_zone0/temp
dmesg | tail

echo "Not crashed yet, probably stable."

捕捉系统按键ctrl-c等

捕捉Ctrl-C键盘事件

import time
import signal
def signal_handler(signal,frame):
    print('You pressed Ctrl+C!')
signal.signal(signal.SIGINT,signal_handler)
print('Press Ctrl+C')
for x in range(1,100):
    time.sleep(2)
    print(x)

import time
if __name__ == "__main__":
    try:
        time.sleep(10)
    except KeyboardInterrupt:
        print("Application exit!")
# 自定义信号处理函数
def my_handler(signum, frame):
    global stop
    stop = True
    print("终止")
# 设置相应信号处理的handler
signal.signal(signal.SIGINT, my_handler)    #读取Ctrl+c信号
stop = False
while True:
    try:
        #读取到Ctrl+c前进行的操作
        if stop:
            # 中断时需要处理的代码
            pass
#            break    #break只能退出当前循坏
            #中断程序需要用 raise
    except Exception as e:
        print(str(e))
        break

esp32 wifi 配置

【摘要】 我们购买智能家居产品后,买回来拆箱后第一件事通常就是给这个新的硬件进行配网,所谓配网,也就是让这个新的物联网设备联入我们的局域网内,让这个物联网设备可以进行网络通讯。我们在上一篇文章《MicroPython(ESP32/ESP8266) 实现web控制GPIO》中已经了解到了如何使用ESP32和ESP8266通过联网来实现在Web中控制板载的 LED 灯开关。本文将介绍…

我们购买智能家居产品后,买回来拆箱后第一件事通常就是给这个新的硬件进行配网,所谓配网,也就是让这个新的物联网设备联入我们的局域网内,让这个物联网设备可以进行网络通讯。我们在上一篇文章《MicroPython(ESP32/ESP8266) 实现web控制GPIO》中已经了解到了如何使用ESP32和ESP8266通过联网来实现在Web中控制板载的 LED 灯开关。本文将介绍基于 MicroPython 来实现的 ESP32/ESP8266 Wifi配网。

准备工作

在开始代码之前,需要先准备以下:

配网流程

回想以下我们的智能家居物联网设备,以小米生态圈的设备为例,新设备开箱通电后,一般是打开米家APP,然后搜索到新买的设备,然后需要手动将wifi连接到这个设配上,然后在 APP 中填入 SSID 和 wifi密码信息,等待传输,传输完成后,就算完成配网,在 APP 的界面中就可以看到新的设备了。

在这里插入图片描述

配网的流程总结如上图所示。然而我们的使用当中,配网通常只发生在新设备加入或者网络环境改变的时候才需要,正常情况下设备重启,是不需要每一次都要来一次配网操作的。所以一般情况下,在一次配网之后,我们会将我们的Wifi信息保存下来,设备重启后如果有存在的配网信息,会自动直接联网。

在这里插入图片描述

而针对我们整个开发版的程序,我们可以在 main.py 执行在开始,就先执行网络检查,然后根据是否成功联网来判断是否需要配网操作,流程如下:

在这里插入图片描述

MicroPython Wifi 操作

上文梳理了整个配网过程的流程。在这个流程中,最开始的步骤就是判断网络是否连接。以下将介绍如何使用 MicroPython 操作开发板的 Wifi。

我们开发板(ESP32/ESP8266)的wifi有AP和STA模式,AP就是开发版上创建一个热点,其他设备连接到AP上,而STA模式和我们普通的手机电脑使用Wifi联网类似。这里的要点就是我们需要检查STA模式下开发版是否能正常联网,如果不能,我们利用开发板的AP模式,让我们的其他设备连接开发板,把我们局域网Wifi的配置信息告知开发板,从而使开发板能正常联网。

import network
wlan_sta = network.WLAN(network.STA_IF)
wlan_sta.isconnected()
  

通过调用 isconnected() 函数,可以获取到开发板是否正常联网,如果正常联网,返回结果会是 True 否则为 False 。

wlan_sta.scan()
  

scan() 函数扫描设备附近可以搜索到的 Wifi,会返回一个列表,列表中每一条为可连接wifi的信息。

[(b'WifiSSID', b'LPw\xb7\xs8\x94', 1, -48, 3, 0),...]
  

以上是省略了部分信息的返回值,可以看到,每一条记录中有6个信息,它们分别代表了 SSID名称 BSSID(MAC地址) 频道 RSSI信号强度 加密模式 是否隐藏 。其中加密模式,包含了 WEP、WPA-PSK、WPA2-PSK、WPA/WPA2-PSK等。

接下来,我们就可以尝试连接Wifi。

wlan_sta.connect('ssid', 'password')
wlan_sta.isconnected()
  

如果连接成功,则返回 True 。如果需要断开连接,可以使用 disconnect() 函数。

wlan_sta.disconnect()
  

MicroPython AP操作

完成了 Wifi 连接和检查网络是否正常后,我们开始解决利用 AP 配网的问题。

先看代码:

import network
import socket
wlan_ap = network.WLAN(network.AP_IF)
wlan_ap.active(True)
wlan_ap.config(essid='MyESP8266',authmode=0)
server_socket = socket.socket()
server_socket.bind(('0.0.0.0', 80))
server_socket.listen(3)
def web_page(): return b"""<html> <head> <title>MYESP8266 AP Test</title> </head> <body> <h1>This is MyESP8266 AP Test Page.</h1> </body> </html>"""
while True: conn, addr = server_socket.accept() print('Connection: %s ' % str(addr)) response = web_page() conn.send('HTTP/1.1 200 OK\n') conn.send('Content-Type: text/html\n') conn.send('Connection: close\n\n') conn.sendall(response) conn.close()
  

从上面的代码我们可以看到,当我们创建好AP后,就打开一个 socket ,并且绑定80端口开始监听,然后开启一个循环,当接受到连接后就给客户端发送页面代码。如果对 socket 不了解的,可以参考《快速了解Python socket编程》

这时用手机或者电脑的 wifi 连接 SSID 名为 MYESP8266 的 Wifi 热点,因为我们authmode选择了 open 所以不需要密码。连接成功后,用浏览器打开地址 192.168.4.1 ,就可以看到我们上面的页面。

针对 MicroPython 的 Web 编程

我们一般情况下,如果要进行 Web 开发,通常会使用 Flask 或者 Django 之类的框架。而针对开发版这种运算能力有限的硬件,也有对应的框架可以用。但我们这里为了能深入的了解,就通过自己完成最基本的功能来了解整个程序的运行方式。

封装HTML响应

根据上面的示例代码,我们可以了解到,如果要在客户端正常显示页面,我们需要先给客户端发一个HTTP的Header信息,然后再发送具体的页面内容。所以,为了方便日后的调用,我们对上面的代码进行修改:

import network
import socket
wlan_ap = network.WLAN(network.AP_IF)
wlan_ap.active(True)
wlan_ap.config(essid='MyESP8266',authmode=0)
server_socket = socket.socket()
server_socket.bind(('0.0.0.0', 80))
server_socket.listen(3)
def send_header(conn, status_code=200, content_length=None ): conn.sendall("HTTP/1.0 {} OK\r\n".format(status_code)) conn.sendall("Content-Type: text/html\r\n") if content_length is not None: conn.sendall("Content-Length: {}\r\n".format(content_length)) conn.sendall("\r\n")
def send_response(conn, payload, status_code=200): content_length = len(payload) send_header(conn, status_code, content_length) if content_length > 0: conn.sendall(payload) conn.close()
def config_page(): return b"""<html> <head> <title>MYESP8266 AP Test</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>Wifi 配网</h1> <form action="configure" method="post"> <div> <label>SSID</label> <input type="text" name="ssid"> </div> <div> <label>PASSWORD</label> <input type="password" name="password"> </div> <input type="submit" value="连接"> <form> </body> </html>"""
while True: conn, addr = server_socket.accept() print('Connection: %s ' % str(addr)) try: conn.settimeout(3) request = b"" try: while "\r\n\r\n" not in request: request += conn.recv(512) except OSError: pass print(request) response = config_page() send_response(conn, response)
	finally: conn.close()
  

我们添加了三个函数,分别为 send_header() send_response() config_page() 。其中 send_header() 把我们需要发送的 Header 信息打包,config_page() 则是创建我们的 HTML 页面,最后由 send_response() 将其整合,发送给客户端。

在这里插入图片描述

运行代码,如果正常,用手机连接开发板的AP,打开 192.168.4.1 ,就可以看到上图的页面。

路由

上面的代码中,页面中有一个 form ,里面可以输入 SSID 和 Wifi 密码,当我们输入完成后,点击连接,将会将我们输入的内容 POST 到 /configure 路径中。处理这个问题,在 Web 框架中,会有现成的路由模块,但这里我们需要自己用代码进行处理。

我们的代码中,当客户端连接后,我们的开发板会接受来自客户端传来的信息——request ,打印这个变量看看客户端传来的内存:

# 连接 192.168.4.1
Connection: ('192.168.4.2', 44794)
b'GET / HTTP/1.1\r\nUser-Agent: Dalvik/2.1.0 (Linux; U; Android 9; MIX 2 MIUI/20.6.18)\r\nHost: 192.168.4.1\r\nConnection: Keep-Alive\r\nAccept-Encoding: gzip\r\n\r\n'
# 连接 192.168.4.1/test
b'GET /test HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Linux; Android 9; MIX 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n\r\n'
# 输入信息,点击连接按钮
b'POST /configure HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nContent-Length: 26\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nOrigin: http://192.168.4.1\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Linux; Android 9; MIX 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nReferer: http://192.168.4.1/\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n\r\nssid=xdbdh&password=ddjxdj'
  

可以看到,当我们连接不同的地址,开发板接受到的信息是不同的,我们就可以通过正则表达式来抓去不同的内容即可实现类似 Web 框架路由的功能。

try: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/") except Exception: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/") print("URL is {}".format(url))
  

我们将上面 print() 函数替换乘上面的代码,再尝试上面三个地址:

# 连接 192.168.4.1
URL is
# 连接 192.168.4.1/test
URL is test
# 输入信息,点击连接按钮
URL is configure
  

这样,我们的精简版路由功能就完成了。

POST 传参获取

解决了页面显示和路由,剩下就是如何获取 POST 的传参了。我们再看一次当我们使用 POST 时,返回过来的信息:

b'POST /configure HTTP/1.1\r\n
Host: 192.168.4.1\r\n
Connection: keep-alive\r\n
Content-Length: 26\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
Origin: http://192.168.4.1\r\n
Content-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Linux; Android 9; MIX 2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.82 Mobile Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nReferer: http://192.168.4.1/\r\n
Accept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n\r\n
ssid=xdbdh&password=ddjxdj'
  

可以看到,信息开头是 POST 方法,然后结 ssid=....&password=... 就是我们传过来的参数,和处理路由的方法类似,我们使用正则表达式过滤一下,即可获取到我们需要的 ssid 和 Wifi 密码了。

# POST 参数解析
def get_wifi_conf(request): match = ure.search("ssid=([^&]*)&password=(.*)", request) if match is None: return False try: ssid = match.group(1).decode("utf-8").replace("%3F", "?").replace("%21", "!") password = match.group(2).decode("utf-8").replace("%3F", "?").replace("%21", "!") except Exception: ssid = match.group(1).replace("%3F", "?").replace("%21", "!") password = match.group(2).replace("%3F", "?").replace("%21", "!") if len(ssid) == 0: return False return (ssid, password)
  

我们再修改一下代码,添加一个新页面,用来显示 ssid 和 Wifi 密码,来确认我们的路由功能和 POST 参数正常获取。

def wifi_conf_page(ssid, passwd): return b"""<html> <head> <title>Wifi Conf Info</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>Post data:</h1> <p>SSID: %s</p> <p>PASSWD: %s</p> <a href="https://bbs.huaweicloud.com/">Return Configure Page</a> </body> </html>""" % (ssid, passwd)
  

修改后的代码:

# 前面相同的部分省略
while True: conn, addr = server_socket.accept() print('Connection: %s ' % str(addr)) try: conn.settimeout(3) request = b"" try: while "\r\n\r\n" not in request: request += conn.recv(512) except OSError: pass # url process try: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/") except Exception: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/") print("URL is {}".format(url)) if url == "": response = config_page() send_response(conn, response) elif url == "configure": ret = get_wifi_conf(request) response = wifi_conf_page(ret[0], ret[1]) send_response(conn, response) finally: conn.close()
  

执行代码,输入 ssid 和密码后,点击连接,应该能跳转到新页面并且显示刚才输入的 ssid 和密码。点击返回,能重新跳回信息输入的页面。

在这里插入图片描述

Wifi连接

《MicroPython(ESP32/ESP8266) 实现web控制GPIO》中,我们已经介绍了如何通过 connect() 方法来连接我们已知的 Wifi。接下来,我们要做的也很简单,就是创建一个 do_connect() 方法来处理我们上面传过来的 ssid 和密码。

def do_connect(ssid, password): wlan_sta.active(True) if wlan_sta.isconnected(): return None print('Connect to %s' % ssid) wlan_sta.connect(ssid, password) for retry in range(100): connected = wlan_sta.isconnected() if connected: break time.sleep(0.1) print('.', end='') if connected: print('\nConnected : ', wlan_sta.ifconfig()) else: print('\nFailed. Not Connected to: ' + ssid) return connected
  

可以看到,这个函数会接受传来的 wifi 配置参数,进行连接,如果成功,会返回 True。然后我们还还需要一个执行连接的方法,这个方法用于连接成功,就自动获取连如局域网后的ip地址。

def handle_wifi_configure(ssid, password): if do_connect(ssid, password): new_ip = wlan_sta.ifconfig()[0] return new_ip else: print('connect fail') return False
  

这些都完成后,我们只需要把开发板 AP 联网配置部分封装好,成为一个 start_ap() 方法,即可:

# response 的方法都为创建 HTML 代码方法,这里省略
# 可以在文末完整代码中查看
def startAP(): global server_socket stop() wlan_ap.active(True) wlan_ap.config(essid='MyEsp8266',authmode=0) server_socket = socket.socket() server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('0.0.0.0', 80)) server_socket.listen(3) while not wlan_sta.isconnected(): conn, addr = server_socket.accept() print('Connection: %s ' % str(addr)) try: conn.settimeout(3) request = b"" try: while "\r\n\r\n" not in request: request += conn.recv(512) except OSError: pass # url process try: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/") except Exception: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/") print("URL is {}".format(url)) if url == "": response = config_page() send_response(conn, response) elif url == "configure": ret = get_wifi_conf(request) ret = handle_wifi_configure(ret[0], ret[1]) if ret is not None: response = connect_sucess(ret) send_response(conn, response) print('connect sucess') elif url == "disconnect": wlan_sta.disconnect() finally: conn.close() wlan_ap.active(False) print('ap exit')
  

这里我们实现的功能为让开发板创建AP,生成一个 Wifi 信息的配置页面,然后通过路由来处理输入和参数,最后执行 Wifi 联网,如果连接成功,即退出循环,关闭 AP 热点。

我们从用手机输入完成点击连接后,如果连接成功,将会自动返回成功连接的页面:

在这里插入图片描述

到这里,我们的 wifi 配网就已经基本完成了。

总结

本文开始先从配网的需求、流程进行分析,然后一步步分别介绍 MicroPython Wifi的操作,AP的使用以及简单的 Web 实现,然后将上述的要点结合我们的配网需求,完成完整的设配配网代码开发。

但是,文章为了比较清晰的展示内容,因此在代码上可能会显得比较冗长和繁复,有很大的优化空间。大家可以根据自己的实际情况,对代码进行进一步的优化和调整,以下给出几个可以调整方法:

  • 优化代码结构,模块化部分功能
  • 将 web 部分整合成一个模块,比如带有 html 模板渲染功能的模块、路由模块灯
  • 尝试在用户体验上优化配网的流程
  • 其他创新的需求等……

此外,还存还存在一个问题,就是可能因为 ESP8266 的内存和算力问题,代码运行的时候有时会出错和跳出,需要重启或者断电,但同样的代码在 ESP32 开发板上,却没有问题。可能是 MircoPython 的问题,也有可能是因为代码设计问题,这方面需要进一步研究和尝试。

物联网开发涉及到很多硬件和软件的问题,但是在实践中,经常会遇到各种奇怪的问题,这很可能打击了学习的热情,加上网上的教程和示例不多,初学者更容易遇到问题解决不了而不得不放弃。本文尽可能的详细解释代码和原理,但由于水平经验有限,难免会有所疏漏, 望读者见谅,并且欢迎大家一起来交流进步。

希望本文对你有用。如果你觉得文章对你用,记得关注收藏。你的关注和收藏是继续更新的动力哦。

附:完整代码

import network
import socket
import ure
import time
NETWORK_PROFILES = 'wifi.dat'
wlan_ap = network.WLAN(network.AP_IF)
wlan_sta = network.WLAN(network.STA_IF)
server_socket = None
def send_header(conn, status_code=200, content_length=None ): conn.sendall("HTTP/1.0 {} OK\r\n".format(status_code)) conn.sendall("Content-Type: text/html\r\n") if content_length is not None: conn.sendall("Content-Length: {}\r\n".format(content_length)) conn.sendall("\r\n")
def send_response(conn, payload, status_code=200): content_length = len(payload) send_header(conn, status_code, content_length) if content_length > 0: conn.sendall(payload) conn.close()
def config_page(): return b"""<html> <head> <title>MYESP8266 AP Test</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>Wifi 配网</h1> <form action="configure" method="post"> <div> <label>SSID</label> <input type="text" name="ssid"> </div> <div> <label>PASSWORD</label> <input type="password" name="password"> </div> <input type="submit" value="连接"> <form> </body> </html>"""
def wifi_conf_page(ssid, passwd): return b"""<html> <head> <title>Wifi Conf Info</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>Post data:</h1> <p>SSID: %s</p> <p>PASSWD: %s</p> <a href="https://bbs.huaweicloud.com/">Return Configure Page</a> </body> </html>""" % (ssid, passwd)
def connect_sucess(new_ip): return b"""<html> <head> <title>Connect Sucess!</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <p>Wifi Connect Sucess</p> <p>IP Address: %s</p> <a href="http://%s">Home</a> <a href="https://bbs.huaweicloud.com/disconnect">Disconnect</a> </body> </html>""" % (new_ip, new_ip)
def get_wifi_conf(request): match = ure.search("ssid=([^&]*)&password=(.*)", request) if match is None: return False try: ssid = match.group(1).decode("utf-8").replace("%3F", "?").replace("%21", "!") password = match.group(2).decode("utf-8").replace("%3F", "?").replace("%21", "!") except Exception: ssid = match.group(1).replace("%3F", "?").replace("%21", "!") password = match.group(2).replace("%3F", "?").replace("%21", "!") if len(ssid) == 0: return False return (ssid, password)
def handle_wifi_configure(ssid, password): if do_connect(ssid, password):
# try:
# profiles = read_profiles()
# except OSError:
# profiles = {}
# profiles[ssid] = password
# write_profiles(profiles)
#
# time.sleep(5)
#  new_ip = wlan_sta.ifconfig()[0] return new_ip else: print('connect fail') return False
def check_wlan_connected(): if wlan_sta.isconnected(): return True else: return False def do_connect(ssid, password): wlan_sta.active(True) if wlan_sta.isconnected(): return None print('Connect to %s' % ssid) wlan_sta.connect(ssid, password) for retry in range(100): connected = wlan_sta.isconnected() if connected: break time.sleep(0.1) print('.', end='') if connected: print('\nConnected : ', wlan_sta.ifconfig()) else: print('\nFailed. Not Connected to: ' + ssid) return connected
def read_profiles(): with open(NETWORK_PROFILES) as f: lines = f.readlines() profiles = {} for line in lines: ssid, password = line.strip("\n").split(";") profiles[ssid] = password return profiles
def write_profiles(profiles): lines = [] for ssid, password in profiles.items(): lines.append("%s;%s\n" % (ssid, password)) with open(NETWORK_PROFILES, "w") as f: f.write(''.join(lines)) def stop(): global server_socket if server_socket: server_socket.close() server_socket = None
def startAP(): global server_socket stop() wlan_ap.active(True) wlan_ap.config(essid='MyEsp8266',authmode=0) server_socket = socket.socket() server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('0.0.0.0', 80)) server_socket.listen(3) while not wlan_sta.isconnected(): conn, addr = server_socket.accept() print('Connection: %s ' % str(addr)) try: conn.settimeout(3) request = b"" try: while "\r\n\r\n" not in request: request += conn.recv(512) except OSError: pass # url process try: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/") except Exception: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/") print("URL is {}".format(url)) if url == "": response = config_page() send_response(conn, response) elif url == "configure": ret = get_wifi_conf(request) ret = handle_wifi_configure(ret[0], ret[1]) if ret is not None: response = connect_sucess(ret) send_response(conn, response) print('connect sucess') elif url == "disconnect": wlan_sta.disconnect() finally: conn.close() wlan_ap.active(False) print('ap exit')
def home(): global server_socket stop() wlan_sta.active(True) ip_addr = wlan_sta.ifconfig()[0] print('wifi connected') server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind(('0.0.0.0', 80)) server_socket.listen(3) while check_wlan_connected(): conn, addr = server_socket.accept() try: conn.settimeout(3) request = b"" try: while "\r\n\r\n" not in request: request += conn.recv(512) except OSError: pass # url process try: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/") except Exception: url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/") if url == "": response = connect_sucess(ip_addr) send_response(conn, response) elif url == "disconnect": wlan_sta.disconnect() finally: conn.close() wlan_sta.active(False) print('sta exit')
def main(): while True: if not check_wlan_connected(): startAP() else: home() main()
  

原文链接:

https://zhuanlan.zhihu.com/p/369017239

http://www.proyy.com/885253b7289c422da561fdfdc78f3bd2.html

更新树莓派固件

树莓派固件更新,一些版本使用sudo UPDATE_SELF=0 rpi-update失败,可以按以下方式更新:

sudo apt update && sudo apt full-upgrade -y
sudo reboot
sudo rpi-eeprom-update -a
sudo reboot
sudo rpi-eeprom-update