Blog

软件设计模式-概述

设计模式简介


设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

什么是 GOF(四人帮,全拼 Gang of Four)?


在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns – Elements of Reusable Object-Oriented Software(中文译名:设计模式 – 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。

四位作者合称 GOF(四人帮,全拼 Gang of Four)。他们所提出的设计模式主要是基于以下的面向对象设计原则。

  • 对接口编程而不是对实现编程。
  • 优先使用对象组合而不是继承。

设计模式的使用


设计模式在软件开发中的两个主要用途。

开发人员的共同平台

设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。

最佳的实践

设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。

设计模式的类型

根据设计模式的参考书 Design Patterns – Elements of Reusable Object-Oriented Software(中文译名:设计模式 – 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。当然,我们还会讨论另一类设计模式:J2EE 设计模式。

序号 模式 & 描述 包括
1 创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。
  • 工厂模式(Factory Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)
2 结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge Pattern)
  • 过滤器模式(Filter、Criteria Pattern)
  • 组合模式(Composite Pattern)
  • 装饰器模式(Decorator Pattern)
  • 外观模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)
3 行为型模式
这些设计模式特别关注对象之间的通信。
  • 责任链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 解释器模式(Interpreter Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 备忘录模式(Memento Pattern)
  • 观察者模式(Observer Pattern)
  • 状态模式(State Pattern)
  • 空对象模式(Null Object Pattern)
  • 策略模式(Strategy Pattern)
  • 模板模式(Template Pattern)
  • 访问者模式(Visitor Pattern)
4 J2EE 模式
这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。
  • MVC 模式(MVC Pattern)
  • 业务代表模式(Business Delegate Pattern)
  • 组合实体模式(Composite Entity Pattern)
  • 数据访问对象模式(Data Access Object Pattern)
  • 前端控制器模式(Front Controller Pattern)
  • 拦截过滤器模式(Intercepting Filter Pattern)
  • 服务定位器模式(Service Locator Pattern)
  • 传输对象模式(Transfer Object Pattern)

下面用一个图片来整体描述一下设计模式之间的关系:


设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。


设计模式这一章 :

主要参考:http://www.runoob.com/design-pattern/factory-pattern.html

树莓派-JAVA操作GPIO

curl -s get.pi4j.com | sudo bash
  • 方式二,下载安装包来安装,下载地址为http://get.pi4j.com/download/pi4j-1.1.deb,下载完毕后执行以下命令,即可完成安装:
sudo dpkg -i pi4j-1.1.deb

安装之后,PI4J的库放在以下目录:

/opt/pi4j/lib

如果你想更新PI4J,可以执行以下命令:

sudo apt-get install pi4j

或者

pi4j --update

如果想要卸载PI4J,可以执行以下命令

sudo apt-get remove pi4j 或 pi4j --uninstall

完全卸载方式:

curl -s get.pi4j.com/uninstall | sudo bash

PI4J的官方demo

安装PI4J之后,就应该尽快入门PI4J了,而学习PI4J最好的办法就是一边看着官方demo,一边动手写代码。
如果你按照上面的方式安装PI4J,那么官方文档在以下目录:

/opt/pi4j/examples

进入该目录,执行以下命令,可编译源代码:

sudo /opt/pi4j/examples/build

编译之后,你可以随意运行某个demo,比如这里运行BlinkGpioExample,那么执行以下命令:

./run BlinkGpioExample

demo简要解析

毕竟大家都是新手,光有代码恐怕还不能困惑,所以这里就拿一个例子讲解一下代码,方便大家入门。

在讲解之前,大家除了准备树莓派,还要先自备好led灯,以及一个开关按钮(某宝上搜有很多),因为大多的官方例子里都使用到。

好了,大家先看看两个图,这两个图表示这树莓派2(树莓派3点此)上的针脚。也就是我们所说的GPIO。

图一是真实树莓派的针脚图,图一的箭头1,箭头2指向图二顶头的两个针脚。这里需要注意的是,图二两边的粗体数字,这是PI4J的针脚编号。

好了,我们开始看代码,打开/opt/pi4j/examples/ControlGpioExample.java:

/*
 * #%L
 * **********************************************************************
 * ORGANIZATION  :  Pi4J
 * PROJECT       :  Pi4J :: Java Examples
 * FILENAME      :  ControlGpioExample.java
 *
 * This file is part of the Pi4J project. More information about
 * this project can be found here:  http://www.pi4j.com/
 * **********************************************************************
 */
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin;

/**
 * This example code demonstrates how to perform simple state
 * control of a GPIO pin on the Raspberry Pi.
 *
 * @author Robert Savage
 */
public class ControlGpioExample {

    public static void main(String[] args) throws InterruptedException {

        System.out.println("<--Pi4J--> GPIO Control Example ... started.");

        // create gpio controller
        final GpioController gpio = GpioFactory.getInstance();

        // provision gpio pin #01 as an output pin and turn on
        final GpioPinDigitalOutput pin = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_01, "MyLED", PinState.HIGH);

        // set shutdown state for this pin
        pin.setShutdownOptions(true, PinState.LOW);

        System.out.println("--> GPIO state should be: ON");

        Thread.sleep(5000);

        // turn off gpio pin #01
        pin.low();
        System.out.println("--> GPIO state should be: OFF");

        Thread.sleep(5000);

        // toggle the current state of gpio pin #01 (should turn on)
        pin.toggle();
        System.out.println("--> GPIO state should be: ON");

        Thread.sleep(5000);

        // toggle the current state of gpio pin #01  (should turn off)
        pin.toggle();
        System.out.println("--> GPIO state should be: OFF");

        Thread.sleep(5000);

        // turn on gpio pin #01 for 1 second and then off
        System.out.println("--> GPIO state should be: ON for only 1 second");
        pin.pulse(1000, true); // set second argument to 'true' use a blocking call

        // stop all GPIO activity/threads by shutting down the GPIO controller
        // (this method will forcefully shutdown all GPIO monitoring threads and scheduled tasks)
        gpio.shutdown();

        System.out.println("Exiting ControlGpioExample");
    }
}

连接图

要查看上面代码的运行效果,需要按照如下图所示接入LED灯:

执行

连接之后,执行命令:

./run ControlGpioExample

输出

这个例子的LED输出应该是这样的:

Turn ON for 5 seconds

Turn OFF for 5 seconds

Turn ON for 5 seconds

Turn OFF for 5 seconds

Turn ON for 1 second

Turn OFF

更多的例子解析,可以直接看PI4J的官方网站,非常详细。官方网站:http://pi4j.com/index.html

开发maven环境

这里简要说一下maven开发环境。毕竟如果真要做一些好东西出来,自然少不了用maven来管理项目。

在树莓派上安装maven,参考官方文档:http://maven.apache.org/install.html

PI4J的maven配置是:

<dependency>
    <groupId>com.pi4j</groupId>
    <artifactId>pi4j-core</artifactId>
    <version>1.1</version>
</dependency>

我的树莓派学习项目github地址:https://github.com/dasheng523/pi
我会一直学习,并加入一些模块的驱动开发代码。有兴趣的同学可以加入进来一起玩转树莓派。

 

转载自:http://blog.hyesheng.com/2016-09-07-raspberry03/

树莓派-常用方法控制GPIO

常用开源工程简介

树莓派内核中已经编译自带了 gpio 的驱动,我们常通过一些第三方写好的库函数来完成具体的操作,比较常见的操作库函数有:

python GPIO

  • 开发语言——python
  • 简单介绍——树莓派官方资料中推荐且容易上手。python GPIO 是一个小型的 python 库,可以帮助用户完成 raspberry 相关 IO 口操作,但是 python GPIO 库还没有支持 SPI、I2C 或者 1-wire 等总线接口。
  • 官方网站—— https://code.google.com/p/raspberry-gpio-python/

wiringPi

  • 开发语言——C 语言
  • 简单介绍——wiringPi 适合那些具有 C 语言基础,在接触树莓派之前已经接触过单片机或者嵌入式开发的人群。wiringPi 的 API 函数和 arduino 非常相似,这也使得它广受欢迎。作者给出了大量的说明和示例代码,这些示例代码也包括 UART 设备,I2C 设备和 SPI 设备等。
  • 官方网站—— http://wiringpi.com/

BCM2835 C Library

  • 开发语言——C 语言
  • 简单介绍——BCM2835 C Library 可以理解为使用C语言实现的相关底层驱动,BCM2835 C Library 的驱动库包括 GPIO、SPI 和 UART 等,可以通过学习 BCM2835 C Library 熟悉 BCM2835 相关的寄存器操作。如果有机会开发树莓派上的 linux 驱动,或自主开发 python 或 PHP 扩展驱动,可以从 BCM2835 C Library 找到不少的“灵感”。
  • 官方网站—— http://www.airspayce.com/mikem/bcm2835/

树莓派GPIO编号方式

  1. 功能物理引脚: 从左到右,从上到下:左边基数,右边偶数:1-40
  2. BCM: 编号侧重 CPU 寄存器,根据 BCM2835 的 GPIO 寄存器编号。
  3. wpi: 编号侧重实现逻辑,把扩展 GPIO 端口从 0 开始编号,这种编号方便编程。正如下图 WiringPi 一栏。

python GPIO

先安装 python-dev,输入以下指令。

sudo apt-get install python-dev

安装 RPi.GPIO,依次输入以下指令。

例子:

# -*- coding: utf-8 -*-    
 import RPi.GPIO as GPIO    
 import time    
 # BOARD编号方式,基于插座引脚编号    
 GPIO.setmode(GPIO.BOARD)    
 # 输出模式    
 GPIO.setup(11, GPIO.OUT)    

 while True:    
     GPIO.output(11, GPIO.HIGH)    
     time.sleep(1)    
     GPIO.output(11, GPIO.LOW)    
     time.sleep(1)   

执行:

sudo python led.py

说明:

  • GPIO.setmode(GPIO.BOARD),采用插座引脚编号方式。
  • 由于采用插座引脚编号方式,此处的 11 脚相当于 BCM2835 寄存器编号方式的引脚 11。

WiringPi GPIO

说明

WiringPi 是应用于树莓派平台的 GPIO 控制库函数,WiringPi 遵守 GUN Lv3。wiringPi 使用 C 或者 C++ 开发并且可以被其他语言包转,例如 Python、ruby 或者 PHP 等。 wiringPi 包括一套 gpio 控制命令,使用 gpio 命令可以控制树莓派 GPIO 管脚。用户可以利用 gpio 命令通过 shell 脚本控制或查询 GPIO 管脚。wiringPi 是可以扩展的,可以利用 wiringPi 的内部模块扩展模拟量输入芯片,可以使用 MCP23x17/MCP23x08(I2C 或者 SPI)扩展 GPIO 接口。另外可通过树莓派上的串口和 Atmega(例如 arduino 等)扩展更多的 GPIO 功能。另外,用户可以自己编写扩展模块并把自定义的扩展模块集成到 wiringPi 中。WiringPi 支持模拟量的读取和设置功能,不过在树莓派上并没有模拟量设备。但是使用 WiringPi 中的软件模块却可以轻松地应用 AD 或 DA 芯片。

wiringPi 安装

  • 方案 A——使用 GIT 工具,通过 GIT 获得 wiringPi 的源代码。
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build

build 脚本会帮助你编译和安装 wiringPi。

tar xfz wiringPi-xx.tar.gz
cd wiringPi-xx
./build

测试

wiringPi 包括一套 gpio 命令,使用 gpio 命令可以控制树莓派上的各种接口,通过以下指令可以测试wiringPi 是否安装成功。

$gpio -v
$gpio readall

即可出现上面的 io 图。

例子

[cpp] view plaincopy 在 CODE 上查看代码片派生到我的代码片

#include <wiringPi.h>    
int main(void)    
{    
  wiringPiSetup() ;    
  pinMode (0, OUTPUT) ;    
  for(;;)     
  {    
    digitalWrite(0, HIGH) ; delay (500) ;    
    digitalWrite(0,  LOW) ; delay (500) ;    
  }    
}   

编译运行

在树莓派上:

gcc -Wall -o test test.c -lwiringPi 
sudo ./test

在虚拟机中:

am-linux-gcc -Wall -o test test.c -lwiringPi 
sudo ./test

注意事项:

  • IO 的编号方式略有不同,采用 wiring 编码方式。
  • -lwiringPi 表示动态加载 wiringPi 共享库。

BCM2835 C Library

  • 下载:$ wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.35.tar.gz
  • 解压缩:$tar xvzf bcm2835-1.35.tar.gz
  • 进入压缩之后的目录:$cd bcm2835-1.35
  • 配置:$./configure
  • 从源代码生成安装包:$make
  • 执行检查:$sudo make check
  • 安装 bcm2835库:$sudo make install
  • 例子[cpp] view plaincopy 在 CODE 上查看代码片派生到我的代码片
#include <bcm2835.h>    

// P1插座第11脚    
#define PIN RPI_GPIO_P1_11    

int main(int argc, char **argv)    
{    
  if (!bcm2835_init())    
  return 1;    

  // 输出方式    
  bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP);    

  while (1)    
  {    
    bcm2835_gpio_write(PIN, HIGH);    
    bcm2835_delay(100);    

    bcm2835_gpio_write(PIN, LOW);    
    bcm2835_delay(100);    
  }    
  bcm2835_close();    
  return 0;    
}   

注意事项:

  • IO 的编号方式略有不同,采用 wiring 编码方式。
  • -lwiringPi 表示动态加载 wiringPi 共享库。

转载自:

http://wiki.jikexueyuan.com/project/raspberry-pi/gpio.html

 

 

 

树莓派-简单入门

前言

我一直梦想着能够做出一款自制的飞行器,然后带着它游历世界各地,拍摄各种好玩的照片。听说树莓派似乎可以将我的编程专业发挥出来,既然如此,那么就开干吧。

那么就从环境的搭建开始做起,并以此博客作为记录和总结,也方便各位树莓派以及飞行器爱好者参考和借鉴。此外,我写的代码都会公开出来,方便大家探讨。

当然,本人初学者更希望有各路大神指点指点。

树莓派(Raspberry Pi)是学习计算机知识、架设服务器的好工具,价格低廉,可玩性高。

本文根据我的亲身经验,介绍如何从零开始,搭建一个树莓派服务器,控制 LED 灯。你会看到,树莓派玩起来实在很容易。

一、型号

树莓派是一个迷你电脑,集成在一块电路板。目前,最新的型号有两个。

(1)Raspberry Pi 3代 B 型

 

(2)Raspberry Pi zero (含 zero w)

虽然后者便宜,但是少了许多接口(比如只有一个 USB 口),CPU 和内存都比较低,配件也少,因此推荐购买第3代的 B 型。以下都针对这个型号,但大部分内容对 zero 也适用。

二、配件

树莓派本身只是一个主机。要运行起来,必须有配件。

(1)电源

Micro USB 接口的手机充电器,就可以充当电源,但输出必须是 5V 电压、至少 2A 电流。充电宝当电源也没问题。

(2)Micro SD 卡

树莓派不带硬盘,Micro SD 卡就是硬盘。最小容量8G,推荐使用16G和32G的卡。

(3)显示器

树莓派有 HDMI 输出,显示器必须有该接口。如果有 HDMI 转 VGA 的转接线,那么 VGA 显示器也可以。我用的是一个 7 寸的液晶监视器。

不过,显示器只在安装系统时需要,后面可以 SSH登录 ,所以就不需要了。

(4)无线键鼠

树莓派内置蓝牙,USB 或蓝牙的无线键鼠都可以用。

就像显示器一样,如果树莓派已经装好系统,而且只当作服务器,无线键鼠也可以不配。

三、电子元件

除了配件,下面的实验还需要一些电子元件。

(1)面包板(一块)

(2)连接线(若干)

注意,连接线必须一端是公头,一端是母头。

另外,最好也备一些两端都是公头的连接线。

(3)LED 二极管(若干)

(4)270欧姆的电阻(若干)

四、安装系统

如果商家已经装好系统,可以跳过这一步,否则需要自己安装操作系统。

官方提供的操作系统是 Raspbian,这是 Debian 系统的定制版。

官方还提供一个安装器 NOOBS,建议通过它来安装 Raspbian,相对简单一点。

下载 NOOBS。
格式化 Micro SD 卡为 FAT 格式(操作指导)。
解压NOOBS.zip到 Micro SD 卡根目录。
插入 Micro SD 卡到树莓派底部的卡槽,接通电源,启动系统。

正常情况下,按照屏幕上的提示,一路回车,就能装好系统。

五、SSH 登录

安装系统后,树莓派就可以上网了(Wifi 或者网线)。这时,你要看一下它的局域网 IP 地址,可以使用下面的命令。

$ sudo ifconfig

然后,更改系统设置,打开 SSH 登录(默认是禁止的)。

接着,从另一台电脑 SSH 登录树莓派。下面的命令是在局域网的另一台电脑上执行的。

$ ssh [email protected]

上面代码中,192.168.1.5是我的树莓派的地址,你要换成你的地址。树莓派的默认用户是pi

树莓派会提示你输入密码。pi的默认密码是raspberry。正常情况下,这样就可以登录树莓派了。接着,就可以进行各种服务器操作了,比如修改密码。

$ passwd

后面的实验需要将用户加入gpio用户组。

$ sudo adduser pi gpio

上面的代码表示将用户pi加入gpio用户组。

六、安装 Node

为了运行 Node 脚本,树莓派必须安装 Node,可以参考这篇文章

$ curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
$ sudo apt install nodejs

正常情况下,Node 8.x 版就已经安装成功了。

$ node -v
v8.1.0

七、点亮 LED

树莓派提供了一组对外的 IO 接口,称为 GPIO( 通用 IO 接口,General-purpose input/output)。

GPIO基本介绍

GPIO(General Purpose I/O Ports)意思为通用输入/输出端口,通俗地说,就是一些引脚,可以通过它们输出高低电平或者通过它们读入引脚的状态-是高电平或是低电平。GPIO是个比较重要的概念,用户可以通过GPIO口和硬件进行数据交互(如UART),控制硬件工作(如LED、蜂鸣器等),读取硬件的工作状态信号(如中断信号)等。GPIO口的使用非常广泛。掌握了GPIO,差不多相当于掌握了操作硬件的能力。

它的 40 个脚的定义如下图。

注意,左上角的第1针(3.3V)是一个方块,其他针脚都是圆的。将树莓派翻过来,背后可以看到 GPIO 有一个角是方的,通过这种方法就可以确认哪一个针眼是3.3V。

补充: 3.3v和5v代表着该针脚会输出3.3v和5v的电压,Ground代表着该针脚是接地的,GPIO0*则是一些待用户开发的针脚。每个针脚都可以使用程序进行控制操作。ground 代表 接地。

通过 GPIO ,树莓派可以与其他电子元件连接。下面根据 Jonathan Perkin 的文章,使用树莓派连接 LED 二极管。

这里需要用到面包板。本质上,面包板就是几根导线,上面开了许多可以连到导线的孔。

+极和-极是两根垂直的导线,标着1510这些数字的行,每一行都是一根水平的导线。导线与导线之间互不连接,另外,面包板的左右两半也是互不连接的。

然后,按照下面的图,将树莓派、面包板、LED 灯、电阻连起来。

上图中,红色导线表示电流的正极,从 GPIO 的第1针(3.3V)连到面包板。黑色导线表示电流的负极,从 GPIO 第三排的第6针(ground)连到面包板。它们连到面包板的哪个眼并不重要,但必须保证能组成一个完整的电路(上图的箭头流向)。注意,LED 二极管也有正负极,长脚表示正极,短脚表示负极。电阻没有正负极。

连接完成后,打开树莓派的电源,LED 应该就会亮起来了。

八、LED 控制脚本

下面,我们使用 Node 脚本控制 LED。

首先,将正极的导线从1号针脚(3.3V)拔出,插到第6排的11号针脚(上图的 GPIO 17)。这个针脚的电流是脚本可以控制的。

然后,在树莓派上新建一个实验目录,并安装控制 GPIO 的 Node 模块rpio。。

$ mkdir led-demo && cd led-demo
$ npm init -y
$ npm install -S rpio

接着,新建一个脚本led-on.js

// led-on.js
var rpio = require('rpio');

// 打开 11 号针脚(GPIO17) 作为输出
rpio.open(11, rpio.OUTPUT);

// 指定 11 号针脚输出电流(HIGH)
rpio.write(11, rpio.HIGH);

运行这个脚本,应该就会看到 LED 灯泡变亮了。

$ node led-on.js

再新建一个led-off.js脚本,只要改一行就行。

var rpio = require('rpio');

// Configure pin 11 (GPIO17) for output (i.e. read/write).
// rpio.setOutput(11);
rpio.open(11, rpio.OUTPUT);


// 指定 11 号针脚停止输出电流(LOW)
// Turn GPIO17 off, also known as 'LOW'.
rpio.write(11, rpio.LOW);

运行这个脚本,LED 灯泡应该就会熄灭了。

$ node led-off.js

有了这两个脚本,让 LED 闪烁就轻而易举了。新建一个led-blink.js脚本。

var rpio = require('rpio');

rpio.open(11, rpio.OUTPUT);

/*
 * Blink the LED quickly (10 times per second).  It is switched on every
 * 100ms, and a timeout is set for 50ms later to switch it off, giving us
 * the regular blink.
 */
setInterval(function blink() {
	rpio.write(11, rpio.HIGH);
	setTimeout(function ledoff() {
		rpio.write(11, rpio.LOW);
	}, 50);
}, 100);

上面的脚本让 LED 每秒闪烁10次。

$ node led-blink.js

九、HTTP 服务器

通过控制 LED 可以做很多事,比如架设一个 HTTP 服务器,每当有人访问,LED 就闪烁一下。

首先,在刚才的目录里面装一个服务器模块。

$ npm install -S server

然后,新建一个脚本server.js

var server = require('server');
var { get } = server.router;

var rpio = require('rpio');

rpio.open(11, rpio.OUTPUT);

function blink() {
  rpio.write(11, rpio.HIGH);
  setTimeout(function ledoff() {
    rpio.write(11, rpio.LOW);
  }, 50);
}

server({ port: 8080 }, [
  get('/' ,  ctx => {
    console.log('a request is coming...');
    blink();
  }),
]);

console.log('server starts on 8080 port');

运行这个脚本。

$ node server.js

然后,再打开一个命令行终端,访问8080端口,LED 就会闪一下。

$ curl http://localhost:8080

好了,今天的教程就到这里。接下来,你可以自己探索,做更多的尝试,比如写一个测试用例脚本,只要测试失败 LED 就会长亮,或者组装一个8位的加法器

 

 


python控制GPIO(教程可能是树莓派2的引脚,自己注意一下)

想用python来控制GPIO,最便捷的办法就是使用一些python类库,比如树莓派系统本身集成的RPi.GPIO。本文详细介绍如何使用RPi.GPIO来控制GPIO。

导入RPi.GPIO模块

可以用下面的代码导入RPi.GPIO模块。

import RPi.GPIO as GPIO

引入之后,就可以使用GPIO模块的函数了。如果你想检查模块是否引入成功,也可以这样写:

try:
    import RPi.GPIO as GPIO
except RuntimeError:
    print("引入错误")

针脚编号

在RPi.GPIO中,同时支持树莓派上的两种GPIO引脚编号。第一种编号是BOARD编号,这和树莓派电路板上的物理引脚编号相对应。使用这种编号的好处是,你的硬件将是一直可以使用的,不用担心树莓派的版本问题。因此,在电路板升级后,你不需要重写连接器或代码。

第二种编号是BCM规则,是更底层的工作方式,它和Broadcom的片上系统中信道编号相对应。在使用一个引脚时,你需要查找信道号和物理引脚编号之间的对应规则。对于不同的树莓派版本,编写的脚本文件也可能是无法通用的。

你可以使用下列代码(强制的)指定一种编号规则:

GPIO.setmode(GPIO.BOARD)
  # or
GPIO.setmode(GPIO.BCM)

下面代码将返回被设置的编号规则

mode = GPIO.getmode()

警告

如果RPi.GRIO检测到一个引脚已经被设置成了非默认值,那么你将看到一个警告信息。你可以通过下列代码禁用警告:

GPIO.setwarnings(False)

引脚设置

在使用一个引脚前,你需要设置这些引脚作为输入还是输出。配置一个引脚的代码如下:

# 将引脚设置为输入模式
GPIO.setup(channel, GPIO.IN)

# 将引脚设置为输出模式
GPIO.setup(channel, GPIO.OUT)

# 为输出的引脚设置默认值
GPIO.setup(channel, GPIO.OUT, initial=GPIO.HIGH)

释放

一般来说,程序到达最后都需要释放资源,这个好习惯可以避免偶然损坏树莓派。释放脚本中的使用的引脚:

GPIO.cleanup()

注意,GPIO.cleanup()只会释放掉脚本中使用的GPIO引脚,并会清除设置的引脚编号规则。

输出

要想点亮一个LED灯,或者驱动某个设备,都需要给电流和电压他们,这个步骤也很简单,设置引脚的输出状态就可以了,代码如下:

GPIO.output(channel, state)

状态可以设置为0 / GPIO.LOW / False / 1 / GPIO.HIGH / True。如果编码规则为,GPIO.BOARD,那么channel就是对应引脚的数字。

如果想一次性设置多个引脚,可使用下面的代码:

chan_list = [11,12]
GPIO.output(chan_list, GPIO.LOW)
GPIO.output(chan_list, (GPIO.HIGH, GPIO.LOW))

你还可以使用Input()函数读取一个输出引脚的状态并将其作为输出值,例如:

GPIO.output(12, not GPIO.input(12))

读取

我们也常常需要读取引脚的输入状态,获取引脚输入状态如下代码:

GPIO.input(channel)

低电平返回0 / GPIO.LOW / False,高电平返回1 / GPIO.HIGH / True。

如果输入引脚处于悬空状态,引脚的值将是漂动的。换句话说,读取到的值是未知的,因为它并没有被连接到任何的信号上,直到按下一个按钮或开关。由于干扰的影响,输入的值可能会反复的变化。
使用如下代码可以解决问题:

GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_UP)
  # or
GPIO.setup(channel, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

需要注意的是,上面的读取代码只是获取当前一瞬间的引脚输入信号。

如果需要实时监控引脚的状态变化,可以有两种办法。最简单原始的方式是每隔一段时间检查输入的信号值,这种方式被称为轮询。如果你的程序读取的时机错误,则很可能会丢失输入信号。轮询是在循环中执行的,这种方式比较占用处理器资源。另一种响应GPIO输入的方式是使用中断(边缘检测),这里的边缘是指信号从高到低的变换(下降沿)或从低到高的变换(上升沿)。

轮询方式

while GPIO.input(channel) == GPIO.LOW:
    time.sleep(0.01)  # wait 10 ms to give CPU chance to do other things

边缘检测

边缘是指信号状态的改变,从低到高(上升沿)或从高到低(下降沿)。通常情况下,我们更关心于输入状态的该边而不是输入信号的值。这种状态的该边被称为事件。
先介绍两个函数:

  • wait_for_edge() 函数。
    wait_for_edge()被用于阻止程序的继续执行,直到检测到一个边沿。也就是说,上文中等待按钮按下的实例可以改写为
channel = GPIO.wait_for_edge(channel, GPIO_RISING, timeout=5000)
if channel is None:
    print('Timeout occurred')
else:
    print('Edge detected on channel', channel)
  • add_event_detect() 函数
    该函数对一个引脚进行监听,一旦引脚输入状态发生了改变,调用event_detected()函数会返回true,如下代码:
GPIO.add_event_detect(channel, GPIO.RISING)  # add rising edge detection on a channel
do_something()
// 下面的代码放在一个线程循环执行。
if GPIO.event_detected(channel):
    print('Button pressed')

上面的代码需要自己新建一个线程去循环检测event_detected()的值,还算是比较麻烦的。

不过可采用另一种办法轻松检测状态,这种方式是直接传入一个回调函数:

def my_callback(channel):
    print('This is a edge event callback function!')
    print('Edge detected on channel %s'%channel)
    print('This is run in a different thread to your main program')

GPIO.add_event_detect(channel, GPIO.RISING, callback=my_callback)

如果你想设置多个回调函数,可以这样:

def my_callback_one(channel):
    print('Callback one')

def my_callback_two(channel):
    print('Callback two')

GPIO.add_event_detect(channel, GPIO.RISING)
GPIO.add_event_callback(channel, my_callback_one)
GPIO.add_event_callback(channel, my_callback_two)

注意:回调触发时,并不会同时执行回调函数,而是根据设置的顺序调用它们。

综合例子:点亮LED灯

好了,上面说明了一大堆函数库的用法,那么现在就应该来个简单的实验了。这个实验很简单,点亮一个LED灯。

  • 编写代码之前,首先你需要将led灯的针脚通过杜邦线连接到树莓派的引脚上,比如你可以连接到11号引脚。
  • 新建一个main.py文件,写入如下代码:
import RPi.GPIO as GPIO  //引入函数库
import time

RPi.GPIO.setmode(GPIO.BOARD)  //设置引脚编号规则
RPi.GPIO.setup(11, RPi.GPIO.OUT)    //将11号引脚设置成输出模式

while True
    GPIO.output(channel, 1)   //将引脚的状态设置为高电平,此时LED亮了
    time.sleep(1)   //程序休眠1秒钟,让LED亮1秒
    GPIO.output(channel, 0)   //将引脚状态设置为低电平,此时LED灭了
    time.sleep(1)   //程序休眠1秒钟,让LED灭1秒

GPIO.cleanup()    //程序的最后别忘记清除所有资源
  • 保存,并退出文件。执行python3 main.py,即可观看效果。Ctrl+C可以关闭程序。
  • 此外,不妨也试试其它的函数吧,增强印象。

使用PWM

这个python函数库还支持PWM模式的输出,我们可以利用PWM来制作呼吸灯效果。详情看代码:

import time
import RPi.GPIO as GPIO   //引入库
GPIO.setmode(GPIO.BOARD)  //设置编号方式
GPIO.setup(12, GPIO.OUT)  //设置12号引脚为输出模式

p = GPIO.PWM(12, 50)  //将12号引脚初始化为PWM实例 ,频率为50Hz
p.start(0)    //开始脉宽调制,参数范围为: (0.0 <= dc <= 100.0)
try:
    while 1:
        for dc in range(0, 101, 5):
            p.ChangeDutyCycle(dc)   //修改占空比 参数范围为: (0.0 <= dc <= 100.0)
            time.sleep(0.1)
        for dc in range(100, -1, -5):
            p.ChangeDutyCycle(dc)
            time.sleep(0.1)
except KeyboardInterrupt:
    pass
p.stop()    //停止输出PWM波
GPIO.cleanup()    //清除

结语

在文中,主要讲解了GPIO的概念,以及如何使用python操作GPIO。如果有条件,建议大家多动动手,你会收获不少满足感。我也是初学者,如果你有任何问题,大家一起探讨学习。
本文参考文档:https://sourceforge.net/p/raspberry-gpio-python/wiki/

本文转载自:

http://www.ruanyifeng.com/blog/2017/06/raspberry-pi-tutorial.html

http://blog.hyesheng.com/2016-09-01-raspberry02/

机器学习练手-Python玩“跳一跳” iOS+Win 硬件实现

皮皮哇皮皮哇

更新了基于OpenCV的算法,计算更准确。

还有Arduino实现,溜了溜了

——————————-

感谢来自 @神奇的战士 的跳跃距离算法

原项目地址:wangshub/wechat_jump_game

知乎专栏:教你用Python来玩微信跳一跳


1.3 再更: 悄咪咪地加上Arduino版

1.3 又更了: 竟然有1k赞还上了日报,一本满足。昨天研究了一晚上,加入了大家喜爱的OpenCV。现在计算准确率已经很好了,不会出现误差累积。感谢 @船D长 :用Python+Opencv让电脑帮你玩微信跳一跳 给我的启发,大神的代码简洁优雅,非常受用。

1.2 更:谢谢大家的400赞,非常开心。想说一下,我是因为手边只有树莓派才用树莓派控制舵机的,它毕竟是一台200+的小型计算机,肯定是大材小用了。想要自己动手做一个的知友们可以不用急着买树莓派,给我一两天的时间。我的arduino已经到啦,正在测试!

本项目源码: yangyiLTS/wechat_jump_game_iOS

认真写的一个简介

现在已有的跳一跳辅助原理有以下这些:

外星力量派:

日天派:

  • 直接抓取post请求包修改分数,服务器不对分数进行验证,想改多少改多少

平民方法:

基本步骤:1、获取游戏画面;2、图像分析计算跳跃距离;3、模拟触摸手机屏幕进行游戏。

其中针对不同平台也有不同的实现方案:

  • Android平台:adb工具实现截图和触摸,PC或手机实现图像分析
  • iOS平台+Mac:使用Mac的WDA工具,原理同adb工具。
  • iOS但没有Mac:我的方法可能可以解决这个问题

先上效果:

基本思路是:

  1. 使用iOS自带Airplay服务将游戏画面投影到电脑上。
  2. 使用Pillow库截取电脑屏幕,获得游戏画面。
  3. 使用OpenCV分析图片,计算出跳跃距离,乘以时间系数获得按压时间。
  4. 将按压时间发送至树莓派/Arduino,树莓派/Arduino控制舵机点击手机屏幕。

运行环境&工具

  • Python 3.6 in Windows
  • Pillow、numpy 、pyfirmata
  • opencv-python
  • 局域网环境
  • iToools Airplayer
  • 树莓派 或 Arduino
  • SG90 舵机
  • 杜邦线、纸板
  • 一小块海绵
  • 橙子或其它多汁水果(可选)

原理&步骤

下载源码

  • 下载wechat_autojump_iOS&Win_opencv.py到Windows。
  • 如果使用树莓派,下载 servo_control.py到树莓派。
  • 如果使用Arduino,下载servo_control_arduino.py到Windows,并且确保windows已经装好Arduino驱动和Arduino IDE。

舵机部分

  • 拿一根杜邦线粘在舵机的摆臂上,并且用纸板固定舵机到合适高度,如图:

  • 取一小块海绵,约10mm*10mm*5mm,不必太精确。海绵中间挖一个小洞。大概是这样:
  • 海绵上滴水浸透,放在手机屏幕上“再来一次”的位置。杜邦线的另一头插进橙子。(触发电容屏需要在屏幕上形成一个电场,我尝试过连接干电池负极的方案,但是效果不理想,最后不得已拿了室友的一个橙子。当然,一直捏着或者含着导线也是可以的。)

如果使用树莓派

  • 舵机连接上树莓派,电源使用5v(Pin #04,Pin #06),舵机控制线接在GPIO18(Pin #12)。

  • 树莓派(OS:Raspbian Jessie)连接上局域网。
  • 打开 servo_control.py,这里需要根据实际安装位置调整舵机高点和低点位置(范围: 2.5~12.5)
servo_down = 3.8
servo_up = 5 

最终效果

  • 海绵放在“再来一次”的位置可以自动重新开始,然后就会一直自动刷分
  • 在wechat_autojump_iOS&Win_opencv.py里

文档的开头需要注释掉这句,这是Arduino使用的

#from servo_control_arduino import arduino_servo_run

设置树莓派的ip地址

ip_addr = '192.168.199.181'

main()函数里面需要选择 send_time()

# #send_time() 为树莓派控制函数
 send_time(t)

 # #arduino_servo_run() 为arduino控制函数
 ##arduino_servo_run(t/1000)
  • 最后树莓派上运行servo_control.py ,监听9999端口,等待Win的计算结果

如果使用Arduino

  • Arduino请选择Arduino UNO或Arduino Mega,因为pyfrimata库不支持Arduino Nano。入门级的Arduino UNO成本在80RMB左右。
  • Arduino需要烧入预置的StandardFirmata程序,在Arduino IDE的自带示例里面可以找到
  • 在工具—端口可以看到当前Arduino连接的串行端口,记下来等下要用到。

  • 安装pyfirmata,cmd运行
pip install pyfirmata
  • 把舵机连接上Arduino,舵机有一个三线的接口。黑色(或棕色)的线是接地线,红线接+5V电压,黄线(或是白色或橙色)接控制信号端。舵机的电源线直接接在Arduino的+5V输出和GND上,控制信号端接在Digital 3输出口(程序设置是3号口,可以修改,但是必须是支持PWM输出的接口)

如果做完下面步骤,程序跑起来之后,发现舵机即使不动也会发出 “滋滋”的声音而且动作缓慢,是因为电脑USB口供电不足所致。这个时候需要对舵机使用外接5V电源,接上5V电源之后,还要把外接电源的地线(负极)跟Arduino的地线(板上的GND口)连在一起。

  • 打开servo_control_arduino.py,这是Arduino的控制脚本

这里填入上一步看到的端口

# 修改串口编号 如果Arduino驱动正确,在Arduino IDE可以看到串口编号
serial_int = 'COM3'

这里根据Arduino的型号选择,不需要的那行注释或删掉

# 如果是Arduino UNO 使用这一行
board = pyfirmata.Arduino(serial_int)

# 如果是Arduino Mega 使用这一行  pyfirmata库暂不支持Nano
board = pyfirmata.ArduinoMega(serial_int)

然后调试一下舵机的最高点和最低点

# 设置舵机的高点和低点  单位:角度
# 范围 0-180°
servo_high = 45
servo_low = 37

舵机要根据实际的安装位置调试,运动幅度不宜太大,直接运行servo_control_arduino.py文件舵机会按设定位置来循环三次,如果舵机运动正常,则Arduino部分工作正常。

  • 打开wechat_autojump_iOS&Win_opencv.py,也有一些地方需要配置

这行代码位于文件开头,确保没有被注释

from servo_control_arduino import arduino_servo_run

到文档的靠后的部分找到main()函数,其中

控制函数选择 arduino_servo_run(),需要把send_time(t)注释掉

# #send_time() 为树莓派控制函数
# send_time(t)

# #arduino_servo_run() 为arduino控制函数
arduino_servo_run(t/1000)

配置完成

橙子有点蔫了。。。

Windows 部分

  • 下载Airplayer(免安装,暂无捆绑)
  • 配置Airplayer,画质什么的统统调到最高。启动iPhone上的Airplay,然后可以在电脑上看到iPhone画面,游戏运行时需要Airplayer全屏显示

  • 安装opencv-python、numpy
pip install numpy
pip install opencv-python
  • 下载wechat_autojump_iOS&Win_opencv.py,我的显示器分辨率是1920*1080,手机是iPhone7。如果使用不同的设备需要注意更改时间系数等参数。
  • 安装Pillow库,本文使用Pillow库的ImageGrab截屏,截屏代码:
im = ImageGrab.grab((654, 0, 1264, 1080)) 
im.save('a.png', 'png')

其中(654, 0, 1264, 1080)是截屏的范围,我的显示器分辨率是1080p,截取屏幕中间的部分得到的图片大小是610*1080,但这个时候图片最左边的一列的像素是黑色的。

  • 全部完成后,运行wechat_autojump_iOS&Win_opencv.py
  • 如果搭建完成后发现落点飘到天上去的情况,如图

是因为截图残留的黑边所致,这个黑边出现在截图的左边或者右边都会导致落点的计算偏差,打开screenshot_backups文件夹里面的图片会发现计算的轨迹像上图一样飘到整个图的上方。这时候的解决办法是:

找到pull_screenshot()函数:

# 使用PIL库截取Windows屏幕
def pull_screenshot():
    im = ImageGrab.grab((654, 0, 1264, 1080))
    im.save('a.png', 'png')

代码中(654, 0, 1264, 1080),表示截图的坐标。其中654,1264为截图的左边界和右边界,需要修改这两个边界使截图的尺寸变小。

举个例子,我发现默认参数的情况下出来的截图左边有3个像素的黑边,右边有1个像素的黑边,这个时候截图函数需要改成:

# 使用PIL库截取Windows屏幕
def pull_screenshot():
    im = ImageGrab.grab((657, 0, 1263, 1080)) # 左边增加3个像素,右边减少一个
    im.save('a.png', 'png')

修改完截图函数之后还需要修改默认图片的宽高,位置是在# Magic Number下面:

# Magic Number,不设置可能无法正常执行,请根据具体截图从上到下按需设置
under_game_score_y = 170  # 截图中刚好低于分数显示区域的 Y 坐标
press_coefficient = 2.38 # 长按的时间系数,
piece_base_height_1_2 = 10  # 二分之一的棋子底座高度,可能要调节
# 图片的宽和高
w,h = 610,1080

继续上面的例子,这个时候图片的宽和高需要改成

# 图片的宽和高
w,h = 606,1080  

然后再次运行程序检查截图是否还有黑边。

OpenCV算法详解

  • 本算法主要使用opencv和numpy两个库,首先要导入
import cv2
import numpy as np
  • 使用OpenCV模板匹配,找到棋子

棋子是一个非常特殊的目标,用PS把它抠出来,保存为模板使用OpevCV的模板匹配函数,准确率几乎完美。

meth = eval('cv2.TM_CCORR_NORMED')
piece_template = cv2.imread('piece.png',0) # 棋子模板
# 模板匹配 获取棋子坐标
def find_piece(img):
    res = cv2.matchTemplate(img, piece_template, meth)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    piece_x, piece_y = max_loc 
    # cv2.matchTemplate函数返回的是模板匹配最大值左上角的坐标
    # 下面修正为棋子底盘中点坐标
    piece_x = int(piece_x + piece_w / 2)
    piece_y = piece_y + piece_h - piece_base_height_1_2 
    return piece_x, piece_y

其中piece.png是棋子模板,长这样:

这个模板是必须的,但是它只适配610*1080的截图尺寸,如果分辨率跟我的有差异,需要另外扣一个模板,保存为png格式。如果对opencv没兴趣的话看到这里就可以跳过了。GitHub上还有其它目标的模板,但是不是一定要重新扣,原因下面讲。

  • 对其它特殊目标尝试模板匹配

我最初的想是对有加分的特殊目标(徐记士多,魔方,下水道,播放器)使用模板匹配,通过函数返回值使主函数增加延时,让我们可以吃到特殊目标的加分。但是后来发现模板匹配的效果不理想,可能是选用的匹配算法问题?或是模板问题?稍后尝试修复。

现在wechat_autojump_iOS&Win_opencv.py文件里有一段代码是进行特殊目标模板匹配的,但是因为我把置信度阈值调得很高(低了又乱匹配),所以匹配成功率非常低。如果不成功,则采用下面的算法寻找目标。

  • 对图片进行边缘检测

接下来继续寻找落点坐标,现在要把图像的边缘提取出来,游戏界面都是纯色,提取边缘非常容易:

因为已经获得了棋子坐标,所以这一步的时候先把棋子范围的像素去掉以免干扰。

代码是

img2 = cv2.GaussianBlur(img2, (3, 3), 0) # 先对图片高斯模糊
img_canny = cv2.Canny(img2, 1, 10) # 执行canny函数

输出的图像已经变成只有边缘的二值图像了

  • 尝试模板匹配小圆点

提取边缘之后尝试对连击之后的小圆点进行模板匹配,但是效果一样不理想,大部分时候会跳过这一步。

  • 找到目标落点

到了最后一步,就是找到棋子的落点,在代码中即board_,board_y。这个点有几个特点:

  1. 落点方块(圆柱)的最高点是整个图的最高点,先定义为board_y_top,这里我们已经排除了分数部分,背景和有可能高过方块的棋子部分。
  2. 落点平面的形状是对称的菱形或者椭圆形,确实有个别特殊的情况我们先不在意这些细节。然后这些形状在垂直方向是轴对称的,所以board_y_top一定在垂直的对称轴上。
  3. 同时落点平面也是水平方向轴对称的,并且从上往下遍历的第一个宽度最大的点是水平对称轴的位置。随便搞个图

然后我的思路是先找board_y_top:

# 遍历起点为分数下沿
board_y_top = under_game_score_y

for i in img_canny[under_game_score_y:]:
    if max(i): # i是一整行像素的list,max(i)返回最大值,一旦最大值存在,则找到了board_y_top
            break
    board_y_top += 1

# board_y_top的像素可能有多个 对它们的坐标取平均值
board_x = int(np.mean(np.nonzero(img_canny[board_y_top]))) 

然后从board_y_top开始找图形的侧边缘,因为是对称图形只要找左右边缘之一就可以了。但是在两个落点非常近的时候,棋子会挡住其中一个边缘,造成影响。所以先根据棋子位置判断棋子在目标落点的左边还是右边,再选择与棋子不同的位置寻找侧边沿

x1 = board_x
    fail_count = 0
    if board_x > piece_x:
        for i in img_canny[board_y_top:board_y_top+80]:
            try:
                x = max(np.nonzero(i)[0])
            except:
                pass
            if x > x1:
                x1 = x
                board_y += 1
                if fail_count < 5 and fail_count != 0:
                    fail_count -= 1
            elif fail_count > 5 and board_y - board_y_bottom >10:
                result = 1
                board_y -= 3
                break
            elif fail_count > 5 and board_y - board_y_bottom <= 10:
                result = 0
                break

            else:
                fail_count += 1

    else:
        for i in img_canny[board_y_top:board_y_top+80]:
            try:
                x = min(np.nonzero(i)[0])
            except:
                pass
            if  x < x1:
                x1 = x
                board_y += 1
                if fail_count < 5 and fail_count != 0:
                    fail_count -= 1
            elif fail_count > 5 and board_y - board_y_bottom > 10:
                board_y -= 3
                result = 1
                break
            elif fail_count > 5 and board_y - board_y_bottom <= 10:
                result = 0
                break
            else:
                fail_count += 1

这段代码非常的不pythonic,得想办法优化,其中零零碎碎的整数是一些容差参数,因为在像素角度不是绝对的圆形和方形,在方型平面上的效果会比圆形平面好,但是总体效果都很不错。

  • 最后,在上面那个算法抽风的情况下,采用原来的旧算法补救,可以说是十分之稳了
if result == 0:
        board_y = piece_y - abs(board_x - piece_x) * math.sqrt(3) / 3

问题&其它

  • 采用新版算法后,计算上的误差已经很小,可以查看screenshot_backups/文件夹,看是否得到正确的计算结果。但是仍然无法一直连续击中中心,这是由于舵机的物理误差引起的,需要调节好时间系数,舵机的高点和低点。如果采用海绵+水的接触方案,注意接触面的高度会因为水的蒸发而改变。评论区也有锡纸接触的方案,就看你们喜欢拉。
  • (1.3 已解决)由于是物理点击屏幕,会产生一定的操作误差。操作误差由时间常数误差、舵机运动时间、杜邦线触点插进海绵的深度等等因素引起。而当前使用的算法在一种情况下会出现误差叠加的问题。

Z形路径误差累积过程

如图:在绿色方块跳至灰色方块的过程中,出现操作误差。连续“Z形”路径中误差会逐渐累积。这个问题在落点方块较小时有一定的发生概率。我尝试过添加一些纠正算法,但效果不明显。这个误差会在Z形路径中断时(出现连续3个落点在一条直线上)自动修正。如果误差较大棋子即将掉落,可以终止程序,手动修改时间系数纠正。

  • 舵机的摆动角度和时间系数没有绝对的数值,需要慢慢尝试,当前使用的时间系数是2.43。
  • 可以使用arduino + pyfirmata组合控制舵机,成本比较低,已经可以用arduino啦~。
  • 这个游戏在跳了200+次之后方块会变的非常小(如题图),已经不是普通人类所能做到的。研究了外挂之后才知道手玩高分有多难,大家还是不要刷分了,会没朋友的。

来自一只正在艰难地转CS的通信狗,并没有二维码。第一次发文章,有很多小问题,欢迎各路大佬指教,给大佬倒茶。

 

转载自:https://zhuanlan.zhihu.com/p/32526110

认知百科精神领域-”阶层流动”必须努力的故事

我走在大学校园里,夏有梧桐秋有银杏,学校有名我无名,然而我走在文化中,我好像文化中的一员。我走在CBD,高楼林立而寂然无声,人们西装革履谈吐优雅,我也西装革履谈吐优雅,我身处大中国的大城市的心脏,我是心脏中热血奔流的一滴。

我并没有钱,然而我拿着过万的工资,我便坦然地按着过万的收入分配消费,我走过百货商场,看着艾格拉夏贝尔圣迪奥这些大学时代买不起的牌子轻轻一笑嫌弃档次,然而我走过一楼安静的奢侈品时不动声色捏紧了荷包。我站在黄金城市的黄金区域,做着这个黄金时代的黄金职业,我觉得自己和黄金融为一体,我是黄金的一员。

我过年回家,从飞机换高铁转长途,窗外的楼一路矮下去,最后坐上自己出身的县城肮脏破旧的公交车,我忍不住地窃喜,我走出来了,真好,看我真有出息。看看这里,落后,裙带关系横飞,公共设施脏乱,啊,这是我从前的故乡,如今不啻心中的深渊。

我走出了深渊,我自我感觉特别良好,我抽出一张清香的纸巾,我包好刚好嚼到没味道的口香糖,我在没人理会的分类垃圾桶前踌躇片刻,我自信地丢去“不可回收”的那一个。我来自都市文明,我来自文明都市。我孝顺,懂礼貌,我走路要直线,我要让家人一同享受,我也要接他们去吃高级日料,带他们听歌剧,让某年会出生的一个幸福小生命住在学区房里,三岁英语五岁钢琴。

然而联系我和这一切都市文明的唯一,不过一份工作而已。

当我停滞不前,当我及时行乐,我发现朝九晚五和白领高薪不可兼得,我不是我引以为傲的母校里唯一优秀的学生,新的人才被不断培养出来,新的血液不断注入,新的行业被不断创造出来,新的知识要不断学习,新的技能要跑着去掌握。还没等年终奖到手,新建立的企业将广告撒满天下,我谋生的公司市场份额步步退缩,苟延残喘。我自诩为擅长的expertise成为明日黄花,行业内已无人再使用。

我失去了这个联系,我安慰自己,岁月静好现世安稳,山河犹在国泰明安,回家吧,让温馨平淡的隐居生活抚慰这道创伤。

我发现家乡没有能匹配我专业的工作,我发现那寥寥的几个公司开出的薪水是我从前一个月的房租,我想看的电影没有3D影院可以放映,我喜欢的歌手从不来这里开演唱会,我踩在马路上没走几步裤腿已经沾灰,我晚上肚子饿没有便利店在等着。我过着柴米油盐自己有空做饭的日子,饭做好了我也摆到好看,我用着前年款的iphone拍了照片,我不屑用美图秀秀,我用PS加了康熙字典体,我也发朋友圈炫耀。年岁到时我被热心的阿姨拉去相亲。我坐在桌前,看着对面靠家里关系拿到职位的年轻人声音嘹亮地高谈阔论,我心中得意一笑,我想指出他的观点逻辑混乱,我想说出他的常识错误,我想和他一辩再辩。为什么不呢?我是从前的学霸而今的知识女性。
他按灭了烟头,喊,服务员!菜怎么还没来?

我真诚地爱着这故乡,可以写出千百篇高考作文来歌颂;我也真诚地、不为人知地嫌弃着它,从空气到土地。

我卑鄙吗?我俗气吗?
我脆弱吗?我无能吗?

我勤奋聪明,我热爱学习,我用知识改变了命运。我知道genetics,我忍不下转基因的谣言碎语;我养成说你好对不起谢谢的习惯,我不愿意去插队占用老弱病残专座;
我得益于良好的环境,我在良好的环境中成长为一个我满意的人。
环境是多么强大啊,同化一切!我感激从前读书上进的日子,我怀念从前理性聪明的同事。我鼓励自己继续上进,我坚信我能继续出淤泥而不染。

多年之后我早晨起来对着镜子梳妆打扮,我出门上班,我看见公交前队伍如龙,我皱起眉头也上前奋力挤去,我到了单位和同事一起淘宝广东来的A货包包,一个男同事把一块钱的红包拆成二十份,我连忙登录微信,我拿到了0.4元,我是手气最好。
深渊原来并非深渊,哪里有所谓深渊?争名逐利又如何?不过是生活细水如流,我大彻大悟,心满意足。
一抬头,见上面有个年轻姑娘在凝视着我,一脸书生气,目光怜悯又自得。

我以为这是我一个人的故事,原来它已有名字——“阶层流动”——而我的故事,不过是万万千千中平庸的一个。

我不是喜爱努力,我只是必须努力。我只是必须努力,并且每时每刻

*文中“我”为虚指。

作者:甘棠
链接:https://www.zhihu.com/question/39057568/answer/80005753

认知百科教育领域-女生靠结婚靠年轻不如靠学历稳

我肯定算底层家庭出身又有几分姿色的女孩。现在在国内top5名校读书。

家里条件不好又算得上漂亮的女生我初中一抓一把,她们在玻璃厂、超市工作,因为未婚先孕草草结婚,男孩子几句甜言蜜语就任人宰割。
当然有擅用美貌的,可是漂亮妞一茬儿一茬儿的来,竞争太激烈了。接近权力就自以为拥有权力,太傻了。
光靠漂亮脸蛋实现阶级跨越,对不起我真没见过。况且,又穷又贪又没见过世面还漂亮,不骗你骗谁。

看到她们,我更庆幸自己拼死考上现在的学校。我去过很多地方参加过比赛,当过交换生,导师带我去过很贵很贵的餐厅,有车有房学历很高的学长追过我。
只要我再努力一点,就可以申csc出国。

我接触过富二代,高官子女,公司老板,学术大牛。虽然不指望和他们有一毛钱关系,但我亲眼见过底层的女孩向往的生活。
因为亲眼见过,我知道饵再诱人,也不能用身体去搏,得不到的。

我承认年轻美貌是资本,但是没脑子没见识,好皮囊会成无妄之灾。
我也深知读了名校以后,想脱离底层一样还有很远的路,可是,比起靠男人靠结婚靠年轻,靠学历靠得住太多太多了。
读书是你唯一抓得住的,你知道吗。

作者:匿名用户
转载自:https://www.zhihu.com/question/59445150/answer/166725891

职场百科专业选择-公务员真的那么好吗?

刚刚考上家附近的公务员。
16年本科毕业计算机专业,考上公务员之前在成都敲了一年的代码,是在一个初创公司,待遇给的其实蛮不错,7k多一个月,对于我这种刚刚毕业的人来说蛮高了,加班也不算多,一般是项目上线前会加班一周,平时上班时间一般是九点半到六点半,我一般晚上七点走,因为不打卡的缘故所以早上也时不时十点才到公司哈哈哈。其实家就在成都旁边某城市,但是最终我还是谋生了离职的想法,离职的原因一方面是我认为互联网虽然发展快,但是技术的更替也快,需要不断学习和提升,但是往往一个公司的技术方向是确定的,那么如果换工作就需要在很短时间里去掌握其他的东西,我虽然现在才20多岁但是假如我30多了我有了家庭有了孩子我的学习能力和精力是否能跟上?第二是我现在选择的是初创公司,这样可以最大发挥我自己的能力,做出的项目也更有成就感,但是我肯定会逐步去大公司发展,我的同学很多一毕业就去迅雷京东这类次顶级互联网公司了,薪水也确实比我可观很多,但是他们却又向我抱怨他们的上班时间长加班时间多,996算是正常工作时间。我记得前不久聚餐,大家都胖了不少,但是我精神面貌比他们都好,因为他们大部分时间晚上十点回家都可能还要继续工作但是我八点就开始吃鸡或者和女友聊天甚至休息了。第三是华为辞退34岁员工的这个事我琢磨了一下,我一个码农假如没法进入管理层那么我在35以后我的价值肯定会降低,到了40岁的时候基本上市场价值和一个工作三年的码农可能就差不多了,因为大公司有更好的选择而小公司又给不了合适的价格,那我以及大多数我这种人肯定只能降低身价,但是那个时候正是家庭最需要钱的时候,那我是继续做一个月一万多的工作透支身体还是选择创业?创业的话埋头苦干交际圈窄的程序员创业的社会资源在哪里?最后一个原因可能也最致命,和女友分手了,原因是我不够关心她,说实话我现在也觉得莫名其妙。然后她一个月后就和别人好上了,社会社会。
然后我琢磨着考公务员吧。公务员在三四线城市可以说性价比极高。收入算中上,压力相对小一些,能接触一定的社会资源同时父母以及未来岳父岳母也满意啊。
由于交接等工作,我八月三十一号离职的九月二十三号考试居然考了第一,这我也得说不破不立啊。
然后因为已经离职了所以时间大把,报了个面试班面试也第一,现在已经在政审阶段了。
好了然后我再说一下公务员哪里好因为我父亲是公务员所以多少了解一点。首先他年纪大了加之经常下乡所以上班时间很随意,如果愿意的话甚至可以九点去上班十点就走,局长也很开明只要你完成工作其他的自己安排时间。所以我爸气色很好随时都乐呵呵的,心态很正。第二每年十五天公休假,再加上其他各种假,一年二三十天吧,因为我妈是教师所以他们可以很容易一起出去旅行,夫妻之间相处时间很多感情很稳定。第三收入,算上公积金的话一年十万左右,根据年终奖的多少而波动。我们这里房价5k左右。然后可以工作之余搞点小副业,公务员贷款利息极低再加上有资源所以可以去买商铺出租,当然这是最稳的,也可以和朋友开店啥的赚钱与否因人而异。第三就是社会地位有,再加上稳定所以很好相亲,考上后各种知道我分手了的来说媒。
所以我觉得公务员在三四线城市属于能考就一定去考着试试的工种,一线城市适合各种二代。
不过目前精准扶贫很累人尤其是基层,但是我敢说比很多给别人打工的还是要轻松一点的。
————————————————–
我现在又想了想,我并不是非要去赚那么多钱,我只是想能够更多的陪伴家人和朋友,钱是永远赚不完的,对我而言人活着,有比钱和理想重要的东西,而且太多太多。
转载自:https://www.zhihu.com/question/20062767/answer/292898341

学术百科论文领域-常见学术名词介绍

SCI、影响因子、引用次数及CNS是什么?

SCI是美国《科学引文索引》(Science Citation Index,简称)由美国科学信息研究所(Institute for Scientific Information,简称ISI,现被并入汤姆森集团)主办,是目前国际上公认的最具权威的科技文献检索工具,它从全球挑选出 6500种刊物(其中中国72种),将其刊载文章收录其中由于不同质量的期刊和文章被引用的频率不同,SCI在实际应用中,逐渐派生出科研绩效评价功能,但目前学术界公认的看法是,SCI在科研绩效评价中只能起辅助作用,学术评价仍应以国际通行的同行评议为主。

 

影响因子(impact factor)是美国汤姆森(Thomson)公司搞出来的引用程度指数。从这个指数可以看出每种杂志文章的引用多寡程度。引用次数及文章被征引次数。例如ISI 在2003年九月底整理出一份到2002年为止所有领域期刊被引用次数的前十名,其中,《生物化学期刊》(Journal of Biological Chemistry)以370,056次,再度蝉联宝座;英国《自然》(Nature)期刊 以326,546次,屈居亚军,《美国国家科学院刊》(Proceedings of the National Academy of Sciences, USA)与《科学》(Science)期刊则是分居第三、四名。临床医学领域与基础医学领域之经典期刊-《新英格兰医学杂志》(New England Journal of Medicine)、《细胞》(Cell)则是分别名列第八名与第十名。由于影响因子前6甚至前10的杂志,全跟生物有关的领域有关。原因是这些年生物方面的文章多,引用也多,影响因子必然高出其他领域很多,使得水平和IF不能直接划等号。在生物以外的其他领域,高水平的杂志IF也比较高,仍然是事实。虽然大家不明说IF,但直接参考IF评价水平,也没什么大问题。在许多地方成为教授是否可以晋升、申请科研基金的一个重要指标。但也不全然可从IF值来评价文章水平的高低,如在生物学领域,很多有重要影响的文章是发表在不知名杂志上的;而有些大学者,一生的文章也没有几篇。比如,Luria和Delbruck证明基因突变的文章,发表在Genetics上;Avery证明DNA是遗传物质的文章,发表在Journal of Experimental Medicine 上;即使是Hershey和Chase的获得诺贝尔奖的那个著名试验,也不过发表在Journal of General Physiology上。获得诺贝尔奖的学者,绝大多数人都是根据一篇或少数几篇文章才获此殊荣的。以量取胜的例子当然也有,但极为罕见。

 

CNS(Cell, Nature, Science)是美国Cell(《细胞》)、英国Nature(《自然》)及美国Science(《科学》)三大举世公认的顶极科学期刊简称。做科学以在其中一个杂志上发表论文为荣。在几个生命科学著名杂志里面,有英国的《自然》、美国的《科学》、《细胞》、《新英格兰医学杂志》和《自然》的几个分科杂志。《自然》和《科学》有不同学科的论文,一般认为《自然》和《科学》的长文(在两个杂志里分别称为articles和research articles)最难进,然后是《细胞》,再是《自然》和《科学》的短文(分别称为letters和reports)。当然长短还和资料多少有关。《新英格兰医学杂志》的权威性和历史要优于《自然·医学》。但绝对区分杂志很难:《自然 · 细胞生物学》与《分子细胞》和《基因与发育》相当,并与《欧洲分子生物学组织杂志》接近,《自然 · 神经科学》与《神经元》相当,《自然·免疫学》与《免疫》和《实验医学杂志》相当。

转载自:http://hanxiumei3061.blog.163.com/blog/static/1076792762009816102823618/

 

wordpress-修改26主题代码记录

修改twenty-sixteen主题代码记录

一、添加了底部日期和版权信息

需要修改 foot.php 文件

<div class="site-info">
				<?php
					/**
					 * Fires before the twentysixteen footer text for footer customization.
					 *
					 * @since Twenty Sixteen 1.0
					 */
					do_action( 'twentysixteen_credits' );
				?>
		
				<span class="site-title">
		© Copyright 2017 -&nbsp;<?php esc_attr_e(date('Y')); ?> &nbsp;|&nbsp;
<a href="<?php echo esc_url( home_url( '/' ) ); ?>" rel="home"><?php bloginfo( 'name' ); ?></a>
		|&nbsp;&nbsp;All Rights Reserved		
				</span>
				<a href="<?php echo esc_url( __( 'https://wordpress.org/', 'twentysixteen' ) ); ?>"><?php printf( __( 'Proudly powered by %s', 'twentysixteen' ), 'WordPress' ); ?></a>
				
			</div><!-- .site-info -->

二、添加了额外的主题的css样式

不需要修改文件,只需要在 appearance 的 customize里面的 additional css 里面添加就行了

@media screen and (min-width: 61.5625em){

body:not(.search-results) article:not(.type-page) .entry-content {
		float: right;
		width: 100%;
	}
	
}

@media screen and (min-width: 56.875em)
{
.sidebar {
    margin-left: 72%;
    width: 28%;
}
}