2014年12月29日月曜日

ラズベリーパイとしりとりで遊ぶよ

もう年末で、今日は早く仕事が終わったので、
すこしラズベリーパイで遊んでみた。

以前にダウンロードしたことがあった、Wikipediaのデータを使って
なにかできないかと思い、
巨大なWikipediaデータからタイトルとそのよみがな、概要だけを抽出して、データベース化。
それをラズベリーパイに覚えてもらって、「しりとり」でもやってみようと思う。


Wikipediaのデータはここにあって
http://dumps.wikimedia.org/jawiki/latest/

以下のURLに説明があった。
http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html

入手したのは「jawiki-latest-abstract.xml」というもので、
タイトルや概要が記載れている1.5GB程度のファイル。
 xml形式になっており、ちょっと困った。

xml2csv的なものはいろいろありそうだけど、phpの勉強を兼ねて自作。
(その他に、いくつかフィルタしたいこともあったので)

先ほどのファイルはこんな感じ
<title>Wikipedia: 言語</title>
<url>http://ja.wikipedia.org/wiki/%E8%A8%80%E8%AA%9E</url>
<abstract>言語(げんご)とは、コミュニケーションのための記号の体系である。</abstract>
<links> …省略

 これの、<title>のWikipedia:以降〜</title>までを名称として、
<abstract>内を概要とした。
よみがなは、以下のルールで生成した。
1.タイトルがすべてひらがな→タイトルをよみがな
2.タイトルがすべてカタカナ→ひらがなに変換してよみがなとする
3.アブストラクトにタイトル(説明) があれば、説明をひらがなにしてみる
3−1.そのさい、スペースや-・などの区切り文字があったら、そこまでで切り取る

そのほか「〜の一覧」などのまとめページは除外した。
全容がこれ
<>
<?php
    mb_language('Japanese');
    mb_internal_encoding('UTF-8');
    mb_regex_encoding("UTF-8");
    
    //タイトル
    $title_start_string="Wikipedia: ";
    $title_start_string_len = mb_strlen($title_start_string);
    $title_end_string="";
    $title_end_string_len   = mb_strlen($title_end_string);
    //アブスト
    $abst_start_string="";
    $abst_start_string_len = mb_strlen($abst_start_string);
    $abst_end_string="";
    $abst_end_string_len   = mb_strlen($abst_end_string);
    //
    $mode = 0;
    $testval_tit = '';
    $testval_abs = '';
    $testval_kana = '';
    $cnt_k = 0;
    $cnt_r = 0;
    $cnt_ng = 0;
    if($argc > 1){
        try {
            $pdo = new PDO('mysql:dbname=test;host=192.168.4.135', 'username', 'password',array(PDO::MYSQL_ATTR_LOCAL_INFILE => true));
        } catch (PDOException $e) {
            exit('データベースに接続できませんでした。' . $e->getMessage());
        }
        
        $stmt = $pdo->query('SET NAMES utf8');
        if (!$stmt) {
            $info = $pdo->errorInfo();
            exit($info[2]);
        }
        $stmt = $pdo->query('set character_set_database=utf8');
        if (!$stmt) {
            $info = $pdo->errorInfo();
            exit($info[2]);
        }
        
        echo "読み取り開始・・・\n";
        $time_start = microtime(true);
        $fp = fopen($argv[1], "r");
        $temp_file = tempnam(sys_get_temp_dir(), 'Tux');
        $fpw = fopen($temp_file, "w");
        while ($line = fgets($fp)) {
            if($mode == 0){
                //タイトル探し
                $testval_tit = searchStr($line,$title_start_string,$title_start_string_len,$title_end_string,$title_end_string_len);
                if($testval_tit !== ''){
                    //echo "->".$line;
                    //echo "名称=".$testval_tit."\n";
                    $cnt_k = $cnt_k + 1;
                    $mode = 1;
                }
            }elseif($mode == 1){
                //アブスト探し
                $testval_abs = searchStr($line,$abst_start_string,$abst_start_string_len,$abst_end_string,$abst_end_string_len);
                if($testval_abs !== ''){
                    //echo "->".$line;
                    //echo " 概要=".$testval_abs."\n";
                    
                        $testval_kana = seikei2($testval_abs,$testval_tit);
                        if($testval_kana !== ''){
                            /*echo "==============================\n";
                            echo "名称=".$testval_tit."\n";
                            echo " 概要=".$testval_abs."\n";
                            echo " よみがな=".$testval_kana."\n";
                            echo "==============================\n";*/
                            fwrite($fpw,$pdo->quote($testval_kana).",".$pdo->quote($testval_tit).",".$pdo->quote($testval_abs)."\n");
                            $cnt_r = $cnt_r + 1;
                            //if($cnt_r > 1000) break;
                        }else{
                        
                            /*echo "【NG】名称=".$testval_tit."\n";
                            echo "【NG】よみがな=".$testval_kana."\n";
                            echo "【NG】概要=".$testval_abs."\n";*/
                            $cnt_ng = $cnt_ng + 1;
                        }
                    
                    $mode = 0;
                }
            }
            

        }
        fclose($fpw);
        fclose($fp);
        
        $timelimit = microtime(true) - $time_start;
        echo "\n".$timelimit." seconds\n";
        echo "総数:".$cnt_k." 有効数:".$cnt_r." 無効数:".$cnt_ng." ".(100*$cnt_r/$cnt_k)."%\n";
        
        echo "DBに書き込み開始・・・"."'".$temp_file."'\n";
        $time_start = microtime(true);
        
        $stmt = $pdo->query("LOAD DATA LOCAL INFILE ".
                            "'".$temp_file."' REPLACE INTO TABLE `test`.`wiki` ".
                            "FIELDS TERMINATED BY ',' ENCLOSED BY '\'' LINES TERMINATED BY '\n' (`kana`, `name`, `gaiyo`);");
        if (!$stmt) {
            $info = $pdo->errorInfo();
            exit($info[2]);
        }
        
        $timelimit = microtime(true) - $time_start;
        echo "\n".$timelimit." seconds\n";
        
        $pdo = null;

    }
    function seikei2($abs,$title){
        $retval = "";
        //〜の一覧ってやつはいらない
        if(!endsWith($title,"一覧")){
            $t_title = mb_convert_kana(seikei($title),"c");
            if(preg_match("/^[ぁ-ゞー]+$/u",$t_title)){
                //タイトルが全部ひらがなだった
                $retval = $t_title;
            //}elseif(preg_match("/^[ァ-ヶー]+$/u",$t_title)){
            //    //タイトルが全部カタカタだった→ひらがなに変換
            //   $retval = $t_title;
                
            }else{
                $testval_k = searchStr($abs,$title."(",mb_strlen($title)+1,")",1);
                    if($testval_k !== ''){
                    //ひらがなにしてみる
                    $testval_kana = mb_convert_kana(seikei($testval_k),"c");
                    if(preg_match("/^[ぁ-ゞー・]+$/u",$testval_kana)){
                        $retval = $testval_kana;
                    }
                }
            }
        }
        return $retval;
    }
    function seikei($str){
        //空白や-を削除、区切り文字があれば先頭を返す
        $retval = "";
        $str_s = mb_ereg_replace("[-\s\u00A0\u3000・]","",$str);
        //$str_s = mb_ereg_replace("・","",$str_s);
        //$str_s = mb_ereg_replace("-","",$str_s);
        //$str_s = mb_ereg_replace(" ","",$str_s);
        //$str_s = mb_ereg_replace("ー",ー",$str_s);
        $ary = mb_split("[、, (,]",$str_s);
        if(count($ary) > 0){
            $retval = $ary[0];
        }else{
            $retval = $str_s;
        }
        
        return $retval;
    }
    function searchStr($str,$start_str,$start_str_len,$end_str,$end_str_len){
        $retval ="";
        $pos_s = mb_strpos($str, $start_str);
        $pos_e = mb_strpos($str, $end_str);
        if(($pos_s === false) || ($pos_e === false)){
            //みつからず
        }else{
            //echo mb_strlen($str)."->s:".$pos_s."->e:".$pos_e;
            //$retval =  mb_substr($str,$pos_s+$start_str_len,mb_strlen($str) - $start_str_len - $end_str_len-1);
            $retval =  mb_substr($str, $pos_s+$start_str_len, $pos_e - $pos_s - $start_str_len);
        }
        return $retval;
    }
    /**
    * endsWith
    * http://blog.anoncom.net/2009/02/20/124.html
    * 
    * @param string $haystack
    * @param string $needle
    * @return boolean
    */
    function endsWith($haystack, $needle){
        $length = (strlen($haystack) - strlen($needle));
        if( $length <0 data-blogger-escaped-false="" data-blogger-escaped-haystack="" data-blogger-escaped-length="" data-blogger-escaped-needle="" data-blogger-escaped-return="" data-blogger-escaped-strpos="">
これを実行すると・・・
php wikicsv.php jawiki-latest-abstract.xml
読み取り開始・・・
29.402565956116 seconds
総数:943216 有効数:408613 無効数:534603 43.321254092382%
DBに書き込み開始・・・'/tmp/TuxUHAuGL'
768.0825881958 seconds

PCで実行しているので、やはり生成は29秒と早い。
でも、ラズベリーパイに転送するのは、768秒・・・なんとか改善できないかな。
で、中身はこんな感じ


んでもって、サーバー側のスクリプトは、
<>
<?php
        mb_language('Japanese');
        mb_internal_encoding('UTF-8');
        mb_regex_encoding("UTF-8");
        
        $s_talk = htmlspecialchars($_GET['talk'], ENT_QUOTES, "utf-8");
        $result = "";
        
        try {
                $pdo = new PDO('mysql:dbname=test;host=192.168.4.135', 'username', 'password');
        } catch (PDOException $e) {
                talk("データベースに接続できませんでした。");
                exit('データベースに接続できませんでした。' . $e->getMessage());
        }
        
        $stmt = $pdo->query('SET NAMES utf8');
        if (!$stmt) {
            $info = $pdo->errorInfo();
            exit($info[2]);
        }
        $stmt = $pdo->query('set character_set_database=utf8');
        if (!$stmt) {
            $info = $pdo->errorInfo();
            exit($info[2]);
        }
        
        if(strlen($s_talk) == 0){
                talk("WEBサーバーを起動しました。");
        }else{
                $result = "あなたの言葉は、".$s_talk."です。";
                if(preg_match("/^[ぁ-ゞー]+$/u",$s_talk)){
                        //ひらがなだった
                        $stmt = $pdo->prepare("select * from wiki where wiki.kana like :word and wiki.kana not like '%ん' order by rand() limit 1");
                        $stmt->bindValue(':word', mb_substr($s_talk,-1).'_%');
                        $flag = $stmt->execute();
                        if (!$flag) {
                                $info = $stmt->errorInfo();
                                exit($info[2]);
                        }
                        while ($data = $stmt->fetch(PDO::FETCH_ASSOC)) {
                                $result=$result."
私の言葉は".$data['name']."(".$data['kana'].")です。

".$data['gaiyo'];
                                talk("あなたの言葉は、".$s_talk."です。そして、私の言葉は、".$data['kana']."です。");
                        }
                }else{
                        $result=$result."
申し訳ありませんが、すべて「ひらがなで」入力してください。";
                        talk("あなたの言葉は、".$s_talk."です。申し訳ありませんが、すべて「ひらがなで」入力してください。");
                }
                

        }
        
        $pdo = null;
        
        function talk($kotoba){
                exec('echo "'.$kotoba.'" | /home/pi/aquestalkpi/AquesTalkPi -v -f1 -f - | aplay > /dev/null &');
        }
        echo <<< EOD



    
    簡易しりとり
    

 
 

    

RasPiと「しりとり」で遊ぶ!

これは、Wikipediaのダウンロード可能なデータを利用した簡易的な「しりとり」です。
あなたの言葉: {$result}
EOD; ?>
そして、ブラウザからアクセス!

ちょっと楽しいかも。

2014年12月21日日曜日

ラズベリーパイがweb経由でしゃべるよ

しばらくラズベリーパイで遊んでいなかったので、
色々と忘れてしまっている。

年末はいろいろあるね。そして仕事もプライベートも停滞気味・・・
よく、仕事と家庭と趣味の3本柱で、どれか1つでも充実していれば、
やっていけるというけれど、なんとかならないかな。

と、現実逃避なのだけど、最近Mysql+PHP+Javascriptの構成で遊んでいる。
直接仕事には関係ないけど、いつか役立つ日が来るはず。

今日やることは、WEB経由でRasPiを喋らせること
node.jsとphpでそれぞれやってみた。



node.js
ar http = require('http');
http.createServer(
    function(req,res){
        res.writeHead(200,{'Content-Type':'text/html'});
        res.end('Raspberry Pi & Node.js');
    }
).listen(8000);


----------------------------------------------------
var http = require('http');
var fs = require('fs');
var url = require('url');
var exec = require("child_process").exec;

var server = http.createServer();
server.on('request', doRequest);
server.listen(8000);
console.log('Server running!');

var cmd = "echo 'ウェブサーバーを起動しました。' | ./aquestalkpi/AquesTalkPi  -v f1 -f - | aplay";
exec(
    cmd,
    function(err, stdout, stderr) {}
);

 
// リクエストの処理
function doRequest(req, res) {
    fs.readFile('./hello.html', 'UTF-8',
        function(err, data) {
            res.writeHead(200, {'Content-Type': 'text/html'});
            res.write(data);
            res.end();
        });
    if(req.method=='GET') {
        var url_parts = url.parse(req.url,true);
        console.log(url_parts.query);
        if('talk' in url_parts.query){
            cmd = "echo '要求" + url_parts.query['talk']+"' | ./aquestalkpi/AquesTalkPi  -v f1 -f - | aplay";
            exec(
                cmd,
                function(err, stdout, stderr) {}
            );
        }
    }
}

----------------------------------------------------



    
    sample
    

 
 

    

Sample Page

これはNode.jsのサンプルページです。
しゃべる内容:

php
 /dev/null &');
 }else{
  exec('echo '.$s_talk.' | /home/pi/aquestalkpi/AquesTalkPi -v -f1 -f - | aplay > /dev/null &');
 }
 echo <<< EOD


 
  
  AquesTalkのテスト
  
 
 
     

WebからRaspiを喋らせる

WebからAquesTalkPiを制御するサンプル。
しゃべる内容:
EOD; ?>

やっていて困ったこと、phpで外部プログラムを実行しようとしたら、
ALSA lib confmisc.c:768:(parse_card) cannot find card '0'
ALSA lib conf.c:4241:(_snd_config_evaluate) function snd_func_card_driver returned error: No such file or directory
ALSA lib confmisc.c:392:(snd_func_concat) error evaluating strings
ALSA lib conf.c:4241:(_snd_config_evaluate) function snd_func_concat returned error: No such file or directory
ALSA lib confmisc.c:1251:(snd_func_refer) error evaluating name
ALSA lib conf.c:4241:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory
ALSA lib conf.c:4720:(snd_config_expand) Evaluate error: No such file or directory
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM default
aplay: main:682: audio open error: No such file or directory

とエラーが出てしまった。
どうやらapacheの権限では、aplayを実行できないような気がする。
そこで、apacheの実行ユーザーを一般ユーザー(pi)に変えてみた
(参考)http://j-caw.co.jp/blog/?p=1407

/etc/apache2/envvars の
export APACHE_RUN_USER=www-data これをpiに変更

これで動いた!


2014年12月13日土曜日

iModela+eagleでプリント基板作成(動作確認)

ようやく、パターンのチェックが終わり想定外のショート・断線が無いことを確認した。
すぐにでも電源を入れてみたかったけど、
肝心のソフトウェアがまだ・・・。

この基板に載っているのはPIC16F818で、I2Cのスレーブにもなれる。
これをラズベリーパイから操作したいと思っている。
その前段階として、PIC自身がカウントアップするプログラムを書いて、
動作確認を行うことにした。

やはり、Linux(X86_64)環境では開発環境が遅すぎるので、
32bit環境で開発を行った。

/* 
 * File:   main.c
 * Author: adeno
 *
 * Created on 2014/12/13
 */

#include 
#include 
#include 
#include "nana_seg_ptn.h"

/*
 * 
 */
// CONFIG
#pragma config FOSC = INTOSCIO  // Oscillator Selection bits (INTRC oscillator; port I/O function on both RA6/OSC2/CLKO pin and RA7/OSC1/CLKI pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RA5/MCLR/VPP Pin Function Select bit (RA5/MCLR/VPP pin function is MCLR)
#pragma config BOREN = ON       // Brown-out Reset Enable bit (BOR enabled)
#pragma config LVP = ON         // Low-Voltage Programming Enable bit (RB3/PGM pin has PGM function, Low-Voltage Programming enabled)
#pragma config CPD = OFF        // Data EE Memory Code Protection bit (Code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off)
#pragma config CCPMX = RB2      // CCP1 Pin Selection bit (CCP1 function on RB2)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

#define _XTAL_FREQ 4000000      //delay用に必要(クロック4MHzを指定)
#define TMR0_CNT 0        //TMR0カウント値設定

static void seg_sel(unsigned char n);

unsigned char keta; //7segの桁
unsigned int num;
unsigned char gcnt;
/*
 *
 */
int main(int argc, char** argv) {

    OSCCON = 0b01100010;
    //PORTA 3-0は7segのd-a 不論理
    //RA4は1桁目、RA6は2桁目 不論理
    //PORTB 3-0は7segのh-e 不論理
    
    //PORTA 0,1,2,3,4,6,7
    //PORTB 0,2,6,7 桁 5
    PORTA = 0b11011111; //PORTAの中身をきれいにする
    TRISA = 0b00000000; //PORTAは 1:入力 0:出力

    PORTB = 0b11100101; //PORTBの中身をきれいにする
    TRISB = 0b00000000; //PORTBは 1:入力 0:出力

    keta = 0;
    num = 0;
    gcnt = 0;

    //TMR0の割り込み初期設定
    //1000ms=0.25us(4MHz)*4*プリスケーラ−256*256
    OPTION_REG = 0b0111; //プリスケーラ値設定0b0111(=256回)
    TMR0 = TMR0_CNT; //TMR0カウント値設定

    INTCONbits.TMR0IE =1; //タイマ割込み許可
    INTCONbits.GIE = 1; //全体割込み許可

    unsigned char ra_bak;

    while(1) {
        //表示を消す
        seg_sel(0);
        //PORTA = 0b01011111;
        __delay_ms(1);

        //表示
        seg_sel(keta);
        //PORTA = 0b01001001;
        //PORTB = 0b00001111;
        //NOP();
        //RA4 = 0;
        __delay_ms(3);
        //桁移動
        keta++;
        if(keta > 5){keta = 1;}



    }


    return (EXIT_SUCCESS);
}


static void interrupt isr(void) { //割り込み関数
    if(INTCONbits.TMR0IF == 1) {  //割込み種がTimer0割込みの場合
        INTCONbits.TMR0IF = 0; //Timer0割り込みフラグクリアー
        TMR0 = TMR0_CNT; //TMR0カウント値設定
        gcnt++;
        if(gcnt > 5){//15){
            num++;
            if(num > 9999){num = 0;}
            gcnt = 0;
        }
    }
}
// =============== セグメントセレクタ    ============
static void seg_sel(unsigned char n){
    //PORTA 0,1,2,3,4,6,7
    //PORTB 0,2,6,7 桁 5

    unsigned char ptn,ra_bak,rb_bak;
    ra_bak = PORTA & 0b00100000;
    rb_bak = PORTB & 0b00011010;

    switch(n){
        default:{ }
        case 0: {
            //初期化
            PORTA = 0b11011111;
            PORTB = 0b11100101;
            break;
        }
        case 1:{
            //セグメント1
            ptn = show_number(num % 10,0);
            if((ptn & 0b00100000) > 0 ){
                PORTB = 0b01100101; //7
            }else{
                PORTB = 0b01000101; //5to7
            }
            
            //PORTB = rb_bak | (0b00001111 & (ptn >> 4));
            //PORTA = 0b01010000 | (0b00001111 & ptn);    //ptn
            //NOP();
            //PORTA = 0b01000000 | (0b00001111 & ptn);    //RA4をL + ptn
            PORTA = ra_bak | (0b11011111 & ptn);
            //PORTA = 0b11000000;
            break;
        }
        case 2:{
            //セグメント2
            ptn = show_number((num / 10)%10,0);
            if((ptn & 0b00100000) > 0 ){
                PORTB = 0b10100101; //6
            }else{
                PORTB = 0b10000101; //5to6
            }
            //PORTB = rb_bak | (0b00001111 & (ptn >> 4));
            //PORTA = 0b01010000 | (0b00001111 & ptn);    //ptn
            //PORTB = 0b11000001; //5と2
            //NOP();
            //PORTA = 0b00010000 | (0b00001111 & ptn);    //RA6をL + ptn
            //PORTA = 0b01000000;
            PORTA = ra_bak | (0b11011111 & ptn);
            break;
        }
        case 3:{
            //セグメント3
            ptn = show_number((num / 100)%10,0);
            if((ptn & 0b00100000) > 0 ){
                PORTB = 0b11100001; //2
            }else{
                PORTB = 0b11000001; //5to2
            }
            //PORTB = rb_bak | (0b00001111 & (ptn >> 4));
            //PORTA = 0b01010000 | (0b00001111 & ptn);    //ptn
            //PORTB = 0b11000001; //5と2
            //NOP();
            //PORTA = 0b00010000 | (0b00001111 & ptn);    //RA6をL + ptn
            //PORTA = 0b01000000;
            PORTA = ra_bak | (0b11011111 & ptn);
            break;
        }
        case 4:{
            //セグメント4
            ptn = show_number((num / 1000)%10,0);
            if((ptn & 0b00100000) > 0 ){
                PORTB = 0b11100100; //0
            }else{
                PORTB = 0b11000100; //5to0
            }
            //PORTB = rb_bak | (0b00001111 & (ptn >> 4));
            //PORTA = 0b01010000 | (0b00001111 & ptn);    //ptn
            //PORTB = 0b11000001; //5と2
            //NOP();
            //PORTA = 0b00010000 | (0b00001111 & ptn);    //RA6をL + ptn
            //PORTA = 0b01000000;
            PORTA = ra_bak | (0b11011111 & ptn);
            break;
        }
    }

}

/*
 * File:   nana_seg_pth.c
 * Author: adeno
 *
 * Created on 2014/11/13
 */

#include "nana_seg_ptn.h"


// =============== 表示パターン ====================
unsigned char show_number(unsigned char n,unsigned char dot){
    static unsigned char ptn = 0b11111111;
    /*
     * 76543210
     * hecagfbd
     */
    switch(n){
        case 0: { //abcdef
            ptn = 0b10001000;
            break;
        }
        case 1: { //bc
            ptn = 0b11011101;
            break;
        }
        case 2: { //abdeg
            ptn = 0b10100100;
            break;
        }
        case 3: { //abcdg
            ptn = 0b11000100;
            break;
        }//hecagfbd
        case 4: { //bcfg
            ptn = 0b11010001;
            break;
        }
        case 5: { //acdfg
            ptn = 0b11000010;
            break;
        }
        case 6: { //acdefg
            ptn = 0b10000010;
            break;
        }
        case 7: { //abc
            ptn = 0b11001101;
            break;
        }
        case 8: { //abcdefg
            ptn = 0b10000000;
            break;
        }
        case 9: { //abcdfg
            ptn = 0b11000000;
            break;
        }
    }

    return ptn;
}

<次回改善>
・数値→7セグパターン出力は汎用的に使えるようにしておく
 今は配線の都合だけでレイアウトしてしまったので、
  RA: 76543210
     7seg:hecagfbd
 みたいなことになっている。。。
 パターンを作る人と出力のために変換する人が必要。

動作確認結果がこちら。
スモーク色のアクリル板を上に載っけておくと表示が見やすい
(というか、板が無いととても見づらい。。。)
カメラで撮るとチラツキが目立つね。



次はI2Cの確認だ−!

2014年12月7日日曜日

iModela+eagleでプリント基板作成(部品実装完了)

朝、会社に行く前とか、帰ってきてからの少しの時間で、少しづつ部品の載せてきた。
今日ようやく完了した。

実装してきて思ったことは、
・銅箔面を素手で触るとすぐに変色してしまう→要手袋
・作業が中断する場合は、密封して極力外気に触れさせない→チャック付きの袋

右側切るの忘れてた・・・。
まだ通電確認していないけど、
 裏側はこんな感じ
き、汚いOrz

次作るときは、
・GNDは熱が逃げやすいので、工夫が必要
・ジャンパーは直線がいい、極力少なくしたい
・チップ部品は意外と楽ちん (今回は1か所しか使っていないけど)
・GND以外の島は削っておきたい:加工時間が長くなってしまうけど、
  はんだが移っちゃうし・・・。

コーティングスプレーしたからもう机上に置きっぱなしで大丈夫なはず・。