2020年07月18日

lwipを用いたFTP Client

イーサネットIFを搭載したSTM32マイコンではCubeMXを用いてイーサネット通信が容易に行うことができます。NucleoボードやDiscoveryボードでは基板上にETHPHYのコントローラICを搭載しているものも多くあり、そのままイーサネット通信の実験を行うことが可能です。

今回はSTM32マイコンを用いてFTPサーバーにデータを格納する方法について紹介します。STM32マイコンがFTPクライアント、サーバーがIIS FTPサーバー@Windows10の環境で実験しました。また、STM32マイコンのイーサネット通信ライブラリとしてlwipを使用し、FTPクライアントはlwftpライブラリを使用しました。lwftpのサンプルコードではそのままでは動かない点や分かりづらい点があったのでそこを踏まえて紹介します。なお、lwftpライブラリはパッシブ通信のFTPクライアントのみサポートしています。

グローバル変数としてセッションを宣言します。
static lwftp_session_t s;


main関数内のメイン処理は下記の通りです。
必要に応じてFTPサーバーのIP、ログインID、ログインパスワードを変更してください。

 // Initialize session data
 memset(&s, 0, sizeof(s));
 IP4_ADDR(&s.server_ip, 192,168,6,3);
 s.server_port = 21;
 s.done_fn = ftp_connect_callback;
 s.user = "ftpuser";
 s.pass = "test";
 s.handle = &s;
 // Start the connection state machine
 error = lwftp_connect(&s);
 if ( error != LWFTP_RESULT_INPROGRESS ) {
  printf("lwftp_connect failed (%d)", error);
 }
 else{
  printf("FTP END \n\r");
 }
 while(1);



main関数の外のグローバル関数として下記のコールバック関数を宣言します。

//FTP通信完了に呼び出される
static void ftp_retr_callback(void *arg, int result)
{
 lwftp_session_t *s = (lwftp_session_t*)arg;
 if ( result != LWFTP_RESULT_OK ){
  LOG_ERROR("retr failed (%d)", result);
  return lwftp_close(s);
 }
 lwftp_close(s);
}

//未使用、FTPサーバからデータ受信で使用
static uint data_sink(void *arg, const char* ptr, uint len)
{
 static const uint mylen = 10;
 static char * const myconfig = (char*)0x20000000;
 static uint offset = 0;
 if (ptr) {
 len = min( len, mylen-offset );
 memcpy( myconfig+offset, ptr, len );
 offset += len;
 }
 return len;
}


static void ftp_stor_callback(void *arg, int result)
{
 lwftp_session_t *s = (lwftp_session_t*)arg;
 err_t error;
 if(s->control_state==LWFTP_DATAEND && result == LWFTP_RESULT_OK ){
  s->control_state=LWFTP_DATAEND;
  return lwftp_close(s);
 }
 if ( result != LWFTP_RESULT_OK && result != LWFTP_RESULT_INPROGRESS && result != LWFTP_RESULT_LOGGED ) {
  LOG_ERROR("stor failed (%d)", result);
  return lwftp_close(s);
 }
 if(s->control_state==LWFTP_CLOSED && result == LWFTP_RESULT_OK)return;
 if(result == LWFTP_RESULT_INPROGRESS)return;
 s->data_sink = data_sink;
 s->done_fn = ftp_retr_callback;
 s->remote_path = "new";
 error = lwftp_retrieve(s);
 if ( result != LWFTP_RESULT_OK && error != LWFTP_RESULT_INPROGRESS && result != LWFTP_RESULT_LOGGED ) {
 LOG_ERROR("lwftp_retrieve failed (%d)", error);
 }
}

//データ元の関数、今回はTEST_DATA:abcdefgの文字列を格納する
char test_data[]={"TEST_DATA:abcdefg"};
static uint data_source(void *arg, const char** pptr, uint maxlen)
{
 static const uint mylen = sizeof(test_data);;
 static const char * const mydata = (char*)&test_data;
 static uint offset = 0;
 uint len = 0;
 if (pptr) {
  len = mylen - offset;
  if ( len > maxlen ) len = maxlen;
  *pptr = mydata + offset;
 }
else {
 offset += maxlen;
 if ( offset > mylen ) offset = mylen;
 }
 return len;
}

//FTP通信接続成功で呼び出される
//logfile.txtというファイル名でFTPサーバーに格納する
static void ftp_connect_callback(void *arg, int result)
{
 lwftp_session_t *s = (lwftp_session_t*)arg;
 err_t error;
 if( result == LWFTP_RESULT_INPROGRESS ){
  return;
 }
 if ( result != LWFTP_RESULT_LOGGED ) {
  LOG_ERROR("login failed (%d)", result);
  return lwftp_close(s);
 }
 s->data_source = data_source;
 s->done_fn = ftp_stor_callback;
 s->remote_path = "logfile.txt";
 error = lwftp_store(s);
 if ( error != LWFTP_RESULT_INPROGRESS ) {
  LOG_ERROR("lwftp_store failed (%d)", error);
 }
}


lwipはコールバック関数で接続や受信などのイベント発生毎に関数が呼び出される構造となっており、lwftpについても同様にイベント毎に関数が実行されます。サンプルコードでは各コールバック関数内のcontrol_state状態に応じた分岐処理がうまくいかず、FTPサーバーに格納できませんでした。Wiresharkで動作を確認しながら、control_stateの分岐条件を追加することでFTPサーバーにデータを格納できるようになりました。lwftpを用いてちょっとしたデータロガーとして応用できそうです。
posted by Crescent at 00:00| Comment(0) | 組込ソフト | このブログの読者になる | 更新情報をチェックする