はじめに
皆さん、こんにちは。
前回は、MSX-BASICでbase64エンコーダを作りました。MSX0上でこのプログラムを動かして、ファイルをエンコードして出力された結果をクライアントPC側でbase64デコードしてしまえば、ファイル転送できるという算段です。
前々回と同様に面倒なところはPythonの兄貴に頑張ってもらいましょう。
環境
MSX: MSX0Stack
クライアントPC: Windows+WSL,TeraTerm
Python: Python3.5 on WSL
実装コード「msx0babaget.py」
それでは、実装したコードです。プログラムは以下のような流れで動作して、ファイルを受信します
1.MSX0のBASIC画面にtelnet接続(DOS画面だったらBASICモードに変更します)
2.newコマンドで初期化
3.転送する対象のファイルのファイルサイズを取得
4.BASICコマンドをMSX0のBASICインタープリタに流し込み
5.runコマンドでプログラムを実行
6.実行されたプログラムは指定されたファイルを54文字ずつ読み込みbase64エンコード、結果を出力。
7.runコマンドでプログラムを実行した後は、1行ごと結果を受け取り、base64デコードして出力先ファイルに出力。
8.BASICプログラムが終わったら(「Ok\r\n」を拾ったら)、ファイルをクローズして終了
9.実行にかかった時間をを表示し、終了。
import sys
import telnetlib
import time
import re
import base64
import os
def is_valid_filename(filename):
# ファイル名が8文字+拡張子3文字であるかどうかをチェックする正規表現
pattern = r'^[A-Za-z0-9_]{1,8}\.[A-Za-z0-9_]{3}$'
return re.match(pattern, filename)
def create_new_file(file_name):
open(file_name, 'wb').close()
def decode_and_write_to_file(encoded_text, file_name):
decoded_data = base64.b64decode(encoded_text)
with open(file_name, 'ab') as file:
file.write(decoded_data)
def execute_full_process_telnetlib(host, program, message_file_path,local_file_path):
start_time = time.time()
message_file_name = os.path.basename(message_file_path)
if not is_valid_filename(message_file_name):
return "Invalid file name format.", time.time() - start_time
try:
# Telnet接続
print("msx0babaput started connecting to msx0")
tn = telnetlib.Telnet(host, 2223)
tn.write(b'\r\n')
# DOSモードで起動している場合、BASICインタープリタモードに切り替える
response = tn.read_until(b'A>', timeout=1) # 1秒以内にプロンプトが表>示されない場合はタイムアウト
if b'A>' in response:
tn.write(b'basic\r\n') # BASICインタープリタモードに切り替え
tn.read_until(b'Ok')
# newコマンドで初期化
sys.stdout.write("Start initilizing.")
sys.stdout.flush()
tn.write(b'new\r\n')
tn.read_until(b'Ok\r\n')
sys.stdout.write("\rStart initilizing..Done.")
sys.stdout.flush()
print()
# ファイルサイズを取得
oneline='OPEN"' + message_file_name + '"AS#1:PRINT LOF(1):CLOSE#1\r\n'
tn.write(oneline.encode('ascii'))
output = tn.read_until(b"Ok\r\n").decode('ascii').strip()
match = re.search(r'\s+(\d+)\s+', output)
message_file_size = 0
if match:
message_file_size = int(match.group(1))
else:
print("Can not get file size!")
return "Error", time.time() - start_time
print("Target file size:" + str(message_file_size))
# BASICプログラムをBASICインタープリタに送信する
sys.stdout.write("Start building base64encoder on msx0 basic.")
sys.stdout.flush()
program = program + '20 LET F$ = "' + message_file_name + '"\r\n'#ファイル名を上書き
tn.write(program.encode('ascii') + b"\r\n") # BASICプログラムをサーバに送信
result = tn.read_until(b'\r\n\r\n')
sys.stdout.write("\rStart building base64encoder on msx0 basic..Done")
sys.stdout.flush()
print()
# BASICプログラムを実行する
print("Start transfer a target file on msx0 basic to local PC.")
create_new_file(local_file_path)
tn.write(b'run\r\n')
tn.read_until(b'run\r\n')
chunk_counter=0
total_chunks= message_file_size // 57 +1
# BASICプログラムの結果を1行ずつ読み取り、base64デコードしファイルに書き込む
while True:
sys.stdout.write("\rTransferring: {}/{} chunks".format(chunk_counter, total_chunks))
sys.stdout.flush()
result = tn.read_until(b'\n')
if "Ok\r\n" == result.decode('ascii'):
break
decode_and_write_to_file(result.decode('ascii').strip(),local_file_path)
chunk_counter += 1
print()
result="Done"
return result, time.time() - start_time
except Exception as e:
return str(e), time.time() - start_time
finally:
# Telnet接続を閉じる
tn.close()
if __name__ == "__main__":
if len(sys.argv) != 4:
print("Usage: python msx0babaget.py <host> <message_file_path> <local_file_path>")
sys.exit(1)
host = sys.argv[1]
message_file_path = sys.argv[2]
local_file_path = sys.argv[3]
# BASICプログラム
basic_program = '''
10 DEFINT A-Z:DIM I$, O$, B$, B(3), OB(4):CLEAR 1000:ON ERROR GOTO 2000\r\n
20 LET F$ = "messages.txt"\r\n
100 S = 57:OPEN F$ AS #1 LEN=S\r\n
110 L = LOF(1): C = L \ S\r\n
120 FOR H=0 TO C\r\n
130 IF C = H THEN S = ( L - 1 ) MOD S\r\n
140 FIELD #1, INT(S) AS I$\r\n
150 GET #1\r\n
160 GOSUB 1000\r\n
170 PRINT O$\r\n
180 NEXT H\r\n
190 CLOSE #1\r\n
999 END\r\n
1000 'B64E(I$, O$, B$)\r\n
1020 DIM T$, C$\r\n
1030 LET O$ = ""\r\n
1040 LET B$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\r\n
1050 FOR I = 1 TO LEN(I$) STEP 3\r\n
1060 T$ = MID$(I$, I, 1)\r\n
1070 IF LEN(I$) >= I + 1 THEN T$ = T$ + MID$(I$, I + 1, 1)\r\n
1080 IF LEN(I$) >= I + 2 THEN T$ = T$ + MID$(I$, I + 2, 1)\r\n
1090 FOR J = 1 TO 3\r\n
1200 B(J) = 0\r\n
1210 NEXT J\r\n
1220 FOR J = 1 TO LEN(T$)\r\n
1230 B(J) = ASC(MID$(T$, J, 1)) \r\n
1240 NEXT J\r\n
1250 OB(1) = B(1) \ 4\r\n
1260 OB(2) = (B(1) AND 3) * 16 + B(2) \ 16\r\n
1270 OB(3) = (B(2) AND 15) * 4 + B(3) \ 64\r\n
1280 OB(4) = B(3) AND 63\r\n
1290 FOR J = 1 TO 4\r\n
1300 IF J >= 3 AND LEN(T$) = 1 THEN O$ = O$ + "=" ELSE IF J = 4 AND LEN(T$) = 2 THEN O$ = O$ + "=" ELSE O$ = O$ + MID$(B$, OB(J) + 1, 1)\r\n
1330 NEXT J\r\n
1340 NEXT I\r\n
1350 RETURN\r\n
2000 IF ERR=55 THEN CLOSE #1:END\r\n
3000 ON ERROR GOTO 0\r\n'''
# 全プロセスを実行
result, execution_time = execute_full_process_telnetlib(host, basic_program, message_file_path,local_file_path)
print(result)
print("Execution time: {} seconds".format(execution_time))
実行
前回、MSX0に送信したじゅげむの一節を受信しましょう。
※【免責】十分な試験を行っていませんので、まだまだバグが含まれていると思います。その点を理解いただいての使用をお願いします。実行した結果についての責任は一切負えません。
実行するコマンドは以下です。
MSX0のIP:192.168.0.10
受信対象のファイル名:messages.txt
受信後に保存するファイル名:dddd.txt
python3 msx0babaget.py 192.168.0.10 messages.txt dddd.txt
実行した結果は、以下のようになります。
nshichi@DESKTOP-BML9KQH:~$ python3 msx0babaget.py 192.168.0.10 messages.txt dddd.txt
msx0babaput started connecting to msx0
Start initilizing..Done.
Target file size:428
Start building base64encoder on msx0 basic..Done
Start transfer a target file on msx0 basic to local PC.
Transferring: 8/8 chunks
Done
Execution time: 42.076099157333374 seconds
nshichi@DESKTOP-BML9KQH:~$
nshichi@DESKTOP-BML9KQH:~$ more dddd.txt
じゅげむじゅげむ ごこうのすりきれず かいじゃりすいぎょの すいぎょうばつ ふうらいばつ くねるところにすむところ やー
ぶらこうじのぶらこうじ ぱいぽぱいぽ ぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴー
ぽんぽこなーの ちょうきゅうめいのちょうすけ
無事受信できました。しかし・・・
Execution time: 42.076099157333374 seconds
遅い!チューニング版はいずれ。
実行結果の動画はXにアップしましたので、参考まで。
現場からは以上です!
---以下、追記。
チューニング①
BASICプログラム部分を見直します。
# BASICプログラム
basic_program = '''
10 DEFINT A-Z:DIM I$, O$, B$, OB(4):CLEAR 1000:ON ERROR GOTO 3000\r\n
20 LET F$ = "messages.txt"\r\n
30 LET B$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\r\n
100 S = 3:OPEN F$ AS #1 LEN=S\r\n
110 L = LOF(1): C = L \ S\r\n
120 FIELD #1, 3 AS I$\r\n
130 FOR H=0 TO C\r\n
140 IF C = H THEN 300\r\n
150 GET #1\r\n
160 GOSUB 1000\r\n
170 O$ = MID$(B$, OB(1) + 1, 1)+MID$(B$, OB(2) + 1, 1)+MID$(B$, OB(3) + 1, 1)+MID$(B$, OB(4) + 1, 1)\r\n
180 PRINT O$;\r\n
190 IF H MOD 19 = 18 THEN PRINT \r\n
200 NEXT H\r\n
300 S = ( L - 1 ) MOD 3\r\n
320 GET #1\r\n
330 GOSUB 1000\r\n
340 LET O$ = ""\r\n
350 FOR J = 1 TO 4\r\n
360 IF J >= 3 AND S= 1 THEN O$ = O$ + "="
ELSE IF J = 4 AND S= 2 THEN O$ = O$ + "=" ELSE O$ = O$ + MID$(B$, OB(J) + 1, 1)\r\n
370 NEXT J\r\n
380 PRINT O$\r\n
900 CLOSE #1\r\n
999 END\r\n
1000 'B64E(B1%,B2%,B3%,OB(4))\r\n
1160 B1% = ASC(MID$(I$, 1, 1))\r\n
1170 B2% = ASC(MID$(I$, 2, 1))*(1+S=1)\\r\n
1180 B3% = ASC(MID$(I$, 3, 1))*(1+(S=1 OR S=2))\\r\n
1190 OB(1) = B1% \ 4\r\n
1200 OB(2) = (B1% AND 3) * 16 + B2% \ 16\r\n
1210 OB(3) = (B2% AND 15) * 4 + B3% \ 64\r\n
1220 OB(4) = B3% AND 63\r\n
1230 RETURN\r\n
3000 IF ERR=55 THEN CLOSE #1:END\r\n
3010 ON ERROR GOTO 0\r\n'''
無駄な処理を取り除きました。
実行した結果は以下です。
nshichi@DESKTOP-BML9KQH:~/msx0-repo$ python3 msx0babaget.py 192.168.0.10 messages.txt dddd.txt
msx0babaput started connecting to msx0
Start initilizing..Done.
Target file size:428
Start building base64encoder on msx0 basic..Done
Start transfer a target file on msx0 basic to local PC.
Transferring: 8/8 chunks
Done
Execution time: 20.64774775505066 seconds
42秒の処理が21秒に。10秒台に乗せたいですね。
チューニング②
さてさらにチューニングしてきます。
方針1:GOUSUB止める
方針2:出力用の文字変数は不要。出力してしまえ。
方針3:変数名見直し。
basic_program = '''
10 DEFINT A-Z:DIM B$,I$,X%,Y%,Z%,O%,P%,Q%,R%,OB%(4) :CLEAR 1000\r\n
20 F$ = "messages.txt"\r\n
30 B$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\r\n
100 S = 3:OPEN F$ AS #1 LEN=S\r\n
110 L = LOF(1): C = (L - 1) \ S\r\n
120 FIELD #1, 3 AS I$\r\n
130 FOR H=1 TO C\r\n
150 GET #1\r\n
160 X% = ASC(MID$(I$, 1, 1))\r\n
170 Y% = ASC(MID$(I$, 2, 1))\r\n
180 Z% = ASC(MID$(I$, 3, 1))\r\n
190 PRINT MID$(B$, X% \ 4 + 1, 1) + MID$(B$, (X% AND 3) * 16 + Y% \ 16 + 1, 1);\r\n
200 PRINT MID$(B$, (Y% AND 15) * 4 + Z% \ 64 + 1, 1) + MID$(B$, (Z% AND 63) + 1, 1);\r\n
210 IF H MOD 19 = 0 THEN PRINT \r\n
220 NEXT\r\n
300 S = ( L - 1 ) MOD 3\r\n
320 GET #1\r\n
340 X% = ASC(MID$(I$, 1, 1))\r\n
350 Y% = ASC(MID$(I$, 2, 1))*(1+S=1)\r\n
360 Z% = ASC(MID$(I$, 3, 1))*(1+(S=1 OR S=2))\r\n
370 OB%(1) = X% \ 4\r\n
380 OB%(2) = (X% AND 3) * 16 + Y% \ 16\r\n
390 OB%(3) = (Y% AND 15) * 4 + Z% \ 64\r\n
400 OB%(4) = Z% AND 63\r\n
420 FOR J = 1 TO 4\r\n
430 IF J >= 3 AND S= 1 THEN PRINT"=";
ELSE IF J = 4 AND S= 2 THEN PRINT"="; ELSE PRINT MID$(B$, OB%(J) + 1, 1);\r\n
440 NEXT\r\n
450 PRINT \r\n
460 CLOSE #1\r\n
999 END\r\n'''
さて、結果は、、
nshichi@DESKTOP-BML9KQH:~/msx0-repo$ python3 msx0babaget.py 192.168.0.10 messages.txt dddd.txt
msx0babaput started connecting to msx0
Start initilizing..Done.
Target file size:428
Start building base64encoder on msx0 basic..Done
Start transfer a target file on msx0 basic to local PC.
Transferring: 8/8 chunks
Done
Execution time: 16.69895577430725 seconds
16秒台!
チューニング③
すっかり忘れていたんですが、MSX0はBASICコマンドで動的にクロックアップができます。
CALL IOTPUT("msx/u0/pm/cpu/percent",100)
詳細仕様は要確認ですが、今回は255に設定してみます。
BASICプログラムの前後にクロック変更コマンドを入れて実行してみましょう。
行番号5と行番号500にクロック変更コマンドを埋め込みました。
basic_program = '''
5 CALL IOTPUT("msx/u0/pm/cpu/percent",255)\r\n
10 DEFINT A-Z:DIM B$,I$,X%,Y%,Z%,O%,P%,Q%,R%,OB%(4) :CLEAR 1000\r\n
20 F$ = "messages.txt"\r\n
30 B$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\r\n
100 S = 3:OPEN F$ AS #1 LEN=S\r\n
110 L = LOF(1): C = (L - 1) \ S\r\n
120 FIELD #1, 3 AS I$\r\n
130 FOR H=1 TO C\r\n
150 GET #1\r\n
160 X% = ASC(MID$(I$, 1, 1))\r\n
170 Y% = ASC(MID$(I$, 2, 1))\r\n
180 Z% = ASC(MID$(I$, 3, 1))\r\n
190 PRINT MID$(B$, X% \ 4 + 1, 1) + MID$(B$, (X% AND 3) * 16 + Y% \ 16 + 1, 1);\r\n
200 PRINT MID$(B$, (Y% AND 15) * 4 + Z% \ 64 + 1, 1) + MID$(B$, (Z% AND 63) + 1, 1);\r\n
210 IF H MOD 19 = 0 THEN PRINT \r\n
220 NEXT\r\n
300 S = ( L - 1 ) MOD 3\r\n
320 GET #1\r\n
340 X% = ASC(MID$(I$, 1, 1))\r\n
350 Y% = ASC(MID$(I$, 2, 1))*(1+S=1)\r\n
360 Z% = ASC(MID$(I$, 3, 1))*(1+(S=1 OR S=2))\r\n
370 OB%(1) = X% \ 4\r\n
380 OB%(2) = (X% AND 3) * 16 + Y% \ 16\r\n
390 OB%(3) = (Y% AND 15) * 4 + Z% \ 64\r\n
400 OB%(4) = Z% AND 63\r\n
420 FOR J = 1 TO 4\r\n
430 IF J >= 3 AND S= 1 THEN PRINT"=";
ELSE IF J = 4 AND S= 2 THEN PRINT"="; ELSE PRINT MID$(B$, OB%(J) + 1, 1);\r\n
440 NEXT\r\n
450 PRINT \r\n
460 CLOSE #1\r\n
500 CALL IOTPUT("msx/u0/pm/cpu/percent",100)\r\n
999 END\r\n'''
実行結果は以下の通り。
nshichi@DESKTOP-BML9KQH:~/msx0-repo$ python3 msx0babaget.py 192.168.0.10 messages.txt dddd.txt
msx0babaput started connecting to msx0
Start initilizing..Done.
Target file size:428
Start building base64encoder on msx0 basic..Done
Start transfer a target file on msx0 basic to local PC.
Transferring: 8/8 chunks
Done
Execution time: 12.947779417037964 seconds
ついに12秒台!