# WAGRI-SAMPLE WAGRI API に観測データを登録するクライアントの、サンプル実装です。Python3.8 で動作確認をしています。 - `init.py`: 圃場情報などの登録プログラム - `main.py`: 測定結果の登録プログラム - `wagri.py`: WAGRI API を叩くライブラリ ## 使い方 依存ライブラリをインストールします: ``` python3 -m venv venv source ./venv/bin/activate pip install -r requirements.txt ``` 認証情報を `conf.py` に作成します: ``` # 設定情報 BaseURL = "https://api.wagri.net" ClientID = "" # https://api.wagri.net/Admin/Vendor から登録してください ClientSecret = "" # https://api.wagri.net/Admin/Vendor から登録してください ``` 圃場情報などを登録します。各種IDなどは `init.py` 内で決め打ちしています: ``` python init.py ``` 測定データを登録します。サンプル実装として、測定時刻はプログラム実行時刻、測定値は固定値としています。 ``` python main.py ``` ## HTTPie による例 サンプル実装と同様の処理をコマンドラインから HTTPie で行った例です。JSON パーサとして jq が必要です。 #### アクセストークンを取得する ``` client_id=hoge secret=foo token=`http -f POST https://api.wagri.net/Token \ grant_type=client_credentials client_id=$client_id client_secret=$secret | jq -r .access_token` ``` `client_id` と `client_secret` は https://api.wagri.net/Admin/Vendor で登録する #### 農家を登録する ``` http POST https://api.wagri.net/API/Private/Farmer/Register \ FarmerId=farmer001 FarmerName=Farmer X-Authorization:$token ``` #### 圃場を登録する ``` http POST https://api.wagri.net/API/Private/Field/Register \ FieldId=field001 FarmerId=farmer001 X-Authorization:$token ``` #### センサー群を登録する ``` http POST https://api.wagri.net/API/Sensing/Private/Things/Register \ key=things001 name="ボックス1" fieldId=field001 X-Authorization:$token ``` #### センサーを登録する ``` http POST https://api.wagri.net/API/Sensing/Master/Sensors/Register \ key=tempSensor001 name="温度センサー" X-Authorization:$token ``` #### 測定項目を登録する ``` http POST https://api.wagri.net//API/Sensing/Master/ObservedProperties/Register \ key=WaterTemperature name="水温" definition="http://example.com/defs" \ X-Authorization:$token ``` #### 測定単位を登録する ``` http POST https://api.wagri.net/API/Sensing/Master/MeasurementUnits/Register \ key=degC name="摂氏温度" symbol="℃" \ uri="http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#DegreeCelsius" \ X-Authorization:$token ``` #### 測定条件を登録する ``` http POST https://api.wagri.net/API/Sensing/Private/ObservationConditions/Register \ key=ObservationCondition001 name="測定条件001" X-Authorization:$token ``` #### データストリームを登録する ``` http POST https://api.wagri.net/API/Sensing/Private/Datastreams/Register \ key="Thing001-WaterTemperature" \ name="Thing001の水温のDatastream" \ ObservedPropertyId=WaterTemperature \ UnitOfMeasurementId=degC \ SensorId=tempSensor001 \ ObservationConditionId=ObservationCondition001 \ ThingId=things001 \ X-Authorization:$token ``` #### 測定結果を登録する ``` http POST https://api.wagri.net/API/Sensing/Private/Observations/Register \ key="2019-10-23T07:40:40" \ phenomenonTime="2019-10-23T07:40:40" \ result=24.1 \ DatastreamId="Thing001-WaterTemperature" \ X-Authorization:$token ``` なお、測定値は int や float ではなく、string 型で登録します。 ## ライセンス WAGRIサンプル実装は MIT ライセンスに従って自由に利用できます。 Copyright (c) 2020 Internet Initiative Japan Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.   import conf from wagri import Wagri def main(): # 認証 wagri = Wagri(conf.BaseURL, conf.ClientID, conf.ClientSecret) if not wagri.getToken(): print("failed to get authorization token") return # 共通情報の登録 # TODO: サンプル実装のため、エラーハンドリングは省略 wagri.registSensor("sensor01", "水温センサー") wagri.registSensor("sensor02", "水位センサー") wagri.registObservedProperties("waterTemp", "水温") wagri.registObservedProperties("waterLevel", "水位") wagri.registMeasurementUnits("degC", "摂氏温度", "℃", "http://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#DegreeCelsius") wagri.registMeasurementUnits("cm", "センチメートル", "cm", "https://www.qudt.org/qudt/owl/1.0.0/unit/Instances.html#Centimeter") wagri.registObservationConditions("condition01", "測定条件01") # 各農家毎の情報の登録 farmerID = "farmer001" thingsID = "00-00-00-00-00-01" # センサー群の ID. 例: LoRa Gateway の MAC Address wagri.registFarmer(farmerID, "Wagri Taro") wagri.registField(f"{farmerID}-field01", farmerID) wagri.registThings(thingsID, thingsID, "自動登録", f"{farmerID}-field01") wagri.registDatastreams(f"{thingsID}-watertemp", "水温のDatastream", "waterTemp", "degC", "sensor01", "condition01", thingsID) wagri.registDatastreams(f"{thingsID}-waterlevel", "水位のDatastream", "waterLevel", "cm", "sensor02", "condition01", thingsID) main()   from datetime import datetime from pprint import pprint import conf from wagri import Wagri def main(): # 認証 wagri = Wagri(conf.BaseURL, conf.ClientID, conf.ClientSecret) if not wagri.getToken(): print("failed to get authorization token") return thingsID = "00-00-00-00-00-01" now = datetime.now() wagri.registObservations(now, 24.5, f"{thingsID}-watertemp") wagri.registObservations(now, 1.7, f"{thingsID}-waterlevel") pprint(wagri.getObservations()) main()   # wagri.py # # Copyright (c) 2019 Internet Initiative Japan Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from datetime import datetime from typing import Dict import requests class Wagri: def __init__(self, baseURL: str, clientID: str, clientSecret: str) -> None: self.baseURL = baseURL self.clientID = clientID self.clientSecret = clientSecret self._token = "" self._tokenExpire = 0 self._session = requests.Session() def getToken(self) -> bool: requrl = self.baseURL + "/Token" reqbody = dict( client_id=self.clientID, client_secret=self.clientSecret, grant_type="client_credentials", ) res = requests.post(requrl, data=reqbody) if res.status_code != 200: return False r = res.json() self._token = r["access_token"] self._tokenExpire = r["expires_in"] self._session.headers.update({"X-Authorization": self._token}) return True def _regist(self, requrl: str, reqbody: Dict) -> bool: res = self._session.post(requrl, json=reqbody) res.raise_for_status() if res.status_code != 201: return False return True def registSensor(self, key: str, name: str) -> bool: requrl = self.baseURL + "/API/Sensing/Master/Sensors/Register" reqbody = dict(key=key, name=name) return self._regist(requrl, reqbody) def registObservedProperties(self, key: str, name: str) -> bool: requrl = self.baseURL + "/API/Sensing/Master/ObservedProperties/Register" reqbody = dict(key=key, name=name) return self._regist(requrl, reqbody) def registMeasurementUnits(self, key: str, name: str, symbol: str, uri: str) -> bool: requrl = self.baseURL + "/API/Sensing/Master/MeasurementUnits/Register" reqbody = dict(key=key, name=name, symbol=symbol, uri=uri) return self._regist(requrl, reqbody) def registObservationConditions(self, key: str, name: str) -> bool: requrl = self.baseURL + "/API/Sensing/Private/ObservationConditions/Register" reqbody = dict(key=key, name=name) return self._regist(requrl, reqbody) def registFarmer(self, key: str, name: str) -> bool: requrl = self.baseURL + "/API/Private/Farmer/Register" reqbody = dict(FarmerId=key, FarmerName=name) return self._regist(requrl, reqbody) def registField(self, key: str, farmerID: str) -> bool: requrl = self.baseURL + "/API/Private/Field/Register" reqbody = dict(FieldId=key, FarmerId=farmerID) return self._regist(requrl, reqbody) def registThings(self, key: str, name: str, description: str, fieldID: str) -> bool: requrl = self.baseURL + "/API/Sensing/Private/Things/Register" reqbody = dict(key=key, name=name, description=description, FieldId=fieldID) return self._regist(requrl, reqbody) def registDatastreams(self, key: str, name: str, observedPropertyID: str, measurementUnitsID: str, sensorID: str, observationConditionId: str, thingID: str) -> bool: requrl = self.baseURL + "/API/Sensing/Private/Datastreams/Register" reqbody = dict( key=key, name=name, ObservedPropertyId=observedPropertyID, UnitOfMeasurementId=measurementUnitsID, SensorId=sensorID, ObservationConditionId=observationConditionId, ThingId=thingID ) return self._regist(requrl, reqbody) def registObservations(self, time: datetime, result: float, datastreamID: str) -> bool: requrl = self.baseURL + "/API/Sensing/Private/Observations/Register" reqbody = dict( key=time.isoformat(), phenomenonTime=time.isoformat(), result=str(result), DatastreamId=datastreamID ) return self._regist(requrl, reqbody) def getObservations(self): requrl = self.baseURL + "/API/Sensing/Private/Observations" res = self._session.get(requrl) if res.status_code != 200: return None return res.json()   # Created by https://www.gitignore.io/api/python # Edit at https://www.gitignore.io/?templates=python ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # pyenv .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # Mr Developer .mr.developer.cfg .project .pydevproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # End of https://www.gitignore.io/api/python venv/ conf.py