2019年9月11日水曜日

ラズパイ(Webサーバー・11)

前回は、ページの日本語対応の調整を行いました。
今回は、ここまで作成してきたサンプルをもとに
WIFIの設定をブラウザからできるようにしてみます。
CUIベースのラズベリーパイにおいて、WIFIの設定は
前回記録した情報の表示ができなかったり、
設定したWIFIは保存毎に追加されるので、
間違えた場合でも、設定がのこってどんどん増えていきます。
こんな感じで、お世辞にも簡単とは言えません。

WIFIの設定ファイルの場所ですが、
/etc/wpa_supplicant/wpa_supplicant.conf

に接続設定が書かれています。
そこで、このファイルを読み書きすれば、WIFIの設定ができそうです。
また、ほかのネットワーク設定も存在する場合、
ファイル内をクリアして上書きすると、既存の情報が
消えてしまうので、前回の変えた情報を控えておいて、
該当する箇所を書き換えるように作ります。

表示時(読み込み表示)
            # WIFIの設定ファイルの場所
            WIFI_CONF_PATH = "/etc/wpa_supplicant/wpa_supplicant.conf"
            # WIFIの作業用ファイルの場所
            WIFI_TMP_PATH = "../wpa.conf"
            
            config.read(WIFI_TMP_PATH)
            
            r_wifi_ssid = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_ssid")):
                r_wifi_ssid = config.get(CONF_WIFI_SEC_NM, "wifi_ssid")
            r_wifi_psk = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_psk")):
                r_wifi_psk = config.get(CONF_WIFI_SEC_NM, "wifi_psk")
            r_wifi_scan_ssid = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_scan_ssid")):
                r_wifi_scan_ssid = config.get(CONF_WIFI_SEC_NM, "wifi_scan_ssid")
            r_wifi_key_mgmt = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_key_mgmt")):
                r_wifi_key_mgmt = config.get(CONF_WIFI_SEC_NM, "wifi_key_mgmt")
            
            ret = {
                "url": r_url,
                "urlwav": r_urlwav,
                "urlpcap": r_urlpcap,
                "iot_id": r_iot_id,
                "voicerec_on": r_voicerec_on,
                
                "smbconnect_on": r_smbconnect_on,
                "smbusername": r_smbusername,
                "smbpassword": r_smbpassword,
                "smbhostname": r_smbhostname,
                "smbipaddress": r_smbipaddress,
                "smbremotedirectory": r_smbremotedirectory,
                "smbremotepath": r_smbremotepath,
                
                "wifi_ssid": r_wifi_ssid,
                "wifi_psk": r_wifi_psk,
                "wifi_scan_ssid": r_wifi_scan_ssid,
                "wifi_key_mgmt": r_wifi_key_mgmt,
                
                "ret": "1"
            }
        
        self.send_response(200)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Content-type", "application/json;charset=utf-8")
        self.end_headers()
        ret_json = json.dumps(ret, sort_keys=False, indent=4, ensure_ascii=False)
        self.wfile.write(ret_json.encode("utf-8"))


書き込み時(保存時)
            wifi_ssid = str(body["wifi_ssid"])
            wifi_psk = str(body["wifi_psk"])
            wifi_scan_ssid = str(body["wifi_scan_ssid"])
            wifi_key_mgmt = str(body["wifi_key_mgmt"])
            
            # 作業設定を読み込む
            config = configparser.ConfigParser()
            config.read(WIFI_TMP_PATH)
            
            r_wifi_ssid = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_ssid")):
                r_wifi_ssid = config.get(CONF_WIFI_SEC_NM, "wifi_ssid")
            r_wifi_psk = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_psk")):
                r_wifi_psk = config.get(CONF_WIFI_SEC_NM, "wifi_psk")
            r_wifi_scan_ssid = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_scan_ssid")):
                r_wifi_scan_ssid = config.get(CONF_WIFI_SEC_NM, "wifi_scan_ssid")
            r_wifi_key_mgmt = ""
            if (config.has_option(CONF_WIFI_SEC_NM, "wifi_key_mgmt")):
                r_wifi_key_mgmt = config.get(CONF_WIFI_SEC_NM, "wifi_key_mgmt")
            
            bef_wifi_cnf = 'network={\n' + \
                '  ssid="' + r_wifi_ssid + '"\n' + \
                '  psk="' + r_wifi_psk + '"\n'
            if (r_wifi_scan_ssid != ''):
                bef_wifi_cnf += '  scan_ssid=1\n'
            if (r_wifi_scan_ssid != ''):
                bef_wifi_cnf += '  key_mgmt=' + r_wifi_key_mgmt + '\n'
            bef_wifi_cnf += '}\n'
            
            if (os.path.exists(WIFI_CONF_PATH)):
                f = open(WIFI_CONF_PATH, 'w+', encoding='utf-8')
                contents = f.read()
                
                logging.info(contents)
                
                contents = contents.replace(bef_wifi_cnf, "") # 前回の設定をクリアする
                f.write(contents)
                f.close()
            
            if (True):
                f = open(WIFI_CONF_PATH, 'a', encoding='utf-8')
                f.write('network={\n')
                f.write('  ssid="' + wifi_ssid + '"\n')
                f.write('  psk="' + wifi_psk + '"\n')
                # ステルス時、書き出す
                if (wifi_scan_ssid != ''):
                    f.write('  scan_ssid=1\n')
                # WPA時は、書き出さない
                if (wifi_key_mgmt != ''):
                    f.write('  key_mgmt=' + wifi_key_mgmt + '\n')
                f.write('}\n')
                f.close()
                
                # 作業設定の書き出し
                if (not config.has_section(CONF_WIFI_SEC_NM)):
                    config.add_section(CONF_WIFI_SEC_NM)
                
                config.set(CONF_WIFI_SEC_NM, "wifi_ssid", wifi_ssid)
                config.set(CONF_WIFI_SEC_NM, "wifi_psk", wifi_psk)
                config.set(CONF_WIFI_SEC_NM, "wifi_scan_ssid", wifi_scan_ssid)
                config.set(CONF_WIFI_SEC_NM, "wifi_key_mgmt", wifi_key_mgmt)
                
                f = open(WIFI_TMP_PATH, "w+", encoding='utf-8')
                config.write(f)
                f.close()
            
            ret = {
                "ret": "1"
            }
https://www.filetalk.info/index.html
 

2019年9月10日火曜日

ラズパイ(Webサーバー・10)

前回は、パスワード設定を変更できるように調整しました。
今回は、ページの日本語化対応についてです。
Webページとして表示したい部分をPythonのファイルだけ利用して
作成する場合ですと、短いページなら問題ないのですが、
ページ切り替えや、多くの外部ファイルと連携しないと
いけない場合など、ソースのコードが増えて
管理が大変になってしまいます。
そこで、表示するHTML部分を別のファイルとして作成して
読み込むようにします。
cur_dir = os.getcwd() # カレントフォルダ取得
tar_file = cur_dir + self.path
if (os.path.isfile(tar_file)):
    f = open(tar_file)
    txt = f.read()
    f.close()
    self.send_response(200)
    self.send_header("Content-type", mimetype)
    self.end_headers()
    self.wfile.write(txt.encode("utf-8"))

通常は、これでOKなのですが、
日本語の文字列が混ざるとエラーが出て表示されなくなってしまう
場合があります。

そこで、読み込む際のファイルのエンコードを以下のように
調整することで、エラーが出なくなります。

    f = open(tar_file, encoding='utf-8')

エンコードを追加しないとエラーがでるという
Webページを配信時のファイル読み込みに
見落としがちな文字コードについてでした。

https://www.filetalk.info/index.html


2019年9月9日月曜日

ラズパイ(Webサーバー・9)

前回は、Webページを守るためBASIC認証の機能を取り入れました。
今回は、このBASIC認証で利用するパスワードを作成する
処理を作りこみます。

認証に使われるパスワードは、内容をみただけでは
分からないようにするため、パスワードからハッシュ関数で作られた
文字列が利用されます。
Pythonからですと、base64を使ったものを利用します。

import base64

id = ""
password = "xxxxxx"
secret = '{}:{}'.format(id, password)
encodedBytes = base64.b64encode(secret.encode("utf-8"))
PAGE_KEY = str(encodedBytes, "utf-8")
logging.info(PAGE_KEY)


IDは、なしでも登録可能です。
作成するとこのような文字列が生成されます。


Onh4eHh4eA==

これを、Authorization 部分に利用することで設定できます。



https://www.filetalk.info/index.html

2019年9月6日金曜日

ラズパイ(Webサーバー・8)

前回は、Python で設定ファイルの読み書きが簡単にできる
方法を行いました。
今回は、誰からでも設定を変更されないように、
認証機能をつけてみます。
まず仕様としては、機器の設定にローカルネットワークからの
接続のみを想定してますので、比較的に単純なBASIC認証を
設定してみます。

そこで、これまで server_class クラスのハンドラに認証機能を追加します。
PAGE_KEY = 'Onxxxxxxx=='

class MyHandler(s.BaseHTTPRequestHandler):
    def do_AUTHHEAD(self):
        self.send_response(401)
        self.send_header("WWW-Authenticate", "Basic realm=\"Please enter your user information\"")
        self.send_header("Content-type", "text/html")
        self.end_headers()
        return
        
    def do_GET(self):
        global PAGE_KEY
        chk_pass = False
        if (self.headers.get("Authorization") == None):
            self.do_AUTHHEAD()
            self.wfile.write('no auth user received'.encode("utf-8"))
            pass
        
        elif self.headers.get("Authorization") == "Basic " + PAGE_KEY:
            chk_pass = True
        
        else:
            self.do_AUTHHEAD()
            self.wfile.write((self.headers.get("Authorization")).encode("utf-8"))
            self.wfile.write('not authenticated'.encode("utf-8"))
            pass


パスワードを入力しないとページが表示されないようにすることができます。




次回は、パスワードの生成を考えてみます。
https://www.filetalk.info/index.html

2019年9月5日木曜日

ラズパイ(Webサーバー・7)

前回までは、http.server のサーバー部分・クライアント部分の処理を
作りやり取りができるようになりました。
今回は、設定ファイルの作成についてです。
設定ファイルの作成って、書き込めることはもちろんですが、
簡単に必要とする項目を、取り出せなければいけないですよね。
また、項目はカテゴリーごとに分けて人が見てもわかりやすい
ものでないと後から大変になります。
ファイルの途中から読んだり書いたりができることが重要ですね。

で、今回つかうのが、configparser です。
これを使うことで、問題が解決されます。
import configparser

CONF_PATH = "../sherpacti.conf"

# 読込み
config = configparser.ConfigParser()
config.read(CONF_PATH)

r_url = config.get(CONF_NRM_SEC_NM, "url")
r_urlwav = config.get(CONF_NRM_SEC_NM, "urlwav")

# 書込み
if (not config.has_section(CONF_WIFI_SEC_NM)):
    config.add_section(CONF_WIFI_SEC_NM)  # セクションがない場合作成

config.set(CONF_WIFI_SEC_NM, "wifi_ssid", wifi_ssid)
config.set(CONF_WIFI_SEC_NM, "wifi_psk", wifi_psk)

f = open(WIFI_TMP_PATH, "w+")
config.write(f)
f.close()


とすると次のようなファイルに書き込まれます
[sherpacti]
url = http://ws3.ipad-fan.info/send
urlwav = http://sherpa.5dx.org/tel2/upd_wave.php


https://www.filetalk.info/index.html
簡単に見やすい設定ファイルが作られますね
次回は、設定ファイルをだれからでもいじられないようにするために
パスワードで保護できるようにしたいと思います。

2019年9月4日水曜日

ラズパイ(Webサーバー・6)

前回までで、http.server のサーバー部分・クライアント部分の処理を
構築し、やり取りができるようになりました。
今回は、クライアントに表示するベースの部分となる
HTMLの内容を書いてみたいと思います。

管理画面として、設定ファイルを作成するページにしたいので、
項目と内容をいれるようなものにします。

<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>SherpaCTI Configuration</title>
<style type="text/css">
.tbl_main {
 margin: 20px; padding: 10px; background: #eee; border-radius: 10px;
}
.tbl_main, TD, TH {
 padding: 0px 5px 0px 5px;
}
.lrg_ttl {
 font-size: 22px; font-weight: bold;
}
.sml_ttl {
 font-size: 11px;
}
.sub_ttl {
 font-size: 17px; font-weight: bold;
}
.fld_ttl {
 padding: 0px 5px 0px 15px; font-size: 15px;
}
.input_l {
 width: 400px;
}
.set_button {
 padding: 5px; margin: 15px 0px 10px 0px; width: 100%; font-size: 20px; font-weight: bold;
}
</style>
<script src="axios.min.js"></script>
<script>
(window.onload = function() {
  axios.post('/', {
    "act": "get"
  })
  .then(function (response) {
    var result = document.getElementById('result1');
    document.getElementById('smbconnect_on').value = response.data['smbconnect_on'];
    document.getElementById('smb_username').value = response.data['smbusername'];
    document.getElementById('smb_password').value = response.data['smbpassword'];
    document.getElementById('smb_hostname').value = response.data['smbhostname'];
    document.getElementById('smb_ipaddress').value = response.data['smbipaddress'];
    document.getElementById('smb_remotedirectory').value = response.data['smbremotedirectory'];
    document.getElementById('smb_remotepath').value = response.data['smbremotepath'];
    
    result.innerHTML = response.data['ret'];
  })
  .catch(function (error) {
    console.log(error)
  });
})();

function setVal() {
  if (window.confirm("Are you sure you want to update it?")) {
    axios.post('/', {
      "act": "set",
      "smbconnect_on": document.getElementById('smbconnect_on').value,
      "smbusername": document.getElementById('smb_username').value,
      "smbpassword": document.getElementById('smb_password').value,
      "smbhostname": document.getElementById('smb_hostname').value,
      "smbipaddress": document.getElementById('smb_ipaddress').value,
      "smbremotedirectory": document.getElementById('smb_remotedirectory').value,
      "smbremotepath": document.getElementById('smb_remotepath').value
    })
    .then(function (response) {
      var result = document.getElementById('result1');
      result.innerHTML = response.data['ret'];
      alert("Finished");
    })
    .catch(function (error) {
      console.log(error)
    });
  }
}
</script>
</head>

<body>
  <center>
    <table class="tbl_main">
      <tr>
        <td colspan="2" align="center">
          <div style="padding: 10px 0px 0px 0px;"><span class="lrg_ttl">SherpaCTI Configuration Menu</span></div>
          <div style="margin: -5px 0px 0px 0px;"><span class="sml_ttl">©2019 Sherpa CO., LTD. ver:1.1a</span></div>
        </td>
      </tr>
      
      <tr>
        <td colspan="2">
          <hr />
          <span class="sub_ttl">SMB Connect Section</span>
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">SMB Connect</td>
        <td>
          <input type="text" class="input_l" name="smbconnect_on" id="smbconnect_on">
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">User Name</td>
        <td>
          <input type="text" class="input_l" name="smb_username" id="smb_username">
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">Password</td>
        <td>
          <input type="text" class="input_l" name="smb_password" id="smb_password">
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">Host Name</td>
        <td>
          <input type="text" class="input_l" name="smb_hostname" id="smb_hostname">
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">IP Address</td>
        <td>
          <input type="text" class="input_l" name="smb_ipaddress" id="smb_ipaddress">
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">Remote Directory</td>
        <td>
          <input type="text" class="input_l" name="smb_remotedirectory" id="smb_remotedirectory">
        </td>
      </tr>
      <tr>
        <td class="fld_ttl">Remote Path</td>
        <td>
          <input type="text" class="input_l" name="smb_remotepath" id="smb_remotepath">
        </td>
      </tr>
      
      <tr>
        <td colspan="2">
          <input type="button" class="set_button" value="Set Value" onclick="javascript:setVal()">
        </td>
      </tr>
      
      <tr>
        <td colspan="2" align="center">
          <table>
            <tr>
              <td class="fld_ttl">Return Value</td>
              <td>
                <div id="result1"></div>
              </td>
            </tr>
          </table>
        </td>
      </tr>
      
    </table>
  </center>
</body>
</html>


Python には、設定ファイルを簡単に読み書きできる configparser という
ライブラリがあるので、こちらを利用します。
次回は、このライブラリを使って、設定の読み書きを行います。

https://www.filetalk.info/index.html

2019年9月2日月曜日

ラズパイ(Webサーバー・5)

前回は、POSTの情報もとれるようになりました。
今回はこのJSONをつかって、やり取りできるようにAjax部分を
組み込んでいきます。
Ajaxを扱うものとしましては、jQuery が有名ですが、
Python の http.server と相性がいい axios を利用してみました。

サーバーへデータをPOSTする部分

  function setWrite() {
  if (window.confirm("Are you sure you want to update it?")) {
    var wifi_scan_ssid = "";
    if (document.getElementById('wifi_scan_ssid').checked) wifi_scan_ssid = "1";
    axios.post('/', {
      "act": "write",
      "wifi_ssid": document.getElementById('wifi_ssid').value,
      "wifi_psk": document.getElementById('wifi_psk').value,
      "wifi_scan_ssid": wifi_scan_ssid,
      "wifi_key_mgmt": document.getElementById('wifi_key_mgmt').value
    })
    .then(function (response) {
      var result = document.getElementById('result2');
      result.innerHTML = response.data['ret'];
      alert("Finished");
    })
    .catch(function (error) {
      console.log(error)
    });
  }
}


サーバーとのやり取りでのXHRの部分で、簡単に実装することができますね。
IE8以上から利用できたりと、古いブラウザのサポートもあったり、
リクエストのタイムアウトも指定できるので重宝しそうです。

https://www.filetalk.info/index.html