3-2. Methodology : NUGU Backend proxy server with Flask

91923 ワード

1.nuguilサーバの完全なコード


python flaskを用いてREST APIを実装しnugu speakerからのpost要求のコードを処理する.
大きく分けてFunctional PartとFlash Partがあります.
from flask import Flask, request, jsonify
from flask_restful import Resource, Api
import json
import os
import requests
from pyproj import Proj
from pyproj import transform
import datetime
import time
import pandas as pd
from pandas import DataFrame
import telepot
from apscheduler.schedulers.background import BackgroundScheduler

#Function part
def location(): #find users location
    url = 'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyDkx6muQn1Jz-y6hLOcTPVdYAhklm6WJQo'
    data = {
        'considerIp': True,
        #'homeMobileCountryCode': 450,
        #'homeMobileNetworkCode': 5,
        #'radioType':'gsm',
        #'carrier': "SKTelecom",
        #"wifiAccessPoints":[{'macAddress':'40:DC:9D:06:EC:CA'}]
    }
    result = requests.post(url, data)
    a=result.json()
    lat=a['location']['lat'] # Y point
    lng=a['location']['lng'] # X point
    return lat,lng

def trans(lat,lng): #wgs84 -> tm128
    WGS84 = { 'proj':'latlong', 'datum':'WGS84', 'ellps':'WGS84', }

    TM128 = { 'proj':'tmerc', 'lat_0':'38N', 'lon_0':'128E', 'ellps':'bessel',
   'x_0':'400000', 'y_0':'600000', 'k':'0.9999',
   'towgs84':'-146.43,507.89,681.46'}

    def wgs84_to_tm128(longitude, latitude):
       return transform( Proj(**WGS84), Proj(**TM128), longitude, latitude )
            
    x_point,y_point=wgs84_to_tm128(lng,lat)
    return x_point,y_point

def ask_oil_type(ans):
    if ans == "2번" : #경유
        return "D047"
    elif ans == "1번" : #휘발유
        return "B027"
    else :
        return None

def browse(x_point,y_point,oil_type):
    url = 'http://www.opinet.co.kr/api/aroundAll.do'
    payload = {
        "code" : "F886201116",
        "out" : "json",
        "x" : x_point,
        "y" : y_point,
        "radius" : "1000",
        "prodcd" : oil_type ,
        "sort" : "1"
        }
    result = requests.get(url,params=payload).json()
    return result

def content():
    global data_num
    global oil_list
    global title
    global cost
    data_num = len(oil_list["RESULT"]["OIL"]) #주유소 개수 확인

    if (data_num == 1):
        for i in range(0, 1):
            title.append(oil_list["RESULT"]["OIL"][i]['OS_NM']) 
    elif (data_num >= 3):
        for i in range(0, 3):  
            title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
    elif (data_num == 2):
        for i in range(0,2):
            title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])

    content_num = data_num

    if content_num == 0: #검색된 주유소가 0개인 경우
        title.append("null")
        cost.append("null")
    elif content_num > 3: #검색된 주유소 3개 초과일 경우 차례대로 3개만 처리
        for i in range(0, 3):
            cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])

    else: #검색된 주유소가 2개 혹은 3개인 경우
        for i in range(content_num):
            cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])
    return title, cost

def action(c):
    global data_num
    keys=['number','title','cost']
    arr=[]
    if data_num == 0: 
        return arr

    elif data_num == 1:
        values = ["1", c[0], c[1]]
        A = dict(zip(keys, values))
        arr.append(A)
        return arr

    elif data_num == 2:
        values_1 = ["1", c[0][0], c[1][0]]
        values_2 = ["2", c[0][1], c[1][1]]
        A = dict(zip(keys, values_1))
        B = dict(zip(keys, values_2))
        arr.append(A)
        arr.append(B)
        return arr

    else:
        values_1 = ["1", c[0][0], c[1][0]]
        values_2 = ["2", c[0][1], c[1][1]]
        values_3 = ["3", c[0][2], c[1][2]]
        A = dict(zip(keys, values_1))
        B = dict(zip(keys, values_2))
        C = dict(zip(keys, values_3))
        arr.append(A)
        arr.append(B)
        arr.append(C)
        return arr
    
def make_response(result_list):
    global select
    global oil_type

    response = {
        "version": "2.0",
        "resultCode": "OK",
        "output": {
            "COUNT": "0",
            "STATION_INFORMATION": "",
        }
    }
    result_len = len(result_list)
    temp = ""
    if result_len == 0:
        return response
    
    elif result_len>0:
        response["output"]["COUNT"] = str(result_len)

    if result_len == 1:
        temp = str(result_list[0]["title"]) + "," + str(result_list[0]["cost"]) + "원"
        temp = temp.replace(']','')
        temp = temp.replace('[','')
        temp = temp.replace("'",'')
        response["output"]["STATION_INFORMATION"] = temp
    else:
        for i in range(result_len):
            temp = temp + str(result_list[i]["title"]) + "," + str(result_list[i]["cost"]) + "원" + ","
            temp = temp.replace("]","")
            temp =temp.replace("[","")
            temp = temp.replace("'","")
        response["output"]["STATION_INFORMATION"] = temp

    return response

#flask part
app = Flask(__name__)
api = Api(app)

class Getparams(Resource):
    def post(self):
        data = request.get_json()
        print(data)
        global select
        global oil_type

        ans = ""
        if 'SELECT' in data['action']['parameters'].keys():
            select = data['action']['parameters']['SELECT']['value']
            if select == "1번" or select == "2번":
                ans = select
        if 'OIL_TYPE' in data['action']['parameters'].keys():
            oil_type = data['action']['parameters']['OIL_TYPE']['value']
            if oil_type == "경유":
                ans = "2번"
            elif oil_type == "휘발유":
                ans = "1번"

        a,b = location()
        a,b = trans(a,b)

        global oil_list
        oil_list = browse(a,b,ask_oil_type(ans))
        print(oil_list)

        global data_num
        global title
        global cost

        title = []
        cost = []
        result = action(content())
        response = make_response(result)

        if 'SELECT' in data['action']['parameters'].keys():
            response["output"]["SELECT"] = select
        if 'OIL_TYPE' in data['action']['parameters'].keys():
            response["output"]["OIL_TYPE"] = oil_type
        print(response)

        return jsonify(response)

api.add_resource(Getparams,'/answer.lowprice','/answer.lowprice.diesel','/answer.lowprice.gasoline','/answer.lowprice.diesel.0','/answer.lowprice.diesel.1','/answer.lowprice.gasoline.0','/answer.lowprice.gasoline.1','/answer.lowprice.select.diesel','/answer.lowprice.select.diesel0','/answer.lowprice.select.diesel1','/answer.lowprice.select.gasoline','/answer.lowprice.select.gasoline0','/answer.lowprice.select.gasoline1')

if __name__ == "__main__":
    app.run()

2.Nuguilサーバー機能部品


a.Geolocation APIを使用して現在位置を検索する

urlは、必要なGoogle Geolocation API URLおよびAPIキーを提供し、dataは、どのような情報要求に基づいて位置付けを要求するかに関するコードを提供する.nuguilはipに基づいてユーザ位置を追跡するため、'considerIp': Trueを追加した.result 변수は、地理的位置apiから取得された情報を受信し、それをjsonフォーマットに変換してa 변수に格納する.ここで必要な値はa['location']['lat']a['location']['lng']であり、それぞれy座標、x座標に対応する値である.これらの価格をそれぞれlat변수lng변수に入れ、return lat,lngを算出した.
def location(): #find users location
    url = 'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyDkx6muQn1Jz-y6hLOcTPVdYAhklm6WJQo'
    data = {
        'considerIp': True,
        #'homeMobileCountryCode': 450,
        #'homeMobileNetworkCode': 5,
        #'radioType':'gsm',
        #'carrier': "SKTelecom",
        #"wifiAccessPoints":[{'macAddress':'40:DC:9D:06:EC:CA'}]
    }
    result = requests.post(url, data)
    a=result.json()
    lat=a['location']['lat'] # Y point
    lng=a['location']['lng'] # X point
    return lat,lng
現在、地理的位置を使用してユーザーの位置を特定することはできません.これは個人情報の問題でnugu speaker側の承認を得てこそ使える機能です.実行するために、コードを完了するために任意の座標を追加することにしました.(欠陥部分を参照)

b.WGS 84フォーマットをオフィスネットワークのTM 128フォーマットに変換する


座標変換に関する関数を含むPyprojパッケージを用いた.parameterとして受け取ったWGS 84形式のlat,lng値をTM 128形式に変換し,x_point,y_point変数を入れて返却する.
def trans(lat,lng): #wgs84 -> tm128
    WGS84 = { 'proj':'latlong', 'datum':'WGS84', 'ellps':'WGS84', }

    TM128 = { 'proj':'tmerc', 'lat_0':'38N', 'lon_0':'128E', 'ellps':'bessel',
   'x_0':'400000', 'y_0':'600000', 'k':'0.9999',
   'towgs84':'-146.43,507.89,681.46'}

    def wgs84_to_tm128(longitude, latitude):
       return transform( Proj(**WGS84), Proj(**TM128), longitude, latitude )
            
    x_point,y_point=wgs84_to_tm128(lng,lat)
    return x_point,y_point

c.オフィスネットワークに変換する「prodcd」コード


この関数はnugu speakerからpostリクエスト受信の情報をパラメータとして受信し、officeapiを使用する場合に入力する「prodcd」コードに変換します.
def ask_oil_type(ans):
    if ans == "2번" : #경유
        return "D047"
    elif ans == "1번" : #휘발유
        return "B027"
    else :
        return None

d.半径5キロ以内のガソリンスタンドは、最低価格で並べ替えます


この関数はofficetapiを用いてx_pointy_pointoil_typeをパラメータとし,request因子を加えて半径5キロ以内で最も安いガソリンスタンドのソート情報を対応する位置から取得する.
def browse(x_point,y_point,oil_type):
    url = 'http://www.opinet.co.kr/api/aroundAll.do'
    payload = {
        "code" : "F886201116",
        "out" : "json",
        "x" : x_point,
        "y" : y_point,
        "radius" : "5000",
        "prodcd" : oil_type ,
        "sort" : "1"
        }
    result = requests.get(url,params=payload).json()
    return result

e.検索したガソリンスタンド数を確認し、最大3つの結果値を提供する


この関数は、brows関数の戻り値で先にリストされたガソリンスタンドのリストをglobalタイプのoil_listに格納し、いくつのガソリンスタンドがあるかを計算し、globalタイプのdata_num변수に格納し、最大3つのガソリンスタンドの名前と価格をglobalタイプのtitlecostにそれぞれ格納する.
def content():
    global data_num
    global oil_list
    global title
    global cost
    data_num = len(oil_list["RESULT"]["OIL"]) #주유소 개수 확인

    if (data_num == 1):
        for i in range(0, 1):
            title.append(oil_list["RESULT"]["OIL"][i]['OS_NM']) 
    elif (data_num >= 3):
        for i in range(0, 3):  
            title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
    elif (data_num == 2):
        for i in range(0,2):
            title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])

    content_num = data_num

    if content_num == 0: #검색된 주유소가 0개인 경우
        title.append("null")
        cost.append("null")
    elif content_num > 3: #검색된 주유소 3개 초과일 경우 차례대로 3개만 처리
        for i in range(0, 3):
            cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])

    else: #검색된 주유소가 2개 혹은 3개인 경우
        for i in range(content_num):
            cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])
    return title, cost

f.結果値をディックシーケンスリストに入れる


Action(c)関数は、content関数のコールバック**title****cost**のリストをcのパラメータとして受け入れ、**keys=['number','title','cost']**をキー値とするdickshernerにc値を入れてコールバックする関数である.
def action(c):
    global data_num
    keys=['number','title','cost']
    arr=[]
    if data_num == 0: 
        return arr

    elif data_num == 1:
        values = ["1", c[0], c[1]]
        A = dict(zip(keys, values))
        arr.append(A)
        return arr

    elif data_num == 2:
        values_1 = ["1", c[0][0], c[1][0]]
        values_2 = ["2", c[0][1], c[1][1]]
        A = dict(zip(keys, values_1))
        B = dict(zip(keys, values_2))
        arr.append(A)
        arr.append(B)
        return arr

    else:
        values_1 = ["1", c[0][0], c[1][0]]
        values_2 = ["2", c[0][1], c[1][1]]
        values_3 = ["3", c[0][2], c[1][2]]
        A = dict(zip(keys, values_1))
        B = dict(zip(keys, values_2))
        C = dict(zip(keys, values_3))
        arr.append(A)
        arr.append(B)
        arr.append(C)
        return arr

G.NUGU PlayBuilderの返信フォーマットを返します


make response(result list)関数は、action関数が返す戻り値が返すディックシーケンスリストを受信し、nugu speakerがpost要求を受信した後にnugu speakerに必要な応答ブロックデータ型のディックシーケンスを返す関数である.response["output"]["COUNT"]はガソリンスタンドの総数を含み、response["output"]["STATION_INFORMATION"]はガソリンスタンドの情報を含む.「~ガソリンスタンド、~一、~ガソリンスタンド、~一」など、最大3つのガソリンスタンドの情報を文字列でリストします.
「~ガソリンスタンド、~一、~ガソリンスタンド、~一」という形式を用いるのは、nuguspeakerでガソリンスタンド情報を後で発言する際に、簡単な方法で途切れることなく情報を発信できるためである.
def make_response(result_list):
    global select
    global oil_type

    response = {
        "version": "2.0",
        "resultCode": "OK",
        "output": {
            "COUNT": "0",
            "STATION_INFORMATION": "",
        }
    }
    result_len = len(result_list)
    temp = ""
    if result_len == 0:
        return response
    
    elif result_len>0:
        response["output"]["COUNT"] = str(result_len)

    if result_len == 1:
        temp = str(result_list[0]["title"]) + "," + str(result_list[0]["cost"]) + "원"
        temp = temp.replace(']','')
        temp = temp.replace('[','')
        temp = temp.replace("'",'')
        response["output"]["STATION_INFORMATION"] = temp
    else:
        for i in range(result_len):
            temp = temp + str(result_list[i]["title"]) + "," + str(result_list[i]["cost"]) + "원" + ","
            temp = temp.replace("]","")
            temp =temp.replace("[","")
            temp = temp.replace("'","")
        response["output"]["STATION_INFORMATION"] = temp

    return response

2.NuguilサーバFlaskpart


まず、data = request.get_json()を介して受信されたrequest bodyには、サービスユーザがどのようなタイプの油を基準に最低価格のガソリンスタンドを検索したいかに関する情報が含まれている.この情報は、nugu playbuilderによって設定されたactionに従って、SELECTまたはOIL_TYPEというパラメータに含まれ、そのうちの1つだけがrequest bodyに渡される.ifクエリ条件により、対応するキー値が存在するか否かを確認し、ans변수にvalue値を入れる.browse関数でoiltypeをパラメータとする場合、関連コードに変換する関数ask oil type(ans)を使用するには、ans変数に入る値を「1番」または「2番」に変換する必要があります.したがって、data['action']['parameters']['OIL_TYPE']['value']の値が「ガソリン」であれば「1号」、「ディーゼル」であれば「2号」となる.
次に、location()関数で求めた位置情報をa,b변수に初期化し、その後、trans(a,b)関数でWGS 84形式のa,b値をTM 128形式に変換する.
location()関数を使用してユーザーの位置を追跡する必要がありますが、個人情報の使用許可が得られていないため、どのスピーカーにも使用できません.したがって、次のコードは任意の座標値を使用して実行されます.
browse(a,b,ask oil type(ans))関数で求めたガソリンスタンドリストをoil_listに初期化し、oil_listの値をaction(content()関数で所望のフォーマットに変換する.
make response(result)関数でresponse body形式で情報を加工し、selectまたはoiltypeで入力した値をresponse bodyに追加します.
リクエストbodyとして受信したパラメータ値もresponse bodyに入力します.
app = Flask(__name__)
api = Api(app)

class Getparams(Resource):
    def post(self):
        data = request.get_json()
        print(data)
        global select
        global oil_type

        ans = ""
        if 'SELECT' in data['action']['parameters'].keys():
            select = data['action']['parameters']['SELECT']['value']
            if select == "1번" or select == "2번":
                ans = select
        if 'OIL_TYPE' in data['action']['parameters'].keys():
            oil_type = data['action']['parameters']['OIL_TYPE']['value']
            if oil_type == "경유":
                ans = "2번"
            elif oil_type == "휘발유":
                ans = "1번"

        #a,b = location()
        #print(a,b)
        a,b = 37.585876,127.143135
        a,b = trans(a,b)

        global oil_list
        oil_list = browse(a,b,ask_oil_type(ans))
        print(oil_list)

        global data_num
        global title
        global cost

        title = []
        cost = []
        result = action(content())
        response = make_response(result)

        if 'SELECT' in data['action']['parameters'].keys():
            response["output"]["SELECT"] = select
        if 'OIL_TYPE' in data['action']['parameters'].keys():
            response["output"]["OIL_TYPE"] = oil_type
        print(response)

        return jsonify(response)

api.add_resource(Getparams,'/answer.lowprice','/answer.lowprice.diesel','/answer.lowprice.gasoline','/answer.lowprice.diesel.0','/answer.lowprice.diesel.1','/answer.lowprice.gasoline.0','/answer.lowprice.gasoline.1','/answer.lowprice.select.diesel','/answer.lowprice.select.diesel0','/answer.lowprice.select.diesel1','/answer.lowprice.select.gasoline','/answer.lowprice.select.gasoline0','/answer.lowprice.select.gasoline1')

if __name__ == "__main__":
    app.run()