ウェブ扇風機

投稿日:

 昨日試した扇風機遊びを少し進め、温度制御を付けてみたい。

 おあつらえ向きに、最初に買った「Seeedstudio SIDEKICK BASIC KIT」には、サーミスタが入っている。これで温度を検知して、温度ごとに扇風機の回転を制御するわけだ。値の変更などはETHERNET SHIELD 2をつなぎ、ウェブインターフェイスで行うと面白いだろう。

 以前にも試したが、しかし、このキット付属のサーミスタは情報が少なくて困る。そこで、ちゃんと計算してそれなりの温度測定をしてみたい。以前は測りたい温度近傍の特性値をテーブルから拾って2次式で近似しただけであった。

 スペックシートによれば、-55℃で10583.3Ω、+125℃で1.277Ωになる、とある。常温付近では0℃で190.07Ω、40℃で24.87Ωだ。もしサーミスタ直付けでArduinoから5V給電すれば、-55℃のときは0.47mAだが、0℃で26.3mA、40℃で201mAと、Arduinoに流せる電流の20mAを大きく逸脱してしまう。

 40度付近でも大丈夫なように、抵抗を付加しなければならない。40℃の時に流したい電流を5mAと仮定すれば、

IMG_3075

……というような計算で975Ωくらいつなげておけばよいということになる。

 しかし、手持ちの抵抗は、キットに入っていた330Ω・1kΩ・10kΩの3種類しかないから、これで賄うしかない。

 一番大きい抵抗は10kΩだから、これで計算しなおすと、40℃の時で

5V / (24.87Ω + 10kΩ) = 0.5mA

2番目は1kΩ、同じく40℃の時で

5V / (24.87Ω + 1kΩ) = 4.88mA

同様に3番目の330Ωだと

5V / (24.87Ω + 330Ω) = 14mA

…ということになる。間をとって、1kΩで回路を作ってみよう。

 次に、サーミスタは抵抗から温度を知る。一方、Arduinoは電圧からデジタル値を知る。したがって、電圧からまず抵抗値を知らなければその先の温度測定に進めない。

 先の電流調整用の1kΩ抵抗をR0として含め、現在のサーミスタの抵抗値を知るには、一般に式は次のようになろう。

IMG_3079

 これをソースコードに表せば、次のようになる。

  const float v0 = 5.0, r0 = 1000.0;  //  Arduino +5Vと電流調整抵抗1kΩ
  const int resolution = 1024;  //  アナログ入力の分解能
  int srcVal = 0;
  float vt = 0.0, rt = 0.0;
  
  srcVal = analogRead(THERMISTOR);
  vt = srcVal * (v0 / (resolution - 1));
  rt = (v0 * r0 - vt * r0) / vt;

 次に、知った抵抗値から温度を知る計算である。

 サーミスタの抵抗は対数特性を持っているので、関数は対数モデルになる。また、サーミスタの特性を表す重要な値は「B値」と呼ばれるものと、Ta(温度)、R0(抵抗)の3つで、それらはスペックシートに書いてある。

 TaとR0については、「温度がTaのとき、抵抗はR0になる」というものだ。私の手元のサーミスタは、スペックシートによると、

Ta = 25℃
R0 = 50kΩ

……とある。

 B値は「抵抗の対数と、温度の比」である。これもスペックシートに関係式が書いてある。

 これらのスペックシートの値と、知った抵抗値から温度を求める式は、次のようになる。

IMG_3080

 これを、先の抵抗値を知る部分と併せて、Arduinoで使える関数にすれば、このようになろうか。

float tempMesure(){
  const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0;  //  MF11-503Kスペックシート記載
  const float K = 273.15;  //  熱力学温度の定数
  const float v0 = 5.0, r0 = 1000.0;  //  Arduino +5Vと電流調整抵抗1kΩ
  const int resolution = 1024;  //  アナログ入力の分解能
  int srcVal = 0;
  float vt = 0.0, rt = 0.0;
  
  srcVal = analogRead(THERMISTOR);
  vt = srcVal * (v0 / (resolution - 1));
  rt = (v0 * r0 - vt * r0) / vt;
  return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}

 これを用いて、少し真面目っぽい温度測定スケッチを書けば、次のようになる。

//
//  thermistor2temp.ino
//    サーミスタでわりと真面目に温度を測る。
//    27.7.5(日)
//    佐藤俊夫
//
const int THERMISTOR = 1;

void setup() 
{ 
  Serial.begin(9600);
} 
 
void loop() 
{ 
  float t = 0.0;
  
  t = tempMesure();
  Serial.print("Temp = ");
  Serial.print(t);
  Serial.println("C");
  delay(500);
}

float tempMesure(){
  const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0;  //  MF11-503Kスペックシート記載
  const float K = 273.15;  //  熱力学温度の定数
  const float v0 = 5.0, r0 = 1000.0;  //  Arduino +5Vと電流調整抵抗1kΩ
  const int resolution = 1024;  //  アナログ入力の分解能
  int srcVal = 0;
  float vt = 0.0, rt = 0.0;
  
  srcVal = analogRead(THERMISTOR);
  vt = srcVal * (v0 / (resolution - 1));
  rt = (v0 * r0 - vt * r0) / vt;
  return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}

 次に、温度に応じて扇風機の回転数が変わるようにしよう。先日作った「ソリッドステート・リレーモジュール」を遺憾なく使用する。

ソリッドステート・リレーモジュール
IMG_3081

 このモジュールの内部には、秋月電子の「ソリッド・ステート・リレー(SSR)キット 25A(20A)タイプ」が組み付けてある。

 27℃で1/fゆらぎエフェクト、28℃で弱風、29℃で強風、とでもしてみようか。1/fゆらぎエフェクトには、先週書いた関数をそのままコピペする。

IMG_3083

//
//  thermistor2windFan.ino
//    サーミスタで温度を測り、扇風機を制御する。
//    27.7.5(日)
//    佐藤俊夫
//
const int THERMISTOR = 1, WINDFAN = 9;
void setup() 
{ 
  Serial.begin(9600);
  pinMode(WINDFAN, OUTPUT);
} 
 
void loop() 
{ 
  const int half = 128, full = 255;
  const float lowTemp = 27.0, midTemp = 28.0, highTemp = 29.0; 
  float t = 0.0, f = 0.0;
  
  t = tempMesure();
  Serial.print("temp=");
  Serial.println(t);
  if(t > highTemp){
    analogWrite(WINDFAN, full);
  }else
  if(t > midTemp){
    analogWrite(WINDFAN, half);
  }else
  if(t > lowTemp){
    f = f1Fluctuation();
    analogWrite(WINDFAN, f);
  }else{
    analogWrite(WINDFAN, 0);
  }
  delay(20);
}

float tempMesure(){
  const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0;  //  MF11-503Kスペックシート記載
  const float K = 273.15;  //  熱力学温度の定数
  const float v0 = 5.0, r0 = 1000.0;  //  Arduino +5Vと電流調整抵抗1kΩ
  const int resolution = 1024;  //  アナログ入力の分解能
  int srcVal = 0;
  float vt = 0.0, rt = 0.0;
  
  srcVal = analogRead(THERMISTOR);
  vt = srcVal * (v0 / (resolution - 1));
  rt = (v0 * r0 - vt * r0) / vt;
  return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}

int f1Fluctuation(){
  static float x = 0.1;
  if(x < 0.5){
    x = x + 2 * x * x;
  } 
  else {
    x = x - 2 * (1.0 - x) * (1.0 - x);
  }
  if(x < 0.05 || x > 0.995){
    x = random(10, 90) / 100.0;
  }
  return((int)(x * 255));
}

 回路図など、描くも愚かというか、こんな簡単なものである。

IMG_3084

 さて次に、ETHERNET SHIELD 2をつなぎ、この扇風機をWeb化する。昨日買っておいた「クリアランス確保用ピンソケット」が役に立つ。

IMG_3067

 組み付けるとこんな感じである。回路はネットにつながない場合と同じでよい。

IMG_3087

 こんなインターフェイスで動かす。

ネット扇風機インターフェイス

 設定した温度になると扇風機が回る。

IMG_3088

 スケッチはこのようになる。

//
//  web2thermistor_windFan.ino
//    サーミスタで温度を測り、扇風機を制御する。
//    27.7.5(日)
//    佐藤俊夫
//
#include <SPI.h>
#include <Ethernet2.h>
//
byte mac[] = {
  0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74
};
IPAddress ip(192, 168, 1, 129);
EthernetServer SERVER(80);
EthernetClient CLIENT;
const int THERMISTOR = 1, WINDFAN = 9;
//
void setup() 
{ 
  Ethernet.begin(mac, ip);
  SERVER.begin();
  Serial.begin(9600);
  pinMode(WINDFAN, OUTPUT);
} 
 
void loop() 
{ 
  const int half = 128, full = 255;
  static float lowTemp = 27.0, midTemp = 28.0, highTemp = 29.0; 
  float t = 0.0, f = 0.0;
  //  Webサーバの動作
  char c;
  String rstr = "";
  CLIENT = SERVER.available();
  if (CLIENT) {
    while (CLIENT.connected()) {
      if (CLIENT.available()) {
        c = CLIENT.read();
        rstr += c;
        if(rstr.endsWith("\r\n")){
          break;
        }
      }
    }
    if(rstr.indexOf("low=") >= 0){
      lowTemp = rstr.substring(rstr.indexOf("low=") + 4, rstr.indexOf("&green=")).toInt();
      midTemp = rstr.substring(rstr.indexOf("mid=") + 4, rstr.indexOf("&blue=")).toInt();
      highTemp = rstr.substring(rstr.indexOf("high=") + 5, rstr.indexOf("&end")).toInt();
    }
    rstr = "";
    sendform(lowTemp, midTemp, highTemp);
    delay(1);
    // close the connection:
    CLIENT.stop();
  }
  // 温度測定
  t = tempMesure();
  Serial.print("temp=");
  Serial.println(t);
  if(t > highTemp){
    analogWrite(WINDFAN, full);
    Serial.println("high");
  }else
  if(t > midTemp){
    analogWrite(WINDFAN, half);
  }else
  if(t > lowTemp){
    f = f1Fluctuation();
    analogWrite(WINDFAN, f);
  }else{
    analogWrite(WINDFAN, 0);
  }
  delay(20);
}

float tempMesure(){
  //  Seeedstudio SIDEKICK BASIC KIT付属のサーミスタ「MF11-503K」で
  //  温度を測る。
  const float B = 4350.0, Ta = 25.0, Rt0 = 50000.0;  //  MF11-503Kスペックシート記載
  const float K = 273.15;  //  熱力学温度の定数
  const float v0 = 5.0, r0 = 1000.0;  //  Arduino +5Vと電流調整抵抗1kΩ
  const int resolution = 1024;  //  アナログ入力の分解能
  int srcVal = 0;
  float vt = 0.0, rt = 0.0;
  
  srcVal = analogRead(THERMISTOR);
  vt = srcVal * (v0 / (resolution - 1));
  rt = (v0 * r0 - vt * r0) / vt;
  return(1.0 / (log(rt / Rt0) / B + 1.0 / (Ta + K)) - K);
}

int f1Fluctuation(){
  //  間欠カオス法により0~255の間で1/fゆらぎを生成して返す。
  static float x = 0.1;
  if(x < 0.5){
    x = x + 2 * x * x;
  } 
  else {
    x = x - 2 * (1.0 - x) * (1.0 - x);
  }
  if(x < 0.05 || x > 0.995){
    x = random(10, 90) / 100.0;
  }
  return((int)(x * 255));
}

void sendform(float lowTemp, float midTemp, float highTemp){
  //  フォームを送る。
  char* formFirstHalf[] = {
    "<html>",
    "  <head>",
    "    <meta charset=\"utf-8\">",
    "  </head>",
    "  <body bgcolor='#ddddff'>",
    "    <center>",
    "      <h1>Arduino ネット扇風機</h1>",
    "      <form method='GET'>",
    "        <table>",
    "          <tr>",
    "  	    <th>ゆらぎ送風温度</th>",
    "  	    <th>弱風温度</th>",
    "  	    <th>強風温度</th>",
    "	  </tr>"
  };  //  14 num.
  char* formSecondHalf[] = {
    "        </table>",
    "        <input type='hidden' name='end'>",
    "        <input type='submit' value='セット'>",
    "      </form>",
    "    </center>",
    "  </body>",
    "</html>"
  };  // 7 num.
  int i = 0;

  CLIENT.println("HTTP/1.1 200 OK");
  CLIENT.println("Content-Type: text/html");
  CLIENT.println("Connection: close");
  CLIENT.println();
  CLIENT.println("<!DOCTYPE HTML>");
  for(i = 0; i < 14; i++){
    CLIENT.println(formFirstHalf[i]);
  }
  CLIENT.println("          <tr>");
  CLIENT.println("            <td>");
  CLIENT.println("           <input type='text' name='low' size='6em' value =");
  CLIENT.print(lowTemp);
  CLIENT.println(">");
  CLIENT.println("            </td>");
  CLIENT.println("            <td>");
  CLIENT.println("           <input type='text' name='mid' size='6em' value =");
  CLIENT.print(midTemp);
  CLIENT.println(">");
  CLIENT.println("            </td>");
  CLIENT.println("            <td>");
  CLIENT.println("           <input type='text' name='high' size='6em' value =");
  CLIENT.print(highTemp);
  CLIENT.println(">");
  CLIENT.println("            </td>");
  CLIENT.println("          </tr>");
  for(i = 0; i < 7; i++){
    CLIENT.println(formSecondHalf[i]);
  }

}






メールサーバお知らせランプ

投稿日:

 今朝の着想、「Arduinoのメールサーバお知らせランプ」というのを実際に作った。

 自分のプロバイダのメールサーバを定期的に監視して、メールがたまっていなければ緑、少し多ければ黄、たまりすぎていれば赤、早急に読み出さなければならないようなら赤の点滅、というふうに、ランプで知らせてくれるのである。

 以前作った「未読メールメータ」とは違って、「ETHERNET SHIELD 2」を使って単独でネットにつながるように作ってあり、パソコンを立ち上げたりサーバを上げたりする必要はないので、使い方も単純だ。電源をつなぎ、そのへんにのたくっているカテ5のケーブルを突っ込めば終わりである。

 まず、100円ショップで買ったアクリルの枠にEHTERNET SHIELD 2を取り付けたArduino UNOをねじ止めする。

 LEDは昨日も使ったアノードコモンのものを、大きく見えるよう、これまた100円ショップで買ってきたピンポン玉の中に封入し、クリップでアクリルの枠にとりつける。

 RGBそれぞれのピンをデジタルの3,4,5番につなぎ、アノードに+5Vを加える。それぞれのピンに330Ωの抵抗を付けておくことを忘れてはならない。

組み付けた様子
IMG_3023

 回路図については、描くほどのものではないが、まあ、描けばこうなる。

回路図
mailLampCircuit

 そうすると、メールの量に応じて、こんな具合に光り方が変わる。

メール10通以下は緑
IMG_3019

メールが50通くらいまでは黄
IMG_3021

メールが100通近くなってくると赤、100通を超えると赤点滅
IMG_3022

 写真の彩度が悪いが、実物はもっと鮮やかな色に見える。

 スケッチはこんな感じだ。

//
//  メールの残りによりランプの色を変える。
//    佐藤俊夫
//    27.06.14(日) 1500~
//
#include <SPI.h>
#include <Ethernet2.h>

byte MAC[] = {
  0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74
};
IPAddress IP(192, 168, 1, 129);
IPAddress MY_DNS(192, 168, 1, 1);
EthernetClient CLIENT;
const char MAIL_SERVER[] = "pop.hogehoge.ne.jp", USER[] = "USER hoge@pop.hage.ne.jp", PASS[] = "PASS passpass";
unsigned long LAST_CONNECTION_TIME = 0;
const unsigned long CONNECTION_INTERVAL = 300L * 1000L;
int R = 0, G = 0, B = 0;
const int LEDR = 3, LEDG = 4, LEDB = 5;
unsigned long LAST_LED_BLINK_TIME = 0;
boolean LED_BLINK_RED = true;
const int LED_BLINK_INTERVAL = 1000;
int MAIL_NUM = 0;
String RECEIVE_STR = "";

void setup() {
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
  digitalWrite(LEDR, HIGH);
  digitalWrite(LEDG, HIGH);
  digitalWrite(LEDB, HIGH);
  delay(1000);
  Ethernet.begin(MAC, IP, MY_DNS);
}

void loop() {
  if (CLIENT.available()) {
    char c = CLIENT.read();
    RECEIVE_STR += c;
  }
  if (millis() - LAST_CONNECTION_TIME > CONNECTION_INTERVAL) {
    pop3connect();
    if(RECEIVE_STR.indexOf("+OK server ready\r\n+OK ") >= 0){
      RECEIVE_STR = RECEIVE_STR.substring(RECEIVE_STR.indexOf("+OK server ready\r\n+OK ") + 22);
      RECEIVE_STR = RECEIVE_STR.substring(0, RECEIVE_STR.indexOf(" "));
      MAIL_NUM = RECEIVE_STR.toInt();
    }
    RECEIVE_STR = "";
  }
  if(MAIL_NUM <= 10){
    digitalWrite(LEDR, HIGH);
    digitalWrite(LEDG, LOW);
    digitalWrite(LEDB, HIGH);
  }else
  if(MAIL_NUM <= 50){
    digitalWrite(LEDR, LOW);
    digitalWrite(LEDG, LOW);
    digitalWrite(LEDB, HIGH);
  }else
  if(MAIL_NUM <= 100){
    digitalWrite(LEDR, LOW);
    digitalWrite(LEDG, HIGH);
    digitalWrite(LEDB, HIGH);
  }else
  if(MAIL_NUM > 100){
    if(millis() - LAST_LED_BLINK_TIME > LED_BLINK_INTERVAL){
      if(LED_BLINK_RED){
        digitalWrite(LEDR, HIGH);
        digitalWrite(LEDG, HIGH);
        digitalWrite(LEDB, HIGH);
        LED_BLINK_RED = false;
      }else{
        digitalWrite(LEDR, LOW);
        digitalWrite(LEDG, HIGH);
        digitalWrite(LEDB, HIGH);
        LED_BLINK_RED = true;
      }
      LAST_LED_BLINK_TIME = millis();
    }
  }
}
void pop3connect() {
  CLIENT.stop();
  if (CLIENT.connect(MAIL_SERVER, 110)) {
    CLIENT.println(USER);
    delay(1000);
    CLIENT.println(PASS);
    delay(1000);
    CLIENT.println("STAT");
    delay(1000);
    CLIENT.println("QUIT");
    delay(1000);
    CLIENT.println();
    LAST_CONNECTION_TIME = millis();
  }
}


LEDをネットにつないでコントロール

投稿日:

 先週少しやりかけて途中だった、Arduino UNO に ETHERNET SHIELD 2 をとりつけ、ウェブインターフェイスを作ってこれでLEDをコントロールするという遊びの続きをやる。

 こんなふうなインターフェイスでコントロールすることにする。

名称未設定 1

 で、ブレッドボードとArduinoはこんな感じ。「SeeedStudio SIDEKICK BASIC KIT」に入っていた3色LEDを使う。

IMG_3004

 スケッチはこんな感じで。ウェブ上で同じようなことを公開している方々は、ブラウザの出力を受けるのに、たいてい普通のポインタ文字列を使っておられるのだが、せっかくStringクラスがあるので、それを活用することにした。Stringクラスを使う欠点は多少メモリを浪費することだが、反面、余裕は十分あり、今回は別にメモリが足りないというわけでもないので、そうした。

//
//  Web2TriColorLED.ino
//    27.06.07(日) 1800~
//    佐藤俊夫
//    3色LEDをウェブインターフェイスで制御
//    ETHERNET SHIELD 2 使用
//
#include <SPI.h>
#include <Ethernet2.h>
//
byte mac[] = {
  0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74
};
IPAddress ip(192, 168, 1, 129);
EthernetServer SERVER(80);
EthernetClient CLIENT;
int R = 0, G = 0, B = 0;
const int LEDR = 3, LEDG = 5, LEDB = 6;
//
void setup() {
  Ethernet.begin(mac, ip);
  SERVER.begin();
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
}


void loop() {
  char c;
  String rstr = "";
  CLIENT = SERVER.available();
  if (CLIENT) {
    while (CLIENT.connected()) {
      if (CLIENT.available()) {
        c = CLIENT.read();
        rstr += c;
        if(rstr.endsWith("\r\n")){
          break;
        }
      }
    }
    if(rstr.indexOf("red=") >= 0){
      R = rstr.substring(rstr.indexOf("red=") + 4, rstr.indexOf("&green=")).toInt();
      G = rstr.substring(rstr.indexOf("green=") + 6, rstr.indexOf("&blue=")).toInt();
      B = rstr.substring(rstr.indexOf("blue=") + 5, rstr.indexOf("&end")).toInt();
    }
    analogWrite(LEDR, R);
    analogWrite(LEDG, G);
    analogWrite(LEDB, B);
    delay(10);
    rstr = "";
    sendform();
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    CLIENT.stop();
  }
}
//
void sendform(){
  char* formFirstHalf[] = {
    "<html>",
    "  <head>",
    "    <meta charset=\"utf-8\">",
    "  </head>",
    "  <body>",
    "    <center>",
    "      <h1>Arduino 3色LEDをウェブインターフェイスで</h1>",
    "      <form method='GET'>",
    "        <table>",
    "          <tr>",
    "  	    <th style='background-color:red;  color:white;'>赤</th>",
    "  	    <th style='background-color:green;color:white;'>緑</th>",
    "  	    <th style='background-color:blue; color:white;'>青</th>",
    "	  </tr>"
  };  //  14 num.
  char* formSecondHalf[] = {
    "        </table>",
    "        <input type='hidden' name='end'>",
    "        <input type='submit' value='セット'>",
    "      </form>",
    "    </center>",
    "  </body>",
    "</html>"
  };  // 7 num.
  int i = 0;

  CLIENT.println("HTTP/1.1 200 OK");
  CLIENT.println("Content-Type: text/html");
  CLIENT.println("Connection: close");
  CLIENT.println();
  CLIENT.println("<!DOCTYPE HTML>");
  for(i = 0; i < 14; i++){
    CLIENT.println(formFirstHalf[i]);
  }
  CLIENT.println("          <tr>");
  CLIENT.println("            <td>");
  CLIENT.println("              <select name='red'>");
  for(i = 0; i <= 255; i++){
    CLIENT.print("                <option value='");
    CLIENT.print(i, DEC);
    if(i == R){
      CLIENT.print("' selected>");
    }else{
      CLIENT.print("'>");
    }
    CLIENT.print(255 - i, DEC);
    CLIENT.println("</option>");
  }
  CLIENT.println("              </select>");
  CLIENT.println("            </td>");
  CLIENT.println("            <td>");
  CLIENT.println("              <select name='green'>");
  for(i = 0; i <= 255; i++){
    CLIENT.print("                <option value='");
    CLIENT.print(i, DEC);
    if(i == G){
      CLIENT.print("' selected>");
    }else{
      CLIENT.print("'>");
    }
    CLIENT.print(255 - i, DEC);
    CLIENT.println("</option>");
  }
  CLIENT.println("              </select>");
  CLIENT.println("            </td>");
  CLIENT.println("            <td>");
  CLIENT.println("              <select name='blue'>");
  for(i = 0; i <= 255; i++){
    CLIENT.print("                <option value='");
    CLIENT.print(i, DEC);
    if(i == B){
      CLIENT.print("' selected>");
    }else{
      CLIENT.print("'>");
    }
    CLIENT.print(255 - i, DEC);
    CLIENT.println("</option>");
  }
  CLIENT.println("              </select>");
  CLIENT.println("            </td>");
  CLIENT.println("          </tr>");
  for(i = 0; i < 7; i++){
    CLIENT.println(formSecondHalf[i]);
  }

}

 気を付けるところは、私が買った「SeeedStudio SIDEKICK BASIC KIT」というキットに入っている3色LEDは「アノードコモン(共通陽極)」というタイプで、プラスに5V、各色のコントロールはマイナスになる、という点だ。ここを間違えると思ったように動作しない。一番長い脚が共通陽極だ。封入の切り欠きを左にして、左から赤、アノード、緑、青の順に脚が並ぶ。

 また、フォームから与える値も、アノードコモンであるゆえに、「0」が最も明るくなり、「255」が最も暗くなるので、インターフェイスを逆にするなど、多少工夫が必要だ。

 それから、PWM(パルス幅変調)の機能を大いに活用し、いろいろな色が出せるようにした。この場合の注意点は、ETHERNET SHIELD 2を取り付けると、10番・11番・12番のピンが使えなくなるので、他のピンで出力しなければならないことだ。今回は3番・5番・6番を使った。

 で、ウェブインターフェイスをタブレットにロードする。

IMG_3005

 動かしてみるとこんな感じで、いろんな色になって面白い。


カメラのシャッターをネット経由で切る

投稿日:

 先週、Arduinoを使ってデジカメのシャッターを切る遊びがやりたくて、ソレノイドでやろうとしたら、ソレノイドの力不足でうまくいかなかった。

 よく考えてみたら、最初に買った入門キットの中に小さいサーボがあり、これは力があるからシャッターぐらい押せるだろう、と思って工夫した。

 そうしたら、うまくいった。こんな具合である。



 ネットワーク経由で動くようになっており、そのプログラムは次の通りである。

//
//  WebServerでサーボを動かし、カメラのシャッターを切る。
//    佐藤俊夫
//    27.5.30(土)1300~
//

#include <SPI.h>
#include <Ethernet2.h>
#include <Servo.h>

byte mac[] = {
  0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74
};
IPAddress ip(192, 168, 1, 129);
EthernetServer server(80);
Servo shutter;
const int SERVO = 9;

void setup() {
  Ethernet.begin(mac, ip);
  server.begin();
  shutter.attach(SERVO);
  shutter.write(90);
}

void loop() {
  String recvbuf;
  EthernetClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        recvbuf += c;
        if (c == '\n' && currentLineIsBlank) {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<head></head>");
          client.println("<body>");
          client.println("<center><h3>Drive solenoid.</h3>");
          client.println("<hr>");
          client.println("<form method=\"POST\">");
          client.println("<input type=\"submit\" value=\"Do!\">");
          client.println("</form>");
          client.println("</center>");
          client.println("</body>");
          client.println("</html>");
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
          if(recvbuf.indexOf("POST") == 0){
            shutter.write(50);
            delay(5000);
            shutter.write(90);            
          } 
          recvbuf = "";
        }
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    client.stop();
  }
}

次に、ETHERNET SHIELD 2というやつを…

投稿日:

 次に、昨日FETやソレノイドと一緒に買っておいた「イーサネットシールド」というやつを試す。

 これを買ったのは、簡単なウェブインターフェイスで、例えばフォームのボタンをクリックするとデジカメのシャッターが落ちる、みたいなのをやりたかったからだ。千石電商で3240円だったが、多分、もっと安い互換品などもあるのだろう。

 ともかく、こんな箱に入っている、こういう形のブツである。

IMG_2880

 この方のサイトなどを勝手に参考にさせていただきつつ、いじくってみる。

 Arduinoに取り付けるとこのようになる。

IMG_2881

 で、ArduinoのIDEにサンプルプログラムが入っているから、それをタッチアップして目的を達する。

 注意しなければならないのは、私の買ったのは「ETHERNET SHIELD 2」という最新のもので、ネットで多く紹介されている「ETHERNET SHIELD R3」というものとは違うということだ。

 私の買った「ETHERNET SHIELD 2」は、最新のArduino IDE 1.7.3でないと、サンプルコードなども違い、扱えない。私が使っているIDEは1.6.4なので、アップグレードしなければならない。

 アップグレードしたIDEで、次のように「ファイル」→「スケッチの例」→「Ethernet2」とメニューを操作していくとサンプルコードが出てくる。

webServerSketchExample2

  • AdvancedChatServer
  • BarometricPressureWebServer
  • ChatServer
  • DhcpAddressPrinter
  • DhcpChatServer
  • TelnetClient
  • UdpNtpClient
  • UDPSendReceiveString
  • WebClient
  • WebClientRepeating
  • WebServer

……このように、けっこういろいろなものが入っている。今日は一番下の「WebServer」を選ぶ。

 このソースのMACアドレスとIPアドレスを、先達サイトを参考に現況に合わせて書き換える。MACアドレスはシールドの基板の裏にシールで貼ってある。
 
 出来上がったらそのへんにのたくっているカテ5のモジュラーをえいっとつっこみ、ブラウザにIPアドレスを入れれば、すぐにミニWebサーバとして動き出す。

ArduinoWebServerScreen

 これは、各アナログピンの現在の値を5秒毎にモニタしている。何かセンサをピンにつなげば、即、ネット温度計やネット照度計の出来上がりである。

 さて、次に、コイツで「POST」を受け取る段取りだ。コイツにアクセスして、フォームのボタンを押すと、ソレノイドが動く、という機構を作るためである。

 本当ならクライアントから来る文字列を標準入力で受ける段取りが必要だが、今日は簡略化して、ブラウザがPOSTを投げたら、内容はなんでもいいから、とりあえずソレノイドを動かす、というふうにする。

 そのソースコードはこんな感じだ。

//
//  WebServerでソレノイドを動かす。
//    佐藤俊夫
//    27.5.24(日)1352~
//

#include <SPI.h>
#include <Ethernet2.h>

const int FET = 9;
byte mac[] = {
  0x90, 0xA2, 0xDA, 0x0F, 0xF6, 0x74
};
IPAddress ip(192, 168, 1, 129);
EthernetServer server(80);

void setup() {
  Ethernet.begin(mac, ip);
  server.begin();
  pinMode(FET, OUTPUT);
}

void loop() {
  String recvbuf;
  EthernetClient client = server.available();
  if (client) {
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        recvbuf += c;
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("<head></head>");
          client.println("<body>");
          client.println("<center><h3>Drive solenoid.</h3>");
          client.println("<hr>");
          client.println("<form method=\"POST\">");
          client.println("<input type=\"submit\" value=\"Do!\">");
          client.println("</form>");
          client.println("</center>");
          client.println("</body>");
          client.println("</html>");
        if (c == '\n' && currentLineIsBlank) {
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
          if(recvbuf.indexOf("POST") == 0){
            digitalWrite(FET, HIGH);
            delay(2000);
            digitalWrite(FET, LOW);            
          } 
          recvbuf = "";
        }
        else if (c != '\r') {
          currentLineIsBlank = false;
        }
      }
    }
    delay(1);
    client.stop();
  }
}

 次に、ちょっと仕掛けを作る。本当はカメラのシャッターを切りたいのだが、買ってきたソレノイドの力があんまりなくて、シャッターが切れない。それで、ガラスのコップをソレノイドで叩いて音を出す。

 ソレノイドは、午前中やったように、電流がちょいとばかり多く流れるので、FETでリレーしてやる。

 こんな仕掛けにする。

IMG_2885

 こんなフォームを作り、携帯電話にロードする。「Do!」というボタンをクリックすると、ソレノイドが動く。

form

 で、ガラスのコップにソレノイドの軸を近づけて……。

IMG_2886

 操作するとこんな風になる。


 

PIC16F628とArduino

投稿日:

 割合にシリアスな話なのだが……。

 休暇中にArduinoなどいじっているのは、実は、多少、以前の仕事上の屈託があってのことだ。

 4年ほど前、仕事で「PIC16F628」という石を触ったことがある。これは、新規の仕事ではなく、古いプロジェクトのメンテナンスだった。

 当時すでに古い石だった「PIC16F628」だ。36ピンのパラレルにつなぐROMライタでプログラムを焼く。

 使えるのはPIC16F628独特の、たしか1語長14ビットという変わったアセンブラのみ。

 しかも、いったいどういうマネジメントか、ソースコードが消失していた。ROMイメージからディスアセンブルした不完全なソースを参考に、バイナリを直接直さなければならなかった。

 そのプロジェクトはもう忘れられかけたプロジェクトで、それが立ち上がった頃は華々しくもてはやされ、かかわった人たちは表彰などされたのだが、私が始末を押し付けられた頃にはもう何年も経っており、腐ったプロジェクトに成り果てていた。

 誠意をもってメンテしたが、誠意を持とうが持つまいが、私には何の得もないし、また、組織にとってもなんの得もないという腐った作業だった。ただ、やらなければならないことになっており、しかも、それをやると私が叱られる、という、わけのわからない仕事だった。つまり、「叱られる奴を出さなければならない」という仕事なのだ。

 実は当時、既にArduinoは発売されて普及していた。私もそれを知っていた。もし、その腐ったプロジェクトが新規に始まったもので、最初から私に任されたものだったら、私はPIC16F628などではなく、携帯電話かArduinoを使用しただろう。

 そうできなかったことが、当時本当に悔しかった。

 ArduinoはPIC16F628のような苦労をしなくてよい。C言語ライクにプログラミングでき、倍長実数まで扱え、アナログ入出力ができ、ごく安い値段でイーサネットやBluetoothまで扱える。

 自宅の小机の上で「Lチカ」なんぞ試し、夜、赤いLEDが間遠に明滅するのを眺めると、ほんの数年前のそのことがホロ苦く思い出される。



メール・メーターはこのように作る。

投稿日:

 さて、一部の変わったユーザ様(ヲイ)のために、Arduinoを使用した未読メールメータの作成方法を書いておきたい。入門キットについてくるサーボを使用する。

 プログラムは次のような簡単なものでよろしい。

 まず、Arduino側には次のように書く。

//
//  シリアルからサーボを制御
//    佐藤俊夫
//    27.05.02(土) 1028~
//
#include <Servo.h>
//
Servo meter;
const int METER_0 = 5;
const int METER_100 = 175;
const int METERMIN = 0;
const int METERMAX = 100;
const int STRLEN = 10;
char buf[STRLEN];
int i = 0;
//
void setup() {
  meter.attach(9);
  meter.write(METER_0);
  Serial.begin(9600);
}

void loop() {
  int v = 0, deg = METER_0;
  if(Serial.available() > 0){  //  もし受信したデータが存在したら
    buf[i] = Serial.read();
    if(i >= STRLEN - 1 || buf[i] == '\n'){
      buf[i] = '\0';
      i = 0;
      Serial.flush();
      //  針をナニする処理
      v = atoi(buf);
      (v > METERMAX) ? v = METERMAX : v;
      (v < METERMIN) ? v = METERMIN : v;
      deg = METER_0 + (int)(v / ((float)METERMAX / (float)(METER_100 - METER_0)));
      meter.write(deg);
// Serial.println(deg, DEC);
// Serial.write('\n');
      delay(1000);
    }else{
      i++;
    }
  }    
}


 テストしてうまく動いたら、次に、POP3サーバにたまっている自分のメールの本数を知る工夫をする。

 私は次のようにした。まず、手近のLinuxマシンにexpectを入れる。

# yum -y install expect
(中略)

 そうすると、telnetなどでPOP3サーバに自動ログインできる環境が整う。

 シェルでこんなのを書く。まあ、遊びなんでrootで。

# ls -Fla pop2arduino
-rwx------ 1 root root 415 2015-05-02 13:58 pop2arduino*
# cat pop2arduino
#!/bin/sh
#  pop2arduino
#    Sat May  2 12:53:57 JST 2015
#    Sato Toshio
#
expect -c "
set timeout 5
spawn telnet pop.hogehoge.ne.jp 110
expect \"+OK POP3 ready\"
send \"USER fugafuga@hagefuge.hogehoge.ne.jp\n\"
expect \"+OK\"
send \"PASS passpass\n\"
expect \"+OK server ready\"
send \"STAT\n\"
expect -re \"\\\+OK (.+) .+$\"
send \"QUIT\n\"
" | egrep "\\+OK ([0-9]+) .+$" | sed -r "s/\\+OK ([0-9]+) .+$/\1/g" >/dev/ttyACM0

 で、このシェルは、まあ、なんだっていいんだけど、安直にcronで定期実行する。

# crontab -e
(以下crontab内)
*/5  *  *  *  *  /hoge/pop2arduino
(crontabおわり)
/etc/rc.d/init.d/crond restart
(出力略)

……で、メールが届くのを待っていると、こういうふうに動く。

IMG_2768



シリアルで制御できたら、次は……

投稿日:

 次も、既にやることは決まっている。

 Arduinoをおもむろに手元のLinuxマシンにつなぐ。で、Linuxへ行って、rootになり、

# dmesg
usb 3-2: new full speed USB device using uhci_hcd and address 2
usb 3-2: configuration #1 chosen from 1 choice
usb 3-2: New USB device found, idVendor=2a03, idProduct=0043
usb 3-2: New USB device strings: Mfr=1, Product=2, SerialNumber=220
usb 3-2: Product: Arduino Uno
usb 3-2: Manufacturer: Arduino Srl
usb 3-2: SerialNumber: 75439333335351303121
cdc_acm 3-2:1.0: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
drivers/usb/class/cdc-acm.c: v0.25:USB Abstract Control Model driver for USB modems and ISDN adapters

……となってればオッケーだ。

 で、

# ls -Fla /dev/ttyACM0
crw-rw---- 1 root uucp 166, 0 2015-05-01 11:50 /dev/ttyACM0

 よっしゃ、あるある。

 スピードなどはsttyで確かめると良い。

# stty -F /dev/ttyACM0
speed 9600 baud; line = 0;
-brkint -imaxbel

 オッケー。

 で、Linuxのコマンドラインから、旗を上げ下げする。

# cat /dev/ttyACM0 &
# echo '1' >/dev/ttyACM0

 おう、旗が上がる。

# echo '0' >/dev/ttyACM0

 ふふっ、旗が下がる。

 ……で、ここまで来たら、もうやりたいことはできたも同然w。



プログラミング言語「R」で遊ぶ

投稿日:

 プログラミング言語「R」というものを知る。

 恥ずかしながら不肖この佐藤、これまで数多くのプログラミング言語を扱ってきたし、10種やそこらはゆうに越える種類のプログラミング言語で実用にかなうプログラムを書き、実際に使用もしてきた。また自らプログラミングしたことがないいろいろな種類の言語でも、その名前や特性、出所来歴は知っている。…つもりでいた。

 迂闊であった。

 この「R」という言語など、まったく全然、少しもちっとも、知らなかった。知ったのは一昨日である。

 なぜ知ったのかというと、マルコフ決定について学ばなければならず、それにはマルコフ連鎖をわきまえなければならない。マルコフ連鎖について書かれたサイトを渉猟していると、「ここをRで説明すると…」などとして説明しているサイトに行き当たった。ナニ、Rで説明だと!?Rて何だ?知らん。聞いたことがない。

 さてそういうわけで、さっそくRのバイナリをダウンロードしてインストールし、試す。オープンソースにしてフリーであり、お金はかからない。Linux/Windows/Macと、色々な種類の計算機で動く。Windows用バイナリは次のURLにある。

 Rは統計解析に向く言語で、Rという言語仕様そのものよりも、そのインタプリタ風実行環境――かつてのBASICに似ている――全体を含めて、統計処理がしやすく作られている。コンソールにコマンドを打ち込んでいくだけですぐに結果が得られる。電卓代わりに使うだけでもなかなか便利だ。

 ふと思いついて、このRと、「ランチェスターの2次則」で土曜日の昼下がりを遊んでみようか、という気になる。

 ランチェスターの2次則は、オペレーションズ・リサーチの古典理論として知られている。もともとはイギリスの技術者ランチェスターが、まだ飛行機が戦力として有望でない第一次大戦の時代に、将来飛行機が大量に使用されるときの損耗の推移を考察・研究し、発表した軍事理論である。

 歴史上のさまざまな戦争の戦闘経過をこの公式にあてはめると、まるで嘘じゃないかというほどよく合致するので、オペレーションズ・リサーチの分野でよく知られ、いまなお良く使用されている。日本では企業の競争などのモデルに使われており、これが実は軍事理論であるとは知らない人も多い。

 式は実に簡単だ。

{B_0} ^ 2 - {B_t} ^ 2 = E({R_0} ^ 2 - {R_t} ^ 2)
 ここに、

B : 青軍

R : 赤軍

B_0, R_0 : 青・赤両軍の最初の兵力

B_t, R_t : ある同じ時点での青・赤両軍の残存兵力

E : 兵力の質の比。赤軍の質が青の倍であれば2、半分であれば0.5。

 まことに単純きわまる。何の疑問もない式だ。「E」が1.0のとき、それぞれ全く同じ人数の青、赤両軍が全員で殺し合いをすれば、双方とも等しく損耗し、同時に全滅する。しかし、兵力に差があると、その差に「自乗」が作用し、思っているよりも損耗差が大きく開いていく、という式だ。

 ここで「だから戦争はしてはいかんのだ」と脱線するのもなかなか楽しそうだが、今日は脱線しない(笑)。

 式を変形すると、例えば、

B_t = \sqrt{{B_0}^2 - E({R_0}^2 - {R_t}^2)}

 などという、まことに楽しげな式ができる。ここで、赤軍(R)が劣軍として、R_tにゼロを代入し、B_0R_0に開戦時の兵力を入れれば、赤軍が全滅したときに青軍がどれくらい残っているか、ということを見積もることができるのである。また、

E=\cfrac{{B_0}^2 - {B_t}^2}{{R_0}^2 - {R_t}^2}

 とすると、例えば兵力が足りない側が、どれくらい優れた兵器を持たなければならないか、ということも簡単に見積ることができる。

 ランチェスターの2次則はほかにもいろいろとイジりがいのある理論で、たとえば「B_t」に関する最初の変形を微分して導関数を出せば、某時点での接線の傾きが求められるから、「傾き1以上」になるときの兵力がいくつか、ということから、「急に敗色が濃厚になってきたのがいつごろか」などというものも計算でき、これがまた、歴史上の色々な戦例に合致したりするから侮れない。

  さて、起動したRのコンソールに、次のように入力する。

> # 関数Bt
> Bt<-function(B0,R0,Rt,E){
+ Bt=sqrt(B0^2-E*(R0^2-Rt^2))
+ Bt
+ }

 これで、ひとつ目の変形、「双方の初期兵力と、赤軍の現在兵力及び双方の兵力の質に応ずる青軍の現在勢力」を求める関数が定義される。
 
 この関数で、実際の勢力の推移を求めよう。Rでは、こんなふうにすると、たちどころに数列が配列に格納される。

> Bts<-Bt(100, 80, 80:0, 1.0)

 これで、劣軍勢力が80から0になるまでの、優軍勢力の推移が配列Btsに格納される。格納された様子を見るには、配列名をタイプするだけでいい。

> Bts
[1] 100.00000 99.20181 98.40732 97.61660 96.82975 96.04686 95.26804 94.49339
[9] 93.72300 92.95698 92.19544 91.43850 90.68627 89.93887 89.19641 88.45903
[17] 87.72685 87.00000 86.27862 85.56284 84.85281 84.14868 83.45058 82.75869
[25] 82.07314 81.39410 80.72174 80.05623 79.39773 78.74643 78.10250 77.46612
[33] 76.83749 76.21680 75.60423 75.00000 74.40430 73.81734 73.23933 72.67049
[41] 72.11103 71.56116 71.02112 70.49113 69.97142 69.46222 68.96376 68.47627
[49] 68.00000 67.53518 67.08204 66.64083 66.21178 65.79514 65.39113 65.00000
[57] 64.62198 64.25730 63.90618 63.56886 63.24555 62.93648 62.64184 62.36185
[65] 62.09670 61.84658 61.61169 61.39218 61.18823 61.00000 60.82763 60.67125
[73] 60.53098 60.40695 60.29925 60.20797 60.13319 60.07495 60.03332 60.00833
[81] 60.00000

 さて、数字の並びを見てもつまらないから、これをグラフにしてみたい。グラフを描くのも、Rでは簡単だ。

> # プロット
> plot(Bt(100, 80, 80:0, 1.0), 80:0, "l", xlim=c(100, 60))

 Btsに値が格納されているなら、

> plot(Bts, 80:0, "l", xlim=c(100, 60))

でよい。そうすると、画像のようなグラフがたちどころに表示される。

 これは、80人対100人で戦って、劣軍(80人)側が全滅したときに優軍(100人)側が何人残るか、というグラフである。「自乗」がよくきき、最初互角に戦っているように見えて、ある時点から急速に80人側が損耗し、80人側が全滅したとき、100人側には60人もの残存兵力があることがわかる。

 そうすると、劣軍のほうは、「量より質」で勝負、ということになるから、先に出た「E」を、互角の損耗になるように求めればよい。Rでは次の如しである。

> # 函数E
> E<-function(B0, Bt, R0, Rt){
+ E<-(B0^2 - Bt^2)/(R0^2 - Rt^2)
+ E
+ }
> E(100, 0, 80, 0)
[1] 1.5625

 最後に出ている、「1.5625」、約1.6というのが、劣軍が持たなければならない「質」である。なんでもよい、命中率が1.6倍でも、飛行機のスピードが1.6倍でもよい。しかし、「モノの性能や人の能力が1.6倍」ということがどんなに難しいことか、論じるまでもない。オリンピックのスキー・ジャンプの選手が、相手が100メートル飛ぶところを160メートル飛ぶなどと、そんな途方もない実力差など到底保ち得ないことからも、それはイメージできる。

 ここで、ちょっと、英雄・東郷平八郎元帥を揶揄してみよう。

 日本海海戦にみごとな勝利をおさめた元帥が、戦後聯合艦隊を解散するに当たり、部下幕僚の秋山真之をして起案せしめた名文に、「聯合艦隊解散之辞」がある。その中の一節は不朽の名文として後世に残る。

(前略)
而して武力なるものは艦船兵器等のみにあらずして、之を活用する無形の実力にあり。百発百中の一砲()く百発一中の敵砲百門に対抗し得るを(さと)らば、我等軍人は主として武力を形而上に求めざるべからず。
(後略)

 さて、では、100対1の勝負、そして100門側の命中率は100発中1発命中、すなわち0.01、かたやは100発中100発命中、というからにはすなわちこれは1.0であり、その性能比は100になんなんとする。

 では、これを、Rを使って確かめてみよう。100対1だとグラフにしにくいから、1000対10にする。

> Bts<-Bt(1000, 10, 10:0, 100.0)
> Bts
[1] 1000.0000 999.0495 998.1984 997.4467 996.7949 996.2429 995.7911 995.4396
[9] 995.1884 995.0377 994.9874

…あっれ~…。どうも、ヘンだぞ、この数字は(笑)。グラフにしてみよう。

> plot(Bts, 10:0, "l", xlim=c(1000, 994))

 ……ダメじゃん。全然。東郷さん、相手を5門もやっつけないうちに、10門、全滅してんじゃん。秒殺じゃん。っていうか、これ、瞬殺のレベルでしょ。

 秋山真之~ッ!!ウソ書くな~ッ(笑)。

 じゃあ、なんで、日本海海戦に、弱い日本が勝てたの?どうしてどうして!?

 ……これは皆さん、実は日本海軍は当時劣勢海軍などではなかったのだ。ユダヤ商人からなりふりかまわず借金しまくり、戦闘艦艇を買いあさり、乾坤一擲の大勢力を作り上げていたのだ。これらはあげて一丸となってバルチック艦隊に襲いかかっている。

 数において劣り、かつアフリカ回り、インド洋、南洋回り航路を遠路はるばるやってきて、疲弊しきっているバルチック艦隊を容赦なく待ち伏せ、さながら弱い者イジメのように袋叩きにしたという歴史的事実は知る人ぞ知るところである。そして、日本がそのためにした借金を返し終わるのに、実に82年後の昭和61年(1986)までかかっているのも、よく知られている。

 「『数において劣る』だって!?いや、たしか、艦艇の数は互角だったんじゃなかったっけ?」

……と、詳しい向きは言うかもしれない。だが、双方の主要な火力であった15サンチ砲の門数だけを見ると、聯合艦隊204門に対してバルチック艦隊152門で、聯合艦隊が(まさ)るのだ。これをRに入れてみると、

> Bts<-Bt(204, 152, 152:0, 1.0)
> Bts
[1] 204.0000 203.2560 202.5142 201.7746 201.0373 200.3023 199.5695 198.8391
~中略~
[153] 136.0588
> plot(Bts, 152:0, "l", xlim=c(200, 133))

 バルチック艦隊全滅時点で、聯合艦隊はまだ半分以上、136門の火力が残存しているのである。聯合艦隊の全艦艇は91隻、平均すると一隻につき2門の15サンチ砲を積んでいたことになるから、その片砲を失っていても、まだ船自体は沈まない。だから東郷平八郎が、「数に劣る日本軍は、腕前と作戦で勝った」と言っているのは、ウソなのである。数で押しまくり、バルチック艦隊を袋叩きにしただけだ。

 さておき、この「東郷平八郎・ランチェスター検証ネタ」は、私・佐藤のオリジナル着目ではない。オペレーションズ・リサーチの専門家の間ではよくネタとして取り上げられるものであることを断っておく。また、恐ろしい戦争で、恐怖に耐えて一生懸命に戦った下士官兵たちを、「よくやった!お前たちの精神力がまさっていたから、勝った!!だが油断するなよ!」と、提督として元気付けている類の話を、数字の計算だけを論拠にウソだなどと言い立てることは、必ずしも正しいことではないと、漏れなく付言しておきたい。

 さて、ここまでならExcelなどでも簡単にできることだ。ひとつ、Excelではちょっと難しい量の数字を、この面白そうな「R」言語に、叩き込んでみようではないか。

お題:「13億4千万の中国人と、1億3千万の日本人が全員で殺し合いをする」

…いや、これ、計算する前から結果は見えてるんですけど(笑)、そうじゃなくて、まあ、デケぇ数字でもRは扱えまっせ、というところを試したいのである。 このお題、エクセルで兵力の推移などを表で見ようとすると、人口が多すぎて、行数が足りなくなったりするからだ。

> Bts<-Bt(1340000000, 130000000, 130000000:0, 1.0)
エラー: サイズ 991.8 Mb のベクトルを割り当てることができません

…ありゃ(笑)。さすがに13億とか1億3千万とか配列に入れると、チトムリだったみたいだ。一桁減らそう。

> Bts<-Bt(134000000, 13000000, 13000000:0, 1.0)

 サクッと配列に表が格納される。プロットしてみよう。

> plot(Bts, 13000000:0, "l", xlim=c(134000000, 133300000))

 中国側が13億4千万から、13億3千万にまで減らない間に、日本はゼロ人。1億3千万人が全滅である。

 では、ハイテク兵器などで武装して、量より質でがんばりましょう、としたとき、日本はどれほどの命中率、どれほどのスピード、どれほどの爆発力、どれほどの優れた人材を備えて、はじめて互角になるでしょうか、という数字が…

> E(1340000000, 0, 130000000, 0)
[1] 106.2485

…となる。106倍。

 そんな、アンタね(笑)。中国の兵隊の知能指数が日本の100分の1であるとか、日本の飛行機が中国の飛行機の100倍のスピードで飛ぶとか、そんなのムリに決まってる。中国軍の100倍の厳しい訓練を自衛隊がしたって、100倍の能力にはならないのだ。

 さて、これが今日の昼下がりの、「R」を使った、ちょっとした暗いお遊びでございました。どっとはらい。

南越谷のレストラン「イタリアの台所・クローチェ」は嫌な店である。

投稿日:

 先週、5月31日の金曜日、南越谷のホテル「サン・オーク」B1階にあるレストラン「イタリアの台所・クローチェ」(越谷市南越谷1丁目22−1)でイヤな目にあい、この店にはもう二度と来るまいと思ったので、悪口を広めておきたい。

 本当は先週すぐに書こうと思ったのだが、忘れていたのだ。しかし、先ほど近くを通りかかって思い出した。

 店をやろうと言う人は、ネット時代であるから、気をつけて置くが良かろう。私も男だ、ちょっとググれば私の顔も住所もわかる、特段逃げも隠れもすまい。

以下、事実---------

 店がオープンして2~3分ほど経った18時過ぎ頃、店に入った。「OPEN」の札が出ているし、「本日貸切」とも「ご予約ダレソレ様」とも、張り出されてはいない。

 客は誰もいなくて、席は全部空いていた。

私  「えーっと、かまいませんか?」
店の人  「はい、いらっしゃいませ。…何名様でしょうか?」
私  「あ、一人なんですが…」
店の人  「すいません、今日は予約のお客様で一杯でして。」
私  「…。はあ、そうですか。お邪魔しました。」
店の人  「またのお越しをお待ちしております。」

 そのまま素直にきびすを返して、私は店を出た。

 帰り際、入り口周りをもう一度良く見たが、「本日貸切」とか「ご予約ダレソレ様」とも何も書かれておらず、単に「OPEN」の札が出ているだけだ。

 私の服装は、半そでの白いワイシャツに、ベージュのスラックス、通勤かばんを持ち、髪は真っ黒の短髪である。

---------事実ここまで

 ここからは推測と、悪口だ。

 店がオープンして2~3分後の、誰も客がいない店に、たった一人の客が入ってきて、「予約で一杯」などというバカな話があるだろうか。ただ一人の客も座れない、カウンター席すらない、というほどの満席ということではないか。

 それなら、なんで、店の中ほどまで私が入ってくるのを漫然と見ていて、

「何名様ですか」

と聞くのだ。本当に予約で一杯で、早い時間のたった一人の客も入れることはできないなら、

「いらっしゃいませ。ご予約のお客様でいらっしゃいますか?」

となぜ聞かないのだ。人数を聞いて、「ゼロ人です」と答えたら座らせてもらえる、ということはありえないし、そんな答えなどあるはずない。だから、これは「一人以上」の答えを期待してする質問だ。

 そもそも、「早い時間のたった一人の客」すら座らせられないというなら、店の前に「本日満席」とでも書いておくべきである。

 私はヤクザのようななりをしていたわけではないし、一人で入ったからと言って追い出されるような乱暴な言葉遣いをした覚えもない。

 おおかた、「金曜日のかきいれ時に、一人客にテーブルを占領などされてはたまらない」というような気持ちで私を追い出したのだろう。私が4人連れででもあれば、座らせるつもりだったに違いない。そうでなければ、人数を聞いてから「今日は一杯です」などという言葉が出てくる説明がつかない。

 一人の見慣れない客だからと言って追い出すようなメシ屋など、看板を上げて商売をする資格がない。それならそうと看板に書くか、看板など出さず、高級店然として、人づてに紹介される有名人だけ相手にでもしておけばよかろう。

 数年前にこの店で飲食したことがあるが、このご時勢に店内がきちんと「分煙」されておらず、「禁煙席、喫煙席どちらになさいます?」と聞かれて「禁煙席を」と言ったにもかかわらず、背中合わせの真後ろの席でプカプカと煙草の煙を立てられて非常に迷惑した。

 その時に「イヤな店だな」と思ったのに、また入ろうとした私がバカだ、と言えばそれもそうだ。

 なので、私より知性のある人は、この店には行くべきではない。

 腹が立つから、店の名前をもう一回、特筆大書しておいてやろう。南越谷のホテル「サン・オーク」B1階にあるレストラン「イタリアの台所・クローチェ」(越谷市南越谷1丁目22−1)である。場所は次のURLのとおりだ。

http://maps.google.com/maps?hl=ja&ll=35.876363,139.792652&spn=0.000004,0.001725&t=m&z=19&layer=c&cbll=35.876266,139.792658&panoid=q1jK8AoNEImcOtT2DuhJWw&cbp=12,45.59,,1,1.87