BBBでACR122Uを動かしたい
BBBでACR122U(NFCカードリーダ)つないで、pcsc_scanするまでのメモ
pcscdをインストールする
$ sudo apt-get install pcscd
動作確認(NG)
$ sudo pcscd -d -a -f 00000000 pcscdaemon.c:240:main() pcscd set to foreground with debug send to stdout 00002456 configfile.l:254:DBGetReaderListDir() Parsing conf directory: /etc/reader.conf.d 00001351 configfile.l:307:DBGetReaderList() Parsing conf file: /etc/reader.conf.d/libccidtwin 00001649 configfile.l:266:DBGetReaderListDir() Skipping non regular file: . 00000855 configfile.l:266:DBGetReaderListDir() Skipping non regular file: .. 00002992 pcscdaemon.c:545:main() pcsc-lite 1.8.10 daemon ready. 00022437 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x1D6B, PID: 0x0002, path: /dev/bus/usb/001/001 00003044 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x1D6B, PID: 0x0002, path: /dev/bus/usb/001/001 00002742 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x1A40, PID: 0x0101, path: /dev/bus/usb/001/002 00003023 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x0411, PID: 0x01A2, path: /dev/bus/usb/001/003 00002633 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x1A40, PID: 0x0101, path: /dev/bus/usb/001/002 00002614 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x072F, PID: 0x2200, path: /dev/bus/usb/001/004 00001405 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x072F, PID: 0x2200, path: /dev/bus/usb/001/004 00001227 hotplug_libudev.c:321:HPAddDevice() Adding USB device: ACS ACR122U PICC Interface 00001368 readerfactory.c:989:RFInitializeReader() Attempting startup of ACS ACR122U PICC Interface 00 00 using /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Linux/libccid.so 00003569 readerfactory.c:874:RFBindFunctions() Loading IFD Handler 3.0 00001718 ifdhandler.c:1910:init_driver() Driver version: 1.4.15 00010156 ifdhandler.c:1927:init_driver() LogLevel: 0x0003 00001240 ifdhandler.c:1938:init_driver() DriverOptions: 0x0004 00003629 ifdhandler.c:83:CreateChannelByNameOrChannel() Lun: 0, device: usb:072f/2200:libudev:0:/dev/bus/usb/001/004 00001643 ccid_usb.c:281:OpenUSBByName() Using: /usr/lib/pcsc/drivers/ifd-ccid.bundle/Contents/Info.plist 00009564 ccid_usb.c:299:OpenUSBByName() ifdManufacturerString: Ludovic Rousseau (ludovic.rousseau@free.fr) 00000993 ccid_usb.c:300:OpenUSBByName() ifdProductString: Generic CCID driver 00001749 ccid_usb.c:301:OpenUSBByName() Copyright: This driver is protected by terms of the GNU Lesser General Public License version 2.1, or (at your option) any later version. 00021725 ccid_usb.c:582:OpenUSBByName() Can't claim interface 1/4: -6 00001490 ccid_usb.c:191:close_libusb_if_needed() libusb_exit 00002553 ifdhandler.c:117:CreateChannelByNameOrChannel() failed 00001181 readerfactory.c:1020:RFInitializeReader() Open Port 0x200000 Failed (usb:072f/2200:libudev:0:/dev/bus/usb/001/004) 00000928 readerfactory.c:312:RFAddReader() ACS ACR122U PICC Interface init failed. 00000907 readerfactory.c:535:RFRemoveReader() UnrefReader() count was: 1 00000795 readerfactory.c:1040:RFUnInitializeReader() Attempting shutdown of ACS ACR122U PICC Interface 00 00. 00000771 readerfactory.c:911:RFUnloadReader() Unloading reader driver. 00001460 hotplug_libudev.c:391:HPAddDevice() Failed adding USB device: ACS ACR122U PICC Interface 00003804 hotplug_libudev.c:269:get_driver() Looking for a driver for VID: 0x1A40, PID: 0x0101, path: /dev/bus/usb/001/002 exit ^C13467170 pcscdaemon.c:726:signal_trap() Received signal: 2 00000160 pcscdaemon.c:745:signal_trap() Preparing for suicide ^C00632125 pcscdaemon.c:726:signal_trap() Received signal: 2 00000189 readerfactory.c:1273:RFCleanupReaders() entering cleaning function 00000103 winscard_svc.c:130:ContextsDeinitialize() remaining threads: 0 00000074 pcscdaemon.c:670:at_exit() cleaning /var/run/pcscd
どうやら、別の何かがデバイスをつかんでいるぽい? https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=57037 ここの「Thu Mar 13, 2014 8:58 am」の書き込みによると、
Also it was not very clear that you need to prevent modprobe from autoload pn533 and nfc modules. To do that, create /etc/modprobe.d/blacklist-libnfc.conf with this content: blacklist pn533 blacklist nfc
ということらしい。 lsmodたたいてみると、確かにこのモジュールがロードされてる。 上記の通りブラックリストに指定して再起動かければpcsc_scanでカードが読み込める。
BBBでwifi接続する
BBBにWifiのドングル指して使いたい。 BBBで利用しているイメージはこれ
まず、状態確認
$ sudo lshw -C network
*-network:0 DISABLED
description: Wireless interface
physical id: 1
bus info: usb@1:1.1
logical name: wlan0
serial: cc:e1:d5:17:9e:f7
capabilities: ethernet physical wireless
configuration: broadcast=yes driver=rt2800usb driverversion=3.14.37-ti-r57 firmware=N/A link=no multicast=yes wireless=IEEE 802.11bgn
DISABLED状態になっているので、起こしてあげる
$ sudo ifconfig wlan0 up
$ ifconfig
wlan0 Link encap:Ethernet HWaddr cc:e1:d5:17:9e:f7
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
apを検索する
$ sudo iwlist wlan0 scan
ap一覧から接続したいapのESSIDをメモっておく pskパスワードを生成する
$ wpa_passphrase SSID WPA_PASS
network={
ssid="SSID"
#psk="WPA_PASS"
psk=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
}
/etc/network/interfacesにネットワーク設定を追記する
$ sudo vi /etc/network/interfaces
auto wlan0
iface wlan0 inet dhcp
wpa-ssid "SSID"
wpa-psk "XXXXXXXXXXXXXXXXXX(さっきのやつ)"
無線LANの有効化
$ sudo ifup wlan0
pingで疎通確認
$ ping google.com
$ ping 8.8.8.8
DNSの設定が上手くいっていない場合は次の設定を行う 192.168.65.1は自宅の環境。8.8.8.8とかにするといいんじゃないかな。
$ sudo vi /etc/resolvconf/resolv.conf.d/tail
# nameserver 192.168.1.1
nameserver 192.168.65.1
$ sudo resolvconf -u
$ cat /etc/resolv.conf
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.0.1
domain localdomain
search localdomain
nameserver 192.168.65.1
$ sudo service networking restart
$ ping google.com
これでつながった。
いろいろ調べていると、/etc/resolvconf/resolv.conf.d/baseにDNSの設定を書き込むのが主流っぽいんだけど、tailにいろいろ予め書かれているからかresolvconf -u
しても設定が変更されなかった。
そのため、直接tailをいじってるかんじです。
参考にさせていただいたサイトとか
http://unix.stackexchange.com/questions/128220/how-do-i-set-my-dns-on-ubuntu-14-04 http://qiita.com/Hiroshi-Ito/items/6c870a3bab55bd06cf57 http://bty.sakura.ne.jp/wp/archives/754
virtual boxでSDカード認識したいとき
これが参考になる。
MacBook ProのSDカードスロットをVirtualBoxのrawdiskとして使う in Qiita
VirtualBox側にraw.vmdkを読み込ませるときにエラーになるときは、もう一度参照先のディスクのマウント状態を確認すること。 手元では、作業途中に何度か自動でマウントされていてエラーもらうことがあった。
Macでgoのクロスコンパイルする
RaspPiにFreeBSD乗せて遊んでて、そこ向けにgoのバイナリ作りたい。
goをinstallする。
brewで--with-cc-all
をつけてインストールした。
この辺を参考に。
GoのソースをMacでクロスコンパイルし、CentOS5でCloudWatchのカスタムメトリクスを取得する
環境がいまいちなのか、記事の通りに行かなかったので以下を追加
こんなエラーが出てた
go build runtime: freebsd/arm must be bootstrapped using make.bash
どうもクロスコンパイル用の環境が整っていないらしい。
以下で環境が整う。 GOOSとGOARCHを変えれば、他環境向けにも対応できる。
$ cd /usr/local/go/src $ sudo GOOS=freebsd GOARCH=arm CGO_ENABLED=0 ./make.bash --no-clean
この後、helloworldをクロスコンパイルするといけた。
$ GOOS=freebsd GOARCH=arm go build hello.go
PUSHっぽい何かを実現する
あくまでもローカルな環境向け。基本的にはGCMをつかうべきです。 WebSocket覚えたし、使ってみたかってん。ぐらいのなにか。
Android側にPushを受けるサービスを実装する
アプリケーション内にServiceを実装し、ws://IPADDRESS:PORT/path/to/url に対しWebSocketを張る部分を作る。 WebSocketの実装には、 Java-WebSocket というライブラリを利用しました。
まず、build.gradleにライブラリをインポートする部分を書きます。
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'org.java-websocket:Java-WebSocket:1.3.0@jar' }
続いて、Activityの実装。 Activityには2個ボタンを用意して、ServiceのStop,Startの制御とServerへのメッセージを投げる部分をハンドリングしています。
@Override public void onClick(View view) { Intent intent = new Intent(); intent.setClass(getApplicationContext(), PushService.class); switch (view.getId()){ case R.id.button1: { if (!isServiceRunning("com.kobashin.sample.PushService")) { isRunning = true; PushService.startForegroundService(getApplicationContext(), SERVER_URI); } else { isRunning = false; PushService.stopForegroundService(getApplicationContext()); } break; } case R.id.button2: { PushService.sendMessageToServer(getApplicationContext(), "Hello Server"); } } } private boolean isServiceRunning(String className){ boolean ret = false; ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Integer.MAX_VALUE); for(ActivityManager.RunningServiceInfo info : services){ if(info.service.getClassName().equals(className)){ ret = true; } } return ret; }
isServiceRunning()メソッドはServiceの実行状態の確認用のメソッドです。 Serviceへの通知にはService側にpublic staticで実装したメソッド(PushService.startForegroundServiceなど)を利用しています。
さらに、Service側の実装。ぺたっと全部。
public class PushService extends Service { private static String LOG_TAG = "PushService"; private NotificationManager mNotificationManager; private final IBinder mBinder = new LocalBinder(); private static final String ACTION_START_FOREGROUND = "com.kobashin.sample.START_FOREGROUND"; private static final String ACTION_STOP_FOREGROUND = "com.kobashin.sample.STOP_FOREGROUND"; private static final String ACTION_SEND_MESSAGE_TO_SERVER = "com.kobashin.sample.SEND_MESSAGE_TO_SERVER"; private WebSocketClient mWebSocketClient; private HandlerThread mHandlerThread; private MsgHandler mMsgHandler; private enum NOTIFY_ID { NOTIFICATION_STARTED(100), NOTIFICATION_END(101), NOTIFICATION_RECEIVE_MESSAGE(102); NOTIFY_ID(int i) { id = i; } private int id; public int getId() { return id; } } private class MsgHandler extends Handler { public MsgHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: //init handleInitAction(msg); break; case 1: //onMessage handleMessageAction(msg); break; case 2: //send message handleSendMessageAction(msg); default: break; } } private void handleSendMessageAction(Message msg) { try { mWebSocketClient.send((String) msg.obj); } catch (NotYetConnectedException e) { e.printStackTrace(); } } private void handleMessageAction(Message msg) { showNotification(NOTIFY_ID.NOTIFICATION_RECEIVE_MESSAGE.getId(), "GetMessage", (String) msg.obj, false); } private void handleInitAction(Message msg) { try { mWebSocketClient = new WebSocketClient(new URI((String) msg.obj)) { @Override public void onOpen(ServerHandshake handshakedata) { Log.i(LOG_TAG, "[WebSocketClient] onOpen"); } @Override public void onMessage(String message) { Log.i(LOG_TAG, "[WebSocketClient] onMessage: " + message); Message msg = mMsgHandler.obtainMessage(); msg.what = 1; // onMessage msg.obj = message; mMsgHandler.sendMessage(msg); } @Override public void onClose(int code, String reason, boolean remote) { Log.i(LOG_TAG, "[WebSocketClient] onClose: code(" + code + ") reason(" + reason + ") remote(" + remote + ")"); } @Override public void onError(Exception ex) { Log.i(LOG_TAG, "[WebSocketClient] onError", ex); } }; mWebSocketClient.connect(); } catch (URISyntaxException e) { e.printStackTrace(); // TODO: stop & notify } } } public static void startForegroundService(Context context, String serverUri) { Intent intent = new Intent(context, PushService.class); intent.putExtra("SERVER_URI", serverUri); intent.setAction(ACTION_START_FOREGROUND); context.startService(intent); } public static void stopForegroundService(Context context) { Intent intent = new Intent(context, PushService.class); intent.setAction(ACTION_STOP_FOREGROUND); context.startService(intent); } public static void sendMessageToServer(Context context, String message) { Intent intent = new Intent(context, PushService.class); intent.putExtra("SEND_MESSAGE", message); intent.setAction(ACTION_SEND_MESSAGE_TO_SERVER); context.startService(intent); } public PushService() { } public class LocalBinder extends Binder { PushService getService() { return PushService.this; } } @Override public void onCreate() { super.onCreate(); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent.getAction().equals(ACTION_START_FOREGROUND)) { startForeground(NOTIFY_ID.NOTIFICATION_STARTED.getId(), createNotification( NOTIFY_ID.NOTIFICATION_STARTED.getId(), true)); mHandlerThread = new HandlerThread("push_service", Process.THREAD_PRIORITY_FOREGROUND); mHandlerThread.start(); mMsgHandler = new MsgHandler(mHandlerThread.getLooper()); Message msg = mMsgHandler.obtainMessage(); msg.what = 0; //init msg.obj = intent.getStringExtra("SERVER_URI"); mMsgHandler.sendMessage(msg); // init } else if (intent.getAction().equals(ACTION_STOP_FOREGROUND)) { stopForeground(true); stopSelf(); } else if (intent.getAction().equals(ACTION_SEND_MESSAGE_TO_SERVER)) { Message msg = mMsgHandler.obtainMessage(); msg.what = 2; //send message msg.obj = intent.getStringExtra("SEND_MESSAGE"); mMsgHandler.sendMessage(msg); } return START_STICKY; } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { super.onDestroy(); cancelNotification(NOTIFY_ID.NOTIFICATION_STARTED.getId()); mWebSocketClient.close(); mHandlerThread.quit(); } private void showNotification(int id, String title, String msg, boolean isOnGoing) { PendingIntent pendingIntent = PendingIntent .getActivity(getApplicationContext(), 0, new Intent(), 0); Notification.Builder builder = new Notification.Builder(getApplicationContext()) .setContentTitle(title) .setContentText(msg) .setSmallIcon(R.mipmap.ic_notificataion) .setColor(0x4fc3f7) .setPriority(Notification.PRIORITY_HIGH) .setFullScreenIntent(pendingIntent, true) .setOngoing(isOnGoing) // can't dismiss .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); mNotificationManager.notify(id, builder.build()); } private Notification createNotification(int id, boolean isOnGoing) { PendingIntent pendingIntent = PendingIntent .getActivity(getApplicationContext(), 0, new Intent(), 0); Notification.Builder builder = new Notification.Builder(getApplicationContext()) .setContentTitle("PushService") .setContentText("push service started") .setSmallIcon(R.mipmap.ic_notificataion) .setColor(0x4fc3f7) .setPriority(Notification.PRIORITY_HIGH) .setFullScreenIntent(pendingIntent, true) .setOngoing(isOnGoing) // can't dismiss .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); return builder.build(); } private void cancelNotification(int id) { mNotificationManager.cancel(id); } }
handleInitAction()内でWebsocketのinit処理を行っています。
WebSocketClientはライブラリに用意されたクラスで、作成時にURI(ここでは ws://192.168.11.4:5000/socket_handset/bing
)を渡します。
Soceketを閉じるときはWebSocketClient.close()を呼び出します。
作成したSocketにメッセージを受けると、WebSocketClient.onMessage()を受けることができます。
Websocketからメッセージを受けると、Notificationを出すように実装してあるので、Socketにサーバーからメッセージを入れればPUSHっぽいことが実現できます。
Server側の実装
#!/usr/bin/env python # -*- coding: utf-8 -*- from flask import Flask, request from flask_sockets import Sockets from werkzeug.exceptions import abort from gevent import pywsgi from geventwebsocket.handler import WebSocketHandler from geventwebsocket.websocket import WebSocketError app = Flask(__name__) sockets = Sockets(app) ws_list = set() @sockets.route('/socket_handset/bind') def bind_sockets(ws): ''' handsetとのwebsocket接続用 :param ws: :return: ''' if not ws: abort(400) ws_list.add(ws) remove = set() print 'adding', len(ws_list) while True: try: msg = ws.receive() print "get message: ", msg for s in ws_list: try: s.send(msg) except Exception: remove.add(s) break except WebSocketError: remove.add(ws) break for s in remove: ws_list.remove(s) @app.route('/send/message', methods=['POST']) def send_message(): ''' JSONがPOSTされたら、つながっているWebsocketにデータを流す JSONは以下形式を期待している { "message": "message to send" } curlでのJSONのPOSTは以下でできる curl -vv -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"message": "message from server"}' http://localhost:5000/send/message :param message: :return: ''' if request.method == 'POST': print "send_message", request.json json_data = request.json print len(ws_list) remove = set() for s in ws_list: try: s.send(json_data["message"]) except Exception: remove.add(s) for s in remove: ws_list.remove(s) return 'ok' abort(400) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': server = pywsgi.WSGIServer(('', 5000), app, handler_class=WebSocketHandler) server.serve_forever()
PUSH確認は以下curlで。
$ curl -vv -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"message": "message from server"}' http://localhost:5000/send/message
Notificationが出たらOK!
python覚えたのでmonkeyrunner試してみる
monkeyrunnerは端末に対して無作為なイベントを送付するmonkeyツールとは異なり、デバイスのキーイベントの送付やスクリーンショットの取得などを実行することができるテスト用のツールです。
うまく使えばframeworkのテストツールとして利用できそうなので、目をつけてたもののPython知らんぜとか思って放置してた。 覚えたついでに調べなおしを。
monkeyrunnerはjthonとして実装されており、用意されているモジュールは3つのみみたい。
- MonkeyDevice 主にメインに使うことになるクラス。アプリケーションのインストールから、タッチイベントの送付、アクティビティの立ち上げなど、いろいろできる。
MonkeyImage 主に画像を扱うときにつかう。2つのイメージの比較などにも利用できるので、実行結果のスクリーンショットと期待値のスクリーンショットの比較とかできる。
MonkeyRunner デバイスとの接続などに使う。ここのメソッドを通じてMonkeyDeviceクラスを取得する
アプリケーションを起動する
Settingsアプリを起動してみる。
# -- coding=utf-8 -- author = 'kobashin'from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
device = MonkeyRunner.waitForConnection()
def startApp(target): device.startActivity(component=target)
def main(): startApp('com.android.settings/.Settings')
if name == "main": main()
MonkeyDevice.startActivity()を使って、Activityの起動ができる。同様に、以下でHomeのIntentが投げれる。
def actionHome(): device.startActivity(action="android.intent.action.MAIN", categories=["android.intent.category.HOME"])
アプリケーションをインストールする
単なる1アプリのインストールであれば、以下でOK。 pathにはHOST PC側のapkパスを指定する。
def installApp(path): device.installPackage(path)
複数のアプリケーションを一気にインストールするのであれば、以下でいけそう。指定したディレクトリ以下のapkファイルをインストールして回る。
import globdef installAppInDir(dirpath): files = glob.glob(dirpath + '/*.apk') for apk in files: installApp(apk)
デバイスのキーを押下する
電源ボタンを押下してみる。suspend/resumeの試験とかこれでいんじゃないの?あ、adb走ってたらwakelock握りっぱなしだっけか。
import timedef keyDownAndUp(code): device.press(code, 'DOWN_AND_UP')
def main(): keyDownAndUp('KEYCODE_POWER') time.sleep(2) keyDownAndUp('KEYCODE_POWER')
スクリーンショットを取得する
取るだけなら、以下で。 pathには絶対PATHを指定すること。
def takeScreenShot(path): result = device.takeSnapshot() result.writeToFile(path, 'png') return result
とった結果と比較するときには、以下で行う。 loadImageFromFileを利用すれば、pngファイルからMonkeyImageが取得できるのでsameAsを使って比較すればよい
def compareImages(src, dest): if src.sameAs(dest, 0.9): print "2 is same" else: print "2 is different"def main(): dest = device.takeSnapshot() src = MonkeyRunner.loadImageFromFile('/Users/skobayashi1/workspace/temp/apps/test1.png') compareImages(src, dest)
一旦ここまで。 CTS実施前の端末設定とか、顧客ごとのキッティングとか、これで作ればいいんじゃないかな。
Flaskお勉強中メモ
※メモ書き
hello world
app.route()にルーティングするPathを指定して、直下にメソッドを定義すると、そのPathへのアクセスがあったときにメソッドが呼び出されるようになる。 app.run()を呼べば、サーバーが起動する。debug=Trueを指定すれば、デバッグオプションが有効になりソースコードの変更時に自動的に読み込み直しがかかるようになる。開発時はこれでいいけど、デプロイするときは外すようにする。 そのほかに、port=5678でポートの変更、host='0.0.0.0'でアドレスの変更がかかる。
from flask import Flask app = Flask(__name__) if __name__ == "__main__": app.run()
routing
変数を使う
@app.route()
に<>を使うとURLを引数として利用することができる
例えば、localhost/user/kobashinにアクセスして、hello kobashin!を表示する場合は、
@app.route("/usr/<username>") def show_user(username): return "hello " + username + "!\n"
<int:username>とすれば、入力値を限定することができる。
redirectとrul_for
redirectはリダイレクト用のメソッド、url_forはそのメソッドのURLに変換してくれるメソッド(?)
from flask import Flask, url_for, redirect @app.route("/") def root_index(): return redirect("/link") @app.route("/redirect_link") def to_link(): return redirect(url_for("link")) @app.route("/link") def link(): return "link"
htmlのテンプレートを使う
render_template
メソッドを使うと、templates以下のディレクトリの指定したhtmlファイルを探しに行ってくれる。
テンプレートとして利用できる書式については、以下ドキュメントが参照できる
http://jinja.pocoo.org/docs/dev/templates/
from flask import Flask, render_template @app.route("/") def index(): return render_template("index.html")
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> hello world! </body> </html>