「オープンソースハードウェアセミナーVol1」レポート Arduino WEBサーバー 新しいWEBサーバースケッチ [ATmarquino Arduino]
昨日は家に帰ってからもう眠くて、眠くて。今から公開です。
※ほんのちょっとの改造ですが、それでもオリジナルのコンテンツを追加するベースにはなるんじゃないかと思います。
もう少し動作検証して、最終的にはライブラリ化した方が良さそうですね。
ちょっとの間だけArduino WEBサーバーに接続できるようにしておきます。
多分下に写真が掲載されている時は
接続可能です。http://hamayan.ddo.jp:8888/
ファイルシステムがある訳ではないので、
{
{"/", IndexHtml, 0},
{"/index.html", IndexHtml, 0},
{"/index.htm", IndexHtml, 0},
{"/chobi_01.jpg", StaticContents, &chobi_01_jpg },
};
の中でuriとどんどん比較しているだけです。むしろファイルシステムから引っ張ってくるより簡単だと思います。
比較uri文字列の次はユーザー関数の先頭アドレスを入れ、その次のパラメーターはユーザー関数に引き渡したいパラメーターを登録しておきます。
例えば関数StaticContentsは引数の内容を返信する関数です。
このサーバーでは”HTTP1.0”と答えているのでブラウザ側も毎回コネクションを解除する事を想定しており、最初のコネクションの返信ではhtml文章の中に含まれるイメージへのリンクを認識し、そこでもう一回コネクションを張ってイメージコンテンツの取得を行っています。
”HTTP1.1”の”KEEP ALIVE”に比べれば効率が落ちますが、だからと言って元々のArduinoのEthernetライブラリ自体が同時に複数コネクションを張れるようにはなっていない(※KEEP ALIVE中は確か30秒程の時間待ち処理が入るので、その間は別の所からの要求に答えられなくなる。)ので、”HTTP1.0”で動作する方が現実的でしょう。
/****************************************************************/
/* Web Serverで色々いじってみる計画 */
/* Copyright (c) せくすぃ部長 since 2009/06/13 */
/****************************************************************/
/****************************************************************/
/* file include */
/****************************************************************/
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <Ethernet.h>
#include <avr/pgmspace.h>
#include "chobi_01.h"
/****************************************************************/
/* 色々定義 */
/****************************************************************/
#define LINE_STRING_SIZE 128
enum HTTP_METHOD_TYPE
{
METHOD_TYPE_IS_GET = 1,
METHOD_TYPE_IS_HEAD,
METHOD_TYPE_IS_PUT,
METHOD_TYPE_IS_POST,
METHOD_TYPE_IS_DELETE,
METHOD_TYPE_IS_LINK,
METHOD_TYPE_IS_UNLINK,
};
/****************************************************************/
/* 大域変数領域 */
/****************************************************************/
char *line;
char meth[10],uri[30],ver[20];
long access_count;
const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const byte ip[] = { 192, 168, 100, 177 };
const byte gateway[] = { 192, 168, 100, 1 };
/****************************************************************/
/* サーバーレスポンスの固定メッセージ */
/****************************************************************/
const char PROGMEM http_ver_and_server_type[] =
"HTTP/1.0 200 OK\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n";
const char PROGMEM mime_type_txt[] =
"Content-Type: text/html; charset=UTF-8\r\n\r\n";
const char PROGMEM mime_type_img_jpeg[] =
"Content-Type: image/JPEG\r\n\r\n";
const char PROGMEM mime_type_img_gif[] =
"Content-Type: image/gif\r\n\r\n";
const char PROGMEM mime_type_img_png[] =
"Content-Type: image/png\r\n\r\n";
const char PROGMEM mime_type_img_bmp[] =
"Content-Type: image/bmp\r\n\r\n";
/****************************************************************/
/* HTTPフレーム */
/****************************************************************/
const char PROGMEM http_head[] =
"<html lang=\"ja\"><head>\r\n" \
"<meta HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=UTF-8\">\r\n" \
"<title>Arduino WEB Server 0.2</title></head>\r\n" \
"<body bgcolor=\"#ccffcc\">\r\n";
const char PROGMEM thanks[] =
"<h3>designed by hamayan</h3>\r\n" \
"<img src=\"./chobi_01.jpg\" align=\"center\"><br>\r\n" \
"アクセス有難うございます。このページはArduino+Ether Shieldで表示しています。<br>\r\n" \
"現在はリクエストのパーサーを作成しています。<br>\r\n";
const char PROGMEM links[] =
"hamayan blog <a href=\"http://hamayan.blog.so-net.ne.jp/\">http://hamayan.blog.so-net.ne.jp/</a><br>\r\n" \
"chip 1 stop <a href=\"http://www.chip1stop.com/\">http://www.chip1stop.com/</a><br>\r\n" \
"オープンソースハードウェアセミナーのページ <a href=\"http://www.chip1stop.com/knowledge/Arduino/\">http://www.chip1stop.com/knowledge/Arduino/</a><br>\r\n" \
"Make:Japan <a href=\"http://jp.makezine.com/blog/\">http://jp.makezine.com/blog/</a><br><br>\r\n";
const char PROGMEM banner_01[] =
"<p><a href=\"http://www.chip1stop.com/knowledge/Arduino/\">" \
"<img src=\"http://www.chip1stop.com/img/link_Arduino.gif\" width=\"468\" height=\"60\" alt=\"Arduinoモニタープログラム参加中\" /></a><br>" \
"<a href=\"http://www.chip1stop.com/\" title=\"電子部品・半導体の通販サイト - チップワンストップ\">電子部品・半導体の通販サイト - チップワンストップ</a></p><br><br>\r\n";
const char PROGMEM http_foot[] =
"</body></html>\r\n\r\n";
const char PROGMEM HtmlRes400[] =
{
"HTTP/1.0 400 Bad Request\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head><title>400 Bad Request</title></head>\r\n" \
"<h1>Bad Request</h1>\r\n" \
"</body></html>\r\n\r\n"
};
const char PROGMEM HtmlRes404[] =
{
"HTTP/1.0 404 Not Found\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head><title>404 Not Found</title></head>\r\n" \
"<h1>Not Found</h1>\r\n" \
"</body></html>\r\n\r\n"
};
const char PROGMEM HtmlRes405[] =
{
"HTTP/1.0 405 Method Not Allowed\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head><title>405 Method Not Allowed</title></head>\r\n" \
"<h1>Method Not Allowed</h1>\r\n" \
"</body></html>\r\n\r\n"
};
const char PROGMEM hello[] =
"<html><body><h1>hello world</h1><br><a href=\"./index.html\">戻る</a></body></html>";
Server server( 8888 );
/****************************************************************/
/* setup */
/****************************************************************/
void setup()
{
Ethernet.begin( (uint8_t *)mac, (uint8_t *)ip, (uint8_t *)gateway );
server.begin();
Serial.begin( 38400 );
line = (char *)malloc( LINE_STRING_SIZE );
}
/****************************************************************/
/* loop */
/****************************************************************/
void loop()
{
Client client = server.available();
if( client )
{
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
char *argv[ 10 ];
int div_num = split( dst, argv, sizeof(argv) / sizeof(argv[0]) ); /*文字列分割*/
strncpy( meth, argv[ 0 ], sizeof(meth) );
strncpy( uri, argv[ 1 ], sizeof(uri) );
strncpy( ver, argv[ 2 ], sizeof(ver) );
break;
}
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
if( dst != NULL && *dst == '\0' ) /*改行のみの行を検出*/
{
server_response( client, meth, uri );
break;
}
}
// delay( 1 );
client.stop();
}
}
/****************************************************************/
/* 一行取得 */
/* HTTPプロトコルでは行末はCR、LFであると言う前提がある */
/****************************************************************/
static char *HTTPGets( Client client, char *dst, int size )
{
int loop;
char c,*ptr,*limit;
ptr = dst;
limit = dst + size;
for( loop = 10; loop > 0; loop-- )
{
if( ptr == limit ) return (char *)0; /*上限の確認*/
if( client.available() )
{
loop = 10; /*タイムアウト延長*/
c = client.read(); /*一文字取得*/
if( c == '\r' ) /*CRはNULLに変換*/
{
*ptr++ = '\0';
}
else if( c == '\n' ) /*LFは終端文字*/
{
*ptr = '\0';
return dst;
}
else /*文字の取得*/
{
*ptr++ = c;
}
}
else
{
delay( 100 ); /*残りのデータが遅れて来る可能性があるので、ここで待ちを入れる*/
}
}
return (char *)0;
}
/****************************************************************/
/* 文字列分割ユーティリティ */
/****************************************************************/
static int split( char *str , char *argv[], int sz )
{
int argc = 0;
while( *str != '\0' && argc < sz )
{
if( isgraph( *str ) != 0 )
{
argv[ argc++ ] = str;
while( *str != '\0' && isgraph( *str ) != 0 ) str++;
}
else *str++ = '\0';
}
return argc;
}
/****************************************************************/
/* サーバー側の応答処理 */
/****************************************************************/
void server_response( Client client, char *method, char *uri )
{
int meth;
static const struct COMMAND_LIST
{
void ( PROGMEM *fn )( Client client, char *uri );
char PROGMEM *meth_name;
} list[] =
{
{ Method_GET, "GET" },
{ Method_HEAD, "HEAD" },
{ Method_PUT, "PUT" },
{ Method_POST, "POST" },
{ Method_DELETE, "DELETE" },
{ Method_LINK, "LINK" },
{ Method_UNLINK, "UNLINK" },
};
for( meth = 0; meth < sizeof( list ) / sizeof( list[0] ); meth++ )
{
/*メソッド文字列の比較を行い、該当するメソッドが見つかれば実行する*/
if( strncmp( method, list[ meth ].meth_name, strlen( list[ meth ].meth_name ) ) == 0 )
{
list[ meth ].fn( client, uri );
break;
}
}
if( meth == sizeof( list ) / sizeof( list[0] ) )
{
Bad_Request( client );
}
}
/****************************************************************/
/* METHOD GET */
/****************************************************************/
static void Method_GET( Client client, char *uri )
{
int req;
struct URL_LIST
{
char PROGMEM *name;
void ( PROGMEM *fn )( Client client, char *uri, const FILE_PROPERTIES *pro );
const FILE_PROPERTIES *property;
} PROGMEM list[] =
{
{"/", IndexHtml, 0},
{"/index.html", IndexHtml, 0},
{"/index.htm", IndexHtml, 0},
{"/chobi_01.jpg", StaticContents, &chobi_01_jpg },
};
for( req = 0; req < sizeof( list ) / sizeof( list[0] ); req++ )
{
/*uriの比較を行い、該当するuriが見つかれば実行する*/
if( strcmp( uri, list[ req ].name ) == 0 )
{
list[ req ].fn( client, uri, list[ req ].property );
break;
}
}
if( req == sizeof( list ) / sizeof( list[0] ) )
{
/*要求されたuriが見付からなかった時*/
client.write_P( HtmlRes404, sizeof( HtmlRes404 ) - 1 );
return;
}
}
/****************************************************************/
/* METHOD HEAD */
/****************************************************************/
static void Method_HEAD( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD PUT */
/****************************************************************/
static void Method_PUT( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD POST */
/****************************************************************/
static void Method_POST( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD DELETE */
/****************************************************************/
static void Method_DELETE( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD LINK */
/****************************************************************/
static void Method_LINK( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD UNLINK */
/****************************************************************/
static void Method_UNLINK( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* BAD REQUEST */
/****************************************************************/
static void Bad_Request( Client client )
{
client.write_P( HtmlRes400, sizeof( HtmlRes400 ) - 1 );
}
/****************************************************************/
/* index.htmlの返信を行う */
/****************************************************************/
static void IndexHtml( Client client, char *uri, const FILE_PROPERTIES *pro )
{
//レスポンスヘッダーの返信
client.write_P( http_ver_and_server_type, sizeof( http_ver_and_server_type ) - 1 );
client.write_P( mime_type_txt, sizeof( mime_type_txt ) - 1 );
//httpヘッダーの返信
client.write_P( http_head, sizeof( http_head ) - 1 );
//サンキューメッセージ
client.write_P( thanks, sizeof( thanks ) - 1 );
//リンク
client.write_P( links, sizeof( links ) - 1 );
//Arduinoバージョン
sprintf( line, "Arduino ver=%d <br>\r\n", ARDUINO );
client.write( line );
//アクセスカウント
sprintf( line, "COUNT=%d <br>\r\n", ++access_count );
client.write( line );
//バナー
client.write_P( banner_01, sizeof( banner_01 ) - 1 );
//Footer
client.write_P( http_foot, sizeof( http_foot ) - 1 );
}
/****************************************************************/
/* 静的コンテンツ(FILE)の返信を行う */
/****************************************************************/
static void StaticContents( Client client, char *uri, const FILE_PROPERTIES *pro )
{
//レスポンスヘッダーの返信
switch( pro->attr )
{
case AddType_TXT :
//レスポンスヘッダーの返信
client.write_P( http_ver_and_server_type, sizeof( http_ver_and_server_type ) - 1 );
client.write_P( mime_type_txt, sizeof( mime_type_txt ) - 1 );
break;
case AddType_JPEG :
client.write_P( http_ver_and_server_type, sizeof( http_ver_and_server_type ) - 1 );
client.write_P( mime_type_img_jpeg, sizeof( mime_type_img_jpeg ) - 1 );
break;
case AddType_GIF :
client.write_P( http_ver_and_server_type, sizeof( http_ver_and_server_type ) - 1 );
client.write_P( mime_type_img_gif, sizeof( mime_type_img_gif ) - 1 );
break;
case AddType_PNG :
client.write_P( http_ver_and_server_type, sizeof( http_ver_and_server_type ) - 1 );
client.write_P( mime_type_img_png, sizeof( mime_type_img_png ) - 1 );
break;
case AddType_BMP :
client.write_P( http_ver_and_server_type, sizeof( http_ver_and_server_type ) - 1 );
client.write_P( mime_type_img_bmp, sizeof( mime_type_img_bmp ) - 1 );
break;
case AddType_APPLI :
case AddType_AUDIO :
case AddType_VIDEO :
default :
return;
}
//静的コンテンツの返信
client.write_P( pro->file, pro->size );
}
「オープンソースハードウェアセミナーVol1」レポート Arduino WEBサーバー 静的コンテンツ用バイナリー to Cソース変換プログラムのソース [ATmarquino Arduino]
下手なプログラムで申し訳ない。でも、これ見ただけでは何の事か判らないですね、、、。
例えばこんな写真を

通すと一番下のコードを出力します。ちなみに写真のデータサイズは12Kbyte程度でした。
作成したプログラムはコマンドラインで動かします。GUIのプログラムは苦手で。
※もうこんな時間なので、この静的コンテンツを使ったスケッチの方は次回公開します。
/* ------------------------------------------------------------------------ */
/* バイナリファイルをCソースファイルに変換するプログラム */
/* */
/* Copyright (C) 2004- by hamayan */
/* hamayan.contact あと 爺mail.com */
/* ------------------------------------------------------------------------ */
#include <stdio.h>
#include <string.h>
#include <time.h>
/* ------------------------------------------------------------------------ */
/* メインね! */
/* ------------------------------------------------------------------------ */
int main( int argc, char *argv[] )
{
char str[ 32 ],header_file[ 32 ];
int i,c;
long length;
FILE *in,*out;
time_t timer;
struct tm *tblock;
if( argc != 3 )
{
fprintf( stderr, "Argument Missmatch.\r\n" );
return EOF;
}
if( (in = fopen( argv[1], "rb" )) == NULL )
{
fprintf( stderr, "Image File Open Error.\r\n" );
return EOF;
}
if( (out = fopen( argv[2], "wb" )) == NULL )
{
fprintf( stderr, "Output File Open Error.\r\n" );
fclose( in );
return EOF;
}
/*ヘッダー部の出力*/
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
fprintf( out, "/* Binary data file convert to C source file program. */\r\n" );
fprintf( out, "/* designed by hamayan */\r\n" );
fprintf( out, "/* Copyright(C) hamayan */\r\n" );
fprintf( out, "/* since 2004 - */\r\n" );
fprintf( out, "/* hamayan.contact あと 爺mail.com */\r\n" );
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
/*ヘッダーファイルの取り込み*/
fprintf( out, "#include <avr/pgmspace.h>\r\n" );
fprintf( out, "#include \"mime.h\"\r\n\r\n" );
/*変換後のデータ表現*/
fprintf( out, "static const unsigned char PROGMEM file[] =\r\n" );
fprintf( out, "{\r\n" );
do
{
fprintf( out, "\t" );
for( i = 0; i < 16; i++ )
{
if( (c = fgetc( in )) == EOF ) break;
fprintf( out, "0x%02X,", c );
}
fprintf( out, "\r\n" );
} while( c != EOF );
fprintf( out, "};\r\n\r\n" );
/*プロパティシートを生成する。*/
strcpy( str, argv[1] );
*( strchr( str, '.' ) ) = '_';
fprintf( out, "const FILE_PROPERTIES %s =\r\n", str );
fprintf( out, "{\r\n" );
/*ファイル名*/
fprintf( out, "\t\"%s\",\t/*ファイル名*/\r\n",
argv[1] );
/*タイムスタンプ*/
timer = time(NULL);
tblock = localtime(&timer);
fprintf( out, "\t\"%4u/%2u/%2u %2u:%2u:%2u\",\t/*タイムスタンプ*/\r\n",
tblock->tm_year + 1900,
tblock->tm_mon + 1,
tblock->tm_mday,
tblock->tm_hour,
tblock->tm_min,
tblock->tm_sec );
/*Mime type*/
fprintf( out, "\t%s,\t/*Mime type*/\r\n",
(strcmp( "htm", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "HTM", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "html", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "HTML", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "txt", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "TXT", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "log", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "LOG", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_TXT" :
(strcmp( "jpg", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_JPEG" :
(strcmp( "JPG", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_JPEG" :
(strcmp( "jpeg", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_JPEG" :
(strcmp( "JPEG", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_JPEG" :
(strcmp( "gif", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_GIF" :
(strcmp( "GIF", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_GIF" :
(strcmp( "png", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_PNG" :
(strcmp( "PNG", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_PNG" :
(strcmp( "bmp", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_BMP" :
(strcmp( "BMP", strchr( argv[1], '.' ) + 1) == 0) ? "AddType_BMP" : ""
);
/*ファイルの開始アドレス*/
fprintf( out, "\tfile,\t/*ファイルの開始アドレス*/\r\n" );
/*著者*/
fprintf( out, "\t\"\",\t/*著者*/\r\n" );
/*認証用*/
fprintf( out, "\t(void *)0,\t/*認証用*/\r\n" );
/*ファイルののサイズ*/
fseek( in, 0L, SEEK_END );
length = ftell( in );
rewind( in );
fprintf( out, "\t%ldU\t/*ファイルのサイズ*/\r\n", length );
fprintf( out, "};\r\n\r\n" );
*(strchr( argv[1], '.' )) = '\0';
/*フッター部の出力*/
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
fprintf( out, "/* designed by hamayan */\r\n" );
fprintf( out, "/* Copyright(C) hamayan */\r\n" );
fprintf( out, "/* since 2004 - */\r\n" );
fprintf( out, "/* hamayan.contact あと 爺mail.com */\r\n" );
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
fclose( in );
fclose( out );
/*ヘッダーファイルの出力*/
strcpy( header_file, argv[2] );
strcpy( strchr( header_file, '.' ), ".h" );
if( (out = fopen( header_file, "wb" )) == NULL )
{
fprintf( stderr, "Output File Open Error.\r\n" );
fclose( in );
return EOF;
}
/*ヘッダー部の出力*/
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
fprintf( out, "/* Binary data file convert to header file program. */\r\n" );
fprintf( out, "/* designed by hamayan */\r\n" );
fprintf( out, "/* Copyright(C) hamayan */\r\n" );
fprintf( out, "/* since 2004 - */\r\n" );
fprintf( out, "/* hamayan.contact あと 爺mail.com */\r\n" );
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
/*ヘッダーファイルの取り込み*/
fprintf( out, "#include <avr/pgmspace.h>\r\n" );
fprintf( out, "#include \"mime.h\"\r\n\r\n" );
/**/
fprintf( out, "extern const FILE_PROPERTIES %s;\r\n\r\n", str );
/*フッター部の出力*/
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
fprintf( out, "/* designed by hamayan */\r\n" );
fprintf( out, "/* Copyright(C) hamayan */\r\n" );
fprintf( out, "/* since 2004 - */\r\n" );
fprintf( out, "/* hamayan.contact あと 爺mail.com */\r\n" );
fprintf( out, "/* ------------------------------------------------------------------------ */\r\n" );
fclose( out );
return 0;
}
/* ------------------------------------------------------------------------ */
/* Copyright (C) 2004- by hamayan */
/* hamayan.contact あと 爺mail.com */
/* ------------------------------------------------------------------------ */
ちょびちゃんのCソース化
/* ------------------------------------------------------------------------ */
/* Binary data file convert to C source file program. */
/* designed by hamayan */
/* Copyright(C) hamayan */
/* since 2004 - */
/* hamayan.contact あと 爺mail.com */
/* ------------------------------------------------------------------------ */
#include <avr/pgmspace.h>
#include "mime.h"
static const unsigned char PROGMEM file[] =
{
0xFF,0xD8,0xFF,0xE0,0x00,0x10,0x4A,0x46,0x49,0x46,0x00,0x01,0x01,0x01,0x00,0x48,
長いので途中省略
0xE0,0xFA,0xFA,0x83,0x45,0x4D,0xE3,0xD9,0x64,0xDF,0xB7,0x79,0xC1,0x19,0xA2,0xAD,
0x23,0x29,0x3B,0x1F,0xFF,0xD9,
};
const FILE_PROPERTIES chobi_01_jpg =
{
"chobi_01.jpg", /*ファイル名*/
"2009/ 6/19 2:10:59", /*タイムスタンプ*/
AddType_JPEG, /*Mime type*/
file, /*ファイルの開始アドレス*/
"", /*著者*/
(void *)0, /*認証用*/
12358U /*ファイルのサイズ*/
};
/* ------------------------------------------------------------------------ */
/* designed by hamayan */
/* Copyright(C) hamayan */
/* since 2004 - */
/* hamayan.contact あと 爺mail.com */
/* ------------------------------------------------------------------------ */
ちょびちゃんのヘッダーファイル
/* ------------------------------------------------------------------------ */ /* Binary data file convert to header file program. */ /* designed by hamayan */ /* Copyright(C) hamayan */ /* since 2004 - */ /* hamayan.contact あと 爺mail.com */ /* ------------------------------------------------------------------------ */ #include <avr/pgmspace.h> #include "mime.h" extern const FILE_PROPERTIES chobi_01_jpg; /* ------------------------------------------------------------------------ */ /* designed by hamayan */ /* Copyright(C) hamayan */ /* since 2004 - */ /* hamayan.contact あと 爺mail.com */ /* ------------------------------------------------------------------------ */

Making Things Talk -Arduinoで作る「会話」するモノたち
- 作者: Tom Igoe
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/11/17
- メディア: 大型本
「オープンソースハードウェアセミナーVol1」レポート Arduino WEBサーバー リクエストの解析をちょっと始める [ATmarquino Arduino]
※なんかイマイチなんで、近日中に書き直すつもり。
表示している内容は変わらないのだけれど、処理が随分機能アップしています、、、嘘!する予定。
GET Methodの解析、uriの解析、Mimeタイプの解析はもう疲れて適当に終了。

表示している内容は変わらないのだけれど、処理が随分機能アップしています、、、嘘!する予定。GET Methodの解析、uriの解析、Mimeタイプの解析はもう疲れて適当に終了。
/****************************************************************/
/* Web Serverで色々いじってみる計画 */
/* Copyright (c) せくすぃ部長 since 2009/06/13 */
/****************************************************************/
/****************************************************************/
/* file include */
/****************************************************************/
#include <Ethernet.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <avr/pgmspace.h>
/****************************************************************/
/* 色々定義 */
/****************************************************************/
#define LINE_STRING_SIZE 128
enum HTTP_METHOD_TYPE
{
METHOD_TYPE_IS_GET = 1,
METHOD_TYPE_IS_HEAD,
METHOD_TYPE_IS_PUT,
METHOD_TYPE_IS_POST,
METHOD_TYPE_IS_DELETE,
METHOD_TYPE_IS_LINK,
METHOD_TYPE_IS_UNLINK,
};
enum HTTP_ADD_TYPE
{
AddHandler, /*ハンドラ*/
AddType_TXT, /*テキスト*/
AddType_IMAGE, /*イメージ*/
AddType_APPLI, /*アプリケーション*/
AddType_AUDIO, /*audio*/
AddType_VIDEO, /*video*/
};
/****************************************************************/
/* 大域変数領域 */
/****************************************************************/
char *line;
char meth[10],uri[30],ver[20];
long access_count;
const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const byte ip[] = { 192, 168, 1, 177 };
const byte gateway[] = { 192, 168, 1, 1 };
/****************************************************************/
/* サーバーレスポンスの固定メッセージ */
/****************************************************************/
const char PROGMEM http_head[] =
"HTTP/1.0 200 OK\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head>\r\n" \
"<meta HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=UTF-8\">\r\n" \
"<title>Arduino WEB Server 0.1</title></head>\r\n" \
"<body bgcolor=\"#ccffcc\">\r\n";
const char PROGMEM thanks[] =
"<h3>designed by hamayan</h3>\r\n" \
"アクセス有難うございます。このページはArduino+Ether Shieldで表示しています。<br>\r\n" \
"現在はリクエストのパーサーを作成しています。<br>\r\n";
const char PROGMEM links[] =
"hamayan blog <a href=\"http://hamayan.blog.so-net.ne.jp/\">http://hamayan.blog.so-net.ne.jp/</a><br>\r\n" \
"chip 1 stop <a href=\"http://www.chip1stop.com/\">http://www.chip1stop.com/</a><br>\r\n" \
"オープンソースハードウェアセミナーのページ <a href=\"http://www.chip1stop.com/knowledge/Arduino/\">http://www.chip1stop.com/knowledge/Arduino/</a><br>\r\n" \
"Make:Japan <a href=\"http://jp.makezine.com/blog/\">http://jp.makezine.com/blog/</a><br><br>\r\n";
const char PROGMEM banner_01[] =
"<p><a href=\"http://www.chip1stop.com/knowledge/Arduino/\">" \
"<img src=\"http://www.chip1stop.com/img/link_Arduino.gif\" width=\"468\" height=\"60\" alt=\"Arduinoモニタープログラム参加中\" /></a><br>" \
"<a href=\"http://www.chip1stop.com/\" title=\"電子部品・半導体の通販サイト - チップワンストップ\">電子部品・半導体の通販サイト - チップワンストップ</a></p><br><br>\r\n";
const char PROGMEM http_foot[] =
"</body></html>\r\n\r\n";
const char PROGMEM img_src[] =
"<p><img src=\"./image2.jpg\" width=\"200\" height=\"140\" align=\"center\"></p><br><br>\r\n";
const char PROGMEM jpeg_head[] =
"HTTP/1.0 200 OK\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: image/JPEG\r\n\r\n";
const char PROGMEM HtmlRes400[] =
{
"HTTP/1.0 400 Bad Request\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head><title>400 Bad Request</title></head>\r\n" \
"<h1>Bad Request</h1>\r\n" \
"</body></html>\r\n\r\n"
};
const char PROGMEM HtmlRes404[] =
{
"HTTP/1.0 404 Not Found\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head><title>404 Not Found</title></head>\r\n" \
"<h1>Not Found</h1>\r\n" \
"</body></html>\r\n\r\n"
};
const char PROGMEM HtmlRes405[] =
{
"HTTP/1.0 405 Method Not Allowed\r\n" \
"Server: Arduino with Ether Shield/ ver.0.2\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head><title>405 Method Not Allowed</title></head>\r\n" \
"<h1>Method Not Allowed</h1>\r\n" \
"</body></html>\r\n\r\n"
};
Server server( 8888 );
/****************************************************************/
/* setup */
/****************************************************************/
void setup()
{
Ethernet.begin( (uint8_t *)mac, (uint8_t *)ip, (uint8_t *)gateway );
server.begin();
Serial.begin( 38400 );
line = (char *)malloc( LINE_STRING_SIZE );
}
/****************************************************************/
/* loop */
/****************************************************************/
void loop()
{
Client client = server.available();
if( client )
{
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
char *argv[ 10 ];
int div_num = split( dst, argv, sizeof(argv) / sizeof(argv[0]) ); /*文字列分割*/
strncpy( meth, argv[ 0 ], sizeof(meth) );
strncpy( uri, argv[ 1 ], sizeof(uri) );
strncpy( ver, argv[ 2 ], sizeof(ver) );
break;
}
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
if( dst != NULL && *dst == '\0' ) /*改行のみの行を検出*/
{
server_response( client, meth, uri );
break;
}
}
// delay( 1 );
client.stop();
}
}
/****************************************************************/
/* サーバー側の応答処理 */
/****************************************************************/
void server_response( Client client, char *method, char *uri )
{
int meth;
static const struct COMMAND_LIST
{
void ( PROGMEM *fn )( Client client, char *uri );
char PROGMEM *meth_name;
} list[] =
{
{ Method_GET, "GET" },
{ Method_HEAD, "HEAD" },
{ Method_PUT, "PUT" },
{ Method_POST, "POST" },
{ Method_DELETE, "DELETE" },
{ Method_LINK, "LINK" },
{ Method_UNLINK, "UNLINK" },
};
for( meth = 0; meth < sizeof( list ) / sizeof( list[0] ); meth++ )
{
/*メソッド文字列の比較を行い、該当するメソッドが見つかれば実行する*/
if( strncmp( method, list[ meth ].meth_name, strlen( list[ meth ].meth_name ) ) == 0 )
{
list[ meth ].fn( client, uri );
break;
}
}
if( meth == sizeof( list ) / sizeof( list[0] ) )
{
Bad_Request( client );
}
}
/****************************************************************/
/* METHOD GET */
/****************************************************************/
static void Method_GET( Client client, char *uri )
{
int req;
static const struct URL_LIST
{
char PROGMEM *name;
int PROGMEM type;
void PROGMEM *property;
} list[] =
{
{"/", AddType_TXT, 0},
{"/index.html", AddType_TXT, 0},
{"/index.htm", AddType_TXT, 0},
};
for( req = 0; req < sizeof( list ) / sizeof( list[0] ); req++ )
{
/*uriの比較を行い、該当するuriが見つかれば実行する*/
if( strcmp( uri, list[ req ].name ) == 0 )
{
break;
}
}
if( req == sizeof( list ) / sizeof( list[0] ) )
{
/*要求されたuriが見付からなかった時*/
client.write_P( HtmlRes404, sizeof( HtmlRes404 ) - 1 );
return;
}
switch( list[ req ].type )
{
case AddType_TXT :
Response_Handler( client, uri );
break;
case AddHandler :
case AddType_IMAGE :
case AddType_APPLI :
case AddType_AUDIO :
case AddType_VIDEO :
default :
client.write_P( HtmlRes404, sizeof( HtmlRes404 ) - 1 );
break;
}
}
/****************************************************************/
/* 応答メッセージの出力 */
/****************************************************************/
static void Response_Handler( Client client, char *uri )
{
if( 1 ) /*今のところ拡張子まで判定していないので、全部同じ動きをさせる*/
{
//httpヘッダーの返信
client.write_P( http_head, sizeof( http_head ) - 1 );
//サンキューメッセージ
client.write_P( thanks, sizeof( thanks ) - 1 );
//リンク
client.write_P( (const uint8_t *)links, sizeof( links ) - 1 );
//Arduinoバージョン
sprintf( line, "Arduino ver=%d <br>\r\n", ARDUINO );
client.write( line );
//アクセスカウント
sprintf( line, "COUNT=%d <br>\r\n", ++access_count );
client.write( line );
//バナー
client.write_P( (const uint8_t *)banner_01, sizeof( banner_01 ) - 1 );
//Footer
client.write_P( (const uint8_t *)http_foot, sizeof( http_foot ) - 1 );
}
}
/****************************************************************/
/* METHOD HEAD */
/****************************************************************/
static void Method_HEAD( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD PUT */
/****************************************************************/
static void Method_PUT( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD POST */
/****************************************************************/
static void Method_POST( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD DELETE */
/****************************************************************/
static void Method_DELETE( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD LINK */
/****************************************************************/
static void Method_LINK( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* METHOD UNLINK */
/****************************************************************/
static void Method_UNLINK( Client client, char *uri )
{
/*現在サポートしていないので、許可されていないメソッドとして返信*/
client.write_P( HtmlRes405, sizeof( HtmlRes405 ) - 1 );
}
/****************************************************************/
/* BAD REQUEST */
/****************************************************************/
static void Bad_Request( Client client )
{
client.write_P( HtmlRes400, sizeof( HtmlRes400 ) - 1 );
}
/****************************************************************/
/* 一行取得 */
/* HTTPプロトコルでは行末はCR、LFであると言う前提がある */
/****************************************************************/
static char *HTTPGets( Client client, char *dst, int size )
{
int loop;
char c,*ptr,*limit;
ptr = dst;
limit = dst + size;
for( loop = 10; loop > 0; loop-- )
{
if( ptr == limit ) return (char *)0; /*上限の確認*/
if( client.available() )
{
loop = 10; /*タイムアウト延長*/
c = client.read(); /*一文字取得*/
if( c == '\r' ) /*CRはNULLに変換*/
{
*ptr++ = '\0';
}
else if( c == '\n' ) /*LFは終端文字*/
{
*ptr = '\0';
return dst;
}
else /*文字の取得*/
{
*ptr++ = c;
}
}
else
{
delay( 100 ); /*残りのデータが遅れて来る可能性があるので、ここで待ちを入れる*/
}
}
return (char *)0;
}
/****************************************************************/
/* 文字列分割ユーティリティ */
/****************************************************************/
static int split( char *str , char *argv[], int sz )
{
int argc = 0;
while( *str != '\0' && argc < sz )
{
if( isgraph( *str ) != 0 )
{
argv[ argc++ ] = str;
while( *str != '\0' && isgraph( *str ) != 0 ) str++;
}
else *str++ = '\0';
}
return argc;
}
/****************************************************************/
/* end of file */
/* Copyright (c) せくすぃ部長 since 2009/06/13 */
/****************************************************************/

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識
- 作者: 戸根 勤
- 出版社/メーカー: 日経BP社
- 発売日: 2007/04/12
- メディア: 単行本(ソフトカバー)
「オープンソースハードウェアセミナーVol1」レポート Arduino PROGMEMの効き具合を確かめる [ATmarquino Arduino]
※chip 1 stopからいらした方!不明点等はコメント欄にお気軽にどうぞ。判る範囲で対応します。
普通、マイコンの開発では、現在のメモリの使用状況を調べ、もし使用したいマイコンの搭載メモリ量を超えてしまった場合、何らかの対策を行わなければならないのです。
何故かと言えば、PC上のプログラミングならば、最近のPCを使用している限りメインメモリなんてGbyte単位で増設可能で、滅多にこれを使い切る事は無いでしょう。いざとなったら遅いですが仮想メモリなんて手もある訳だし。
ですがマイコンのメモリ搭載量は、特にArduinoで使用しているAVRの様なOne chipマイコンではとても小さなメモリしか搭載されておらず、また増設も不可能か、もしくは容易な事ではありません。
なのでマイコンの開発環境にはコンパイル後(実際にはリンク後)のメモリの使用状況を報告してくれる機能が基本的に備わっています。勿論Arduinoにも有る筈なのですけれど。
※GCCはあまり使わないので良く知らなかったのですね。
ArduinoのスケッチのコンパイルにはGCCツールチェインが動いています。何故かこれをJavaが動いていると紹介しているところも有ったりしますが、JavaはArduino IDEその物を動かしている方でしょう。スケッチの言語も「C言語っぽい簡単ななんとか言語」とか言う怪しい言語ではなく、C++をベースに若干の構文整形ツールがあると言ったところでしょう。 ※なんでいつも「簡単な」って言葉で誤魔化すのがよく判りませんが、言語面では結局のところ使いこなす為にはCやC++を勉強するのが一番の近道だと思います。
「PS3とLinux、電子工作も」さんの最近の話題「Arduinoメールチェッカー(その3)」
http://todotani.cocolog-nifty.com/blog/2009/06/arduino-36b3.html
で、スタックの話題に触れており、その中でメモリの使用状況を調べるツールに付いて書かれていましたので、今回あらためてPROGMEMの効用を確認してみました。
※PROGMEMの効用自体は知っていましたが、具体的な数字を出す事が今まで出来なかった。
さてPROGMEMの効用を調べる為には適当なお題が必要ですので、例のWEBサーバーを引き合いに出してみます。以下のスケッチです。
PROGMEMを使用すると、使い方によってはAVRに搭載されているメモリの内、RAMの使用量を減らす事が可能です。詳細は下の方で述べます。
13行目にコンパイル制御を行う為の定義(#define __PROGMEM_USE__)を行っています。この一文が有るとPROGMEMを使ってコンパイルするように、またこの一文を削除またはコメントアウトするとPROGMEMを使わずにコンパイルするようになっています。
このスケッチはWEBサーバーですので、クライアント(ブラウザ)からの要求を受け付けて応答を返します。
htmlファイルを出力しますが、htmlのフォーマットには固定メッセージで済むところが沢山あります。例えばhtml文の開始と終了は<html></html>で括られますが、これなんかは普通固定メッセージで済みますよね。
PROGMEMを使って修飾した定数はROMに配置され、プログラムの実行中もROMから参照されます。
PROGMEMを使わずにconstを付けただけだと、ROMにデータとして保存されますがC言語のmainが実行される前にその内容をRAMにコピーされ、プログラムの実行中もRAMから参照されます。
※ただしこれはAVR特有の事です。他のマイコンの場合はconstを使って修飾すればROMに配置され、参照もROMから行われる物がほとんどです。
この為、PROGMEMを使わずに定数を使うと、その分だけどんどんRAMを消費してしまいます。参照するだけ(内容を読み出すだけで変化させて使ったりしない)なのにです。
勿論1byte、2byte分の定数ならば面倒なのでそのまま使ってもあまり害は無い場合がほとんどですが、それが今回の様に固定メッセージを沢山使ったりした場合は、積極的にPROGMEMを使用しないと、あっと言う間にRAMを使い切ってしまいます。なんせATmega328Pですら搭載RAM容量は2Kbyte(2048byte)しか無いのですから。
実際にPROGMEMを使った場合と、使わなかった場合でどれ位違いが出るのか、それを調べたのが冒頭の図です。ツールの出力を以下に記載しておきます。
PROGMEMを使った場合です。
PROGMEMを使わなかった場合です。
共に注目すべきは0番のdataセクションです。dataセクションはRAM上に展開される初期化済み(変数)領域です。
PROGMEMを使用した場合は00000050、つまり80byteであるのに、使用しなかった場合は0000059c(1436byte)となっています。使用しなかった場合は2KbyteのRAM領域の内、実に7割もの領域が使われています。おそらくこの値はこのスケッチが動作可能なギリギリの値だと思われます。これに更に何かメッセージを追加したら、謎のプログラム停止とかに成りかねません。
※textセクションはプログラムコード、bssセクションは未初期化変数領域です。
結果、PROGMEMを使用するようにした場合は、1356byteも使用済みRAM領域を減らせました。
※今回の様にプログラム実行中に値の変化しないデータまでRAMに配置されてしまい、無駄にRAMを消費する問題は、別に今回の様な文字データの配列を使ったからに限りません。
試しに以下の様なスケッチを書いてみます。
この中で定数は0~9までの数字とアルファベット、それに見落としてしまい勝ちな38400です。これを先のメモリの状況を調べるツールに掛けて見ます。
データセクションで00000030(48byte)消費している事が判ります。0~9の数字で10+1byte、アルファベットで26+1byte、38400はint型なら2byteの合計40byteが定数です。あれ8byteは何処に???。
※毎回、毎回同じ事をタイプするのは面倒なので(忘れちゃうし)、batファイルを作ってやっています。
avr-objdump -h %1 | more
avr-nm -n -C %1 | more
本当はビルド時に作成するように出来れば良いのだけれど、、、検討中です。情報求む!。
※追記 write_Pの実体

普通、マイコンの開発では、現在のメモリの使用状況を調べ、もし使用したいマイコンの搭載メモリ量を超えてしまった場合、何らかの対策を行わなければならないのです。何故かと言えば、PC上のプログラミングならば、最近のPCを使用している限りメインメモリなんてGbyte単位で増設可能で、滅多にこれを使い切る事は無いでしょう。いざとなったら遅いですが仮想メモリなんて手もある訳だし。
ですがマイコンのメモリ搭載量は、特にArduinoで使用しているAVRの様なOne chipマイコンではとても小さなメモリしか搭載されておらず、また増設も不可能か、もしくは容易な事ではありません。
なのでマイコンの開発環境にはコンパイル後(実際にはリンク後)のメモリの使用状況を報告してくれる機能が基本的に備わっています。勿論Arduinoにも有る筈なのですけれど。
※GCCはあまり使わないので良く知らなかったのですね。
ArduinoのスケッチのコンパイルにはGCCツールチェインが動いています。何故かこれをJavaが動いていると紹介しているところも有ったりしますが、JavaはArduino IDEその物を動かしている方でしょう。スケッチの言語も「C言語っぽい簡単ななんとか言語」とか言う怪しい言語ではなく、C++をベースに若干の構文整形ツールがあると言ったところでしょう。 ※なんでいつも「簡単な」って言葉で誤魔化すのがよく判りませんが、言語面では結局のところ使いこなす為にはCやC++を勉強するのが一番の近道だと思います。
「PS3とLinux、電子工作も」さんの最近の話題「Arduinoメールチェッカー(その3)」
http://todotani.cocolog-nifty.com/blog/2009/06/arduino-36b3.html
で、スタックの話題に触れており、その中でメモリの使用状況を調べるツールに付いて書かれていましたので、今回あらためてPROGMEMの効用を確認してみました。
※PROGMEMの効用自体は知っていましたが、具体的な数字を出す事が今まで出来なかった。
さてPROGMEMの効用を調べる為には適当なお題が必要ですので、例のWEBサーバーを引き合いに出してみます。以下のスケッチです。
PROGMEMを使用すると、使い方によってはAVRに搭載されているメモリの内、RAMの使用量を減らす事が可能です。詳細は下の方で述べます。
13行目にコンパイル制御を行う為の定義(#define __PROGMEM_USE__)を行っています。この一文が有るとPROGMEMを使ってコンパイルするように、またこの一文を削除またはコメントアウトするとPROGMEMを使わずにコンパイルするようになっています。
/*
* PROGMEMの効き具合を確認してみる
*/
#include <Ethernet.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <avr/pgmspace.h>
#define LINE_STRING_SIZE 128
#define __PROGMEM_USE__
char *line;
char meth[10],url[30],ver[20];
long access_count;
const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const byte sip[] = { 192, 168, 1, 177 };
const byte gateway[] = { 192, 168, 1, 1 };
/*contents*/
#ifdef __PROGMEM_USE__
const char PROGMEM http_head[] =
#else
const char http_head[] =
#endif
"HTTP/1.0 200 OK\r\n" \
"Server: Arduino with Ether Shield/ ver.0.1\r\n" \
"Content-Type: text/html; charset=UTF-8\r\n\r\n" \
"<html lang=\"ja\"><head>\r\n" \
"<meta HTTP-EQUIV=\"Content-type\" CONTENT=\"text/html; charset=UTF-8\">\r\n" \
"<title>Arduino WEB Server 0.1</title></head>\r\n" \
"<body bgcolor=\"#ccffcc\">\r\n" \
"<h3>designed by hamayan</h3>\r\n";
#ifdef __PROGMEM_USE__
const char PROGMEM http_foot[] =
#else
const char http_foot[] =
#endif
"</body></html>\r\n\r\n";
#ifdef __PROGMEM_USE__
const char PROGMEM thanks[] =
#else
const char thanks[] =
#endif
"アクセス有難うございます。このページはArduino+Ether Shieldで表示しています。<br>\r\n" \
"現在はリクエストのパーサーを作成しています。<br>\r\n";
#ifdef __PROGMEM_USE__
const char PROGMEM links[] =
#else
const char links[] =
#endif
"hamayan blog <a href=\"http://hamayan.blog.so-net.ne.jp/\">http://hamayan.blog.so-net.ne.jp/</a><br>\r\n" \
"chip 1 stop <a href=\"http://www.chip1stop.com/\">http://www.chip1stop.com/</a><br>\r\n" \
"オープンソースハードウェアセミナーのページ <a href=\"http://www.chip1stop.com/knowledge/Arduino/\">http://www.chip1stop.com/knowledge/Arduino/</a><br>\r\n" \
"Make:Japan <a href=\"http://jp.makezine.com/blog/\">http://jp.makezine.com/blog/</a><br><br>\r\n";
#ifdef __PROGMEM_USE__
const char PROGMEM banner_01[] =
#else
const char banner_01[] =
#endif
"<p><a href=\"http://www.chip1stop.com/knowledge/Arduino/\">" \
"<img src=\"http://www.chip1stop.com/img/link_Arduino.gif\" width=\"468\" height=\"60\" alt=\"Arduinoモニタープログラム参加中\" /></a><br>" \
"<a href=\"http://www.chip1stop.com/\" title=\"電子部品・半導体の通販サイト - チップワンストップ\">電子部品・半導体の通販サイト - チップワンストップ</a></p><br><br>\r\n";
#ifdef __PROGMEM_USE__
const char PROGMEM img_src[] =
#else
const char img_src[] =
#endif
"<p><img src=\"./image2.jpg\" width=\"200\" height=\"140\" align=\"center\"></p><br><br>\r\n";
#ifdef __PROGMEM_USE__
const char PROGMEM jpeg_head[] =
#else
const char jpeg_head[] =
#endif
"HTTP/1.0 200 OK\r\n" \
"Server: Arduino with Ether Shield/ ver.0.1\r\n" \
"Content-Type: image/JPEG\r\n\r\n";
Server server( 8888 );
void setup()
{
Ethernet.begin( (uint8_t *)mac, (uint8_t *)sip, (uint8_t *)gateway );
server.begin();
line = (char *)malloc( LINE_STRING_SIZE );
}
void loop()
{
Client client = server.available();
if( client )
{
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
char *argv[ 10 ];
int div_num = split( dst, argv, sizeof(argv) / sizeof(argv[0]) ); /*文字列分割*/
strncpy( meth, argv[ 0 ], sizeof(meth) );
strncpy( url, argv[ 1 ], sizeof(url) );
strncpy( ver, argv[ 2 ], sizeof(ver) );
break;
}
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
if( dst != NULL && *dst == '\0' ) /*改行のみの行を検出*/
{
index_html( client );
break;
}
}
delay( 1 );
client.stop();
}
}
void index_html( Client client )
{
//httpヘッダーの返信
#ifdef __PROGMEM_USE__
client.write_P( http_head, sizeof( http_head ) - 1 );
#else
client.write( (const uint8_t *)http_head, sizeof( http_head ) - 1 );
#endif
//サンキューメッセージ
#ifdef __PROGMEM_USE__
client.write_P( thanks, sizeof( thanks ) - 1 );
#else
client.write( (const uint8_t *)thanks, sizeof( thanks ) - 1 );
#endif
//リンク
#ifdef __PROGMEM_USE__
client.write_P( (const uint8_t *)links, sizeof( links ) - 1 );
#else
client.write( (const uint8_t *)links, sizeof( links ) - 1 );
#endif
//Arduinoバージョン
sprintf( line, "Arduino ver=%d <br>\r\n", ARDUINO );
client.write( line );
//アクセスカウント
sprintf( line, "COUNT=%d <br>\r\n", ++access_count );
client.write( line );
//バナー
#ifdef __PROGMEM_USE__
client.write_P( (const uint8_t *)banner_01, sizeof( banner_01 ) - 1 );
#else
client.write( (const uint8_t *)banner_01, sizeof( banner_01 ) - 1 );
#endif
//Footer
#ifdef __PROGMEM_USE__
client.write_P( (const uint8_t *)http_foot, sizeof( http_foot ) - 1 );
#else
client.write( (const uint8_t *)http_foot, sizeof( http_foot ) - 1 );
#endif
}
このスケッチはWEBサーバーですので、クライアント(ブラウザ)からの要求を受け付けて応答を返します。
htmlファイルを出力しますが、htmlのフォーマットには固定メッセージで済むところが沢山あります。例えばhtml文の開始と終了は<html></html>で括られますが、これなんかは普通固定メッセージで済みますよね。
PROGMEMを使って修飾した定数はROMに配置され、プログラムの実行中もROMから参照されます。PROGMEMを使わずにconstを付けただけだと、ROMにデータとして保存されますがC言語のmainが実行される前にその内容をRAMにコピーされ、プログラムの実行中もRAMから参照されます。
※ただしこれはAVR特有の事です。他のマイコンの場合はconstを使って修飾すればROMに配置され、参照もROMから行われる物がほとんどです。
この為、PROGMEMを使わずに定数を使うと、その分だけどんどんRAMを消費してしまいます。参照するだけ(内容を読み出すだけで変化させて使ったりしない)なのにです。
勿論1byte、2byte分の定数ならば面倒なのでそのまま使ってもあまり害は無い場合がほとんどですが、それが今回の様に固定メッセージを沢山使ったりした場合は、積極的にPROGMEMを使用しないと、あっと言う間にRAMを使い切ってしまいます。なんせATmega328Pですら搭載RAM容量は2Kbyte(2048byte)しか無いのですから。
実際にPROGMEMを使った場合と、使わなかった場合でどれ位違いが出るのか、それを調べたのが冒頭の図です。ツールの出力を以下に記載しておきます。
PROGMEMを使った場合です。
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 00000050 00800100 000023c2 00002456 2**0
CONTENTS, ALLOC, LOAD, DATA
1 .text 000023c2 00000000 00000000 00000094 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .bss 00000091 00800150 00800150 000024a6 2**0
ALLOC
PROGMEMを使わなかった場合です。
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 0000059c 00800100 00001e76 00001f0a 2**0
CONTENTS, ALLOC, LOAD, DATA
1 .text 00001e76 00000000 00000000 00000094 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .bss 00000091 0080069c 0080069c 000024a6 2**0
ALLOC
共に注目すべきは0番のdataセクションです。dataセクションはRAM上に展開される初期化済み(変数)領域です。
PROGMEMを使用した場合は00000050、つまり80byteであるのに、使用しなかった場合は0000059c(1436byte)となっています。使用しなかった場合は2KbyteのRAM領域の内、実に7割もの領域が使われています。おそらくこの値はこのスケッチが動作可能なギリギリの値だと思われます。これに更に何かメッセージを追加したら、謎のプログラム停止とかに成りかねません。
※textセクションはプログラムコード、bssセクションは未初期化変数領域です。
結果、PROGMEMを使用するようにした場合は、1356byteも使用済みRAM領域を減らせました。
※今回の様にプログラム実行中に値の変化しないデータまでRAMに配置されてしまい、無駄にRAMを消費する問題は、別に今回の様な文字データの配列を使ったからに限りません。
試しに以下の様なスケッチを書いてみます。
void setup()
{
Serial.begin( 38400 );
Serial.println("0123456789");
Serial.println("abcdefghijklmnopqrstuvwxyz");
}
void loop()
{
}
この中で定数は0~9までの数字とアルファベット、それに見落としてしまい勝ちな38400です。これを先のメモリの状況を調べるツールに掛けて見ます。
Sections:
Idx Name Size VMA LMA File off Algn
0 .data 00000030 00800100 00000576 0000060a 2**0
CONTENTS, ALLOC, LOAD, DATA
1 .text 00000576 00000000 00000000 00000094 2**1
CONTENTS, ALLOC, LOAD, READONLY, CODE
2 .bss 0000009f 00800130 00800130 0000063a 2**0
ALLOC
データセクションで00000030(48byte)消費している事が判ります。0~9の数字で10+1byte、アルファベットで26+1byte、38400はint型なら2byteの合計40byteが定数です。あれ8byteは何処に???。
※毎回、毎回同じ事をタイプするのは面倒なので(忘れちゃうし)、batファイルを作ってやっています。
avr-objdump -h %1 | more
avr-nm -n -C %1 | more
本当はビルド時に作成するように出来れば良いのだけれど、、、検討中です。情報求む!。
※追記 write_Pの実体
void Client::write_P( PGM_VOID_P buf, int size )
{
uint8_t *temp = (uint8_t *)malloc( TEMP_HEAP_SIZE );
int sz;
PGM_P ptr = (PGM_P)buf;
for( ; size > 0; )
{
sz = ( size < TEMP_HEAP_SIZE ) ? size : TEMP_HEAP_SIZE;
memcpy_P( temp, ptr, sz );
send( _sock, (const uint8_t *)temp, sz );
size -= sz;
ptr += sz;
}
free( temp );
}

Making Things Talk -Arduinoで作る「会話」するモノたち
- 作者: Tom Igoe
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/11/17
- メディア: 大型本
「オープンソースハードウェアセミナーVol1」レポート Arduino host by name ライブラリ こっそり公開 [ATmarquino Arduino]
※C++に関しては全く自信が無い。もっと上手な書き方があると思うけれど。知っていたら教えてね。
あと、PROGMEMはやっぱり面倒くさい。
勿論これを動かす為には、既に公開しているUDPライブラリも一緒に用意しておく必要があります。
※追記!以下のネットワーク設定の特殊事情に付いて。
家のネットワークは、通常のネットワークグループの下にネットワークの実験用のネットワークグループが有って、そことはルーターで区切ってあります。今回は192.168.1.0/24にArduino+Ethernet Shieldを設置しているので、このネットワークグループのデフォルトゲートウエイが192.168.1.1になっています。
また、通常ブロードバンド回線の出入り口にはブロードバンドルーターがあるかと思いますが、プライマリDNSはこのルーターとなるので、まず最初の問合せ先が192.168.100.1となっております。
const byte sip[] = { 192, 168, 1, 177 };
const byte gateway[] = { 192, 168, 1, 1 };
const byte Pri_DNS[] = { 192, 168, 100, 1 };
※ご利用は自由ですが、著作権表示の削除、改竄はできません。
Dns.cpp
Dns.h
sampleスケッチ
あと、PROGMEMはやっぱり面倒くさい。
勿論これを動かす為には、既に公開しているUDPライブラリも一緒に用意しておく必要があります。※追記!以下のネットワーク設定の特殊事情に付いて。
家のネットワークは、通常のネットワークグループの下にネットワークの実験用のネットワークグループが有って、そことはルーターで区切ってあります。今回は192.168.1.0/24にArduino+Ethernet Shieldを設置しているので、このネットワークグループのデフォルトゲートウエイが192.168.1.1になっています。
また、通常ブロードバンド回線の出入り口にはブロードバンドルーターがあるかと思いますが、プライマリDNSはこのルーターとなるので、まず最初の問合せ先が192.168.100.1となっております。
const byte sip[] = { 192, 168, 1, 177 };
const byte gateway[] = { 192, 168, 1, 1 };
const byte Pri_DNS[] = { 192, 168, 100, 1 };
※ご利用は自由ですが、著作権表示の削除、改竄はできません。
Dns.cpp
/* ------------------------------------------------------------------------ */ /* Hyper Operating System V4 */ /* test program for Navajo */ /* お題: いい加減なDNS IPアドレス正引き */ /* */ /* このプログラムは、m-arai氏の好意で、Project HOSの著作物となりました。 */ /* Copyright (C) 1998-2004 by Project HOS */ /* http://sourceforge.jp/projects/hos/ */ /* ------------------------------------------------------------------------ */ #include <ctype.h> #include <string.h> #include <stdlib.h> #include <wiring.h> #include <Ethernet.h> #include <Udp.h> #include <avr/pgmspace.h> #include "Dns.h" byte _dnsip[4]; Udp udp = Udp(); const unsigned char PROGMEM head_qa[] = { 0x01,0x00, /* 再帰照会 */ 0x00,0x01, /* 質問数 1 */ 0x00,0x00, /* 回答数 */ 0x00,0x00, /* 権威 */ 0x00,0x00 /* 追加 */ }; const unsigned char PROGMEM tail_qa[] = { 0x00, /* ドメイン名終端 */ 0x00,0x01, /* type A */ 0x00,0x01 /* class INET */ }; const unsigned char PROGMEM repl_head[] = { 0x81, 0x00, /* FLAG: 回答,再帰照会 */ 0x00, 0x01, /* Qnum: 質問数 1 */ 0x00, 0x01, /* Anum: 回答数 1 */ 0x00, 0x00, /* 権威: 0 */ 0x00, 0x00, /* 追加: 0 */ }; Dns::Dns( const byte addr[] ) { memcpy( _dnsip, addr, LENGTHIPV4 ); } /* DNS Aの問い合わせパケットを作成する 返り値はパケットサイズあるいは負のエラーコード */ int Dns::setup_query_pkt( const char *dname, byte buff[] ) { const char *p,*q; int i,len,lid; static int id; /* idをユニークに */ lid = id++; /* ID設定 */ buff[0] = (lid >> 8) & 0xff; buff[1] = lid & 0xff; /* FLAG~ドメイン名以前の設定 */ // memcpy( &buff[2], head_qa, sizeof( head_qa)); memcpy_P( &buff[2], head_qa, sizeof( head_qa ) ); for( i = 12, p = dname;*p != '\0'; i += len, p = q + 1 ) { /* 次の'.'又は文末の検索 */ for( q = p; *q != '.' && *q != '\0'; q++ ) ; /* パケット溢れチェック */ if( (len = q-p)+i > DNS_PSIZE-5) { return E_LONG; } buff[i++] = len; /* 文字列コピー */ strncpy( (char *)&buff[i], p, len); if (*q=='\0') { i += len; break; } } /* ドメイン名終端~パケット終了の設定 */ // memcpy( &buff[i], tail_qa, sizeof( tail_qa) ); memcpy_P( &buff[i], tail_qa, sizeof( tail_qa) ); return i + sizeof( tail_qa ); } int Dns::getaddr_from_pkt( int qid, byte buff[], byte addr[] ) { int i; /* 回答のIDが合致しているかチェック */ if( qid != ((buff[0] << 8) + buff[1]) ) { return E_BADID; } /* ヘッダ部が正常回答がチェック 2009/6/8 修正:一つの質問に複数の回答があるケースに対応させる為*/ if( ((buff[2] & 0xfb) != 0x81) || ((buff[3] & 0x0f) != 0) || ((buff[4] << 8) + buff[5]) != 1 ) { return E_BADHD; } /* ドメインの終端を探す */ for( i = 12; buff[i] != '\0'; i++ ) ; /* もうこの後,回答のタイプは一切チェックしない */ if( ( buff[i+5] & 0xc0) == 0xc0 ) { /* ドメインは圧縮されている */ memcpy( addr, &buff[ i + 17], LENGTHIPV4 ); } else { /* 多分有り得ないが,圧縮されていない */ /* ドメイン終端を検索 */ for( i = i + 6; buff[i] != '\0'; i++ ) ; memcpy( addr, &buff[i + 11], LENGTHIPV4 ); } return 0; } int Dns::getaddrbydns( const char *dname, byte addr[] ) { int ssz,ret,qid; byte server_ip[4]; uint16 dport; byte *buff; /*UDPソケットOPEN*/ if( !udp.Open() ) return E_SCKFAIL; /* 共有メモリからDNSパケットバッファ用メモリを取得 */ buff = (byte *)malloc( DNS_PSIZE ); /* DNS A問い合わせパケット作成 */ ssz = setup_query_pkt( dname, buff); qid = ( buff[0] << 8 ) + buff[1]; /* パケット送信 */ udp.sendTo( (const uint8 *)buff, ssz, (uint8 *)_dnsip, DNS_PORT ); delay( 100 ); while( udp.recvFrom( (uint8 *)buff, DNS_PSIZE, (uint8 *)server_ip, &dport ) == (-1) ) delay( 10 ); /* 回答からIPアドレスを取得する */ ret = getaddr_from_pkt( qid, buff, (byte *)addr ); /* DNSパケットバッファ用メモリを解放 */ free( buff ); /*ソケットのクローズ*/ udp.Close(); return ret; } /* ------------------------------------------------------------------------ */ /* Copyright (C) 1998-2004 by Project HOS */ /* http://sourceforge.jp/projects/hos/ */ /* ------------------------------------------------------------------------ */
Dns.h
#ifndef Dns_h
#define Dns_h
#define DNS_PSIZE 512
#define DNS_PORT 53
#define LENGTHIPV4 4
#define E_LONG (-1) /* ドメイン名が長過ぎる */
#define E_BADID (-2) /* 回答のIDが違う */
#define E_BADHD (-3) /* 回答のヘッダ部が正常でない */
#define E_SNDFAIL (-4) /* なんか知らんがUDPsendに失敗 */
#define E_SCKFAIL (-5) /* なんか知らんがUDPsocketに失敗 */
typedef uint8_t byte;
class Dns
{
private:
byte _dnsip[];
int getaddr_from_pkt( int qid, byte buff[], byte addr[] );
int setup_query_pkt( const char *dname, byte buff[] );
public:
Dns( const byte addr[] );
int getaddrbydns( const char *dname, byte addr[] );
};
#endif
sampleスケッチ
#include <Ethernet.h>
#include <Dns.h>
const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const byte sip[] = { 192, 168, 1, 177 };
const byte gateway[] = { 192, 168, 1, 1 };
const byte Pri_DNS[] = { 192, 168, 100, 1 };
const char query_hamayan_name[] = "hamayan.ddo.jp";
const char query_google_name[] = "google.com";
Dns dns = Dns( Pri_DNS );
void setup()
{
int result;
byte dip[4];
delay( 1000 );
Ethernet.begin( (uint8_t *)mac, (uint8_t *)sip, (uint8_t *)gateway );
Serial.begin( 38400 );
delay( 3000 );
if( (result = dns.getaddrbydns( query_hamayan_name, dip )) == 0 )
{
Serial.print( "IP Adr=" );
Serial.print( dip[0], DEC );
Serial.print( '.' );
Serial.print( dip[1], DEC );
Serial.print( '.' );
Serial.print( dip[2], DEC );
Serial.print( '.' );
Serial.print( dip[3], DEC );
Serial.println();
}
else
{
Serial.print( "NG1 type=" );
Serial.println( result, DEC );
}
if( (result = dns.getaddrbydns( query_google_name, dip )) == 0 )
{
Serial.print( "IP Adr=" );
Serial.print( dip[0], DEC );
Serial.print( '.' );
Serial.print( dip[1], DEC );
Serial.print( '.' );
Serial.print( dip[2], DEC );
Serial.print( '.' );
Serial.print( dip[3], DEC );
Serial.println();
}
else
{
Serial.print( "NG2 type=" );
Serial.println( result, DEC );
}
}
void loop()
{
}
「オープンソースハードウェアセミナーVol1」レポート Arduino Ethernet Shieldでインターネットから名前を引こう DNSの実現 [ATmarquino Arduino]
しまった。「名前を引こう!」では逆引きみたいだな。
結構この機能欲しい人も多い筈!。
元々DNSの正引き機能は、HOS上にプロトコルスタックを載せたNavajoのUDPアプリケーションの一つとして、HOSの開発メンバーであるm-arai氏に作ってもらい、その後Project-HOSの著作物となったものです。
それをArduino用に載せ換えました。
なので、このプログラムの二次利用は自由ですが、著作権表示の削除や改竄はしないでくださいね。
今回UDPの機能を実現したのでやってみました。スケッチは下の方に掲載して置きます。
上の図はDOS窓からnslookupを行って正引きを行った結果です。
下は勿論Arduino上で実行した結果です。
ね!、引けているでしょう。
今までこのDNS正引きを使った感触ですが、結構引けます。でも完全に引ける訳ではないのも事実です。
その辺に注意して使ってね。
|
|
元々DNSの正引き機能は、HOS上にプロトコルスタックを載せたNavajoのUDPアプリケーションの一つとして、HOSの開発メンバーであるm-arai氏に作ってもらい、その後Project-HOSの著作物となったものです。
それをArduino用に載せ換えました。
なので、このプログラムの二次利用は自由ですが、著作権表示の削除や改竄はしないでくださいね。
今回UDPの機能を実現したのでやってみました。スケッチは下の方に掲載して置きます。
上の図はDOS窓からnslookupを行って正引きを行った結果です。
下は勿論Arduino上で実行した結果です。
ね!、引けているでしょう。
今までこのDNS正引きを使った感触ですが、結構引けます。でも完全に引ける訳ではないのも事実です。
その辺に注意して使ってね。
/* ------------------------------------------------------------------------ */ /* Hyper Operating System V4 */ /* test program for Navajo */ /* お題: いい加減なDNS IPアドレス正引き */ /* */ /* このプログラムは、m-arai氏の好意で、Project HOSの著作物となりました。 */ /* Copyright (C) 1998-2004 by Project HOS */ /* http://sourceforge.jp/projects/hos/ */ /* ------------------------------------------------------------------------ */ #include <Ethernet.h> #include <ctype.h> #include <string.h> #include <stdlib.h> #define DNS_PSIZE 512 #define DNS_PORT 53 #define LENGTHIPV4 4 #define E_LONG (-1) /* ドメイン名が長過ぎる */ #define E_BADID (-2) /* 回答のIDが違う */ #define E_BADHD (-3) /* 回答のヘッダ部が正常でない */ #define E_SNDFAIL (-4) /* なんか知らんがUDPsendに失敗 */ #define E_SCKFAIL (-5) /* なんか知らんがUDPsocketに失敗 */ const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; const byte ip[] = { 192, 168, 100, 177 }; const byte gateway[] = { 192, 168, 100, 1 }; const byte DomainNameServer[] = { 192, 168, 100, 1 }; const char query_hamayan_name[] = "hamayan.ddo.jp"; const char query_google_name[] = "google.com"; static const byte head_qa[] = { 0x01,0x00, /* 再帰照会 */ 0x00,0x01, /* 質問数 1 */ 0x00,0x00, /* 回答数 */ 0x00,0x00, /* 権威 */ 0x00,0x00 /* 追加 */ }; static const byte tail_qa[] = { 0x00, /* ドメイン名終端 */ 0x00,0x01, /* type A */ 0x00,0x01 /* class INET */ }; static const byte repl_head[] = { 0x81, 0x00, /* FLAG: 回答,再帰照会 */ 0x00, 0x01, /* Qnum: 質問数 1 */ 0x00, 0x01, /* Anum: 回答数 1 */ 0x00, 0x00, /* 権威: 0 */ 0x00, 0x00, /* 追加: 0 */ }; static int id; Udp udp = Udp(); /*UDP socketの生成*/ void setup() { byte addr[4]; Ethernet.begin( (uint8_t *)mac, (uint8_t *)ip, (uint8_t *)gateway ); Serial.begin( 38400 ); delay( 3000 ); /*シリアルモニタへの切り替え時間分、待ちを入れてある*/ getaddrbydns( query_hamayan_name, addr ); Serial.print( "ip=" ); for( int i = 0; i < sizeof(addr) / sizeof(addr[0]); i++ ) { Serial.print( addr[i], DEC ); Serial.print( "." ); } Serial.println(); delay( 100 ); getaddrbydns( query_google_name, addr ); Serial.print( "ip=" ); for( int i = 0; i < sizeof(addr) / sizeof(addr[0]); i++ ) { Serial.print( addr[i], DEC ); Serial.print( "." ); } Serial.println(); } void loop() { } /* DNS Aの問い合わせパケットを作成する 返り値はパケットサイズあるいは負のエラーコード */ int setup_query_pkt( const char *dname, byte buff[] ) { const char *p,*q; int i,len,lid; /* idをユニークに */ lid = id++; /* ID設定 */ buff[0] = (lid >> 8) & 0xff; buff[1] = lid & 0xff; /* FLAG~ドメイン名以前の設定 */ memcpy( &buff[2], head_qa, sizeof( head_qa)); for( i = 12, p = dname;*p != '\0'; i += len, p = q + 1 ) { /* 次の'.'又は文末の検索 */ for( q = p; *q != '.' && *q != '\0'; q++ ) ; /* パケット溢れチェック */ if( (len = q-p)+i > DNS_PSIZE-5) { return E_LONG; } buff[i++] = len; /* 文字列コピー */ strncpy( (char *)&buff[i], p, len); if (*q=='\0') { i += len; break; } } /* ドメイン名終端~パケット終了の設定 */ memcpy( &buff[i], tail_qa, sizeof( tail_qa) ); return i + sizeof( tail_qa ); } int getaddr_from_pkt( int qid, byte buff[], byte addr[] ) { int i; /* 回答のIDが合致しているかチェック */ if( qid != ((buff[0] << 8) + buff[1]) ) { return E_BADID; } /* ヘッダ部が正常回答がチェック 2009/6/8 修正:一つの質問に複数の回答があるケースに対応させる為*/ if( ((buff[2] & 0xfb) != 0x81) || ((buff[3] & 0x0f) != 0) || ((buff[4] << 8) + buff[5]) != 1 ) { return E_BADHD; } /* ドメインの終端を探す */ for( i = 12; buff[i] != '\0'; i++ ) ; /* もうこの後,回答のタイプは一切チェックしない */ if( ( buff[i+5] & 0xc0) == 0xc0 ) { /* ドメインは圧縮されている */ memcpy( addr, &buff[ i + 17], LENGTHIPV4 ); } else { /* 多分有り得ないが,圧縮されていない */ /* ドメイン終端を検索 */ for( i = i + 6; buff[i] != '\0'; i++ ) ; memcpy( addr, &buff[i + 11], LENGTHIPV4 ); } return 0; } int getaddrbydns( const char *dname, byte addr[] ) { int ssz,ret,qid; byte server_ip[4]; uint16 dport; byte *buff; /*UDPソケットOPEN*/ if( !udp.Open() ) return E_SCKFAIL; /* 共有メモリからDNSパケットバッファ用メモリを取得 */ buff = (byte *)malloc( DNS_PSIZE ); /* DNS A問い合わせパケット作成 */ ssz = setup_query_pkt( dname, buff); qid = ( buff[0] << 8 ) + buff[1]; /* パケット送信 */ udp.sendTo( (const uint8 *)buff, ssz, (uint8 *)DomainNameServer, DNS_PORT ); delay( 100 ); while( udp.recvFrom( (uint8 *)buff, DNS_PSIZE, (uint8 *)server_ip, &dport ) == (-1) ) delay( 10 ); /* 回答からIPアドレスを取得する */ ret = getaddr_from_pkt( qid, buff, (byte *)addr ); /* DNSパケットバッファ用メモリを解放 */ free( buff ); /*ソケットのクローズ*/ udp.Close(); return ret; } /* ------------------------------------------------------------------------ */ /* Copyright (C) 1998-2004 by Project HOS */ /* http://sourceforge.jp/projects/hos/ */ /* ------------------------------------------------------------------------ */
「オープンソースハードウェアセミナーVol1」レポート Arduino Ethernetライブラリ UDPの実現2 [ATmarquino Arduino]
|
|
インスタンスの生成やソケットオープン周りの変更と、データグラムの受信機能を追加しました。
作成したコードは下の方に記載しておきます。
UDPの送受信を確認する為には、なんらかのUDPデータグラムを送ってくれる相手が必要です。
そこでパケットモニタ(PacMon)が仕掛けられているPCに自前で作成したDAYTIMEサーバーを起動しておきます。
このサーバーは、内容はなんでも良いのですがUDPでアクセスされると、そのアクセス元に現在の時刻を返信します。受信した内容は一切関知していません。
パケットモニタでハイライトされているパケットがその時刻の返信です。
TCPソケットとの併用を確認する為に、例のWEBサーバーの中にUDPの送受信プログラムを埋め込んでしまいました。なんと雑な!。
使い方ですが、インスタンス生成時に
Udp udp = Udp( 5000 );
とすれば指定したポート番号を自分のポート番号として使用します。
Udp udp = Udp();
とすればエフェメラルポートの開始番号1024を自分のポート番号として使用します。
問題なのは複数のインスタンスを生成した場合のポート番号の排他制御をまだ実装していない点ですね。今後に”期待”しましょう。
udp.Open();
と記述すれば現行のポート番号を自分のポート番号として使用します。ちょっと説明し辛いのですが、大域変数に現在設定されているポート番号の領域が取られていて、その変数を自ポート番号として利用すると言う事です。
udp.Open( 5000 );
と記述すれば5000を自ポート番号として利用すると言う事です。
どちらのOpenの場合も、現行ポート番号は自動的にインクリメントされます。
あとは実際に記述したスケッチを見れば判ると思います。
修正したudp.cppです。
extern "C" {
#include "types.h"
#include "w5100.h"
#include "socket.h"
}
#include "Ethernet.h"
#include "Udp.h"
Udp::Udp()
{
_port = 1024;
}
Udp::Udp( uint16_t port )
{
_port = port;
}
uint8_t Udp::Open() /*エフェメラルポートの利用*/
{
_sock = GetSockNum();
if( _sock != (-1) )
{
socket( _sock, Sn_MR_UDP, _port, 0 );
if( ++_port == 0 ) _port = 1024;
return 1;
}
return 0;
}
uint8_t Udp::Open( uint16_t port ) /*ポート番号をBINDして使う*/
{
_sock = GetSockNum();
if( _sock != (-1) )
{
socket( _sock, Sn_MR_UDP, port, 0 );
return 1;
}
return 0;
}
void Udp::Close()
{
close( _sock );
}
void Udp::sendTo( const uint8 * buf, uint16 len, uint8 * dip, uint16 dport )
{
sendto( _sock, buf, len, dip, dport );
}
int16 Udp::recvFrom( uint8 * buf, uint16 len, uint8 * dip, uint16 *dport )
{
return recvfrom( _sock, buf, len, dip, dport );
}
int Udp::GetSockNum()
{
int sock = (-1);
for( int i = 0; i < MAX_SOCK_NUM; i++ )
{
uint8_t s = getSn_SR( i );
if( s == SOCK_CLOSED )
{
sock = i;
break;
}
}
return sock;
}
修正したudp.hです。
#ifndef Udp_h
#define Udp_h
class Udp
{
private:
uint8_t _sock;
uint16_t _port;
int GetSockNum();
public:
Udp();
Udp( uint16_t port );
uint8_t Open();
uint8_t Open( uint16_t port );
void Close();
void sendTo( const uint8 * buf, uint16 len, uint8 * addr, uint16 port );
int16 recvFrom( uint8 * buf, uint16 len, uint8 * addr, uint16 *dport );
};
#endif
修正したWEBサーバーのスケッチの一部です。
#include <Ethernet.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <avr/pgmspace.h>
#define LINE_STRING_SIZE 128
#define ECHO_PORT 7
#define DAYTIME_PORT 13
char *line;
char meth[10],url[30],ver[20];
long access_count;
const byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
const byte ip[] = { 192, 168, 100, 177 };
const byte gateway[] = { 192, 168, 100, 1 };
const byte dip[] = { 192, 168, 100, 13 };
Server server( 8888 );
Udp udp = Udp( 5000 ); /*UDP socketの生成*/
//Udp udp = Udp(); /*UDP socketの生成*/
void setup()
{
Ethernet.begin( (uint8_t *)mac, (uint8_t *)ip, (uint8_t *)gateway );
server.begin();
Serial.begin( 38400 );
line = (char *)malloc( LINE_STRING_SIZE );
}
void loop()
{
byte server_ip[4];
uint16 dport;
int16 rcv_size;
char buffer[ 32 ];
Client client = server.available();
if( client )
{
while( client.connected() )
{
if( udp.Open() )
{
udp.sendTo( (const uint8 *)"udp test start.", sizeof("udp test start."), (uint8 *)dip, DAYTIME_PORT );
delay( 100 );
while( (rcv_size = udp.recvFrom( (uint8 *)buffer, sizeof(buffer) - 1, (uint8 *)server_ip, &dport )) == (-1) ) delay( 1 );
buffer[ rcv_size ] = '\0';
Serial.println( buffer );
Serial.print( "from ip=" );
for( int i = 0; i < sizeof(server_ip) / sizeof(server_ip[0]); i++ )
{
Serial.print( server_ip[i], DEC );
Serial.print( "." );
}
Serial.print( " from port=" );
Serial.print( dport, DEC );
Serial.println();
udp.Close();
}
else
{
Serial.println( "UDP Open NG." );
}
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
char *argv[ 10 ];
int div_num = split( dst, argv, sizeof(argv) / sizeof(argv[0]) ); /*文字列分割*/
strncpy( meth, argv[ 0 ], sizeof(meth) );
strncpy( url, argv[ 1 ], sizeof(url) );
strncpy( ver, argv[ 2 ], sizeof(ver) );
break;
}
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
if( dst != NULL && *dst == '\0' ) /*改行のみの行を検出*/
{
index_html( client );
break;
}
}
delay( 1 );
client.stop();
}
}
「オープンソースハードウェアセミナーVol1」レポート Arduino Ethernetライブラリ 複数ソケットとUDPの利用 [ATmarquino Arduino]
ネットワークプログラミング、、、に限らずネットワークを利用していると、UDPを使った有用なサービスに囲まれている事に気が付きます。ほら、お家のLANだってDHCPで自動的にIPアドレスを割り付けてもらったり、インターネットを利用するならDNSは欠かせない。PCの時刻をいつも正確に保つならNTPで!といった具合に。
だがArduinoの公式ライブラリを利用している限りこのUDPは利用できないと言うか、はなっから無視されている状況ですね。
公式のEthernetライブラリのMethodを実現しているのはClient.cppですが、ここにあるMethodは全てTCPを前提にしています。
ところがこのClient.cpp内では通信に関する処理は基本的に別のファイルの関数を呼んでいるだけです。
その別のファイルと言うのがutility以下にあるsocket.cです。
実はこのsocket.cはなかなか良く出来ているライブラリ郡で、ここではTCPだけではなくUDPやIGMPもサポートされています。
このsocket.cのUDPに関係する関数は
| 1 | socket | ソケットの割付 |
| 2 | close | ソケットの開放 |
| 3 | sendto | データグラムの送信 |
| 4 | recvfrom | データグラムの受信 |
と言う事になるでしょう。
さて以下に続く文章、コードは、C++がよく判っていない私が適当に作成したプログラムなので、間違っているとかなんとかご指摘があれば連絡ください。
※まだ送信を行うところまでしか進んでいません(笑)。受信はちょっと待ってね。
上記の関数を呼び出すコードを集めたC++ファイルを作成します。勿論udp.cppとudp.hと言う事になりますね。
udp.cpp
extern "C" {
#include "types.h"
#include "w5100.h"
#include "socket.h"
}
#include "Ethernet.h"
#include "Udp.h"
Udp::Udp( uint8_t sock, uint16_t port )
{
_sock = sock;
_port = port;
}
void Udp::sendTo( const uint8 * buf, uint16 len, uint8 * dip, uint16 dport )
{
sendto( _sock, buf, len, dip, dport );
}
void Udp::open()
{
(void)socket( _sock, Sn_MR_UDP, _port, 0 );
}
udp.h
#ifndef Udp_h
#define Udp_h
class Udp
{
private:
uint8_t _sock;
uint16_t _port;
public:
Udp( uint8_t sock, uint16_t port );
void sendTo( const uint8 * buf, uint16 len, uint8 * addr, uint16 port );
void open();
};
#endif
これらライブラリを作成したら、Arduino IDEでスケッチのビルドを行いますが、その前にライブラリが保存されているフォルダー内のオブジェクトファイルは削除しておいた方が良いかもしれません。何度かリンカーエラーっぽいエラー出力を経験しましたから。
さて先のライブラリをスケッチ内で使用してみます。長いですが例のWEBサーバーの一部を掲載します。
Server server( 8888 );
Udp udp = Udp( 1, 5000 ); /*UDP socketの生成*/
void setup()
{
Ethernet.begin( (uint8_t *)mac, (uint8_t *)ip, (uint8_t *)gateway );
server.begin();
line = (char *)malloc( LINE_STRING_SIZE );
udp.open();
udp.sendTo( (const uint8 *)"udp test start.", sizeof("udp test start."), (uint8 *)dip, 10000 );
}
void loop()
{
Client client = server.available();
if( client )
{
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
char *argv[ 10 ];
int div_num = split( dst, argv, sizeof(argv) / sizeof(argv[0]) ); /*文字列分割*/
strncpy( meth, argv[ 0 ], sizeof(meth) );
strncpy( url, argv[ 1 ], sizeof(url) );
strncpy( ver, argv[ 2 ], sizeof(ver) );
break;
}
while( client.connected() )
{
char *dst = HTTPGets( client, line, LINE_STRING_SIZE );
if( dst != NULL && *dst == '\0' ) /*改行のみの行を検出*/
{
index_html( client );
if( !(access_count % 10) )
udp.sendTo( (const uint8 *)"udp test start.", sizeof("udp test start."), (uint8 *)dip, 10000 );
break;
}
}
delay( 1 );
client.stop();
}
}
setupの中でUDPソケットを生成してその時点で適当なデータグラムを送信しています。
また、WEBサーバーへのアクセス10回毎にやはりデータグラムを送信しています。
送り先はパケットモニタ(PacMon)が仕掛けられたPCなのですが、このPCにはポート番号10000にUDPのサービスなんて存在しないので、ハイライト直後のパケットはICMPの「宛先到達不能」エラーです。まあこの結果を見るとUDPでメッセージが送信されている事がよく判りますね。
次は受信にチャレンジですね。
さてここでもう一つ判った事があります。つまりW5100上では同時に複数のソケットをオープンに出来るという事です。上記の例ではTCPで1つ、UDPで1つのソケットを利用していますから。
UDPではソケット番号を1番固定で使っていますが、Client.cpp内のコネクション処理では空きのソケット番号を探して利用するようになっていますので、一応ソケット番号が衝突する事無く運用できているようです。
※ArduinoでのEthernetプログラミングに慣れてきたら、Client.cppのライブラリではなく、自分でsocket.cの関数を直接呼び出すライブラリを作成した方が、より柔軟なシステムを構築できるような気がします。
※追記
本来はソケット番号なんてユーザーが意識しないで済む様に作るべきですが、それは次の段階で。
「オープンソースハードウェアセミナーVol1」レポート Arduino IDEが0016に更新されたようです。 [ATmarquino Arduino]
きいたんさん http://plaza.rakuten.co.jp/keytanosaka/ からの連絡で知ったのですが、ArduinoのIDEが0016に上がったようです。特にこのblogのネタにしてきたEthernet関連のバグの内、
[core / libraries]
* Adding write(str) and write(buf, size) methods to Print, Serial, and the
Ethernet library Client and Server classes. This allows for more efficient
(fewer packet) Ethernet communication. (Thanks to mikalhart.)
* Improvements to the way the Ethernet library Client class connects and
disconnects. Should reduce or eliminate failed connections and long
timeouts. (Thanks to Bruce Luckcuck.)
この辺りがFIXされているようです。0015お使いでEthernetも使っている方は更新した方が宜しいでしょう。
Clientクラスにwriteメソッドが追加されており、まあこれは私が自分で追加した物と同じですが、あのネットワークにとって最悪な1文字送信をこれで解決できます。
但し、相変わらずサンプルスケッチはclient.printlnを使っているので、こちらは自力で書き換えが必要ですね。
と言うか直せよサンプルスケッチ。
※あとPROGMEMには対応していません。

Making Things Talk -Arduinoで作る「会話」するモノたち
- 作者: Tom Igoe
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/11/17
- メディア: 大型本
「オープンソースハードウェアセミナーVol1」レポート Arduino Ethernet Shield チップワンストップさんで売り切れ [ATmarquino Arduino]
今のところこのblogでは全然よい事を書けていないArduino+Ethernet Shieldですが、世間の目はこんなマイナーblogには向いておらず、現在国内で最安の3990円でEthernet Shieldを提供していたチップワンストップさんでは早々に入荷分を売り切ったそうです。以下のリンクからこの企画のサイトに入ると、ページの上の方にArduino製品の購入バナーが貼られています。
以前はここをクリックすると購入ページに進み、そこにチップワンストップさんで取り扱いのArduino製品の一覧が有りましたが、現在このリストにはEthernet Shieldの姿は消えております。
※お勧めの機能拡張ボードのところにEthernet Shieldが、値段と納期が空欄で表示されています。
http://www.chip1stop.com/UE051_M10.cfm?PID=ARDU-0000001&DID=21&FROM=UE018
Ethernet Shieldは、なんでも近日中に再入荷の予定だとか。
一応、この企画に参加させてもらっている立場からも、Ethernet Shield売れているのか!良かった、良かった!
と言う話でした。
※再入荷の詳しい予定は、見積もりを取って確認してみて下さいね。
※ちょっと心配しちゃったよ!。

Making Things Talk -Arduinoで作る「会話」するモノたち
- 作者: Tom Igoe
- 出版社/メーカー: オライリージャパン
- 発売日: 2008/11/17
- メディア: 大型本



















