2018年1月3日水曜日

RaspberryPi 2B + moode audio 4.0

概略

Raspberry Pi 2B にmoodeaudio.org の moode 4.0 をインストールする。
audio 出力は USB DAC を使う。

インストール

download するのではなかった。
http://moodeaudio.org にあるように、Raspberry Pi 上で作業する。

手順のまとめ

1、普通に動いている Raspberry Pi を用意する。
2、SD メモリを Raspberry Pi に USB 接続できるようにしておく。
3、その SD メモリに最新の lite 系の OS を書き込む。
dd コマンドの進捗状況は、MacOS X の場合、Control + t で表示される。
$ diskutil unmountDisk /dev/disk1
Unmount of all volumes on disk1 was successful
$ sudo dd bs=1m if=~/Downloads/2016-05-27-raspbian-jessie-lite.img of=/dev/disk1

Password:
$ diskutil eject /dev/disk1
Disk /dev/disk1 ejected

4、Raspberry Pi に SSH 接続して、次のスクリプトを実行する。

ssh 接続を有効にしておく。
Enter these commands via SSH on a Raspberry Pi running Raspbian.

1. Download the Image Builder

cd /home/pi
sudo wget -q http://moodeaudio.org/downloads/mos/mosbuild.sh -O /home/pi/mosbuild.sh
sudo chmod +x /home/pi/mosbuild.sh

2. Start the Image Builder

sudo ./mosbuild.sh


$ sudo ./mosbuild.sh 
****************************************************************
**
**  Moode OS Image Builder v1.0
**
**  Welcome to the automated process for creating the wonderful
**  custom Linux OS that runs moOde audio player.
**
**  You will need a Raspberry Pi running Raspbian with SSH
**  enabled, at least 2.5GB free space on the boot SDCard and
**  a spare USB or USB-SDCard drive that the new OS will be
**  written to during the build process.
**
**  Be sure to backup the SDCard used to boot your Pi
**
****************************************************************

////////////////////////////////////////////////////////////////
//
// STEP 1 - Download Raspbian Lite and create a new, base image
//
////////////////////////////////////////////////////////////////

** Check free disk space
** Do you have a backup of your boot SDCard (y/n)? y
** Unplug all USB storage devices from the Pi
** Are all USB storage devices unplugged (y/n)? y
** Plug in target USB drive for the new OS
** Is target USB drive plugged in (y/n)? y
** USB drive detected on /dev/sda
** Partitions unmounted on /dev/sda
** Option 1-5: use a proxy server for Internet access (y/n)? n
** Option 2-5: use a WiFi connection instead of Ethernet (y/n)? n
** Option 3-5: configure /var/www as squashfs (y/n)? n
** Option 4-5: install latest Linux Kernel (y/n)? y
** Option 5-5: Airplay, Ashuffle, LocalUI, Scrobbler, Squeezelite and UPnP/DLNA
** Install additional components (y/n)? y
** Ready for automated image build
** Proceed (y/n)? y
** Test Internet connection
** Download helper files
** Add options to properties file
** Load properties into env
** Download Rasbian Stretch Lite 2017-11-29-raspbian-stretch-lite.zip
2017-11-29-raspbian-stretch-li 100%[===================================================>] 347.83M  1.28MB/s    in 9m 0s   
** Unzip 2017-11-29-raspbian-stretch-lite.zip
Archive:  2017-11-29-raspbian-stretch-lite.zip
  inflating: 2017-11-29-raspbian-stretch-lite.img  
** Mount Raspbian image partitions
** Modify image
** Enable SSH
** Remove auto-resize task
** Enable familiar network interface names
** Install main worker script
** Enable script for autorun after reboot
** Change host name to moode
** Flush cached disk writes
** Image unmounted
** Write image to USB drive on /dev/sda
3629056+0 records in
3629056+0 records out
1858076672 bytes (1.9 GB, 1.7 GiB) copied, 744.411 s, 2.5 MB/s
** Flush cached disk writes
**
** New base OS image created
**
** Remove the USB drive and use it to boot a Raspberry Pi
** The build will automatically continue at STEP 2 after boot
**
** Save base OS img for additional builds (y/n)? n
$ exit


5、しばらくして上記コマンドによる USB 接続した SD メモリの書き込みが終わったら、その SD メモリを使って起動する

なかなかの長時間。1時間くらい?
最初に色々聞かれるが、適当に答えてしまった。
後で変更できると予想している。

6、なにやら作業が続いて、何度かかってに再起動を繰り返す。

3〜4時間くらいかかった。

7、USB DAC が使えない?

I2S のデバイスは使えるが、USB DAC は使えないようです。
使用中止。


2017年11月3日金曜日

RaspberryPi + OpenCV

あらためて
ここを参考にしてインストールする。
http://uepon.hatenadiary.com/entry/2017/02/26/143915
OpenCV 2.4.9
Python2

これでパッケージをインストール。
$ sudo apt-get install libopencv-dev
$ sudo apt-get install python-opencv
$ sudo apt-get install libgl1-mesa-dri

USB のカメラを接続しておいて、Raspberry Pi の Window 環境で次のプログラムを実行。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import cv2.cv as cv
import time

cv.NamedWindow("camera", 1)

capture = cv.CaptureFromCAM(0)

# 画像サイズの指定
cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_WIDTH,320)
cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_HEIGHT,240)

while True:
    img = cv.QueryFrame(capture)
    cv.ShowImage("camera", img)
    if cv.WaitKey(10) > 0:
        break
cv.DestroyAllWindows()

これでスクリーンにカメラで捉えた画像が表示される。

Mac では、こう。
$ sudo port install opencv python36
(env_opencv) /Documents/env_opencv: $ pip install opencv-python


これはだめだった。
pip3 で、opencv-python はインストールできますが。
import cv2 でエラーになってしまう。
$ sudo pip3 install opencv-python==3.3.0.10
Collecting opencv-python==3.3.0.10
  Downloading https://www.piwheels.org/simple/opencv-python/opencv_python-3.3.0.10-cp35-cp35m-linux_armv6l.whl (6.3MB)
    100% |████████████████████████████████| 6.3MB 9.8kB/s 
Requirement already satisfied: numpy>=1.12.1 in /usr/lib/python3/dist-packages (from opencv-python==3.3.0.10)
Installing collected packages: opencv-python
Successfully installed opencv-python-3.3.0.10

$ sudo pip3 install opencv-python -U
Collecting opencv-python
  Downloading https://www.piwheels.org/simple/opencv-python/opencv_python-3.4.2.17-cp35-cp35m-linux_armv6l.whl (7.0MB)
    100% |████████████████████████████████| 7.0MB 9.2kB/s 
Collecting numpy>=1.12.1 (from opencv-python)
  Downloading https://www.piwheels.org/simple/numpy/numpy-1.15.0-cp35-cp35m-linux_armv6l.whl (6.4MB)
    100% |████████████████████████████████| 6.4MB 10kB/s 
Installing collected packages: numpy, opencv-python
  Found existing installation: numpy 1.12.1
    Not uninstalling numpy at /usr/lib/python3/dist-packages, outside environment /usr
  Found existing installation: opencv-python 3.3.0.10
    Uninstalling opencv-python-3.3.0.10:
      Successfully uninstalled opencv-python-3.3.0.10

Successfully installed numpy-1.15.0 opencv-python-3.4.2.17



16G にしてもう一度。20180815
blogger がつづりを勝手に変更しているときがある。
RaspberryPi 2B, stretch, Python3.5.3, OpenCV3.4.1
2B で、make -j4 は良いのか?

https://www.pyimagesearch.com/2017/09/04/raspbian-stretch-install-opencv-3-python-on-your-raspberry-pi/
https://raspberrypi.stackexchange.com/questions/69169/how-to-install-opencv-on-raspberry-pi-3-in-raspbian-jessie
https://www.pyimagesearch.com/2015/02/23/install-opencv-and-python-on-your-raspberry-pi-2-and-b/

$ sudo apt-get purge wolfram-engine
$ sudo apt-get purge libreoffice*
$ sudo apt-get clean
$ sudo apt-get autoremove

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo rpi-update
$ sudo reboot

$ sudo apt-get install build-essential git cmake pkg-config
$ sudo apt-get install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev
sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev
$ sudo apt-get install libxvidcore-dev libx264-dev
$ sudo apt-get install libgtk2.0-dev libgtk-3-dev
$ sudo apt-get install libatlas-base-dev gfortran
$ sudo apt-get autoremove

cd ~
$ git clone https://github.com/Itseez/opencv.git
cd ~/opencv
git checkout 3.1.0
cd ~
git clone https://github.com/Itseez/opencv_contrib.git
cd ~/opencv_contrib
git checkout 3.1.0
だめだった 3.1.0 ではなく、3.4.1 でやってみる。
$ cd ~/opencv
$ rm -fr build
$ git checkout ./
$ git checkout 3.4.1
$ cd ~/opencv_contrib
$ git checkout ./
$ git checkout 3.4.1

$ sudo apt-get install python3 python3-setuptools python3-dev
$ wget https://bootstrap.pypa.io/get-pip.py
$ sudo python3 get-pip.py
$ pip install numpy

$ cd ~/opencv
$ mkdir build
$ cd build
$ cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D INSTALL_C_EXAMPLES=OFF \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
    -D BUILD_EXAMPLES=ON ..

$ sudo nano /etc/dphys-swapfile

CONF_SWAPSIZE=100
=>
CONF_SWAPSIZE=1024
$ sudo /etc/init.d/dphys-swapfile stop
$ sudo /etc/init.d/dphys-swapfile start

$ make -j4
sudo make install
sudo ldconfig


--------

$ tightvncserver -geometry 1100x700 -depth 24
vnc://192.168.100.178:5901

2017年1月4日水曜日

Raspberry Pi B + python3 + flask + i2c + PWM 完了

Raspberry Pi B + python3 + flask + i2c + PWM ということで、html に slider を16個表示して、PCA9685 の PWM を制御します。
PCA9685 は「16-channel, 12-bit PWM Fm+ I²C-bus LED controller」です。秋月やアマゾンでも製品が売られています。ここでは、表面実装タイプの素子を秋月の変換基板に乗せて使っています。
PCA9685 はアドレスを持たせることができて、16個とか32個とか接続可能です。ひとつで 16ch なので16個繋げば 256 個の LED を制御できます。

前回のものに修正を加えます。

ディレクトリ構造を示します。
pwm_flask,py が主となるファイルです。

pwm_flask/
├── i2c_lcd.py
├── i2c_pwm.py
├── pwm_flask.py
├── static
│   ├── css
│   │   └── style.css
│   └── js
│       ├── jquery-3.1.1.min.js
│       └── jquery-3.1.1.min.map
└── templates
    ├── base.html
    ├── input_text_for_lcd.html
    └── set_pwm.html

これで clone して、「e55eabb」でチェックアウトすると、今回のバージョンになります。
$ git clone git@bitbucket.org:nagasako/pwm_flask.git
$ cd pwm_flask
$ git checkout e55eabb


i2c_lcd.py
#!/usr/bin/env python
# coding: UTF-8
'''
$ sudo apt-get install python-smbus i2c-tools

comment out this line in /etc/modprobe.d/raspi-blacklist.conf
blacklist i2c-bcm2708

add the following lines to /etc/modules
 i2c-dev
 i2c-bcm2708
and then reboot.

search all addr.
$ sudo i2cdetect -y 1
'''

import time
import datetime
from i2c_pwm import PWM


class MySingleton(object):
    __instance = None

    def __new__(cls):
        if cls.__instance is None:
            cls.__instance = super(MySingleton, cls).__new__(cls)
            cls.__instance.__initialized = False
            cls.__instance.socket = None
        return cls.__instance

    def __init__(self):
        if (self.__initialized):
            return
        self.__initialized = True
        self.st7032i = None
        self.pwm = None

    def init_i2c(self, raspi=True):
        if self.st7032i is None:
            self.st7032i = St7032i(0x3e, 1, 0x20, raspi)
        if self.pwm is None:
            self.pwm = PWM(0x40, False, raspi)


class St7032i(object):
    def __init__(self, addr=0x3e, ch=1, contrast=0x20, raspi=True):
        try:
            self.addr = addr
            self.ch = ch
            self.prev_now = 0
            self.raspi = raspi
            if raspi:
                import smbus
                self.bus = smbus.SMBus(ch)
                self.contrast = contrast
                self.reset()
                self.enable = True
            else:
                self.enable = False
        except:
            self.enable = False

    def reset(self):
        if not self.raspi:
            return
        contrast_h = 0x70 | (self.contrast & 0x0f)
        contrast_l = 0x54 | ((self.contrast >> 4) & 0x03)
        self.bus.write_i2c_block_data(
            self.addr, 0, [0x38, 0x39, 0x14, contrast_h, contrast_l, 0x6c])
#         // ST7032 Initial Program Code Example For 8051 MPU(8 Bit Interface) の初期化サンプルの通り
#         // Function Set : 8bit bus mode, 2-line mode,normal font,normal instruction mode
#         LCD_write(LCD_RS_CMD, 0b00111000, fd);      // 0x38
#         // Function Set : extension instruction mode
#         LCD_write(LCD_RS_CMD, 0b00111001, fd);      // 0x39
#         // Internal OSC frequency(extension instruction mode)
#         LCD_write(LCD_RS_CMD, 0b00010100, fd);      // 0x14
#         // Contrast set(extension instruction mode) コントラスト値下位4bit設定
#         LCD_write(LCD_RS_CMD, 0b01110000 | (LCD_CONTRAST & 0xF), fd); // 0x78 = 0x70 + 0x8
#         // Power/ICON/Contrast set(extension instruction mode) コントラスト値上位2bit設定
#         LCD_write(LCD_RS_CMD, 0b01011100 | ((LCD_CONTRAST >> 4) & 0x3), fd); // 0x5c + 0
#         // Follower control。internal follower on,
#         LCD_write(LCD_RS_CMD, 0b01101100, fd);      // 0x6c

        time.sleep(0.25)
        self.bus.write_i2c_block_data(self.addr, 0, [0x0c, 0x01, 0x06])
#         // Function Set。normal instruction mode。
#         // LCD_write(LCD_RS_CMD, 0b00111000, fd);   // 0x38
#         // Display On
#         LCD_write(LCD_RS_CMD, 0b00001100, fd);      // 0x0c
#         // Clear Display
#         LCD_write(LCD_RS_CMD, 0b00000001, fd);      // 0x01
#         // Entry Mode Set
#         LCD_write(LCD_RS_CMD, 0b00000110, fd);      // 0x06
        time.sleep(0.05)

    def clear(self):
        if self.enable and self.raspi:
            self.bus.write_i2c_block_data(self.addr, 0, [0x01])

    def mov_to(self, row=0, col=0):
        if self.enable and self.raspi:
            self.bus.write_i2c_block_data(self.addr, 0, [0x80 + 0x40 * row + col])

    def put_str(self, the_str):
        if self.enable and self.raspi:
            self.bus.write_i2c_block_data(self.addr, 0x40, list(map(ord, the_str)))


if __name__ == '__main__':
    try:
        my_lcd = St7032i(0x3e, 1)
        if True and my_lcd.enable:
            while True:
                time_str = datetime.datetime.now().strftime("%H:%m:%S")
                date_str = datetime.datetime.now().strftime("%y/%m/%d")
                my_lcd.clear()
                my_lcd.mov_to(0, 0)
                my_lcd.put_str(date_str)
                my_lcd.mov_to(1, 0)
                my_lcd.put_str(time_str)
                print(date_str + ' ' + time_str)
                time.sleep(1)
        # if False and my_lcd.enable:
        #     light_channel = 0
        #     adc = SpiAdc(light_channel)
        #     if adc is not None and my_lcd.enable:
        #         # Define delay between readings
        #         delay = 1
        #         while True:
        #             # Read the light sensor data
        #             light_level = adc.readChannel()
        #             # light_volts = adc.convertVolts(light_level, 2)

        #             # Print out results
        #             # print("Light: {} ({}V)".format(light_level,light_volts))
        #             # print("%4d %1.2f" % (light_level, light_volts))
        #             the_str = "    " + str(light_level)
        #             my_lcd.mov_to(0, 0)
        #             my_lcd.put_str(the_str[-4:])
        #             # Wait before repeating loop
        #             time.sleep(delay)

    except:
        print("Error accessing default I2C bus")


i2c_pwm.py
#!/usr/bin/env python
# coding: UTF-8
'''
i2c_pwm.py
'''

import time


class i2c_lib(object):
    def __init__(self, address, busnum=1, debug=False, raspi=True):
        self.address = address
        self.raspi = raspi
        if raspi:
            import smbus
            self.bus = smbus.SMBus(busnum)
        self.debug = debug

    def errMsg(self, msg):
        print("Error in %s accessing 0x%02X: Check your I2C address" % (msg, self.address))
        return -1

    def write8(self, reg, value):
        """ Writes an 8-bit value to the specified register/address """
        if not self.raspi:
            return 0
        try:
            self.bus.write_byte_data(self.address, reg, value)
            if self.debug:
                print("I2C: Wrote 0x%02X to register 0x%02X" % (value, reg))
            return 0
        except IOError:
            return self.errMsg('write8(reg=0x%02x, value=%d)' % (reg, value))

    def readU8(self, reg):
        """ Read an unsigned byte from the I2C device """
        if not self.raspi:
            return 0
        try:
            result = self.bus.read_byte_data(self.address, reg)
            if self.debug:
                print(
                    "I2C: Device 0x%02X returned 0x%02X from reg 0x%02X" % (
                        self.address, result & 0xFF, reg))
            return result
        except IOError:
            return self.errMsg('readU8(reg=0x%02x)' % (reg))


class PWM(object):
    # Registers/etc.
    __SUBADR1 = 0x02
    __SUBADR2 = 0x03
    __SUBADR3 = 0x04
    __ALLCALLADR = 0x05
    __MODE1 = 0x00
    __MODE2 = 0x01
    __PRESCALE = 0xFE
    __LED0_ON_L = 0x06
    __LED0_ON_H = 0x07
    __LED0_OFF_L = 0x08
    __LED0_OFF_H = 0x09
    __ALLLED_ON_L = 0xFA
    __ALLLED_ON_H = 0xFB
    __ALLLED_OFF_L = 0xFC
    __ALLLED_OFF_H = 0xFD

    def __init__(self, address=0x40, debug=False, raspi=True):
        self.i2c = i2c_lib(address, 1, debug, raspi)
        self.address = address
        self.debug = debug
        if self.debug:
            print("Reseting PCA9685 (%02x)" % (address))
        result = self.i2c.write8(self.__MODE1, 0x00)
        if self.debug:
            print("result = %d" % (result))
        if result < 0:
            self.i2c = None

        if self.debug:
            if self.i2c is not None:
                result = self.i2c.readU8(self.__ALLCALLADR)
                print('__ALLCALLADR = %02x' % (result))
                result = self.i2c.readU8(self.__SUBADR1)
                print('__SUBADR1 = %02x' % (result))
                result = self.i2c.readU8(self.__SUBADR2)
                print('__SUBADR2 = %02x' % (result))
                result = self.i2c.readU8(self.__SUBADR3)
                print('__SUBADR3 = %02x' % (result))

    def setPWMFreq(self, freq):
        """ Sets the PWM frequency """
        prescaleval = 25000000.0  # 25MHz
        prescaleval /= 4096.0  # 12-bit
        prescaleval /= float(freq)
        prescaleval = int(round(prescaleval)) - 1
        if self.debug:
            print("Setting PWM frequency to %d Hz" % (freq))
            print("Estimated pre-scale: %d" % (prescaleval))
        if self.debug:
            print("Final pre-scale: %d" % (prescaleval))

        oldmode = self.i2c.readU8(self.__MODE1)
        newmode = (oldmode & 0x7F) | 0x10  # sleep
        self.i2c.write8(self.__MODE1, newmode)  # go to sleep
        self.i2c.write8(self.__PRESCALE, prescaleval)
        self.i2c.write8(self.__MODE1, oldmode)
        time.sleep(0.005)
        self.i2c.write8(self.__MODE1, oldmode | 0x80)

        # self.i2c.write8(self.__MODE2, 0x10) # INVRT = 1, OUTDRV = 0
        if self.debug:
            result = self.i2c.readU8(self.__MODE1)
            print('__MODE1 = %02x' % (result))
            result = self.i2c.readU8(self.__MODE2)
            print('__MODE2 = %02x' % (result))

    def setPWM(self, channel, on, off):
        """ Sets a single PWM channel """
        self.i2c.write8(self.__LED0_ON_L + 4 * channel, on & 0xFF)
        self.i2c.write8(self.__LED0_ON_H + 4 * channel, on >> 8)
        self.i2c.write8(self.__LED0_OFF_L + 4 * channel, off & 0xFF)
        self.i2c.write8(self.__LED0_OFF_H + 4 * channel, off >> 8)
        # print('ch = %d, on = %d, off = %d' % (channel, on, off))

    def all_led_on(self):
        self.i2c.write8(self.__ALLLED_ON_L, 0x00)
        self.i2c.write8(self.__ALLLED_ON_H, 0x10)

    def all_led_off(self):
        self.i2c.write8(self.__ALLLED_OFF_L, 0x00)
        self.i2c.write8(self.__ALLLED_OFF_H, 0x10)


pwm_flask.py
#!/usr/bin/env python
# coding: UTF-8
'''
$ export FLASK_APP=pwm_flask.py
$ python -m flask run --host=0.0.0.0
'''
from flask import Flask, request, redirect
from flask import url_for, render_template, flash
# from flask import session, g, abort
from i2c_lcd import MySingleton
import datetime
import os
import json


OS_NAME = os.uname()
if 'raspberrypi' in OS_NAME:
    # Raspberry Pi
    RASPI = True
else:
    RASPI = False
print(OS_NAME, RASPI)

DEBUG = True
SECRET_KEY = 'something_secret_pwm_flask'
USERNAME = 'admin'
PASSWORD = 'adminpass'

app = Flask(__name__)
app.config.from_object(__name__)
# app.run(host='0.0.0.0', port=5000)


@app.route('/')
def input_text_for_lcd():
    return render_template('input_text_for_lcd.html')


@app.route('/send_text', methods=['POST'])
def send_text():
    date_str = datetime.datetime.now().strftime("%y/%m/%d")
    time_str = datetime.datetime.now().strftime("%H:%m:%S")
    msg_text = request.form['msg_text']
    my_singleton = MySingleton()
    my_singleton.init_i2c(RASPI)
    if RASPI:
        # my_lcd = St7032i(0x3e, 1)
        my_lcd = my_singleton.st7032i
        if my_lcd:
            my_lcd.clear()
            my_lcd.mov_to(0, 0)
            my_lcd.put_str(time_str)
            my_lcd.mov_to(1, 0)
            if msg_text:
                my_lcd.put_str(msg_text)
    else:
        print('send_text: ', msg_text)
    flash('New entry was successfully posted "' + msg_text + '" at ' + date_str + ' ' + time_str)
    return redirect(url_for('input_text_for_lcd'))


class DevPCA(object):
    def __init__(self, ch, val):
        self.ch = ch
        self.val = val


@app.route('/set_pwm', methods=['GET'])
def set_pwm_get():
    dev_list = []
    my_singleton = MySingleton()
    my_singleton.init_i2c(RASPI)
    pwm = my_singleton.pwm
    val = 0
    for ch in range(16):
        dev_pca = DevPCA(ch, val)
        dev_list.append(dev_pca)
        set_pwm(pwm, ch, val)
    return render_template('set_pwm.html', pca_channels=dev_list)


@app.route('/set_pwm_post', methods=['POST'])
def set_pwm_post():
    ch_str = request.form['ch']
    val_str = request.form['val']
    now_str = request.form['now']
    # print('set_pwm_post ch_str, val_str, now_str=', ch_str, val_str, now_str)
    ch = int(ch_str)
    val = float(val_str)
    now = int(now_str)
    my_singleton = MySingleton()
    my_singleton.init_i2c(RASPI)
    my_lcd = my_singleton.st7032i
    pwm = my_singleton.pwm
    if pwm and my_lcd and (now > my_lcd.prev_now):
        my_lcd.prev_now = now
        set_pwm(pwm, ch, val)
        my_lcd.clear()
        my_lcd.mov_to(0, 0)
        my_lcd.put_str(str(ch + 1))
        my_lcd.mov_to(1, 0)
        my_lcd.put_str(val_str)
    return json.dumps({'status': 'OK', 'ch': ch, 'val': val})


def set_pwm(pwm, ch, val):
    """ val is 0..100% float """
    width = int(4095.0 * val / 100.0)
    pwm.setPWM(ch, 0, width)


if __name__ == '__main__':
    app.run()


style.css
body {
    font-family: sans-serif;
    background: #eee;
}


base.html
<!doctype html>
<html>
<head>
  <title>PWM_Flask</title>
  <meta charset="utf-8">
  <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
  {% block head %}{% endblock %}
</head>
<body>
  {% block body %}{% endblock %}
</body>
  {% block bottom %}{% endblock %}
</html>


input_text_for_lcd.html
{% extends "base.html" %}
{% block body %}
  <form action="{{ url_for('send_text') }}" method=post>
    <dl>
      <dt>Message:
      <dd><input type="text" size="30" name="msg_text">
      <dd><input type="submit" value="send">
    </dl>
  </form>
  <div>
    <a href="{{ url_for('set_pwm_get') }}">set_pwm</a>
  </div>
  <div>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      <ul class=flashes>
      {% for message in messages %}
        <li>{{ message }}</li>
      {% endfor %}
      </ul>
    {% endif %}
  {% endwith %}
  </div>
{% endblock %}


set_pwm.html
{% extends "base.html" %}
{% block head %}
  <style>
    .range_class {
      width: 400px!important;
    }
  </style>
  <script src="{{ url_for('static', filename='js/jquery-3.1.1.min.js') }}"></script>
{% endblock %}

{% block body %}
  <form action="" method=post>
    <table>
      <thead>
        <tr>
          <th> ch </th>
          <th> PCA9685 value </th>
        </tr>
      </thead>
      <tbody>
      {% for pca_ch in pca_channels %}
        <tr>
          <td>{{ pca_ch.ch + 1 }}</td>
          <td>
            <input type="range" name="{{ pca_ch.ch }}" class="range_class"
                value="{{ pca_ch.val }}" min="0" max="100" step="0.1">
          </td>
        </tr>
      {% endfor %}
      </tbody>
    </table>
  </form>

  <div>
    <span id="pwm_ch"></span>
    <span id="pwm_val"></span>
  </div>

  <div>
    <a href="{{ url_for('input_text_for_lcd') }}">input_text_for_lcd</a>
  </div>

  <div>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      <ul class=flashes>
      {% for message in messages %}
        <li>{{ message }}</li>
      {% endfor %}
      </ul>
    {% endif %}
  {% endwith %}
  </div>
{% endblock %}

{% block bottom %}
  <script type="text/javascript">
  <!--
    $('.range_class').on('input', function() {
      var the_name = $(this).attr('name');
      var the_ch = +the_name + 1;
      var the_value = $(this).val();
      var now = Date.now();
      var msg = the_ch + ' value=' + the_value;
      var data_str = 'ch=' + the_name;
      data_str += '&val=' + the_value;
      data_str += '&now=' + now;
      $('#pwm_ch').text(the_ch);
      $('#pwm_val').text(the_value);
      // console.log(msg);
      $.ajax({
        type: "POST",
        url: "{{ url_for('set_pwm_post') }}",
        data: data_str,
        success: function(resp) {
          // console.log('success:' + resp);
        },
        error: function(resp) {
          console.log('error:' + resp);
        }
      });
    });
  // -->
  </script>
{% endblock %}


実行コマンドはこれです。
pi~/Documents/env_flask/proj/pwm_flask:develop $ export FLASK_APP=pwm_flask.py
pi~/Documents/env_flask/proj/pwm_flask:develop $ python -m flask run --host=0.0.0.0

python3 でも動作します。


Lock() はやめて Singleton にしました。
i2c_lcd.py の「class MySingleton(object)」がそれです。

2017年1月3日火曜日

Raspberry Pi B + python3 + flask + i2c + PWM Slider

Raspberry Pi B + python3 + flask + i2c + PWM Slider ということで、html に slider を16個表示して、PCA9685 の PWM チャンネルに対応させます。
PCA9685 は「16-channel, 12-bit PWM Fm+ I²C-bus LED controller」です。秋月やアマゾンでも製品が売られています。ここでは、表面実装タイプの素子を秋月の変換基板に乗せて使っています。

画面にスライダを表示して、1チャンネル毎に明るさを制御してみますが、今回はスライダを作るところまでにします。
前回のものに修正を加えます。

ディレクトリ構造を変更しました。
こうなります。
pwm_flask,py が主となるファイルです。

pwm_flask/
├── i2c_lcd.py
├── i2c_pwm.py
├── pwm_flask.py
├── static
│   ├── css
│   │   └── style.css
│   └── js
│       ├── jquery-3.1.1.min.js
│       └── jquery-3.1.1.min.map
└── templates
    ├── base.html
    ├── input_text_for_lcd.html
    └── set_pwm.html

スライダで値をセットして送信ボタン、では寂しいので、スライダをグリグリしている時の値も送信されるようにします。
そうするためには Ajax を使うことになります。
そのために jQuery を使います。static/js におきます。

これで clone して、「9119531」でチェックアウトすると、今回のバージョンになります。
$ git clone git@bitbucket.org:nagasako/pwm_flask.git
$ cd pwm_flask
$ git checkout 9119531


i2c_lcd.py
#!/usr/bin/env python
# coding: UTF-8
'''
$ sudo apt-get install python-smbus i2c-tools

comment out this line in /etc/modprobe.d/raspi-blacklist.conf
blacklist i2c-bcm2708

add the following lines to /etc/modules
 i2c-dev
 i2c-bcm2708
and then reboot.

search all addr.
$ sudo i2cdetect -y 1
'''

import smbus
import time
import datetime


class st7032i:
    def __init__(self, addr=0x3e, ch=1, contrast=0x20):
        try:
            self.addr = addr
            self.ch = ch
            self.bus = smbus.SMBus(ch)
            self.contrast = contrast
            self.reset()
            self.enable = True
            self.prev_now = 0
        except:
            self.enable = False

    def reset(self):
        contrast_h = 0x70 | (self.contrast & 0x0f)
        contrast_l = 0x54 | ((self.contrast >> 4) & 0x03)
        self.bus.write_i2c_block_data(
            self.addr, 0, [0x38, 0x39, 0x14, contrast_h, contrast_l, 0x6c])
#         // ST7032 Initial Program Code Example For 8051 MPU(8 Bit Interface) の初期化サンプルの通り
#         // Function Set : 8bit bus mode, 2-line mode,normal font,normal instruction mode
#         LCD_write(LCD_RS_CMD, 0b00111000, fd);      // 0x38
#         // Function Set : extension instruction mode
#         LCD_write(LCD_RS_CMD, 0b00111001, fd);      // 0x39
#         // Internal OSC frequency(extension instruction mode)
#         LCD_write(LCD_RS_CMD, 0b00010100, fd);      // 0x14
#         // Contrast set(extension instruction mode) コントラスト値下位4bit設定
#         LCD_write(LCD_RS_CMD, 0b01110000 | (LCD_CONTRAST & 0xF), fd); // 0x78 = 0x70 + 0x8
#         // Power/ICON/Contrast set(extension instruction mode) コントラスト値上位2bit設定
#         LCD_write(LCD_RS_CMD, 0b01011100 | ((LCD_CONTRAST >> 4) & 0x3), fd); // 0x5c + 0
#         // Follower control。internal follower on,
#         LCD_write(LCD_RS_CMD, 0b01101100, fd);      // 0x6c

        time.sleep(0.25)
        self.bus.write_i2c_block_data(self.addr, 0, [0x0c, 0x01, 0x06])
#         // Function Set。normal instruction mode。
#         // LCD_write(LCD_RS_CMD, 0b00111000, fd);   // 0x38
#         // Display On
#         LCD_write(LCD_RS_CMD, 0b00001100, fd);      // 0x0c
#         // Clear Display
#         LCD_write(LCD_RS_CMD, 0b00000001, fd);      // 0x01
#         // Entry Mode Set
#         LCD_write(LCD_RS_CMD, 0b00000110, fd);      // 0x06
        time.sleep(0.05)

    def clear(self):
        if self.enable:
            self.bus.write_i2c_block_data(self.addr, 0, [0x01])

    def mov_to(self, row=0, col=0):
        if self.enable:
            self.bus.write_i2c_block_data(self.addr, 0, [0x80 + 0x40 * row + col])

    def put_str(self, the_str):
        if self.enable:
            self.bus.write_i2c_block_data(self.addr, 0x40, list(map(ord, the_str)))


if __name__ == '__main__':
    try:
        my_lcd = st7032i(0x3e, 1)
        if True and my_lcd.enable:
            while True:
                time_str = datetime.datetime.now().strftime("%H:%m:%S")
                date_str = datetime.datetime.now().strftime("%y/%m/%d")
                my_lcd.clear()
                my_lcd.mov_to(0, 0)
                my_lcd.put_str(date_str)
                my_lcd.mov_to(1, 0)
                my_lcd.put_str(time_str)
                print(date_str + ' ' + time_str)
                time.sleep(1)
        # if False and my_lcd.enable:
        #     light_channel = 0
        #     adc = SpiAdc(light_channel)
        #     if adc is not None and my_lcd.enable:
        #         # Define delay between readings
        #         delay = 1
        #         while True:
        #             # Read the light sensor data
        #             light_level = adc.readChannel()
        #             # light_volts = adc.convertVolts(light_level, 2)

        #             # Print out results
        #             # print("Light: {} ({}V)".format(light_level,light_volts))
        #             # print("%4d %1.2f" % (light_level, light_volts))
        #             the_str = "    " + str(light_level)
        #             my_lcd.mov_to(0, 0)
        #             my_lcd.put_str(the_str[-4:])
        #             # Wait before repeating loop
        #             time.sleep(delay)

    except:
        print("Error accessing default I2C bus")


i2c_pwm.py
#!/usr/bin/env python
# coding: UTF-8
'''
i2c_pwm.py
'''


def set_pwm(ch, val):
    print('sned_pwm: ', ch, val)


if __name__ == '__main__':
    set_pwm(1, 123)


pwm_flask.py
#!/usr/bin/env python
# coding: UTF-8
'''
$ export FLASK_APP=pwm_flask.py
$ python -m flask run --host=0.0.0.0
'''
from threading import Lock
from flask import Flask, request, redirect
from flask import url_for, render_template, flash
# from flask import session, g, abort
from i2c_pwm import set_pwm
import datetime
import os
import json


OS_NAME = os.uname()
if 'raspberrypi' in OS_NAME:
    # Raspberry Pi
    RASPI = True
    from i2c_lcd import st7032i
    my_lcd = st7032i(0x3e, 1)
    lock = Lock()
else:
    RASPI = False
print(OS_NAME, RASPI)

DEBUG = True
SECRET_KEY = 'something_secret_pwm_flask'
USERNAME = 'admin'
PASSWORD = 'adminpass'

app = Flask(__name__)
app.config.from_object(__name__)
# app.run(host='0.0.0.0', port=5000)


@app.route('/')
def input_text_for_lcd():
    return render_template('input_text_for_lcd.html')


@app.route('/send_text', methods=['POST'])
def send_text():
    date_str = datetime.datetime.now().strftime("%y/%m/%d")
    time_str = datetime.datetime.now().strftime("%H:%m:%S")
    msg_text = request.form['msg_text']
    if RASPI:
        # my_lcd = st7032i(0x3e, 1)
        with lock:
            my_lcd.clear()
            my_lcd.mov_to(0, 0)
            my_lcd.put_str(time_str)
            my_lcd.mov_to(1, 0)
            if msg_text:
                my_lcd.put_str(msg_text)
    else:
        print('send_text: ', msg_text)
    flash('New entry was successfully posted "' + msg_text + '" at ' + date_str + ' ' + time_str)
    return redirect(url_for('input_text_for_lcd'))


class DevPCA:
    def __init__(self, ch, val):
        self.ch = ch
        self.val = val


@app.route('/set_pwm', methods=['GET'])
def set_pwm_get():
    dev_list = []
    for ch in range(16):
        dev_pca = DevPCA(ch, 50)
        dev_list.append(dev_pca)
    return render_template('set_pwm.html', pca_channels=dev_list)


@app.route('/set_pwm_post', methods=['POST'])
def set_pwm_post():
    ch_str = request.form['ch']
    val_str = request.form['val']
    now_str = request.form['now']
    # print('set_pwm_post ch_str, val_str, now_str=', ch_str, val_str, now_str)
    ch = int(ch_str)
    val = float(val_str)
    now = int(now_str)
    if RASPI:
        # my_lcd = st7032i(0x3e, 1)
        with lock:
            if now > my_lcd.prev_now:
                print(now, my_lcd.prev_now)
                my_lcd.prev_now = now
                set_pwm(ch, val)
                my_lcd.clear()
                my_lcd.mov_to(0, 0)
                my_lcd.put_str(ch_str)
                my_lcd.mov_to(1, 0)
                my_lcd.put_str(val_str)
    else:
        msg_text = "ch={0:2d} val={1:3.1f} now={2:d}".format(ch, val, now)
        print('set_pwm_post: ', msg_text)
    return json.dumps({'status': 'OK', 'ch': ch, 'val': val})


if __name__ == '__main__':
    app.run()


style.css
body {
    font-family: sans-serif;
    background: #eee;
}


base.html
<!doctype html>
<html>
<head>
  <title>PWM_Flask</title>
  <meta charset="utf-8">
  <link rel=stylesheet type=text/css href="{{ url_for('static', filename='css/style.css') }}">
  {% block head %}{% endblock %}
</head>
<body>
  {% block body %}{% endblock %}
</body>
  {% block bottom %}{% endblock %}
</html>


input_text_for_lcd.html
{% extends "base.html" %}
{% block body %}
  <form action="{{ url_for('send_text') }}" method=post>
    <dl>
      <dt>Message:
      <dd><input type="text" size="30" name="msg_text">
      <dd><input type="submit" value="send">
    </dl>
  </form>
  <div>
    <a href="{{ url_for('set_pwm_get') }}">set_pwm</a>
  </div>
  <div>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      <ul class=flashes>
      {% for message in messages %}
        <li>{{ message }}</li>
      {% endfor %}
      </ul>
    {% endif %}
  {% endwith %}
  </div>

{% endblock %}


set_pwm.html
{% extends "base.html" %}
{% block head %}
  <style>
    .range_class {
      width: 400px!important;
    }
  </style>
  <script src="{{ url_for('static', filename='js/jquery-3.1.1.min.js') }}"></script>
{% endblock %}

{% block body %}
  <form action="" method=post>
    <table>
      <thead>
        <tr>
          <th> ch </th>
          <th> PCA9685 value </th>
        </tr>
      </thead>
      <tbody>
      {% for pca_ch in pca_channels %}
        <tr>
          <td>{{ pca_ch.ch + 1 }}</td>
          <td>
            <input type="range" name="{{ pca_ch.ch }}" class="range_class"
                value="{{ pca_ch.value }}" min="0" max="100" step="0.1">
          </td>
        </tr>
      {% endfor %}
      </tbody>
    </table>
  </form>

  <div>
    <span id="pwm_ch"></span>
    <span id="pwm_val"></span>
  </div>

  <div>
    <a href="{{ url_for('input_text_for_lcd') }}">input_text_for_lcd</a>
  </div>

  <div>
  {% with messages = get_flashed_messages() %}
    {% if messages %}
      <ul class=flashes>
      {% for message in messages %}
        <li>{{ message }}</li>
      {% endfor %}
      </ul>
    {% endif %}
  {% endwith %}
  </div>
{% endblock %}

{% block bottom %}
  <script type="text/javascript">
  <!--
    $('.range_class').on('input', function() {
      var the_name = $(this).attr('name');
      var the_ch = +the_name + 1;
      var the_value = $(this).val();
      var now = Date.now();
      var msg = the_ch + ' value=' + the_value;
      var data_str = 'ch=' + the_name;
      data_str += '&val=' + the_value;
      data_str += '&now=' + now;
      $('#pwm_ch').text(the_ch);
      $('#pwm_val').text(the_value);
      // console.log(msg);
      $.ajax({
        type: "POST",
        url: "{{ url_for('set_pwm_post') }}",
        data: data_str,
        success: function(resp) {
          // console.log('success:' + resp);
        },
        error: function(resp) {
          console.log('error:' + resp);
        }
      });
    });
  // -->
  </script>
{% endblock %}


実行コマンドはこれです。
pi~/Documents/env_flask/proj/pwm_flask:develop $ export FLASK_APP=pwm_flask.py
pi~/Documents/env_flask/proj/pwm_flask:develop $ python -m flask run --host=0.0.0.0

python3 でも動作します。

set_pwm.html の下の方に ajax でスライダの値を送信する処理があります。
ch とその値を送信しています。もうひとつ Date.now() の値も送っています。
これは、例えば0から100%まで値を変えた時、データの発生順で処理されるとは限らないためです。最後は100%になっているはずなのに、受信データの最後が100%になっているとは限らないのです。古いデータを受信した場合はそのデータを捨てるために Date.now() でその時の時刻を利用しています。

set_pwm.py で lock = Lock() をしています。なんだか気持ち悪いので、修正予定です。