日本語版Botanicalls Twitter 自作 ボタニカルコール ツイッター
以下の説明記事「Botanicalls Twitter DIY」は、2008年2月に発表されたものです。更新された説明記事と新しい「Botanicalls Kit」の詳細については、こちらをご覧ください→http://www.botanicalls.com/kits
「Botanicalls Twitter」は「植物はどうですか?」の問いに答えます。
世界中のどこにでも届くTwitterで、状況のお知らせをツイートすることにより、緑豊かな仲間とやりとりできます。
植物は水やりをして欲しいとき、お知らせするためにTwitterへ投稿し、あなたの植物に対する愛情に、感謝を捧げるのです。
Twitterは「最近どう?」みたいなシンプルなやり取りをするソーシャル・ソフトウェアです。
Botanicallsは、人に何かしてほしいとき、植物から電話をかけることが出来る様にするために、開発されたシステムです。
Botanicallsネットワーク上ある植物が、水やりをしてほしいとき、植物の方から電話をかけ、何をしてほしいのかを正確に伝えることが出来ます。
人間が植物に電話をかけると、植物はその習性や特徴を、電話した人に伝えます。「212-202-8348」に電話して、(Botanicallsネットワーク上にある)植物について、もっと詳しく聞いてみましょう。
【訳者注:本記事は、Botanicallsシステムについての記述ではなく、電話の部分をTwitterに置き換えたDIY向けシステムについての記述となっております。農業作物、果樹、盆栽などに応用すると面白いかもしれません】
必要な物
- すてきな植物(写真は、種から育てた観賞用の唐辛子です)1鉢
- トランジスター「2N2222A」または「2N3904」 1本
- 抵抗器 100Ω 1本
- 抵抗器 10kΩ 1本
- LED 1本
- 亜鉛メッキ釘(溶融処理されたものが良い)2本
- 小型ブレッドボード 1枚
- adafruit XPortシールド 1枚
- XPortまたはXPort Direct【訳者注:シリアル通信~イーサネット間の変換を行なう小型デバイス。XPortシールドに搭載することにより、ArduinoをLANに接続させることが出来るようです】 1枚
- Arduino USB基板 1枚
- 9V DC電源アダプター 1個
- 配線用ワイヤー(色分けされたもの)
- ハンダ
- USB A→Bケーブル
- USB A→miniBケーブル
- FTDI製 USBシリアル通信アダプター(必要に応じて)
- LANケーブル
【用具など】
- ハンダこて
- ヘルピング・ハンズ【訳者注:ハンダ付けする時に使用するハンズフリー・ツールのことと思われます】
- PC(ZTermかハイパーターミナルが導入されている)
- Arduino IDE
- Twitterアカウント
水分測定プローブ
土の水分測定プローブとして使うため、2本の釘が必要になります。
その辺に転がっている導電性の釘ならどんな種類でも使えますが、亜鉛メッキ/亜鉛溶融処理された釘のほうが、より腐食しにくいのでお奨めです。
- 釘にワイヤーを巻き付け、十分に時間をかけてワイヤーを加熱して、ワイヤー上にハンダが染み渡るようにさせしょう。こうすれば、ワイヤーに釘が引っ付いてしまうのを防ぎ、ワイヤーを釘から外せるようにすることが出来ます。
- ワイヤーが加熱されていれば、釘にハンダをつけると、ハンダが流れ出すかと存じます。
- このようにして、2本の釘にワイヤーを接続してください。
【お知らせ】電気分解により品質低下しないような良い金属を、いまだに探しております。何かご提案があれば、我々にお伝えいただければ幸いです。
配線
- ブレッドボードの両側の電源ラインに、Arduinoの3.3V電源とGNDを接続します。
また、ArduinoのAREFピンにも、3.3V電源を接続しますが、これは水分センサーの基準電圧として使用されます。
- Arduinoのデジタル13番ピンに、LEDの+側の足を接続します。LEDの−側の足(短い方の足)をGNDに接続します。
- 土の水分測定センサー(トランジスター使用)回路をXPortシールドに接続します。トランジスターの底部から出ている3本のリード線はそれぞれ
(1)エミッター (2)ベース (3)コレクター といいます。
各リードが正しく接続されていることが重要なので、このステップで気を付けていただくとともに、回路に電源を供給する前にもう一度配線を確認してください。
エミッターは、Arduinoのアナログ0番ピンへ接続され、また、10kΩの抵抗をGNDへ直列に接続します。
ベースは、1本目の水分測定プローブ用釘に配線されます。もう1本の水分測定プローブ用釘は、100Ωの抵抗を3.3 V電源へ直列に接続します。
最後に、トランジスターのコレクターは、3.3 V電源へ直接接続します。
この回路図を、水分測定回路接続の理解にお役立てください。
XPortとArduinoシールドを、以下のように接続します。
- XPort TX→Arduino デジタル3番ピン
- XPort RX→Arduino デジタル2番ピン
- XPort Reset→Arduino デジタル4番ピン
- XPort DTR→Arduino デジタル5番ピン
- XPort CTS→Arduino デジタル6番ピン
- XPort RTS→Arduino デジタル7番ピン
- デバッグのため、FTDI製 USB−シリアル通信アダプターを取付けて接続します(この手順は省略可能です)。
アダプター側のGND端子は、ブレッドボード側のGNDに接続し、RXピンをArduinoのデジタル1番ピンへ接続します(写真ではTXピンを、Arduinoのデジタル0番ピンへ接続していますが、これは不要です)。
- XPortのチャネル1を「Flow (02)」「ConnectMode (D4)」を使用するように設定する必要があります。
XPortの接続/設定の詳細は、こちらの素晴らしいXPortチュートリアルをご覧ください→http://ladyada.net/make/eshield/configure.html
また、こちらのXPortユーザーガイド(Lantronixドキュメンテーションサイト上にあります)もご覧になるとよろしいかと思います→http://www.lantronix.com/pdf/XPort_UG.pdf
ソフトウェア
- まずは電源を入れ、イーサネット・ケーブルを挿した後、USBケーブルでPCに接続します。Arduinoにコードをダウンロード出来るよう、また、ハイパーターミナルやZTermのようなターミナルソフトにデバッグ出力できるよう設定します。
- お持ちのPCに、Arduino IDEをArduinoのサイトからダウンロードしてください。 Arduinoは、フリー/クロスプラットフォーム/オープンソースの電子回路試作用システムです。コンピューター愛好家による世界的なネットワークのもとで開発・管理されております。
- 「Botanicalls Twitter」のサンプルコードは、こちらからダウンロードしたのち解凍してください。→http://www.botanicalls.com/archived_kits/twitter/BotanicallsTwitter.zip
解凍して出来た「BotanicallsTwitter」というフォルダーを、Arduinoのドキュメント・ディレクトリーに配置します(Windowsの場合「MyDocuments\Arduino」、Macの場合「Documents:Arduino」)。
Arduino「ファイル」メニューから、スケッチブックを選択すると表示されるかと存じます。
なお、ソースは以下の通りになります。
--------------------------------------------------------------------------------
【BotanicallsTwitter.pde】
--------------------------------------------------------------------------------
// Botanicalls により、植物は、人に助けを求めることが出来ます。
// Rob Faludi http://www.faludi.com 公開されている様々なサンプルや、
// LadyAda の Twitter/ソフトウェアシリアルのサンプルをもとに、コードを追加
// http://www.botanicalls.com
// Botanicalls は以下の各氏によるプロジェクトです。
// Kati London, Rob Faludi, Kate Hartman and Rebecca Bray
#define VERSION "2.03" // このバージョンは XPort シールドをそのまま使用
///// BOTANICALLS 定義 ////////
#define USERNAMEPASS "username:password" // ご利用の twitter ユーザー名、パスワードを「:」区切りで指定
#define MOIST 450 // 植物が満足する水分量の最低限レベル
#define DRY 350 // 植物が乾燥に耐えうる水分量の最低限レベル
#define SOAKED 600 // 水やり後の最適な水分量のレベル
#define WATERING_CRITERIA 100 //水やりが行なわれた事を示す水分変化量の最小値
#define MOIST_SAMPLE_INTERVAL 30 // 水分量の平均値をサンプリングする間隔(秒)
#define WATERED_INTERVAL 60 // 水やりが行なわれた事を検知する間隔(秒)
#define TWITTER_INTERVAL 1// Twitter投稿の最小間隔(秒)
#define MOIST_SAMPLES 10 // 水分量の平均算出用サンプル数
int moistValues[MOIST_SAMPLES];
#define LEDPIN 13 // ステータス表示用LED
#define MOISTPIN 0 // 水分センサー入力はアナログ0番ピン
#define MOISTLED 9 // 植物が水やり要求している事を示すLED
unsigned long lastMoistTime=0; // 水分量測定が行なわれた最終時刻を保持(ミリ秒単位)
unsigned long lastWaterTime=0; // 水やりが行なわれた最終時刻を保持(ミリ秒単位)
unsigned long lastTwitterTime=0; // Twitter投稿が行なわれた最終時刻を保持(ミリ秒単位)
int lastMoistAvg=0;
int lastWaterVal=0;
///// TWITTER 定義 ///////
#include <AFSoftSerial.h>
#include <avr/io.h>
#include <string.h>
#include <avr/pgmspace.h>
// プログラムメモリーの文字列を読出す「putstring」関数の定義
#define putstring(x) ROM_putstring(PSTR(x), 0)
#define putstring_nl(x) ROM_putstring(PSTR(x), 1)
#define putstringSS(x) ROM_putstringSS(PSTR(x), 0)
#define putstringSS_nl(x) ROM_putstringSS(PSTR(x), 1)
#define IPADDR "128.121.146.100" // twitter.com
#define PORT 80 // HTTP
#define HTTPPATH "/statuses/update.xml" // フォローしてほしい方々
#define TWEETLEN 141
char linebuffer[256]; // 入出力バッファ
int lines = 0;
#define XPORT_RXPIN 3 // XPortシールド接続用ピン定義
#define XPORT_TXPIN 2
#define XPORT_RESETPIN 4
#define XPORT_DTRPIN 5
#define XPORT_CTSPIN 6
#define XPORT_RTSPIN 7
#define ERROR_NONE 0 // エラーメッセージ用コード定義
#define ERROR_TIMEDOUT 2
#define ERROR_BADRESP 3
#define ERROR_DISCONN 4
uint8_t errno;
AFSoftSerial mySerial = AFSoftSerial(XPORT_RXPIN, XPORT_TXPIN); // ソフトウェアシリアルモジュール(AFSoftSerial.cpp)の初期化
uint32_t laststatus = 0, currstatus = 0;
void setup() {
uint8_t ret;
pinMode(LEDPIN, OUTPUT);
pinMode(MOISTLED, OUTPUT);
for(int i = 0; i < MOIST_SAMPLES; i++) { // 水分量測定値を格納する配列を初期化
moistValues[i] = 0;
}
Serial.begin(9600); // ハードウェアシリアルポートのボーレートを設定
mySerial.begin(9600); // ソフトウェアシリアルポートのボーレートを設定
Serial.println(""); // デバッグ出力を開始
Serial.println("Botanicalls starting...");
// xport
pinMode(XPORT_RESETPIN, OUTPUT); // XPortシールドの入出力ポートを設定
if (XPORT_DTRPIN) {
pinMode(XPORT_DTRPIN, INPUT);
}
if (XPORT_CTSPIN) {
pinMode(XPORT_CTSPIN, OUTPUT);
}
if (XPORT_RTSPIN) {
pinMode(XPORT_RTSPIN, INPUT);
}
// uint8_t response = posttweet("Botanicalls!"); // スタートアップメッセージをTwitterへ送信
// notify(response);
}
void loop() // プログラムのメインループ
{
moistureCheck(); // 水分量がTwitter投稿を必要とするレベルであるかどうかチェック
wateringCheck(); // 水やりが行なわれたか(水やりイベントが発生したか)どうかをチェック
}
--------------------------------------------------------------------------------
【checks.pde】
--------------------------------------------------------------------------------
//土の水分量のしきい値チェック用関数
void moistureCheck() {
static int counter = 1;// 内部カウンターを初期化
int moistAverage = 0; // 土の水分量平均値を初期化
if((millis() - lastMoistTime) / 1000 > (MOIST_SAMPLE_INTERVAL / MOIST_SAMPLES)) {
for(int i = MOIST_SAMPLES - 1; i > 0; i--) {
moistValues[i] = moistValues[i-1]; //前の要素の測定値を次の測定値に設定、これを配列の終わりの要素に達するまで行なう
}
moistValues[0] = analogRead(MOISTPIN);//測定し先頭の要素に設定
lastMoistTime = millis();
int moistTotal = 0;//水分測定値配列の平均を算出するための小計用変数
for(int i = 0; i < MOIST_SAMPLES; i++) {//測定値の平均を算出 (NULLにはなりません)
moistTotal += moistValues[i];//平均を出すために合計値へ加算
}
if(counter<MOIST_SAMPLES) {
moistAverage = moistTotal/counter;
counter++; //関数が呼び出されるたびにカウンターに足し込み
}
else {
moistAverage = moistTotal/MOIST_SAMPLES;//測定値合計が算出されたので、配列の要素数で割り算する
}
//lastMeasure = millis();
Serial.print("moist: ");
Serial.println(moistAverage,DEC);
///戻り値(【訳者注】水分量平均値と、各種しきい値を比較し判定)
if ((moistAverage < DRY) && (lastMoistAvg >= DRY) && (millis() > (lastTwitterTime + TWITTER_INTERVAL)) ) {
uint8_t response = posttweet("URGENT! Water me!"); // Twitterへ投稿
notify(response);
}
else if ((moistAverage < MOIST) && (lastMoistAvg >= MOIST) && (millis() > (lastTwitterTime + TWITTER_INTERVAL)) ) {
uint8_t response = posttweet("Water me please"); // Twitterへ投稿
notify(response);
}
lastMoistAvg = moistAverage; // この関数の次回呼出時で比較するため、水分量平均値を保管
}
}
//水やりイベントをチェックする関数
void wateringCheck() {
int moistAverage = 0; // 土の水分量平均値を初期化
if((millis() - lastWaterTime) / 1000 > WATERED_INTERVAL) {
int waterVal = analogRead(MOISTPIN);//水分量を計測
lastWaterTime = millis();
Serial.print("watered: ");
Serial.println(waterVal,DEC);
if (waterVal >= lastWaterVal + WATERING_CRITERIA) { // 水やりイベントを検知した時
if (waterVal >= SOAKED && lastWaterVal < MOIST && (millis() > (lastTwitterTime + TWITTER_INTERVAL))) {
uint8_t response = posttweet("Thank you for watering me!"); // Twitterへ投稿
notify(response);
}
else if (waterVal >= SOAKED && lastWaterVal >= MOIST && (millis() > (lastTwitterTime + TWITTER_INTERVAL)) ) {
uint8_t response = posttweet("You over watered me"); // Twitterへ投稿
notify(response);
}
else if (waterVal < SOAKED && lastWaterVal < MOIST && (millis() > (lastTwitterTime + TWITTER_INTERVAL)) ) {
uint8_t response = posttweet("You didn't water me enough"); // Twitterへ投稿
notify(response);
}
}
lastWaterVal = waterVal; //この関数の次回呼出時で比較するため、水分計測値を保管
}
}
// Twitter投稿結果をデバッグポートへ出力する関数
void notify( uint8_t resp) {
if (resp)
Serial.println("twitter sent");
else {
Serial.println("twitter failed");
}
}
--------------------------------------------------------------------------------
【twitter.pde】
--------------------------------------------------------------------------------
// Twitter Code by Limor http://www.ladyada.net
/***********************ソフトウェアUART(シリアル)*************************/
uint8_t serialavail_timeout(int timeout) { // ミリ秒で引数指定
while (timeout) {
if (mySerial.available()) {
if (XPORT_CTSPIN) { // 何らかのデータを受けたら停止
digitalWrite(XPORT_CTSPIN, HIGH);
}
return 1;
}
// キューに何もデータが無い場合、何も送信し無い旨通知
if (XPORT_CTSPIN) {
digitalWrite(XPORT_CTSPIN, LOW);
}
timeout -= 1;
delay(1);
}
if (XPORT_CTSPIN) { // 何か処理をする事があれば停止
digitalWrite(XPORT_CTSPIN, HIGH);
}
return 0;
}
uint8_t readline_timeout(int timeout) {
uint8_t idx=0;
char c;
while (serialavail_timeout(timeout)) {
c = mySerial.read();
linebuffer[idx++] = c;
if ((c == '\n') || (idx == 255)) {
linebuffer[idx] = 0;
errno = ERROR_NONE;
return idx;
}
}
linebuffer[idx] = 0;
errno = ERROR_TIMEDOUT;
return idx;
}
/********************XPORT部**********************/
uint8_t XPort_reset(void) {
char d;
// 200ミリ秒のリセット・パルス(信号)
digitalWrite(XPORT_RESETPIN, LOW);
delay(200);
digitalWrite(XPORT_RESETPIN, HIGH);
// 'D' (接続断)待ち
if (serialavail_timeout(5000)) { // 3 second timeout
d = mySerial.read();
//putstring("Read: "); Serial.print(d, HEX);
if (d != 'D'){
return ERROR_BADRESP;
} else {
return 0;
}
}
return ERROR_TIMEDOUT;
}
uint8_t XPort_disconnected(void) {
if (XPORT_DTRPIN != 0) {
return digitalRead(XPORT_DTRPIN);
}
return 0;
}
uint8_t XPort_connect(char *ipaddr, long port) {
char ret;
mySerial.print('C');
mySerial.print(ipaddr);
mySerial.print('/');
mySerial.println(port);
// 'C' 待ち
if (serialavail_timeout(3000)) { // 3秒でタイムアウト
ret = mySerial.read();
Serial.print("Read: "); Serial.print(ret, HEX);
if (ret != 'C') {
return ERROR_BADRESP;
}
} else {
return ERROR_TIMEDOUT;
}
return 0;
}
void XPort_flush(int timeout) {
while (serialavail_timeout(timeout)) {
mySerial.read();
}
}
/********************TWITTER部**********************/
uint8_t posttweet(char *tweet) {
uint8_t ret;
uint8_t success = 0;
ret = XPort_reset();
//Serial.print("Ret: "); Serial.print(ret, HEX);
switch (ret) {
case ERROR_TIMEDOUT: {
putstring_nl("Timed out on reset!");
return 0;
}
case ERROR_BADRESP: {
putstring_nl("Bad response on reset!");
return 0;
}
case ERROR_NONE: {
putstring_nl("Reset OK!");
break;
}
default:
putstring_nl("Unknown error");
return 0;
}
// 接続します...
ret = XPort_connect(IPADDR, PORT);
switch (ret) {
case ERROR_TIMEDOUT: {
putstring_nl("Timed out on connect");
return 0;
}
case ERROR_BADRESP: {
putstring_nl("Failed to connect");
return 0;
}
case ERROR_NONE: {
putstring_nl("Connected..."); break;
}
default:
putstring_nl("Unknown error");
return 0;
}
base64encode(USERNAMEPASS, linebuffer);
// HTTPコマンド送信, 例えば "GET /username/"
putstringSS("POST "); putstringSS(HTTPPATH);putstringSS_nl(" HTTP/1.1");
putstring("POST "); putstring(HTTPPATH); putstring_nl(" HTTP/1.1");
// 続いて認証実行
putstringSS("Host: "); putstringSS_nl(IPADDR);
putstring("Host: "); putstring_nl(IPADDR);
putstringSS("Authorization: Basic ");
putstring("Authorization: Basic ");
mySerial.println(linebuffer);
Serial.println(linebuffer);
putstringSS("Content-Length: "); mySerial.println(7+strlen(tweet), DEC);
putstring("Content-Length: "); Serial.println(7+strlen(tweet), DEC);
putstringSS("\nstatus="); mySerial.println(tweet);
putstring("\nstatus="); Serial.println(tweet);
mySerial.print("");
while (1) {
// 1行分のデータを XPortから一度に読込み
ret = readline_timeout(3000); // 3秒でタイムアウト
// フロー制御を使用する場合、同時に行データをダンプできます
Serial.print(linebuffer);
if (strstr(linebuffer, "HTTP/1.1 200 OK") == linebuffer)
success = 1;
if (((errno == ERROR_TIMEDOUT) && XPort_disconnected()) ||
((XPORT_DTRPIN == 0) &&
(linebuffer[0] == 'D') && (linebuffer[1] == 0))) {
putstring_nl("\nDisconnected...");
return success;
}
}
}
void base64encode(char *s, char *r) {
char padstr[4];
char base64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
uint8_t i, c;
uint32_t n;
c = strlen(s) % 3;
if (c > 0) {
for (i=0; c < 3; c++) {
padstr[i++] = '=';
}
}
padstr[i]=0;
i = 0;
for (c=0; c < strlen(s); c+=3) {
// 3つの8ビット (ASCII) 文字を1つの24ビット値へ結合
n = s[c]; n <<= 8;
n += s[c+1];
if (c+2 > strlen(s)) {
n &= 0xff00;
}
n <<= 8;
n += s[c+2];
if (c+1 > strlen(s)) {
n &= 0xffff00;
}
// 24ビット値は4つの6ビット値に分割されます。
// 4つの6ビット値はbase64文字列内の索引として使われます
r[i++] = base64chars[(n >> 18) & 63];
r[i++] = base64chars[(n >> 12) & 63];
r[i++] = base64chars[(n >> 6) & 63];
r[i++] = base64chars[n & 63];
}
i -= strlen(padstr);
for (c=0; c<strlen(padstr); c++) {
r[i++] = padstr[c];
}
r[i] = 0;
Serial.println(r);
}
--------------------------------------------------------------------------------
【utility.pde】
--------------------------------------------------------------------------------
//// プログラムメモリー上の文字列を読出す関数
//// created by Limor of LadyAda.net
void ROM_putstring(const char *str, uint8_t nl) {
uint8_t i;
for (i=0; pgm_read_byte(&str[i]); i++) {
uart_putchar(pgm_read_byte(&str[i]));
}
if (nl) {
uart_putchar('\n'); uart_putchar('\r');
}
}
void ROM_putstringSS(const char *str, uint8_t nl) {
uint8_t i;
for (i=0; pgm_read_byte(&str[i]); i++) {
uart_putcharSS(pgm_read_byte(&str[i]));
}
if (nl) {
uart_putcharSS('\n');
}
}
void uart_putchar(char c) {
while (!(UCSR0A & _BV(UDRE0)));
UDR0 = c;
}
void uart_putcharSS(char c) {
mySerial.print(c);
}
--------------------------------------------------------------------------------
【AFSoftSerial.h】
--------------------------------------------------------------------------------
/*
SoftwareSerial.h - ソフトウェア・シリアル・ライブラリー
Copyright (c) 2006 David A. Mellis. All right reserved.
このライブラリーはフリーソフトです。
GNU GPLライセンスのもとで、再頒布/再利用が可能です。
GNU GPL2.1または(必要に応じ)それ以降のバージョンにおいて可能です。
このライブラリーは有効利用していただくために提供いたしますが、無保証です。
商用利用・特定目的利用にあたりましては、
いかなる事項についても免責とさせていただいております。
詳細につきましては、GNU GPLライセンスをご参照ください。
GNU GPLライセンス文言がこのライブラリーに添付されているかと思います。
そうでない場合は、以下へご連絡ください。
the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef AFSoftSerial_h
#define AFSoftSerial_h
#include <inttypes.h>
static void recv(void);
class AFSoftSerial
{
private:
long _baudRate;
void printNumber(unsigned long, uint8_t);
public:
AFSoftSerial(uint8_t, uint8_t);
void begin(long);
int read();
uint8_t available(void);
void print(char);
void print(const char[]);
void print(uint8_t);
void print(int);
void print(unsigned int);
void print(long);
void print(unsigned long);
void print(long, int);
void println(void);
void println(char);
void println(const char[]);
void println(uint8_t);
void println(int);
void println(long);
void println(unsigned long);
void println(long, int);
};
#endif
--------------------------------------------------------------------------------
【AFSoftSerial.cpp】
--------------------------------------------------------------------------------
/*
SoftwareSerial.cpp - ソフトウェア・シリアル・ライブラリー
Copyright (c) 2006 David A. Mellis. All right reserved. - ladyada 作成
このライブラリーはフリーソフトです。
GNU GPLライセンスのもとで、再頒布/再利用が可能です。
GNU GPL2.1または(必要に応じ)それ以降のバージョンにおいて可能です。
このライブラリーは有効利用していただくために提供いたしますが、無保証です。
商用利用・特定目的利用にあたりましては、
いかなる事項についても免責とさせていただいております。
詳細につきましては、GNU GPLライセンスをご参照ください。
GNU GPLライセンス文言がこのライブラリーに添付されているかと思います。
そうでない場合は、以下へご連絡ください。
the Free Software Foundation, Inc.,
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/******************************************************************************
* インクルード・ファイル
******************************************************************************/
#include <avr/interrupt.h>
#include "WConstants.h"
#include "AFSoftSerial.h"
/******************************************************************************
* 定義
******************************************************************************/
#define AFSS_MAX_RX_BUFF 8
/******************************************************************************
* 内部変数
******************************************************************************/
static uint8_t _receivePin;
static uint8_t _transmitPin;
static int _bitDelay;
static char _receive_buffer[AFSS_MAX_RX_BUFF];
static uint8_t _receive_buffer_index;
#if (F_CPU == 16000000)
void whackDelay(int delay) {
while (delay != 0) {
delay--;
}
}
#endif
/******************************************************************************
* 割り込み処理
******************************************************************************/
SIGNAL(SIG_PIN_CHANGE0) {
if ((_receivePin >=8) && (_receivePin <= 13)) {
recv();
}
}
SIGNAL(SIG_PIN_CHANGE2)
{
if (_receivePin <8) {
recv();
}
}
void recv(void) {
char i, d = 0;
if (digitalRead(_receivePin))
return; // レディーでない(通信不可)
whackDelay(_bitDelay - 8);
for (i=0; i<8; i++) {
//PORTB |= _BV(5);
whackDelay(_bitDelay*2 - 6); // digitalread に要す時間ウェイト
//PORTB &= ~_BV(5);
if (digitalRead(_receivePin))
d |= (1 << i);
}
whackDelay(_bitDelay*2);
_receive_buffer[_receive_buffer_index] = d; // データを保存
_receive_buffer_index++; // 1バイト取得
}
/******************************************************************************
* コンストラクター
******************************************************************************/
AFSoftSerial::AFSoftSerial(uint8_t receivePin, uint8_t transmitPin)
{
_receivePin = receivePin;
_transmitPin = transmitPin;
_baudRate = 0;
}
/******************************************************************************
* ユーザーAPI
******************************************************************************/
void AFSoftSerial::begin(long speed)
{
pinMode(_transmitPin, OUTPUT);
digitalWrite(_transmitPin, HIGH);
pinMode(_receivePin, INPUT);
digitalWrite(_receivePin, HIGH); // プルアップされています!
_baudRate = speed;
switch (_baudRate) {
case 57600:
_bitDelay = 17; break;
case 38400:
_bitDelay = 28; break;
case 31250:
_bitDelay = 36; break;
case 19200:
_bitDelay = 62; break;
case 9600:
_bitDelay = 128; break;
case 4800:
_bitDelay = 270; break;
default:
_bitDelay = 0;
}
if (_receivePin < 8) {
// a PIND pin, PCINT16-23
PCMSK2 |= _BV(_receivePin);
PCICR |= _BV(2);
} else if (_receivePin <= 13) {
// a PINB pin, PCINT0-5
PCICR |= _BV(0);
PCMSK0 |= _BV(_receivePin-8);
}
whackDelay(_bitDelay*2); // LOWの場合は終了と判定
}
int AFSoftSerial::read(void)
{
uint8_t d,i;
if (! _receive_buffer_index)
return -1;
d = _receive_buffer[0]; // 先頭の文字をセット
// ニフティーのキューアクションの恐れがありますが、
// これについてはサポートしません
for (i=0; i<_receive_buffer_index; i++) {
_receive_buffer[i] = _receive_buffer[i+1];
}
_receive_buffer_index--;
return d;
}
uint8_t AFSoftSerial::available(void)
{
return _receive_buffer_index;
}
void AFSoftSerial::print(uint8_t b)
{
if (_baudRate == 0)
return;
byte mask;
cli(); // 割り込み処理を停止(送信をクリアするため)
digitalWrite(_transmitPin, LOW); // スタートビット
whackDelay(_bitDelay*2);
for (mask = 0x01; mask; mask <<= 1) {
if (b & mask){ // choose bit
digitalWrite(_transmitPin,HIGH); // 1を送信
}
else{
digitalWrite(_transmitPin,LOW); // 0を送信【訳者注:原典では'send 1'とありましたが誤植と思われます】
}
whackDelay(_bitDelay*2);
}
digitalWrite(_transmitPin, HIGH);
sei(); // 割り込み処理を復活
whackDelay(_bitDelay*2);
}
void AFSoftSerial::print(const char *s)
{
while (*s)
print(*s++);
}
void AFSoftSerial::print(char c)
{
print((uint8_t) c);
}
void AFSoftSerial::print(int n)
{
print((long) n);
}
void AFSoftSerial::print(unsigned int n)
{
print((unsigned long) n);
}
void AFSoftSerial::print(long n)
{
if (n < 0) {
print('-');
n = -n;
}
printNumber(n, 10);
}
void AFSoftSerial::print(unsigned long n)
{
printNumber(n, 10);
}
void AFSoftSerial::print(long n, int base)
{
if (base == 0)
print((char) n);
else if (base == 10)
print(n);
else
printNumber(n, base);
}
void AFSoftSerial::println(void)
{
print('\r');
print('\n');
}
void AFSoftSerial::println(char c)
{
print(c);
println();
}
void AFSoftSerial::println(const char c[])
{
print(c);
println();
}
void AFSoftSerial::println(uint8_t b)
{
print(b);
println();
}
void AFSoftSerial::println(int n)
{
print(n);
println();
}
void AFSoftSerial::println(long n)
{
print(n);
println();
}
void AFSoftSerial::println(unsigned long n)
{
print(n);
println();
}
void AFSoftSerial::println(long n, int base)
{
print(n, base);
println();
}
// Privateメソッド /////////////////////////////////////////////////////////////
void AFSoftSerial::printNumber(unsigned long n, uint8_t base)
{
unsigned char buf[8 * sizeof(long)]; // 8ビット文字として扱われます
unsigned long i = 0;
if (n == 0) {
print('0');
return;
}
while (n > 0) {
buf[i++] = n % base;
n /= base;
}
for (; i > 0; i--)
print((char) (buf[i - 1] < 10 ? '0' + buf[i - 1] : 'A' + buf[i - 1] - 10));
}
- Arduinoのスケッチ「BotanicallsTwitter」を開きます。コードをアップロードする前に、最低1点、変更を加える必要があります。
・「#define USERNAMEPASS "username:password"」で始まる行ですが、お持ちのTwitterアカウントの、実際のユーザー名、パスワードを「:」で区切った文字列で書き換えてください。
・「#define USERNAMEPASS "username:password"」で始まる行ですが、お持ちのTwitterアカウントの、実際のユーザー名、パスワードを「:」で区切った文字列で書き換えてください。
また、他の諸設定の変更も必要かもしれません。Botanicallsシステムは、水分量の色々なしきい値に基づいて動作します。これらは、植物、土の組成、さらにはご自身の園芸スタイルによって変わってきます。まずはデフォルト値で起動し、システムの挙動に満足が得られるまで調整を行ないます。
こちらの行は、最低限必要な水やり量を、0~1023までの間の値で定義します。
・「#define MOIST 450」
こちらの行は、乾燥しているという緊急的状況を、0~1023までの間の値で定義します。
・「#define DRY 350」
こちらの行は、適切な水やり量を、0~1023までの間の値で定義します。
・「#define SOAKED 600」
最後に、この行は、水やりイベントが発生したことを表す、水分の上昇変化量を定義しています。
・「#define WATERING_CRITERIA 100」
【訳者注:水やり量が100上昇すると、システムが水やりを行なったと判断するロジックになっている様です。】
植物のTwitterメッセージは、タブ「checks.pde」で定義されています。 ArduinoのRAMは、テキスト文字列を保存するにはとても小さい領域であるため、Twitterメッセージは短めに抑えてください。長めのメッセージですと、スタックメモリーがオーバーフローし、システム挙動が不安定になり始めます。
このシステムには他に様々な設定がありまして、サンプリング間隔やサンプル量、Arduinoピン接続など、お好みに合わせて変更が簡単に出来ます。 内容については、各設定に対するコメント記述に記載されております。
コードをアップロード(書込み)する用意ができましたら、「Tools」メニューに表示されるシリアル・ポートとボードタイプを選択し、「File」メニューから「Upload」を選択します。
(エラーメッセージが表示されるのを回避するため、USB−シリアル変換アダプターのTX端子は、Arduinoのデジタル0番ピンから切断した方がよろしいかと思います。いずれにしろ、基本的なサンプルでは、こういった接続は行ないません。)
- コードをアップロードしたら、システムは実行できます。ハイパーターミナル、ZTermのようなターミナルソフトを使い、FTDI USB−シリアル接続をモニターしてください。ターミナルソフトは以下の設定とします。
- ボーレート=9,600
- データビット=8
- スタートビット=1
- パリティー=なし
- フロー制御=なし(ハードウェア・ハンドシェイク、Xon/Xoffは有効化しません)
システムの電源を投入すると「Botanicalls starting...」というメッセージが表示されます。次に、連続して水分量の検出を行い、ある程度間隔が空いたところで水やりが行なわれたことを検出するでしょう。
釘をお互いに触れさせる(【訳者注】測定用プローブである2本の釘をショートさせる)と、高レベルの水分含有度をシミュレーションする事ができます。釘を離すと、高レベルの水分乾燥度をシミュレーションする事ができます。この手順を使用し、システムのテストができます。
Botanicalls Twitterが送信するメッセージがある場合、HTMLレスポンスの長い文字列に続き、Twitterの接続のメッセージが表示されます。
Twitterメッセージが正常に送信されたか否かを知るには、末尾のメッセージをご確認ください。また、メッセージが正しく到達したかを確認するため、Twitterアカウントをチェックしてください。そうしたら、ちょっとハッピーダンスしたくなるでしょう・・・「Botanicalls」ができたのですから!
システム導入
テストが完了したシステムを、いよいよ植物に「つなぎ」ます。
デバッグ出力や、ArduinoのUSB接続は、Botanicalls Twitterシステムの日常運用には不要ですので、それらは外してもよろしいかと思います。
釘で作られた水分測定プローブを土の中に埋めてください。位置は、鉢の縁と植物の中間ぐらいが良いかと思います。
プローブは1インチ(3cm)程度の間隔を空けてください。
腐食を進行させ過ぎないよう、釘(プローブ)を定期的にチェックしてください。また、ハンダも(ワイヤーに)きっちりと付いているままであることを確認してください。
さて、Botanicallsを起動し、ツイートし始めたら、何を求めているか知りたいでしょう。
システムのコードは、現在の状態に基づき、5種類の状況を知らせしてくれます。
- 土の水分量が所定のしきい値を下回る場合、植物が「水やりしてほしい」状況のお知らせを送信します。
- 土の水分量が限界レベルを下回る場合、植物は「緊急に水やりしてほしい」旨ツイートします。
- 土の水分量が急に上昇した場合、水やりが行なわれたというイベントとして検出されます。土の水分量が所定のレベルまで上昇した場合は、ちゃんとした水やりが行なわれたと判断します。そしてその場合、感謝のツイートをしてくれます。
- 水やりイベントが発生したが、土の水分量が所定のレベルに達していない場合は、水やりをしてもらったが十分ではないというツイートをしてくれます。
- 水やりイベントが発生したが、植物に対し、まだ水やりの必要がなかった場合も同じように、水のやり過ぎであるという文句のツイートをしてくれます。
緑のお友達(植物)は、ちゃんと光のあたるところに置いてあげてください。また、定期的に土に肥料を与えるようにしてください。
きちんと面倒を見てあげれば、植物は、何年の間も、離れたところからツイートし続けるでしょう。
No Comment to " 農業用園芸用Arduino Botanicalls Twitter 自作 日本語版 ボタニカルコール "