特集 : 最新組み込みLinux実践講座Part6

対象製品: Armadillo-500Armadillo-300Armadillo-9Armadillo-240Armadillo-230Armadillo-220Armadillo-210Armadillo(HT1070)Armadillo-J
先頭 Part 5 Part 7

(株)アットマークテクノ
中井真大
NAKAI Masahiro

この文書は技術評論社「Software Design」2007年11月号に掲載されたものです。

ここまで解説してきた内容を踏まえて,アンケートツールのメインとなるアプリケーションを作成していきます.

プログラム設計

本章では,アンケートツールのメインアプリケーションを作成します.アプリケーションの名前は,「CST(CustomerSurveyTool)」とすることにします.ソースコードの一覧を表1に示します.

●表1 CST のソースコード一覧
ファイル名収録内容
cst.c メインループと共通関数など
cst_dfb_common.c DirectFB のAPI を使いやすいようにまとめた関数など
cst_survey.c アンケートシートを解析する関数など
cst_survey_window.c アンケート画面の制御関数など
cst_start_window.c 最初の画面の制御関数など
cst_end_window.c 最後の画面の制御関数など
cst_card.c 名刺スキャナの制御関数など
cst_storage.c USB メモリの制御関数など

プログラムを作成する場合,状態遷移図やデータフローダイアグラム(DFD)などを使用して設計する場合がほとんどでしょう.最近では,プログラムの大規模化に伴いUML(Unified Modeling Language)でモデリングを行い,モデルによりシステム全体を管理するといった動きもあるようです.

今回はとくに大規模プログラムではないので,必要最低限の画面遷移のみを検討してみます(図1).

●図1 CST の画面遷移図

基本的に,右下隅にあるボタンをクリックすることで次の画面に遷移し,逆に左下隅にあるボタンをクリックすることで前の画面へ戻るようにします.

コーディング

必要な機能をどのように実現してコーディングを行うかを説明します.誌面の都合上,すべてのソースコードを説明することはできないので,必要に応じてhttp://download.atmark-techno.com/misc/softwaredesign_2007-11/chapter6/cst.tar.gzからダウンロードして参照してください.

名刺スキャナとの連携

名刺スキャナが名刺を読み取ったことを検出するには,SIGCHLDシグナルを利用します.親プロセスが子プロセスを実行した場合,子プロセスが終了するときに親プロセスへSIGCHLDが送られます.これを利用して,CSTがSIGCHLDを受けた場合,DirectFB のイベントバッファにイベントをポストするようにします.ポストされたイベントは,各プロシージャが適切に処理するように記述する必要があります.

また,スキャンした画像をそのままDirectFBで使用することはできないので,SIGCHLDを受けた場合に適切に画像変換されるようにします(リスト1).

●リスト1 名刺スキャナとの連携(cst_card.c より抜粋)


(略)
 19: static pid_t pid_child = -1;
 20: static char *current_card_label;
 21: static CSTCardResult_t cardres;
 22: 
 23: static int
 24: CSTCard_Exec(void)
 25: {
(略)
 31:         current_card_label = CSTRequestLabel();
 32: 
 33:         pid_child = pid = fork();
(略)
 39:         if (pid == 0) {
 40:                 char *args[] = {"scanimage", "--resolution", "150", "-l", "4",
 41:                                 "-x", "95", "-y", "62", NULL};
 42: 
 43:                 char file[FILENAME_MAX];
 44:                 int fd;
 45: 
 46:                 sprintf(file, "%s.pnm", current_card_label);
 47:                 CSTDEBUG("CMD(scanimage > %s)\n", file);
 48:                 CSTINFO("SCAN(%s)\n", file);
 49: 
 50:                 close(1);
 51:                 close(2);
 52:                 fd = open(file, O_WRONLY | O_CREAT, 0644);
 53:                 dup2(fd, 1);
 54: 
 55:                 ret = execvp(args[0], args);
 56: 
 57:                 exit(1);
 58:         }
 59: 
 60:         return 0;
 61: }
 62: 
 63: /* signal handler for SIGCHLD */
 64: static void
 65: sighandler(int sig, siginfo_t *info, void *arg)
 66: {
 67:         char file[FILENAME_MAX];
 68:         int status;
 69:         int ret;
 70: 
 71:         DFBUserEvent uevt;
 72: 
 73:         if (info->si_pid == pid_child) {
 74:                 ret = waitpid(info->si_pid, &status, 0);
 75:                 if (ret == 0 || ret == -1)
 76:                         return;
 77: 
 78:                 if (WIFEXITED(status) == 1) {
 79:                         if (WEXITSTATUS(status) != 0)
 80:                                 return;
 81:                 } else
 82:                         return;
 83: 
 84:                 /* Convert */
(略)
 98:                 /* Save JPEG file */
 99:                 sprintf(file, "%s.jpg", current_card_label);
100:                 ret = CSTStorage_SaveFile(file);
101: 
102:                 cardres.label = current_card_label;
103:                 cardres.code = ret;
104: 
105:                 /*Event*/
106:                 uevt.clazz = DFEC_USER;
107:                 uevt.type = DUET_INSERTCARD;
108:                 uevt.data = (void *)&cardres;
109:                 CSTDFBCommon_EventPost(DFB_EVENT(&uevt));
110: 
111:                 CSTCard_Exec();
112:         } else
113:                 waitpid(info->si_pid, NULL, 0);
114: }
115: 
116: int
117: CSTCard_Start(void)
118: {
119:         struct sigaction action;
120: 
121:         CSTFUNC();
122: 
123:         memset(&action, 0, sizeof(action));
124: 
125:         action.sa_sigaction = sighandler;
126:         action.sa_flags = SA_SIGINFO;
127:         sigemptyset(&action.sa_mask);
128: 
129:         sigaction(SIGCHLD, &action, NULL);
130: 
131:         CSTCard_Exec();
132: 
133:         return 0;
134: }
135: 
136: void
137: CSTCard_Stop(void)
138: {
139:         if (pid_child != -1)
140:                 kill(pid_child, SIGINT);
141: }
  • CSTCard_Exec関数(リスト1:23~61行目)

    スキャンしたときに保存する画像ファイルのファイル名を取得し(31行目),scanimageの標準出力先をそのファイルに設定してから実行します.こうすることで,scanimageが標準出力した内容をファイルに書き出すことができます.また,標準エラーが出力されないようにファイルディスクリプタをクローズしておきます.

  • sighandler 関数(63~114行目)

    CSTがSIGCHLDを受信したときの振る舞いを記述します.scanimageが正常に終了して発生したシグナルの場合,画像変換,ストレージへ保存,イベントのポストを行います.続いて,次のスキャンの準備を行います.

  • CSTCard_Start 関数(116~134行目)

    シグナルハンドラの設定を行い,CSTCard_Exec関数を呼び出します.この関数コールにより,CST が終了するまでの間つねにスキャン可能状態が続きます.

  • CSTCard_Stop 関数(136~141行目)

    CSTの終了時にscanimageが終了するようにシグナルを送信します.

    スキャン完了イベントを処理するプロシージャの例をリスト2に示します.

●リスト2 名刺スキャナとの連携(cst_start_window.c より抜粋)


248: int
249: CSTStartWindow_Proc(int sheet_id, CSTProcResult_t *result)
250: {
251:         DFBEvent evt;
252:         int ret;
253:         char file[FILENAME_MAX];
254: 
255:         CSTFUNC();
256: 
257:         while(1) {
258:                 DFBCHECK(CSTEvent->WaitForEvent(CSTEvent));
259: 
260:                 while (CSTEvent->HasEvent(CSTEvent) == DFB_OK) {
261:                         DFBCHECK(CSTEvent->GetEvent(CSTEvent, &evt));
262: 
263:                         if (evt.clazz == DFEC_WINDOW) {
(略)
309:                         } else if (evt.clazz == DFEC_USER) {
310:                                 if (sheet_id == 0x0001 &&
311:                                     evt.user.type == DUET_INSERTCARD) {
312:                                         CSTCardResult_t *cardres =
313:                                                 (CSTCardResult_t *)evt.user.data;
314:                                         CSTINFO("EVENT(CARD INSERTION)\n");
315:                                         CSTSetFileLabel(cardres->label);
316:                                         CSTReleaseLabel(cardres->label);
317: 
318:                                         sprintf(file, "%s.jpg", CSTGetFileLabel());
319:                                         CSTDFBCommon_LoadImage(hCard.primary_sf,
320:                                                                file);
321:                                         CSTDFBCommon_UpdateSurface(&hCard,
322:                                                                    hCard.primary_sf);
323: 
324:                                         if (cardres->code == -1)
325:                                                 CSTDFBCommon_PopupMsg(&hBackground, 340,
326:                                                                       CST_STR_DISK, 2);
327:                                         result->type = PROC_RESULT_NEXT;
328:                                         result->page = 0x0002;
329:                                         return CSTStartWindow_Exit(0);
330:                                 }
331:                         }
332:                 }
333:         }
334: }
  • イベントのチェック(リスト2:309~311行目)

    ユーザ定義イベントでかつ名刺がスキャンされたイベントであるかチェックしています.

  • プロシージャ応答(リスト2:327~329行目)

    プロシージャの応答を設定しています.名刺が入力されたので確認画面に遷移するよう応答を返します.

ストレージへのデータ保存

CST では,アンケートデータや名刺画像のファイルをUSBメモリに保存します.保存するためには,USB メモリを書き込み可能でマウントしておかなければなりません.しかし,書き込み可能でマウントしている際にUSB メモリを抜かれると,ファイルシステムがダメージを受けてしまう可能性があります.

これを回避するために,通常時は読み込み専用でマウントしておき,書き込みを行う場合にだけ書き込み可能にリマウントするようにします.これらは,USB メモリにファイルを保存する関数(CSTStorage_SaveFile)のバックエンドで処理することとします(リスト3).

●リスト3 ストレージへのデータ保存(cst_storage.c より抜粋)


(略)
 13: static char *fs_types[] = {
 14:         "vfat",
 15:         "msdos",
 16:         "ext3",
 17:         "ext2",
 18: };
(略)
 22: static int
 23: CSTStorage_FindDisk(void)
 24: {
(略)
 30:         do {
 31:                 sprintf(path, "/sys/block/sd%c", 'a' + i);
 32:                 ret = stat(path, &filestat);
 33:         } while (ret != 0 && ++i < NR_SUPPORT_DISK);
(略)
 39: }
(略)
148: int
149: CSTStorage_SaveFile(char *file)
150: {
151:         char cmd[FILENAME_MAX];
152:         int ret = 0;
153: 
154:         if (minfo.disk != -1) {
155:                 ret = CSTStorage_CheckDisk(&minfo);
156:                 if (ret == 0)
157:                         ret = CSTStorage_RemountDisk(&minfo, CST_MNT_RDWR);
158:                 else {
159:                         CSTStorage_UnmountDisk();
160:                         minfo.disk = -1;
161:                 }
162:         }
163: 
164:         if (minfo.disk == -1 || ret != 0) {
165:                 minfo.disk = CSTStorage_FindDisk();
166:                 if (minfo.disk != -1)
167:                         ret = CSTStorage_MountDisk(&minfo, CST_MNT_RDWR);
168:         }
169: 
170:         if (minfo.disk != -1 && ret == 0) {
171:                 /* copy to disk from work */
172:                 sprintf(cmd, "mkdir -p %s", STORAGE_DIR);
173:                 system(cmd);
174: 
175:                 sprintf(cmd, "cp %s %s", file, STORAGE_DIR);
176:                 system(cmd);
177:                 CSTDEBUG("CMD(%s)\n", cmd);
178:                 CSTINFO("SAVE(%s)\n", file);
179: 
180:                 CSTStorage_RemountDisk(&minfo, CST_MNT_RDONLY);
181:                 return 0;
182:         }
183: 
184:         CSTWARN("Warning: Can't find Storage.\n");
185:         minfo.disk = -1;
186:         return -1;
187: }

  • 対応ファイルシステム(リスト3:13~18行目)

    対応するファイルシステムを設定していますが,実際は/proc/filesystemsの内容をパースして設定するのが望ましいです.カーネルがサポートしていないファイルシステムはマウントできないためです.

  • CSTStorage_FindDisk関数(リスト3:22~39行目)

    USB メモリが接続されているかどうか把握する方法はいろいろあります.Armadillo-500はsysfsに対応しているので,/sys/block/sd* があるかどうかでSCSIディスクの存在を判定しています.

  • CSTStorage_SaveFile関数(リスト3:148~187行目)

    直前の状態がマウントされていてデバイスが存在する場合は,書き込み可能にリマウントを行い,デバイスが存在しない場合はアンマウントします.マウントされていない場合,再度マウントを試みファイルを保存します.保存後,読み込み専用でリマウントします.

    ここでは,ディレクトリの作成やファイルのコピーをsystem 関数を使い実装していますが,mkdirやcpに依存させたくない場合は,coreutilsなどのソースを参考に実装すると良いでしょう.

画像描画の高速化

バックグラウンドや各リソースなどの画像を描画する場合,ファイルを読み込みそのデータを縮小/ 拡大するといった処理が発生します.Armadillo-500 は組み込みボードの中では高速な部類に入りますが,こうした処理がひんぱんに発生するようだとどうしてもストレスを感じるようになります.

これを改善するために,CST ではサーフェイスを3枚ずつ持たせ,一度使用したリソースを解放することなく使用し続けるようにしています.具体的には,画像の切り替えが発生してしまうものに対して,表示サーフェイスと予備のサーフェイス2枚を持ち,各リソースを初期化時に予備サーフェイスへ読み込ませ,必要に応じて表示サーフェイスにコピーするようにします(リスト4~6).

●リスト4 WindowHandle 構造体(cst_dfb_common.h より抜粋)


46: typedef struct WindowHandle {
47:         IDirectFBWindow *win;
48:         IDirectFBSurface *master_sf;
49:         IDirectFBSurface *primary_sf;
50:         IDirectFBSurface *secondary_sf;
51:         DFBWindowID id;
52:         EventProc *proc;
53:         char *img_primary;
54:         char *img_secondary;
55:         int data;
56: } HWND_t;
  • WindowHandle構造体(リスト4:46~56行目)

    ボタンなどのリソースをウィンドウ化するときに使用する構造体です.master_sfメンバが表示サーフェィスで,primary_sfとsecondary_sfメンバが予備サーフェイスになります.このウィンドウのイベントを扱う場合は,idとprocメンバを使用します.

●リスト5 画像描画(cst_dfb_common.c より抜粋)


 70: int
 71: CSTDFBCommon_UpdateSurface(HWND_t *hWnd, IDirectFBSurface *surface)
 72: {
 73:         hWnd->master_sf->Blit(hWnd->master_sf, surface, NULL, 0, 0);
 74:         return 0;
 75: }
 76: 
 77: int
 78: CSTDFBCommon_LoadImage(IDirectFBSurface *surface, char *image_file)
 79: {
 80:         IDirectFBImageProvider *provider;
 81: 
 82:         DFBCHECK(dfb->CreateImageProvider(dfb, image_file, &provider));
 83:         provider->RenderTo(provider, surface, NULL);
 84:         provider->Release(provider);
 85: 
 86:         return 0;
 87: }
(略)
 98: int
 99: CSTDFBCommon_CreateWindow(HWND_t *hWnd,
100:                           int x, int y, int width, int height, EventProc *proc)
101: {
102:         DFBWindowDescription win_desc;
103:         DFBSurfaceDescription sf_desc;
104: 
105:         win_desc.flags = (DWDESC_POSX | DWDESC_POSY |
106:                           DWDESC_WIDTH | DWDESC_HEIGHT | DWDESC_CAPS);
107:         win_desc.posx = x;
108:         win_desc.posy = y;
109:         win_desc.width = width;
110:         win_desc.height = height;
111:         win_desc.caps = DWCAPS_ALPHACHANNEL;
112: 
113:         DFBCHECK(layer->CreateWindow(layer, &win_desc, &hWnd->win));
114:         hWnd->win->GetSurface(hWnd->win, &hWnd->master_sf);
115: 
116:         /* create surfaces */
117:         sf_desc.flags = (DWDESC_WIDTH | DWDESC_HEIGHT);
118:         sf_desc.width = width;
119:         sf_desc.height = height;
120: 
121:         DFBCHECK(dfb->CreateSurface(dfb, &sf_desc, &hWnd->primary_sf));
122:         DFBCHECK(dfb->CreateSurface(dfb, &sf_desc, &hWnd->secondary_sf));
123: 
124:         hWnd->win->SetOpacity(hWnd->win, 0x00);
125:         hWnd->win->GetID(hWnd->win, &hWnd->id);
126: 
127:         hWnd->proc = proc;
128: 
129:         return 0;
130: }

  • CSTDFBCommon_UpdateSurface関数(リスト5:70~75行目)

    指定するサーフェイスを表示サーフェイスにコピーします.

  • CSTDFBCommon_LoadImage関数(リスト5:77~87行目)

    指定するサーフェイスへ画像を読み込ませます.

  • CSTDFBCommon_CreateWindow関数(リスト5:98~130行目)

    ウィンドウを作成し,各サーフェイスを取得します.作成したウィンドウは,実際に表示させるときまで非表示にしておきます.

●リスト6 「SKIP」ボタンのウィンドウ処理(cst_start_window.c より抜粋)


 15: static HWND_t hSkipButton = {
 16:         .img_primary = RESOURCE_DIR"icon-skip-up.jpg",
 17:         .img_secondary = RESOURCE_DIR"icon-skip-down.jpg",
 18: };
(略)
 36: static int
 37: CSTStartWindow_ButtonProc(HWND_t *hWnd, DFBWindowEvent *evt)
 38: {
 39:         static int clicked = 0;
 40: 
 41:         switch (evt->type) {
 42:         case DWET_GOTFOCUS:
 43:                 CSTDFBCommon_UpdateSurface(hWnd, hWnd->secondary_sf);
 44:                 CSTDFBCommon_UpdateBackground(&hBackground);
 45:                 break;
 46:         case DWET_LOSTFOCUS:
 47:                 CSTDFBCommon_UpdateSurface(hWnd, hWnd->primary_sf);
 48:                 CSTDFBCommon_UpdateBackground(&hBackground);
 49:                 if(clicked)
 50:                         clicked = 0;
 51:                 break;
(略)
 69: }
(略)
133: int
134: CSTStartWindow_PrepareResource(void)
135: {
(略)
140:         CSTDFBCommon_CreateWindow(&hSkipButton,
141:                                   VVAL(520), VVAL(430), VVAL(120), VVAL(50),
142:                                   CSTStartWindow_ButtonProc);
143:         CSTDFBCommon_LoadImage(hSkipButton.primary_sf,
144:                                hSkipButton.img_primary);
145:         CSTDFBCommon_LoadImage(hSkipButton.secondary_sf,
146:                                hSkipButton.img_secondary);
147:         CSTDFBCommon_UpdateSurface(&hSkipButton, hSkipButton.primary_sf);
(略)
184: }
185: 
186: int
187: CSTStartWindow_Create(int sheet_id)
188: {
(略)
196:                 CSTDFBCommon_WindowActivate(&hSkipButton);
197:                 CSTDFBCommon_EventRegistration(&hSkipButton);
(略)
224:         CSTDFBCommon_UpdateBackground(&hBackground);
225: 
226:         return 0;
227: }
228: 
229: static int
230: CSTStartWindow_Exit(int retval)
231: {
(略)
239:         CSTDFBCommon_WindowInactivate(&hSkipButton);
(略)
246: }

リスト6 では,スタート画面の「SKIP」ボタンのウィンドウ処理について見てみます.

  • 画像ファイルの指定(リスト6:15~18行目)

    hSkipButtonには,デフォルトで画像ファイルのパスを設定しています.ここで指定される画像ファイルが予備サーフェイスへ読み込まれます.

  • チェインプロシージャ(リスト6:36~69行目)

    「SKIP」ボタン用のチェインプロシージャです.「SKIP」ボタンのウィンドウイベントが発生した場合に,対応した処理を行います.

    まず,フォーカスを得た場合(42~44行目)は,secondary_sfを表示サーフェイスにコピーし画面を更新しています.フォーカスを失った場合(46 ~48行目)は,primary_sfを表示サーフェイスにコピーし画面を更新しています.

  • ボタンウィンドウの作成(リスト6:140~147行目)

    ウィンドウを作成し,各予備サーフェイスに画像を読み込ませています.その後に,primary_sfを表示サーフェィスにコピーしています.この段階では,ウィンドウはアクティブになっていません.

  • 作成したウィンドウ表示(リスト6:186~227行目)

    スタート画面を表示する関数の中で,作成した各ウィンドウを表示するようにしています.196行目で「SKIP」ボタンウィンドウをアクティブにし,197行目でウィンドウイベントを取得できるように登録しています.

  • ウィンドウを表示(リスト6:229~246行目)

    スタート画面を非表示にする関数の中で,「SKIP」ボタンウィンドウを非表示にしています.

イベントプロシージャと画面遷移

イベントループを作成する場合,イベントを処理するための分岐が複雑になりがちです.これを改善するために,CST では各画面ごとにイベントプロシージャを持たせ,メインループでは画面の遷移情報のみを扱うようにしています.このようにすることで,画面が増えたとしても容易に拡張できるようになります(リスト7)

●リスト7 イベントプロシージャ(cst_start_window.c より抜粋)


248: int
249: CSTStartWindow_Proc(int sheet_id, CSTProcResult_t *result)
250: {
251:         DFBEvent evt;
252:         int ret;
253:         char file[FILENAME_MAX];
254: 
255:         CSTFUNC();
256: 
257:         while(1) {
258:                 DFBCHECK(CSTEvent->WaitForEvent(CSTEvent));
259: 
260:                 while (CSTEvent->HasEvent(CSTEvent) == DFB_OK) {
261:                         DFBCHECK(CSTEvent->GetEvent(CSTEvent, &evt));
262: 
263:                         if (evt.clazz == DFEC_WINDOW) {
264:                                 if (sheet_id == 0x0001 &&
265:                                     evt.window.window_id == hSkipButton.id) {
266:                                         ret = hSkipButton.proc(&hSkipButton,
267:                                                                &evt.window);
268:                                         if (ret == 1) {
269:                                                 CSTSurveyWindow_PrepareCard(NULL);
270:                                                 result->type = PROC_RESULT_NEXT;
271:                                                 result->page = PROC_PAGE_FINISH;
272:                                                 return CSTStartWindow_Exit(0);
273:                                         }
274:                                 }
(略)
331:                         }
332:                 }
333:         }
334: }
  • イベントプロシージャ (リスト7:257~333行目)

    イベントが発生するまでスレッドをアイドル状態にします(258行目).「SKIP」ボタンのイベントかを判定し,チェインプロシージャをコールします(263 ~ 267 行目).ボタンがクリックされた場合は,メインループへ戻ります.

また,「戻る」ボタンを実装するにあたっては,現在までの遷移情報を保持する必要があります.この機能は,ページごとのID情報をスタックさせることで逆に辿ることができるよう実装しています(リスト8).

●リスト8 ページスタックとメインループ(cst.c より抜粋)


 24: typedef unsigned int page_t;
 25:
 26: enum {
 27:         PAGE_TYPE_MASK         = 0xffff0000,
 28:         PAGE_TYPE_INIT         = 0x00000000,
 29:         PAGE_TYPE_START        = 0x00010000,
 30:         PAGE_TYPE_SURVEY       = 0x00020000,
 31:         PAGE_TYPE_END          = 0x00030000,
 32: };
 33:
 34: #define PAGESTACK_SIZE (64)
 35:
 36: static page_t PageStackBuffer[PAGESTACK_SIZE];
 37: static page_t *PageStack;
(略)
194: static void
195: PageStackPush(page_t id)
196: {
197:         *(--PageStack) = id;
198: }
199:
200: static page_t
201: PageStackPop(void)
202: {
203:         return *(PageStack++);
204: }
(略)
286: int
287: main(int argc, char *argv[])
288: {
289:         int flag_exit = 0;
290:         page_t page_id;
291:         int sheet_id;
292:         CSTProcResult_t result;
293:         int ret;
(略)
315:         while (!flag_exit) {
316:                 PageStackPush(page_id);
317:                 PageStackDump();
318:
319:                 switch (page_id & PAGE_TYPE_MASK) {
320:                 case PAGE_TYPE_INIT:
321:                         page_id = (PAGE_TYPE_START | PROC_PAGE_START);
322:                         break;
323:                 case PAGE_TYPE_START:
324:                         sheet_id = (page_id & ?PAGE_TYPE_MASK);
325:
326:                         if (sheet_id == PROC_PAGE_START) {
327:                                 CSTINFO("---\n");
328:                                 CSTCleanWorkDir();
329:                                 CSTSurveyDataReset();
330:                                 CSTSetFileLabel(NULL);
331:                         }
332:
333:                         CSTStartWindow_Create(sheet_id);
334:                         CSTStartWindow_Proc(sheet_id, &result);
335:                         CSTDEBUG("ProcResult(ID: %08x type: %04x,"
336:                                  "page: %04x, data: %p)\n",
337:                                  page_id, result.type,
338:                                  result.page, result.data);
339:
340:                         switch (result.type) {
341:                         case PROC_RESULT_NEXT:
342:                                 if (result.page == PROC_PAGE_FINISH) {
343:                                         char *label = CSTGetFileLabel();
344:                                         if (label[0] == '\0') {
345:                                                 label = CSTRequestLabel();
346:                                                 CSTSetFileLabel(label);
347:                                                 CSTReleaseLabel(label);
348:                                         }
349:                                         page_id = (PAGE_TYPE_SURVEY |
350:                                                    CSTSurvey_GetTopSheetID());
351:                                 } else
352:                                         page_id = (PAGE_TYPE_START |
353:                                                    result.page);
354:                                 break;
355:
356:                         case PROC_RESULT_CANCEL:
357:                                 PageStackPop();
358:                                 page_id = PageStackPop();
359:                                 break;
360:
361:                         case PROC_RESULT_EXIT:
362:                                 flag_exit = 1;
363:                                 break;
364:                         default:
365:                                 break;
366:                         }
367:                         break;
(略)
416:                 }
417:         }
  • ページIDスタック(リスト8:194~204行目)

    スタックのアクセスメソッドとして,Push とPop のみ実装しています.各画面にID を持たせることで,遷移情報を保持するように実装しています.

  • メインループ(リスト8:315~417行目)

    遷移情報に基づいて,各ページの画面作成,イベントプロシージャコールを行っています.必要に応じて初期化なども行います.

ファイル名の命名規則

ファイル名の一部を連番で付けようとした場合,対象ディレクトリにあるファイル名をすべて取得して比較する必要があります.この処理は意外と手間がかかるため,CST での出力ファイル(アンケートデータや名刺画像ファイル)名はランダムに決定しています.

まず,ランダムファイル名を生成し,対象ディレクトリに一致するファイルがあるかチェックします.ない場合はそのファイルを使用するようにし,ある場合には再度ランダムファイル名を生成し同様に繰り返しています(リスト9).

●リスト9 ファイル名関連(cst.c より抜粋)


118: static char FileLabel[FILENAME_MAX];
(略)
135: char *
136: CSTRequestLabel(void)
137: {
138:         static int init = 1;
139:         char *label;
140:         int ret;
141:
142:         if (init) {
143:                 srand(time(NULL));
144:                 init = 0;
145:         }
146:
147:         label = (char *)malloc(FILENAME_MAX);
148:         if (!label)
149:                 return NULL;
150:
151:         while(1) {
152:                 char file[FILENAME_MAX];
153:                 char *macaddr = CSTGetMACaddrString();
154:                 char id[FILENAME_MAX];
155:
156:                 sprintf(id, "ID_%6s_%d",&macaddr[7], rand());
157:                 CSTINFO("ID(%s)\n", id);
158:
159:                 sprintf(label, STORAGE_DIR"%s", id);
160:
161:                 sprintf(file, "%s.txt", label);
162:                 ret = CSTStorage_CheckFile(file);
163:                 if (ret == 1)
164:                         continue;
165:
166:                 sprintf(file, "%s.jpg", label);
167:                 ret = CSTStorage_CheckFile(file);
168:                 if (ret == 1)
169:                         continue;
170:
171:                 sprintf(label, WORK_DIR"%s", id);
172:                 break;
173:         }    
174:
175:         return label;
176: }
177:
178: void
179: CSTReleaseLabel(char *label)
180: {
181:         free(label);
182: }

なお,この方法が使用できるのは,対象ディレクトリにファイルを作成しようとするプロセスが唯一である場合のみです.複数のプロセスが同様にファイルを作成する場合,何らかの方法でファイル名が重複しないような機構を作る必要があります.

  • ランダムファイル名の生成(リスト9:142~145,156行目)

    CST では,rand 関数を使用してランダムファイル名を生成しています.
    初回は,疑似乱数整数系列の新しい種を設定するsrand 関数を呼び出しています.この種には,1970年1月1日からの経過秒数を設定することで毎回違う乱数テーブルを使用するようにしています.

  • 重複ファイル名のチェック(リスト9:161~169行目)

    ファイルの重複をチェックしています.CST では,「.txt」と「.jpg」を保存するため,この2 つの拡張子に対して重複チェックを行っています.

アンケートシートフォーマット

アンケートシートファイル(survey.txt)は,図2のようなフォーマットで記述します.今回作成するCSTのアンサーデータには,「その他」などのユーザ入力文字列のサポートを行っていませんが,これを実装しようとしたときにでも対応できるようにしています.

●図2 アンケートシート


#
# Question[シート設定]:質問文
#       質問に関する設定を行います
# Default[振る舞い設定]
#       解答に対する規定の振る舞いを設定します.
# Answer[振る舞い設定※]:解答文
#       解答に対する例外の振る舞いを設定します.
#       ※単一選択時のみ有効
#
# [シート設定]
#       S:単一選択(デフォルト)
#       M:複数選択可
# [振る舞い設定]
#       N:次のシートへ移行します(デフォルト)
#       J#:指定のシートへ移行します
#                Ex. J2 Sheet2へ移行します
Sheet1{
        Question[M]:普段購読している雑誌は何ですか?
        Default[N]
        Answer[]:Interface
        Answer[]:DesignWave
        Answer[]:トランジスタ技術
        Answer[]:SoftwareDesign
        Answer[]:日経ソフトウェア
        Answer[]:日経ネットワーク
        Answer[]:日経エレクトロニクス
        Answer[]:組み込みプレス
        Answer[]:EDN
        Answer[]:NEP
}

アンケートデータ

アンケートデータは,CSV 形式で保存します.CSV 形式とは,1 行の中にカンマ「,」を区切り文字とした複数要素のデータを記録するデータフォーマットです.一般的には,表計算ソフトやデータベースのデータ移動に使用されます.CSV 形式にすることで,Microsoft Excel などを使って簡単に集計することが可能となります(図3).

●図3 アンケートデータ例


2007/09/04_16:50:54,ID_0A0020_967546136,[1],1,1,0,0,1,0,0,0,0,0,[2],0,0,1,0,0,0,0,0,0,・・・
       日時             アンケートID       質問1の解答             質問2の解答

Makefile

CST をビルドするためのMakefile(リスト10)です.CST では,DirectFB のライブラリを使用するため,リンクパスやインクルードパスの追加やリンクライブラリの追加を行う必要があります.

●リスト10 Makefile


 1: -include $(CONFIG_CONFIG)
 2:
 3: DIRECTFB_DIR = $(ROOTDIR)/lib/directfb/preinstall/
 4:
 5: TARGET = cst
 6:
 7: SRCS = cst.c cst_dfb_common.c cst_survey.c cst_survey_window.c
 8: SRCS += cst_start_window.c cst_end_window.c cst_card.c cst_storage.c
 9:
10: OBJS = $(SRCS:.c=.o)
11:
12: CFLAGS += -Wall -O2 -I$(DIRECTFB_DIR)/usr/local/include/directfb
13: LDFLAGS += -L$(DIRECTFB_DIR)/usr/local/lib
14: LDLIBS += -ldirectfb -ldirect -lfusion
15:
16: ifdef CONFIG_USER_BUSYBOX_SYSLOGD
17: CFLAGS += -DUSE_SYSLOG
18: endif
19:
20: # CFLAGS += -DDEBUG_ENABLE
21:
22: all: $(TARGET)
23:
24: $(TARGET): $(DEPS) $(OBJS)
25:         $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)
26:
27: romfs: $(TARGET)
28:         $(ROMFSINST) /bin/$(TARGET)
29:         $(ROMFSINST) data /usr/local/share/cst
30:
31: clean:
32:         rm -f *.o $(TARGET)
  • コンフィギュレーションの内容を読み込む(リスト10:1,16~18行目)

    CSTは,Busyboxのsyslogを使用してログを閲覧できるようにします.ユーザーランド全体のコンフィギュレーションを取得するために$(CONFIG_CONFIG)をインクルードさせています.また,行頭の「-」は,インクルードに失敗したときにエラーを無視するように設定する識別子です.

  • サーチパスの指定(リスト10:3,12~13行目)

    DirectFB のライブラリやインクルードファイルを使用するため,Atmark Distに組み込まれているDirectFB のディレクトリを指定します.「-I」でインクルードファイルを検索するディレクトリのパスを追加し,「-L」でライブラリファイルを検索するディレクトリのパスを追加します.

  • リンクライブラリの指定(リスト10:14行目)

    リンク時にDirectFBのライブラリをリンクするように指定します.

  • インストール処理(リスト10:27~29行目)

    make romfsが行われたときに,CSTとリソースデータがインストールされるようにします.ここでは,リソースデータはcst/dataディレクトリに保存します.

先頭 Part 5 Part 7