Commit 1b9fec20 authored by Kento HASEGAWA's avatar Kento HASEGAWA

Add python scripts for communication between a server and clients

parent 0cbdf572
# DAS2017 ADC RaspberryPi・PYNQ間HTTP通信プログラム
# 概要
DAS2017アルゴリズムデザインコンテストに向けた,端末間通信プラグラム.
親となるRaspberry Piから,子となる複数のPYNQに対し問題を配信し,結果を受け取る.
# 構成
+ server: Raspberry Pi上で実行するためのサーバプラグラム.問題をクライアントに配信し,結果を受け取る.
+ client: PYNQ上で実行するためのクライアントプログラム.問題をサーバから受け取り,問題を解いて回答をサーバに返す.
+ README.md: このファイル.
# Requirements
- Python 3.4以上
- Flask (pipからインストール)
DAS2017 ADC クライアントプログラム
===
DAS2017 アルゴリズムデザインコンテスト用クライアントプログラム
## Description
問題データをサーバから受信し,結果をサーバへ返すプログラム.
## Requirements
- Python 3.4以上(くらい)
- Flask
## Usage
```
python3 main.py [--port XXXX] [--host XXXX]
```
### Options
<dl>
<dt>-H, --host</dt>
<dd>サーバホストのアドレス (デフォルト:192.168.4.1:5000)</dd>
<dt>-p, --port</dt>
<dd>使用するポート (デフォルト:5000)</dd>
</dl>
#!/usr/bin/env python3
"""
This script provides a PYNQ client.
This is intended to run on the client server (PYNQ).
"""
import argparse
import json
import os
import platform
import requests
import sys
import threading
import time
from flask import Flask, render_template, request, g
from urllib.parse import urlparse
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../../solver')
import BoardStr
import pynqrouter
app = Flask(__name__)
args = {}
pynq_thread = None
client_baseurl = ""
# Reference: https://teratail.com/questions/52593
class StoppableThread(threading.Thread):
def __init__(self, target, qname, qstr, qseed):
super(StoppableThread, self).__init__(target=target)
self._th_stop = threading.Event()
self._qname = qname
self._qstr = qstr
self._qseed = int(qseed)
self._answer = None
def run(self):
global client_baseurl
global pynq_thread
global args
# Main funciton (the solver should be placed here)
boardstr = BoardStr.conv_boardstr(self._qstr.split('\n'), 'random', self._qseed)
result = pynqrouter.solve(boardstr, self._qseed)
if result['solved']:
answer = result['solution']
else:
answer = None
pynq_thread = None
return
res = {
"client": client_baseurl,
"qname": self._qname,
"answer": answer,
"cputime": result['elapsed']
}
self._answer = answer
r = requests.post("http://{}/post".format(args["host"]), data=res)
if args["verbose"]:
print(res)
pynq_thread = None
def stop(self):
self._th_stop.set()
def stopped(self):
return self._th_stop.isSet()
def get_answer(self):
return self._answer
@app.route('/start', methods=["POST"])
def start():
global pynq_thread
global args
global client_baseurl
ans = {"status": "None", "answer": "", "client": client_baseurl}
if args["verbose"]:
print(request.form)
if pynq_thread is None:
qstr = request.form["question"]
qname = request.form["qname"]
qseed = request.form["qseed"]
pynq_thread = StoppableThread(target=StoppableThread, qname=qname, qstr=qstr, qseed=qseed)
pynq_thread.start()
# 実行だけ開始する
ans["status"] = "Processing"
else:
ans = {"status": "Not ready", "answer": ""}
if args["verbose"]:
print(ans)
return json.dumps(ans)
@app.route('/stop')
def stop():
global pynq_thread
if pynq_thread is None:
ans = {"status": "No threads"}
else:
pynq_thread.stop()
ans = {"status": "Stopped"}
pynq_thread = None
return json.dumps(ans)
@app.route("/status")
def status():
global pynq_thread
res_mes = ""
if pynq_thread is None:
res_mes = "Ready"
else:
res_mes = "Working"
res = {"status": res_mes}
return json.dumps(res)
@app.route("/")
def index():
return platform.node()
@app.before_request
def before_request():
global client_baseurl
_url = request.url
parse = urlparse(_url)
client_baseurl = parse.netloc
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="PYNQ client.")
parser.add_argument("-p", "--port", action="store", type=int, default=5000, help="Port")
parser.add_argument("-H", "--host", action="store", type=str, default="192.168.4.1:5000", help="Host address")
parser.add_argument("--debug", action="store_true", default=False, help="Debug mode.")
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="Verbose.")
args = vars(parser.parse_args())
if args["debug"]:
app.debug = True
app.run(host='0.0.0.0', port=args["port"], threaded=True)
DAS2017 ADC サーバプログラム
===
DAS2017 アルゴリズムデザインコンテスト用サーバプログラム
## Description
問題データをクライアントへ配信し,結果を受け取るプログラム.
## Requirements
- Python 3.4以上(くらい)
- Flask
## Usage
```
python3 main.py [--question XXXX] [--port XXXX] [--clients XXXX]
```
### Options
<dl>
<dt>-c, --client</dt>
<dd>クライアントを定義したテキストファイル.1行ずつホスト名を記述する(必要ならばポート番号も記述する),必須</dd>
<dt>-q, --question</dt>
<dd>問題ファイルのパス (デフォルト:./)</dd>
<dt>-o, --out</dt>
<dd>回答を出力するパス (デフォルト:./)</dd>
<dt>-p, --port</dt>
<dd>サーバのポート (デフォルト:5000)</dd>
<dt>-l, --line-num-th</dt>
<dd>処理を分岐させるライン数の閾値</dd>
<dt>-t, --timeout</dt>
<dd>PYNQで処理させるタイムアウト(秒)</dd>
</dl>
## Comments
This project uses some libraries: jQuery, Bootstrap
192.168.11.4:5000
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
/*
This script provides a PYNQ manager
*/
var PynqManager = (function(){
"use strict";
// Constructor
var PynqManager = function(clients){
if (!(this instanceof PynqManager)){
return new PynqManager(clients);
}
this.clients = clients;
}
var _p = PynqManager.prototype;
// Status check for clients
_p.getStatus = function(){
for (var key in this.clients){
(function(_key, _client){
var statusObj = $(".client-status-row[data-cname='"+_key+"']").find(".client-status-value").eq(0)
$.ajax({
type: "POST",
url: "/status",
dataType: "json",
data: {
"client": _client
}
}).done(function(res){
var answer = res["status"];
var message = "";
if (answer) {
message = answer;
}else{
message = "Illegal response: " + res;
}
statusObj.text(message);
}).fail(function(){
statusObj.text("Connection error");
});
})(key, this.clients[key]);
}
}
_p.sendQuestion = function(qname, after=null){
$("#solving-question-name").text(qname);
$("#solving-question-status").text('Processing');
$.ajax({
type: "POST",
dataType: "json",
url: "/start",
data: {
"qname": qname
}
}).done(function(res){
var answer = res;
$("#solving-question-status").text(answer["status"]);
$("#solved-result").text(answer["answer"]["answer"]);
$("#solved-client").text(answer["answer"]["client"]);
if (after !== null){
after();
}
});
}
_p.sendStop = function(){
console.log("Not implemented!");
}
return PynqManager;
})();
<!DOCTYPE html>
<html>
<head>
<title>PYNQ Router Control Panel for ADC2018</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/pynq-manager.js"></script>
<style>
html, body{
/*width: 800px;*/
/*height: 500px;*/
font-size: 11px;
overflow: hidden;
}
body{
margin-top: 20px;
}
/* scrollbar settings */
::-webkit-scrollbar{
width: 30px;
border: 1px solid rgba(0, 0, 50, .2);
}
::-webkit-scrollbar-thumb{
background-color: rgba(0, 0, 50, .5);
}
#question-table-wrapper{
height: 350px;
overflow: scroll;
}
#question-table-wrapper th.large-cell,
#question-table-wrapper td.large-cell{
width: 24%;
}
#question-table-wrapper th.small-cell,
#question-table-wrapper td.small-cell{
width: 14%;
}
#client-control-pane table th,
#client-control-pane table td{
width: 50%;
}
</style>
<script>
$(function(){
var pynqClients = {}
var pm = null;
var refresh_question_table = function(){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_question_table"
}).done(function(d){
$("#question-table-wrapper").find("#question-table").remove();
$("#question-table-wrapper").html(d);
$(".show-question-status-button").click(function(){
var qname = $(this).data("qname");
show_question_status(qname);
return false;
});
});
}
var refresh_client_table = function(){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_client_table"
}).done(function(d){
$("#client-control-pane").html("");
$("#client-control-pane").html(d);
$.ajax({
type: "GET",
dataType: "json",
url: "/get_clients"
}).done(function(d){
pynqClients = d;
pm = PynqManager(pynqClients);
pm.getStatus();
});
});
}
var show_question_status = function(qname){
$.ajax({
type: "GET",
dataType: "html",
url: "/get_question_status",
data: {qname: qname}
}).done(function(d){
$("#client-control-pane").html("");
$("#client-control-pane").html(d);
$("#client-control-pane").find(".start-button").eq(0).click(function(){
var qname = $(this).data("qname");
pm.sendQuestion(qname, after=refresh_question_table);
});
});
}
refresh_question_table();
refresh_client_table();
$("#view-server-status-button").click(function(){
refresh_client_table();
return false;
});
});
</script>
</head>
<body>
<div id="wrapper">
<div id="contorol-panel-wrapper" class="container">
<div class="row">
<div class="col" id="question-control-pane">
<h3>問題一覧</h3>
<p><a href="#" id="view-server-status-button">サーバ状況</a></p>
<div id="question-table-wrapper">
<p>Loading...</p>
</div>
</div>
<div class="col" id="client-control-pane">
<p>Loading...</p>
</div>
</div>
</div>
</div>
</body>
</html>
<h3>クライアント状況</h3>
<table class="table table-bordered" id="clients-table">
<tr>
<th>クライアント名</th>
<th>ステータス</th>
</tr>
{% for c in clients %}
<tr class="client-status-row" data-cname="{{c}}">
<td class="client-status-name">{{c}}</td>
<td class="client-status-value"></td>
</tr>
{% endfor %}
</table>
<h3>問題 【{{qname}}】</h3>
<p>
<button class="btn btn-default start-button" type="button" data-qname="{{qname}}">Start</button>
</p>
<p>状況:<span id="solving-question-status">{{qdata.status}}</span></p>
<h4>結果</h4>
<p>クライアント:<span id="solved-client">{{qdata.answer.client}}</span></p>
<pre id="solved-result">{{qdata.answer.answer}}</pre>
<table class="table table-bordered" id="question-table">
<tr>
<th class="large-cell">ファイル名</th>
<th class="large-cell">Size</th>
<th class="small-cell">Line</th>
<th class="large-cell">ステータス</th>
<th class="small-cell">操作</th>
</tr>
{% for k, v in questions.items() %}
<tr>
<td class="large-cell">{{k}}</td>
<td class="large-cell">{{v.board_size}}</td>
<td class="small-cell">{{v.line_num}}</td>
<td class="large-cell">{{v.status}}</td>
<td class="small-cell">
<a href="#" class="show-question-status-button" data-qname="{{k}}">選択</a>
</td>
</tr>
{% endfor %}
</table>
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
問題ファイルを boardstr に変換したりするクラス。
"""
import random
import re
def conv_boardstr(lines, terminals='initial', _seed=12345):
"""
問題ファイルを boardstr に変換
"""
random.seed(_seed)
boardstr = ''
for line in lines:
if 'SIZE' in line:
x, y, z = line.strip().split(' ')[1].split('X')
boardstr += ('X%02dY%02dZ%d' % (int(x), int(y), int(z)))
if 'LINE_NUM' in line:
pass
if 'LINE#' in line:
_line = re.sub(r', +', ',', line)
_line = re.sub(r' +', ' ', _line)
sp = _line.strip().replace('-', ' ').replace('(', '').replace(')', '').split(' ')
#print(sp)
# s (スタート) -> g (ゴール)
s_str = sp[1].split(',')
g_str = sp[2].split(',')
s_tpl = (int(s_str[0].strip()), int(s_str[1].strip()), int(s_str[2].strip()) - 1)
g_tpl = (int(g_str[0].strip()), int(g_str[1].strip()), int(g_str[2].strip()) - 1)
# 端に近い方をスタートにしたいから各端までの距離計算する
# (探索のキューを小さくしたいから)
s_dist_x = min(s_tpl[0], int(x) - 1 - s_tpl[0])
s_dist_y = min(s_tpl[1], int(y) - 1 - s_tpl[1])
s_dist_z = min(s_tpl[2], int(z) - 1 - s_tpl[2])
s_dist = s_dist_x + s_dist_y + s_dist_z
#print(s_dist_x, s_dist_y, s_dist_z, s_dist)
g_dist_x = min(g_tpl[0], int(x) - 1 - g_tpl[0])
g_dist_y = min(g_tpl[1], int(y) - 1 - g_tpl[1])
g_dist_z = min(g_tpl[2], int(z) - 1 - g_tpl[2])
g_dist = g_dist_x + g_dist_y + g_dist_z
#print(g_dist_x, g_dist_y, g_dist_z, g_dist)
# start と goal
start_term = '%02d%02d%d' % (int(s_str[0]), int(s_str[1]), int(s_str[2]))
goal_term = '%02d%02d%d' % (int(g_str[0]), int(g_str[1]), int(g_str[2]))
# 端に近い方をスタートにするオプションがオンのときは距離に応じて端点を選択する
if terminals == 'edgefirst':
if s_dist <= g_dist:
boardstr += ('L' + start_term + goal_term)
else:
boardstr += ('L' + goal_term + start_term)
# ランダムにスタート・ゴールを選ぶ
elif terminals == 'random':
if random.random() < 0.5:
boardstr += ('L' + start_term + goal_term)
else:
boardstr += ('L' + goal_term + start_term)
# 問題ファイルに出てきた順
else:
boardstr += ('L' + start_term + goal_term)
return boardstr
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import os, sys
import BoardStr
def main():
parser = argparse.ArgumentParser(description='gen_boardstr')
parser.add_argument('input', nargs=None, default=None, type=str,
help='Path to input problem file')
parser.add_argument('-t', '--terminals', action='store', nargs='?', default='initial', type=str,
help='Terminals order (initial, edgefirst, or random) (default: initial)')
parser.add_argument('-s', '--seed', action='store', nargs='?', default=12345, type=int,
help='Random seed')
args = parser.parse_args()
# 問題ファイルの読み込み
with open(args.input, 'r') as f:
lines = f.readlines()
# 問題ファイルを boardstr に変換
boardstr = BoardStr.conv_boardstr(lines, args.terminals, args.seed)
# 表示
print(boardstr)
if __name__ == '__main__':
main()
#!/opt/python3.6/bin/python3.6
# -*- coding: utf-8 -*-
"""
PYNQ 上で pynqrouter を実行する。
"""
from pynq import Overlay
from pynq import PL
from pynq import MMIO
import argparse
import sys
import time
import BoardStr
# Settings -- pynqrouter
IP = 'SEG_pynqrouter_0_Reg'
OFFSET_BOARD = 65536 # 0x10000 ~ 0x1ffff
OFFSET_SEED = 131072 # 0x20000
OFFSET_STATUS = 131080 # 0x20008
MAX_X = 72
MAX_Y = 72
MAX_Z = 8
BITWIDTH_Z = 3
# Settings -- LED
IP_LED = 'SEG_axi_gpio_0_Reg'
def solve(boardstr, seed=12345, zero_padding=False):
print('boardstr:')
print(boardstr)
print('seed:')
print(seed)
print('')
# ボード文字列から X, Y, Z を読んでくる
size_x = (ord(boardstr[1]) - ord('0')) * 10 + (ord(boardstr[2]) - ord('0'))
size_y = (ord(boardstr[4]) - ord('0')) * 10 + (ord(boardstr[5]) - ord('0'))
size_z = (ord(boardstr[7]) - ord('0'))
# Overlay 読み込み
OL = Overlay('pynqrouter.bit')
OL.download()
print(OL.ip_dict)
print('Overlay loaded!')
# MMIO 接続 (pynqrouter)
mmio = MMIO(int(PL.ip_dict[IP][0]), int(PL.ip_dict[IP][1]))
# MMIO 接続 & リセット (LED)
mmio_led = MMIO(int(PL.ip_dict[IP_LED][0]), int(PL.ip_dict[IP_LED][1]))
mmio_led.write(0, 0)
# 入力データをセット
imem = pack(boardstr)
for i in range(len(imem)):
mmio.write(OFFSET_BOARD + (i * 4), imem[i])
mmio.write(OFFSET_SEED, seed)
# スタート
# ap_start (0 番地の 1 ビット目 = 1)
mmio.write(0, 1)
print('Start!')
time_start = time.time()
# ap_done (0 番地の 2 ビット目 = 2) が立つまで待ってもいいが
# done は一瞬だけ立つだけのことがあるから
# ap_idle (0 番地の 3 ビット目 = 4) を待ったほうが良い
iteration = 0
while (mmio.read(0) & 4) == 0:
# 動いてるっぽく見えるようにLチカさせる
iteration += 1
if iteration == 10000:
mmio_led.write(0, 3)
elif 20000 <= iteration:
mmio_led.write(0, 12)
iteration = 0
# 完了の確認
print('Done!')
print('control:', mmio.read(0))
time_done = time.time()
elapsed = time_done - time_start
print('elapsed:', elapsed)
print('')
# 状態の取得
status = int(mmio.read(OFFSET_STATUS))
print('status:', status)
if status != 0:
# 解けなかったらLEDを消す
mmio_led.write(0, 0)
sys.stderr.write('Cannot solve it!\n')
return { 'solved': False, 'solution': '', 'elapsed': -1.0 }
print('Solved!')
# 解けたらLEDを全部つける
mmio_led.write(0, 15)
# 出力
omem = []
for i in range(len(imem)):
omem.append(mmio.read(OFFSET_BOARD + (i * 4)))
boards = unpack(omem)
# 回答の生成
solution = ('SIZE ' + str(size_x) + 'X' + str(size_y) + 'X' + str(size_z) + '\n')
for z in range(size_z):
solution += ('LAYER ' + str(z + 1) + '\n')
for y in range(size_y):
for x in range(size_x):
if x != 0:
solution += ','
i = ((x * MAX_X + y) << BITWIDTH_Z) | z
if zero_padding:
solution += '{0:0>2}'.format(boards[i]) # 2桁の0詰め
else:
solution += str(boards[i]) # 普通に表示
solution += '\n'
return { 'solved': True, 'solution': solution, 'elapsed': elapsed }
def main():
parser = argparse.ArgumentParser(description="Solver with pynqrouter")
# 入出力
parser.add_argument('-i', '--input', action='store', nargs='?', default=None, type=str,
help='Path to input problem file')
parser.add_argument('-b', '--boardstr', action='store', nargs='?', default=None, type=str,
help='Problem boardstr (if you want to solve directly)')
parser.add_argument('-o', '--output', action='store', nargs='?', default=None, type=str,
help='Path to output answer file')
# ソルバオプション
parser.add_argument('-t', '--terminals', action='store', nargs='?', default='initial', type=str,
help='Terminals order (initial, edgefirst, or random) (default: initial)')
parser.add_argument('-s', '--seed', action='store', nargs='?', default=12345, type=int,
help='Random seed')
# 出力オプション
parser.add_argument('-p', '--print', action='store_true', default=False,
help='Enable to print the solution')
parser.add_argument('-z', '--zero-padding', action='store_true', default=False,
help='Enable to do zero-padding')
args = parser.parse_args()
if args.input is not None:
# 問題ファイルの読み込み
with open(args.input, 'r') as f:
lines = f.readlines()
# 問題ファイルを boardstr に変換
boardstr = BoardStr.conv_boardstr(lines, args.terminals, args.seed)
elif args.boardstr is not None:
boardstr = args.boardstr
else:
sys.stderr.write('Specify at least "input" or "boardstr"!\n')
sys.exit(1)
result = solve(boardstr, args.seed)
if result['solved'] == False:
sys.exit(1)
# 表示 & ファイル出力
if args.print:
print('')
print('SOLUTION')
print('========')
print(result['solution'])
if args.output is not None:
with open(args.output, 'w') as f:
f.write(result['solution'])
def pack(_str):
"""
ボードストリング文字列をMMIOメモリにパックする
"""
_mem = [ 0 for _ in range(MAX_X * MAX_Y * MAX_Z // 4) ]
for i in range(len(_str)):
_index = i // 4;
_offset = i % 4;
if _offset == 0:
_mem[_index] = _mem[_index] | (ord(_str[i]))
elif _offset == 1:
_mem[_index] = _mem[_index] | (ord(_str[i]) << 8)
elif _offset == 2:
_mem[_index] = _mem[_index] | (ord(_str[i]) << 16)
elif _offset == 3:
_mem[_index] = _mem[_index] | (ord(_str[i]) << 24)
return _mem
def unpack(_mem):
"""
MMIOメモリからボード情報をアンパックする
"""
_boards = [ 0 for _ in range(MAX_X * MAX_Y * MAX_Z) ]
for i in range(len(_mem)):
_boards[4 * i ] = (_mem[i]) & 255
_boards[4 * i + 1] = (_mem[i] >> 8) & 255
_boards[4 * i + 2] = (_mem[i] >> 16) & 255
_boards[4 * i + 3] = (_mem[i] >> 24) & 255
return _boards
if __name__ == '__main__':
main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment