출처: https://webnautes.tistory.com/829


안드로이드 앱이  PHP 프로그램을 매개로 하여 MySQL 데이터베이스 서버에 데이터를 JSON 형식으로 가져오는 간단한 예제입니다.




1. 웹브라우저로 PHP 동작  테스트


2. Android 앱에서 테스트


3. 코드 설명


4. 관련 포스팅


5. 참고




마지막 업데이트 2018. 12. 7




안드로이드 앱이 웹서버에 있는 PHP 파일 실행시키면, PHP 코드는 질의를 하여 MySQL 서버로부터 데이터를 가져옵니다.  

그리고 나서 데이터를 JSON 포맷으로 가공하여 안드로이드 앱으로 넘겨줍니다.

안드로이드 앱은 JSON 포맷의 데이터를 파싱하여 원하는 UI에 데이터를 보기 좋게 보여주게 됩니다.


JSON(제이슨, JavaScript Object Notation)은 사람이 읽을 수 있는 텍스트를 사용하여  데이터를 전달할 수 있도록 해주는 개방형 표준 포맷입니다.

데이터를 키(key)와 값(value)가 쌍을 이루는 형식을 변환하여 사용합니다.


다음 포스팅에 연결된 포스팅입니다.



Android PHP MySQL 예제 - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828




1. 웹브라우저로 PHP 동작  테스트

Android 앱으로 테스트를  진행하기 전에 다음 PHP 코드를  웹 브라우저로 간단한 테스트를 해보겠습니다.



1-1. 앞에서 진행했던 포스팅의 결과로 현재 testdb 데이터베이스의 person 테이블에  데이터가 저장되어 있는 상태입니다.



Android PHP MySQL 예제  - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828




다음 두 개의 파일이 필요합니다.


dbcon.php

MySQL 서버 접속을 위해 사용되는 코드입니다. 위에서 언급한 포스팅에 있습니다.


getjson.php

MySQL에 질의를 보내어 결과를 가져오고 JSON 포맷으로 변환하여 출력하는 역할을 합니다.


getjson.php


<?php

   error_reporting(E_ALL);
   ini_set('display_errors',1);

   include('dbcon.php');
       

   $stmt = $con->prepare('select * from person');
   $stmt->execute();

   if ($stmt->rowCount() > 0)
   {
       $data = array();

       while($row=$stmt->fetch(PDO::FETCH_ASSOC))
       {
           extract($row);
   
           array_push($data,
               array('id'=>$id,
               'name'=>$name,
               'country'=>$country
           ));
       }

       header('Content-Type: application/json; charset=utf8');
       $json = json_encode(array("webnautes"=>$data), JSON_PRETTY_PRINT+JSON_UNESCAPED_UNICODE);
       echo $json;
   }

?>




1-2. 윈도우라면 C:\wamp64\www\ 경로에 파일을 생성합니다.



1-3. 우분투라면 /var/www/html/ 경로에 파일을 생성합니다.



1-4. 웹브라우저에서 localhost/getjson.php 주소에 접속하면 캡쳐화면처럼 JSON 형식으로 데이터를 보여줍니다.





2. Android 앱에서 테스트


웹서버의 PHP 파일을 매개로하여  데이터베이스의 데이터를 가져오는 안드로이드 앱을 작성합니다.



2-1. 매니페스트 파일 AndroidManifest.xml의  manifest 태그 하위 항목으로 인터넷 접근 허용 퍼미션을 추가합니다.



<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.tistory.webnautes.phptest">

   <uses-permission android:name="android.permission.INTERNET" />
   
   <application
       android:allowBackup="true"




2-2. 레이아웃 파일 activity_main.xml을 아래 내용으로 대체합니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context=".MainActivity"
   android:layout_margin="15dp"
   android:padding="10dp">

   <android.support.v7.widget.RecyclerView
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="6"
       android:layout_margin="5dp"
       android:padding="5dp"
       android:id="@+id/listView_main_list" />

   <Button
       android:layout_margin="2dp"
       android:id="@+id/button_main_all"
       android:layout_width="match_parent"
       android:layout_weight="2"
       android:layout_height="0dp"
       android:text="전체보기" />

   <TextView
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="4"
       android:layout_margin="5dp"
       android:padding="5dp"
       android:id="@+id/textView_main_result" />



</LinearLayout>



상단의 RecyclerView에는 데이터베이스로부터 가져온 JSON 형식의 데이터를 파싱해서 항목별로 보여줍니다.

하단의 TextView에는 JSON 형식의 데이터 또는 각종 에러를 보여주는 용도로 사용됩니다.




2-3. 레이아웃 파일 item_list.xml을 추가로 생성하여 아래 내용으로 바꿉니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="horizontal"
   android:layout_margin="5dp"
   android:padding="5dp">

   <TextView
       android:id="@+id/textView_list_id"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_margin="5dp"
       android:padding="5dp" />


   <TextView
       android:id="@+id/textView_list_name"
       android:layout_width="100dp"
       android:layout_height="wrap_content"
       android:layout_margin="5dp"
       android:padding="5dp" />


   <TextView
       android:id="@+id/textView_list_country"
       android:layout_width="100dp"
       android:layout_height="wrap_content"
       android:layout_margin="5dp"
       android:padding="5dp" />

</LinearLayout>



RecyclerView의 한 줄을 여러 개의 열로 표현하기 위해 필요한 레이아웃입니다.

필요한 열 개수 만큼 TextView를 추가하고 보여주는 데이터 값에 맞게 크기를 지정해주면 됩니다.




2-4. RecyclerView를 사용하려면  build.gradle에 다음 노란줄을 추가해야 합니다.



dependencies {
   implementation fileTree(dir: 'libs', include: ['*.jar'])
   implementation 'com.android.support:appcompat-v7:27.1.1'
   implementation 'com.android.support.constraint:constraint-layout:1.1.2'
   implementation 'com.android.support:recyclerview-v7:27.1.1'
   testImplementation 'junit:junit:4.12'
   androidTestImplementation 'com.android.support.test:runner:1.0.2'
   androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}




2-5. 자바 파일 MainActivity.java을 아래 내용으로 바꿉니다.


다음 줄에 있는 IP 주소를 아파치 웹서버가 설치된  컴퓨터의 IP로 수정하세요.

private static String IP_ADDRESS = "IP주소";


안드로이드 에뮬레이터와 서버가 같은 컴퓨터에 동작하는 경우에는 다음 아이피를 입력합니다.

Android Studio의 에뮬레이터  - 10.0.2.2

GenyMotion - 192.168.56.1


package com.tistory.webnautes.phptest;

import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;


public class MainActivity extends AppCompatActivity {

   private static String IP_ADDRESS = "IP주소";
   private static String TAG = "phptest";

   private EditText mEditTextName;
   private EditText mEditTextCountry;
   private TextView mTextViewResult;
   private ArrayList<PersonalData> mArrayList;
   private UsersAdapter mAdapter;
   private RecyclerView mRecyclerView;
   private EditText mEditTextSearchKeyword;
   private String mJsonString;


   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       mTextViewResult = (TextView)findViewById(R.id.textView_main_result);
       mRecyclerView = (RecyclerView) findViewById(R.id.listView_main_list);
       mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

       mTextViewResult.setMovementMethod(new ScrollingMovementMethod());



       mArrayList = new ArrayList<>();

       mAdapter = new UsersAdapter(this, mArrayList);
       mRecyclerView.setAdapter(mAdapter);


       Button button_all = (Button) findViewById(R.id.button_main_all);
       button_all.setOnClickListener(new View.OnClickListener() {
           public void onClick(View v) {

               mArrayList.clear();
               mAdapter.notifyDataSetChanged();

               GetData task = new GetData();
               task.execute( "http://" + IP_ADDRESS + "/getjson.php", "");
           }
       });

   }


 
   private class GetData extends AsyncTask<String, Void, String>{

       ProgressDialog progressDialog;
       String errorString = null;

       @Override
       protected void onPreExecute() {
           super.onPreExecute();

           progressDialog = ProgressDialog.show(MainActivity.this,
                   "Please Wait", null, true, true);
       }


       @Override
       protected void onPostExecute(String result) {
           super.onPostExecute(result);

           progressDialog.dismiss();
           mTextViewResult.setText(result);
           Log.d(TAG, "response - " + result);

           if (result == null){

               mTextViewResult.setText(errorString);
           }
           else {

               mJsonString = result;
               showResult();
           }
       }


       @Override
       protected String doInBackground(String... params) {

           String serverURL = params[0];
           String postParameters = params[1];


           try {

               URL url = new URL(serverURL);
               HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();


               httpURLConnection.setReadTimeout(5000);
               httpURLConnection.setConnectTimeout(5000);
               httpURLConnection.setRequestMethod("POST");
               httpURLConnection.setDoInput(true);
               httpURLConnection.connect();


               OutputStream outputStream = httpURLConnection.getOutputStream();
               outputStream.write(postParameters.getBytes("UTF-8"));
               outputStream.flush();
               outputStream.close();


               int responseStatusCode = httpURLConnection.getResponseCode();
               Log.d(TAG, "response code - " + responseStatusCode);

               InputStream inputStream;
               if(responseStatusCode == HttpURLConnection.HTTP_OK) {
                   inputStream = httpURLConnection.getInputStream();
               }
               else{
                   inputStream = httpURLConnection.getErrorStream();
               }


               InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
               BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

               StringBuilder sb = new StringBuilder();
               String line;

               while((line = bufferedReader.readLine()) != null){
                   sb.append(line);
               }

               bufferedReader.close();

               return sb.toString().trim();


           } catch (Exception e) {

               Log.d(TAG, "GetData : Error ", e);
               errorString = e.toString();

               return null;
           }

       }
   }


   private void showResult(){

       String TAG_JSON="webnautes";
       String TAG_ID = "id";
       String TAG_NAME = "name";
       String TAG_COUNTRY ="country";


       try {
           JSONObject jsonObject = new JSONObject(mJsonString);
           JSONArray jsonArray = jsonObject.getJSONArray(TAG_JSON);

           for(int i=0;i<jsonArray.length();i++){

               JSONObject item = jsonArray.getJSONObject(i);

               String id = item.getString(TAG_ID);
               String name = item.getString(TAG_NAME);
               String country = item.getString(TAG_COUNTRY);

               PersonalData personalData = new PersonalData();

               personalData.setMember_id(id);
               personalData.setMember_name(name);
               personalData.setMember_country(country);

               mArrayList.add(personalData);
               mAdapter.notifyDataSetChanged();
           }



       } catch (JSONException e) {

           Log.d(TAG, "showResult : ", e);
       }

   }

}




2-6. PersonalData.java 파일을 추가합니다.


MainActivity와 UsersAdapter에서 사용하는 클래스로 각각 다음 용도로 사용합니다.

  • MainActivity- ArrayList에 데이터를 저장하기 위해 사용됩니다.

  • UsersAdapter - ArrayList에 있는 데이터를 RecyclerView에 보여줄 때 사용됩니다.


package com.tistory.webnautes.phptest;


public class PersonalData {
   private String member_id;
   private String member_name;
   private String member_country;

   public String getMember_id() {
       return member_id;
   }

   public String getMember_name() {
       return member_name;
   }

   public String getMember_country() {
       return member_country;
   }

   public void setMember_id(String member_id) {
       this.member_id = member_id;
   }

   public void setMember_name(String member_name) {
       this.member_name = member_name;
   }

   public void setMember_country(String member_address) {
       this.member_country = member_address;
   }
}




2-7. UsersAdapter.java 파일을 추가합니다.

ArrayList에 있는 PersonalData 타입의 데이터를 RecyclerView에 보여주는 작업을 합니다.


package com.tistory.webnautes.phptest;

import android.app.Activity;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;


public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.CustomViewHolder> {

   private ArrayList<PersonalData> mList = null;
   private Activity context = null;


   public UsersAdapter(Activity context, ArrayList<PersonalData> list) {
       this.context = context;
       this.mList = list;
   }

   class CustomViewHolder extends RecyclerView.ViewHolder {
       protected TextView id;
       protected TextView name;
       protected TextView country;


       public CustomViewHolder(View view) {
           super(view);
           this.id = (TextView) view.findViewById(R.id.textView_list_id);
           this.name = (TextView) view.findViewById(R.id.textView_list_name);
           this.country = (TextView) view.findViewById(R.id.textView_list_country);
       }
   }


   @Override
   public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
       View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_list, null);
       CustomViewHolder viewHolder = new CustomViewHolder(view);

       return viewHolder;
   }

   @Override
   public void onBindViewHolder(@NonNull CustomViewHolder viewholder, int position) {

       viewholder.id.setText(mList.get(position).getMember_id());
       viewholder.name.setText(mList.get(position).getMember_name());
       viewholder.country.setText(mList.get(position).getMember_country());
   }

   @Override
   public int getItemCount() {
       return (null != mList ? mList.size() : 0);
   }

}




2-8. 실행결과 입니다.


상단 RecyclerView는 MySQL에서 가져온 데이터를 파싱해서 리스트 형태로 출력합니다.

하단 TextView는 파싱 전  JSON 형식의 데이터 또는 에러를 출력합니다.





3. 코드  설명

1-1. PersonalData.java 파일에 사용할 데이터가 선언되어 있습니다.


public class PersonalData {
   private String member_id;
   private String member_name;
   private String member_country;

}



데이터 추가시 클래스의 데이터 저장 및 읽어올때 사용되는 멤버 메소드를 추가하는 방법은 다음 포스팅을 참고하세요.



Android RecyclerView 간단한 예제

https://webnautes.tistory.com/1214





1-2. UsersAdapter.java 파일에 데이터를 보여주기 위한 처리가 있습니다.



레이아웃 파일에 있는 UI 컴포넌트를 CustomViewHolder 클래스의 멤버변수와 연결합니다.

   class CustomViewHolder extends RecyclerView.ViewHolder {
       protected TextView id;
       protected TextView name;
       protected TextView country;


       public CustomViewHolder(View view) {
           super(view);
           this.id = (TextView) view.findViewById(R.id.textView_list_id);
           this.name = (TextView) view.findViewById(R.id.textView_list_name);
           this.country = (TextView) view.findViewById(R.id.textView_list_country);
       }
   }

onBindViewHolder 호출될때 CustomViewHolder에 데이터를 추가합니다.

   @Override
   public void onBindViewHolder(@NonNull CustomViewHolder viewholder, int position) {

       viewholder.id.setText(mList.get(position).getMember_id());
       viewholder.name.setText(mList.get(position).getMember_name());
       viewholder.country.setText(mList.get(position).getMember_country());
   }




3. 버튼을 클릭시 GetData AsyncTask가 실행됩니다.

안드로이드 코드에서 실행시킬 서버의 IP_ADDRESS와 PHP 파일 이름을 지정해줍니다.


Button button_all = (Button) findViewById(R.id.button_main_all);
button_all.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {

mArrayList.clear();
mAdapter.notifyDataSetChanged();

GetData task = new GetData();
task.execute( "http://" + IP_ADDRESS + "/getjson.php", "");
}
});


4. doInBackground 메소드에서 서버에 있는  PHP 파일을 실행시키고 응답을 저장하고 스트링으로 변환하여  리턴합니다.

@Override
  protected String doInBackground(String... params) {



다음 포스팅에서 설명한 부분이라 설명을 생략합니다.  



Android PHP MySQL 예제 - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828



5. 에러가 있는 경우 에러메시지를 보여주고 아니면 JSON을 파싱하여 화면에 보여주는 showResult 메소드를 호출합니다.


     @Override
protected void onPostExecute(String result) {
super.onPostExecute(result);

progressDialog.dismiss();
mTextViewResult.setText(result);
Log.d(TAG, "response - " + result);

if (result == null){

mTextViewResult.setText(errorString);
}
else {

mJsonString = result;
showResult();
}
}


6. 안드로이드 앱에서 받은 JSON 포맷의 데이터입니다.

간단한 규칙이 있는데 중괄호 {} 는 JSONObject  대괄호 [] JSONArray입니다.


{
   "webnautes": [
       {
           "id": "1",
           "name": "홍길동",
           "country": "조선"
       },
       {
           "id": "2",
           "name": "잔다르크",
           "country": "프랑스"
       }
   ]
}


         


7. 첫번째 괄호는 중괄호 {} 이므로 JSONObject입니다.


JSONObject jsonObject = new JSONObject(mJsonString);



8. jsonObject에서 TAG_JSON 키를 갖는 JSONArray를 가져옵니다.


JSONArray jsonArray = jsonObject.getJSONArray(TAG_JSON);


이제 다음같은 구조를 jsonArray가 갖고 있게 됩니다.

[
       {
           "id": "1",
           "name": "홍길동",
           "country": "조선"
       },
       {
           "id": "2",
           "name": "잔다르크",
           "country": "프랑스"
       }
   ]



9. JsonArray에는 JSONObject가 데이터 갯수만큼 포함되어 있습니다.

인덱스를 사용하여 JsonArray에서 JSONObject를 하나씩 가져옵니다.

for(int i=0;i<jsonArray.length();i++){
JSONObject item = jsonArray.getJSONObject(i);


10. JSONObject에서 키 id, name, country의 값을 가져옵니다.


String id = item.getString(TAG_ID);
String name = item.getString(TAG_NAME);
String country = item.getString(TAG_COUNTRY);


11. 데이터를 새로 생성한 PersonalData 클래스의 멤버변수에 입력하고 ArrayList에 추가합니다.

PersonalData personalData = new PersonalData();

personalData.setMember_id(id);
personalData.setMember_name(name);
personalData.setMember_country(country);

mArrayList.add(personalData);

12. 리스트에 데이터가 변경되었음을 알려줍니다. 화면에 추가된 데이터들이 보입니다.


mAdapter.notifyDataSetChanged();



4. 관련 포스팅



Android PHP MySQL 예제 - 데이터베이스에 데이터 입력하기

http://webnautes.tistory.com/828


Android PHP MySQL 예제 - 데이터베이스 질의(query) 결과 출력하기

https://webnautes.tistory.com/1159


Android PHP MySQL 예제  - 데이터베이스에 데이터 저장 및 JSON 형식으로 가져오는 예제 프로젝트

http://webnautes.tistory.com/1189  





5. 참고

[1] http://codeigniter-kr.org/bbs/view/tip?idx=8300


[2] http://www.simplifiedcoding.net/android-json-parsing-retrieve-from-mysql-database/

 

[3] http://www.simplifiedcoding.net/android-json-tutorial-to-get-data-from-mysql-database/

출처: https://jandh.tistory.com/16


아두이노와 ESP8266을 활용하여 센싱한 데이터의 데이터베이스 저장과 리트리빙

 

 

1.     사전 작업

 

WAMP 기반 웹서버를 Bitnami wordpress를 이용하여 구축한다. 물론 취향에 맞게 다른 AMP 스택을 설치할 수도 있다. 본 문서는 위의 Bitnami Wordpress를 기준으로 하여 설명한다.

Wordpress 설치 시에는 관리자id을 통상 admin으로 설정하는데, 해킹의 대상이 될 수 있으므로 가급적이면 다른 id로 설정하는 것을 권장한다.

 

Bitnami Wordpress를 설치한 후의 폴더 경로 및 구조는 아래와 같다.

 

 

<그림 1> Bitnami Wordpress 설치 폴더 구조

 

Wordpress는 아래와 같이 apps\wordpress 폴더에 설치가 되며, document root 는 htdocs이다.

 

<그림 2> Wordpress 설치 폴더

 

 

2.     센싱할 데이터 및 데이터베이스 테이블 정의

 

센싱하여 저장할 데이터는 센싱 시각, 온도 및 습도이며, mySql 기준으로 다음 [표.1]의 데이터 속성을 갖는다. DHT11 센서의 경우 온도, 습도를 정수형(TINYINT, SMALINT 등)으로도 선언 가능하겠으나, 확장성 등을 고려하여 FLOAT로 선언하였다.

여기서 sensDate는 편의상 실제로 측정한 시각이 아닌, 데이터베이스에 기록된 시각을 사용한다.

 

[표. 1] 데이터베이스 테이블 구조

Field

Data Type

Null

비고

sensDate

DATETIME

Not null

 

temperature

FLOAT

Not Null

 

humidity

FLOAT

Not null

 

 

 

3.     데이터베이스 생성

 

더 진행하기 전에 몇 가지 명칭을 아래와 같이 정의한다(실제 구현 시에는 다른 이름으로 설정하여 사용하여야 하며, 그렇지 않음으로 발생하는 손해는 사용자 개인 책임이다).

 

-       센싱한 데이터를 저장하기 위한 데이터베이스 이름 : abc_sens

-       데이터베이스의 테이블 이름 : sens_table

-       데이터베이스 사용자 : abc_user 

-       데이터베이스 사용자 비번 :  abc_1234

 

명령 프롬프트(cmd창)를 열고 <그림 1>의 mysql\bin 폴더로 이동하여 다음과 같이 root 권한으로 mysql을 실행한다. 이 때의 비번은 Wordpress 관리자 비번과 동일하다.

 

C:\Bit…\mysql\bin>mysql –u root -p

 

 

위에서 정의한 사용자 abc_user, 비번 abc_1234를 다음과 같이 생성한다.

 

mysql> create user ‘abc_user’@’localhost’ identified by ‘abc_1234’;

 

 

사용자를 생성하였으면 이번에는 데이터베이스를 생성한다.

 

mysql> create database abc_sens;

 

 

생성한 데이터베이스에 사용자가 액세스할 수 있는 권한을 아래와 같이 부여한다.

 

mysql> grant usage on *.* to 'abc_user'@'localhost';

 

아울러 사용할 데이터베이스인 abc_sens에 대한 모든 권한을 부여한다.

 

mysql> grant all on abc_sens.* to 'abc_user'@'localhost';

 

 

여기까지는 root 권한으로 mysql에서 작업을 한 것이다. exit 하고, 위에서 새로 생성한 사용자인 id는 abc_user, 비번은 abc_1234 로 mysql에 다시 접속한다.

 

>mysql –u abc_user –p (비번 입력 프롬프트가 나오면 비번인 abc_1234 입력)

 

이 상태에서 mysql> show databases; 명령을 입력하면 위에서 생성한 abc_sens가 나타난다. 이 데이터베이스를 사용한다는 선언을 mysql> use abc_sens; 입력으로 해준다.

 

 

 

이제 abc_sens 데이터베이스에 앞에서 정의한 테이블을 생성한다.

 

mysql> create table sens_table (sensDate DATETIME , temperature float, humidity float);

 

 

이상의 과정을 통하여 아래와 같이 테이블이 생성된 것을 확인할 수 있다.

 

mysql> describe sens_table;

 

 

이상을 확인하면 mysql을 빠져나온다.

 

 

4.     데이터 쓰기

 

4-1.      아두이노 ESP8266에서 웹서버로 데이터 보내기

 

아두이노에서 ESP8266의 AP 접속은 다음의 AT 커멘드를 순서대로 실행하여 완료한다. 여기서 AP_SSID와 AP_PWD는 ESP8266이 접속하고자 하는 AP의 SSID, password 이다. 각 AT 커멘드는 ESP8266 AT Instruction Set를 참조한다.

 

-       “AT+GMR\r\n”

-       “AT+CWMODE=1\r\n”

-       "AT+CWJAP=\"AP_SSID",\"AP_PWD\"\r\n"

 

위와 같이 AP에 접속한 후에는 다음의 명령을 실행한다.

 

-       "AT+CIPMUX=0\r\n"

 

 

데이터 센싱을 한 이후에, 그 센싱한 데이터를 웹서버로 전송하기 위하여 아두이노 프로그램에서 다음을 실행한다. 아래는 웹서버 IP주소가 192.168.5.4 이고, 웹서비스 포트가 80인 예이다.

 

-       "AT+CIPSTART=\"TCP\",\"192.168.5.4\",80\r\n"

 

이제 ESP8266의 AT커멘드로 웹서버로 전송할 데이터를 구성해본다. 앞에서 센싱한 온도가 20도, 습도가 30%라고 가정하면, AT+CIPSEND 명령으로 보낼 데이터는 다음과 같이 GET 방식을 이용하여 구성한다.

 

-       “GET /wordpress/abc-write.php?temprature=20&humidity=30\r\n”

 

여기서 /wordpress는 <그림. 2>의 htdocs가 위치한 경로명이고, abc-write.php는 htdocs에 위치한 데이터 저장을 위한 php 파일이다.

 

 

4-2.      데이터베이스에 저장하기

 

앞에서 웹서버가 수신한 데이터를 데이터베이스에 저장하기 위한 파일로 abc-write.php를 언급하였는데, 이 php 파일이 어떻게 코딩되는지 보기로 한다.

 

우선 데이터베이스를 액세스하기 위하여 다음의 각 변수를 지정한다. 각각의 변수에 해당하는 값들은 앞의 데이터베이스 생성에서 정의한 값들이다.

 

$servername = "localhost";

$username = "abc_user";

$password = "abc_1234";

$dbname = "abc_sens";

 

웹서버로 전송된 데이터는 parse_str() 함수로 파싱을 하고,

 

parse_str( html_entity_decode( $_SERVER['QUERY_STRING']) , $out);

 

temperature 인자가 있는 경우, mysqli() 함수를 이용하여 데이터베이스에 접속, insert into 명령으로 데이터를 기록하게 한다.

성공적으로 기록되면 리턴되는 값은 “Sensed data saved." 이고, 실패 시에는 "Error:”와 함께 에러 메시지를 리턴한다.

 

앞에서 설명했던 ESP8266을 통한 데이터 저장은 웹브라우저를 통해서도 확인이 되는데, 웹브라우저의 주소 창에 AT+CIPSEND로 전송될 데이터인 temprature=20&humidity=30 값을 이용하여, 다음과 같이 입력하면 그 결과를 확인할 수 있다.

 

-       http://192.168.5.4/wordpress/abc-write.php?temperature=20&humidity=30

 

<그림 3> 웹브라우저를 통한 데이터 전송

 

 

다음은 abc-write.php의 전체 코드이다.

 

<?php

  $servername = "localhost";

  $username = "abc_user";

  $password = "abc_1234";

  $dbname = "abc_sens";

  date_default_timezone_set('Asia/Seoul');

  $now = new DateTime();

  parse_str( html_entity_decode( $_SERVER['QUERY_STRING']) , $out);

  if ( array_key_exists( 'temperature', $out ) ) {

    // Create connection

    $conn = new mysqli($servername, $username, $password, $dbname);

    // Check connection

    if ($conn->connect_error) {

      die("Connection failed: " . $conn->connect_error);

    }

    $datenow = $now->format("Y-m-d H:i:s");

    $temp  = $out['temperature'];

    $humid = $out['humidity'];

    $sql = "INSERT INTO sens_table (sensDate , temperature, humidity) VALUES ('$datenow' , $temp, $humid)";

    if ($conn->query($sql) === TRUE) {

      echo "Sensed data saved.";

    } else {

      echo "Error: " . $sql . "<br>" . $conn->error;

    }

    $conn->close();

  }

?>

[Source Code] abc-write.php

 

 

4-3.      저장된 데이터 리트리빙하기

 

데이터베이스에 접근하기 이전에 wordpress 로그인 사용자를 확인하기 위하여

 

include_once("./wp-config.php");

$current_user = wp_get_current_user();

 

함수를 실행한다. 여기서는 로그인 하지 않은 경우에는 서비스를 거부하는 예로 코딩하였다. 즉, 현재 로그인 ID를 체크하여 로그인 사용자가 아니면 실행을 중단하도록 한다.

 

if( 0 == $current_user->ID )

 

로그인 사용자임이 확인되면, mysqli_connect() 함수를 이용하여 데이터베이스를 열고, mysqli_query() 함수를 이용하여 테이블의 데이터를 읽어 온다. 다음은 그 전체 소스코드이다.

 

<!DOCTYPE html><html>

<head>

<title>Sensed Data</title>

</head><body>

<center>

<H1>My Sensor Data</H1>

</center>

<?php

include_once("./wp-config.php");

$current_user = wp_get_current_user();

if( 0 == $current_user->ID ) {

        echo '<center>';

//      echo 'Login name is: ' . $current_user->display_name . '<br />';

        echo 'Login required... <br />';

        echo '</center>';

        exit();

}

echo "<center>";

echo "[" . $current_user->display_name . "() 로그인]<br />";

echo "</center>";

$con = mysqli_connect('localhost','abc_user','abc_1234','abc_sens');

// Check connection

if ($conn->connect_error) {

  die("Connection failed: " . $conn->connect_error);

}

//Selecting the data from table but with limit

$query = "SELECT * FROM sens_table order by sensDate desc LIMIT 0, 20";

$result = mysqli_query ($con, $query);

?>

<table align="center" border="0" cellpadding="3">

<tr><th>Send Date</th><th>Temperature(&deg;C)</th><th>Humidity(&#37;)</th></tr>

<?php

while ($row = mysqli_fetch_array($result)) {

?>

<tr align="center">

<td><?php echo $row['sensDate']; ?></td>

<td><?php echo $row['temperature']; ?></td>

<td><?php echo $row['humidity']; ?></td>

</tr>

<?php

};

?>

</table>

</body>

</html>

[Source Code] abc-read-text.php

 

 

다음의 그림은 위의 코드를 실행한 결과를 보여준다. 데이터가 많아서 페이지네이션이 필요한 경우에는 쿼리 문장에서 LIMIT 옵션을 이용하여 구현이 가능하다.

 

<그림 4> 데이터 리트리빙 결과

 

 

다음의 그림은 유사한 방법으로 Google Charts를 이용하여 그래픽으로 나타낸 것이다. 자세한 것을 아래의 소스코드 abc-read-chart.php를 참고한다.

 

<그림 5> Google Charts를 이용한 결과

 

<?php

// to check if user logged in

include_once("./wp-config.php");

$current_user = wp_get_current_user();

if( 0 == $current_user->ID ) {

        echo '<center>';

        echo '<h1>My Sensor Data</h1> <br />';

        echo '<p>Login required... <br /></p>';

        echo '</center>';

        exit();

}

// db initialization

$servername = "localhost";

$username = "abc_user";

$password = "abc_1234";

$dbname = "abc_sens";

date_default_timezone_set('Asia/Seoul');

// Check connection

$con = new mysqli($servername, $username, $password, $dbname);

if ($con->connect_error) {

  die("Connection failed: " . $con->connect_error);

}

?>

<!DOCTYPE HTML>

<html>

<head>

 <meta charset="utf-8">

 <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>

 <script type="text/javascript">

 google.charts.load('current', {packages:['corechart']});

 google.charts.setOnLoadCallback(drawChart);

 function drawChart() {

        var data = google.visualization.arrayToDataTable([

        ['Date', 'Temperature', 'Humidity'],

        <?php

        $query = "SELECT * From sens_table order by sensDate desc LIMIT 0, 200";

        $exec = mysqli_query($con,$query);

        while($row = mysqli_fetch_array($exec)){

               echo "['" . $row['sensDate'] . "'," . $row['temperature']. "," . $row['humidity']. "],";

        }

        ?>

        ]);

        var options = {

               title: 'Sensor Data',

               curveType: 'function',

               legend: {position: 'bottom'},

               hAxis: {direction: -1}

               }

               vAxis: {

                       viewWindow: {

                              min: -10

                       }

        };

        var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));

        chart.draw(data, options);

 }

 </script>

</head>

<body>

<center>

 <h1>My Sensor Data</h1>

 <div id="curve_chart" style="width: 1000px; height: 450px;"></div>

</center>

</body>

</html>

[Source Code] abc-read-chart.php

 

 

5.     결어

 

본 문서에서는 아두이노 보드와 ESP8266 WiFi모듈, 그리고 DHT11 온습도센서를 이용, 센싱한 온도와 습도를 네트워크로 전송하면 Bitnami Wordpress가 설치된 웹서버 데이터베이스에 데이터를 저장하고, 텍스트 모드로 출력하는 방법을 설명하였다. 아울러 구글 차트를 이용, 그래픽 모드로 리트리빙하는 방법도 설명하였다.


보안문제는 별도로 언급을 하지 않았으므로 본 문서를 참고로 하여 시스템을 시험 구축하고자 하는 사람은 보안에 대한 별도의 대책을 마련하여야 함에 유념하여야 한다.

 

[References]

 

[1] Bitnami Wordpress, https://bitnami.com/stack/wordpress

[2] ESP8266 AT Instruction Set Version 1.5.4, http://www.espressif.com/sites/default/files/4a-esp8266_at_instruction_set_en_v1.5.4_0.pdf

[3] Wordpress Code Reference, https://developer.wordpress.org/reference/

[4] PHP MySQL Improved Extension, http://php.net/manual/kr/book.mysqli.php

[5] Google Charts, https://developers.google.com/chart/

 



출처: https://jandh.tistory.com/16 [Inspiration]

출처: https://webnautes.tistory.com/783



아두이노에 연결된 온도센서의 값을 읽어서 ESP8266과 PHP를 이용하여 MySQL에 저장하는 예제입니다.



2016.  5. 21. 최초작성

2018. 10. 17. 마지막 업데이트




1. 다음 포스팅에 Arduino Uno와 ESP8266 연결하는 방법과 필요한 라이브러리를 설치하는 방법이 소개되어있습니다.

먼저 진행을 해야 합니다.



Arduino Uno에 ESP8266 WiFi 모듈을 연결하여 사용하는 방법

http://webnautes.tistory.com/755





2. 다음 포스팅에 Arduino Uno와 DB18B20 연결 방법과 필요한 라이브러리를 설치하는 방법이 소개되어 있습니다.

먼저 진행을 해야 합니다.



Arduino Uno에서 DS18B20 1-Wire 온도 센서 사용하기

http://webnautes.tistory.com/631





2.  윈도우 또는 리눅스에 아파치 웹서버, MySQL, PHP를 설치합니다.



Ubuntu 16.04에 LAMP ( Apache2, MySQL , PHP7) 설치하는 방법

http://webnautes.tistory.com/1028


Ubuntu 18.04에 LAMP ( Apache2, MySQL , PHP 7) 설치하는 방법

http://webnautes.tistory.com/1185


윈도우 기반 웹 개발 환경 만들기 ( Apache2, PHP, MySQL, PhpMyAdmin )

http://webnautes.tistory.com/1206


Raspberry Pi 3에 LAMP (Linux, Apache, MySQL, PHP) 설치하는 방법

http://webnautes.tistory.com/842





3. 데이터를 저장할 테이블을 생성합니다.


mysql> create database db;
Query OK, 1 row affected (0.00 sec)

mysql> use db;
Database changed
mysql> create table data(num int(10));
Query OK, 0 rows affected (0.01 sec)




4. 데이터베이스에 값을 넣을때 사용할 PHP 파일을 생성하여 다음 내용을 복사하여 붙여넣기 해줍니다.


리눅스

sudo nano /var/www/html/insert_data.php


윈도우

C:\wamp64\www 위치에 insert_data.php 파일 생성



<?php
$con = mysqli_connect("localhost", "MySQL 계정 아이디", "MySQL 계정 패스워드", "사용할 데이터베이스 이름");

if (mysqli_connect_errno())
{
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}

  $num = $_GET["num"];

  $sql = "insert into db.data(num) values($num)";
  mysqli_query($con, $sql);

  mysqli_close($con);
?>




5. 다음 아두이노 코드를 업로드 합니다.


#include <OneWire.h>    
#include <DallasTemperature.h>    
#include <string.h>  

#include "ESP8266.h"
#include <SoftwareSerial.h>  
   

#define SSID        "공유기의 SSID"

#define PASSWORD    "공유기의 비밀번호"

#define SERVERIP   "서버 아이피"

 

SoftwareSerial mySerial(11, 10); /* RX:D11, TX:D10 */

   
     
//DS18B20 온도 센서의 데이터선인 가운데 핀을 아두이노 3번에 연결합니다.     
#define ONE_WIRE_BUS 3  
     
//1-wire 디바이스와 통신하기 위한 준비    
OneWire oneWire(ONE_WIRE_BUS);    
     
// oneWire선언한 것을 sensors 선언시 참조함.    
DallasTemperature sensors(&oneWire);    
     
//다비아스 주소를 저장할 배열 선언    
DeviceAddress insideThermometer;    
     
     
     
char * floatToString(char * outstr, double val, byte precision, byte widthp){  
char temp[16]; //increase this if you need more digits than 15  
byte i;  
 
temp[0]='\0';  
outstr[0]='\0';  
 
if(val < 0.0){  
  strcpy(outstr,"-\0");  //print "-" sign  
  val *= -1;  
}  
 
if( precision == 0) {  
  strcat(outstr, ltoa(round(val),temp,10));  //prints the int part  
}  
else {  
  unsigned long frac, mult = 1;  
  byte padding = precision-1;  
    
  while (precision--)  
    mult *= 10;  
 
  val += 0.5/(float)mult;      // compute rounding factor  
    
  strcat(outstr, ltoa(floor(val),temp,10));  //prints the integer part without rounding  
  strcat(outstr, ".\0"); // print the decimal point  
 
  frac = (val - floor(val)) * mult;  
 
  unsigned long frac1 = frac;  
 
  while(frac1 /= 10)   
    padding--;  
 
  while(padding--)   
    strcat(outstr,"0\0");    // print padding zeros  
 
  strcat(outstr,ltoa(frac,temp,10));  // print fraction part  
}  
 
// generate width space padding   
if ((widthp != 0)&&(widthp >= strlen(outstr))){  
  byte J=0;  
  J = widthp - strlen(outstr);  
 
  for (i=0; i< J; i++) {  
    temp[i] = ' ';  
  }
 
  temp[i++] = '\0';  
  strcat(temp,outstr);  
  strcpy(outstr,temp);  
}  
 
return outstr;  
}  
 
 
     
void setup(void)    
{    
   
 //시리얼 포트 초기화    
 Serial.begin(9600);    
 
  
/////////////////////////////////////////////////////////////////////////  
 Serial.setTimeout(5000);  
 mySerial.begin(9600);   
 Serial.println("ESP8266 connect");  
 
   
  boolean connected=false;  
  for(int i=0;i<10;i++)  
  {
      if(connectWiFi())  
      {
        connected = true;  
        break;  
      }
  }
    
  if (!connected){while(1);}  
  delay(5000);  
   
  mySerial.println("AT+CIPMUX=0");  
 ///////////////////////////////////////////////////////////////////////////     
      
        
 //1-wire 버스 초기화    
 sensors.begin();    
       
 //발견한 디바이스 갯수    
 Serial.print("Found ");    
 Serial.print(sensors.getDeviceCount(), DEC);    
 Serial.println(" devices.");    
     
 // parasite power 모드일 때에는  2핀(GND와 DQ 핀)만 연결하면 됨.
 Serial.print("Parasite power is: ");     
 if (sensors.isParasitePowerMode()) Serial.println("ON");    
 else Serial.println("OFF");    
       
      
 //버스에서 첫번째 장치의 주소를 가져온다.    
 if (!sensors.getAddress(insideThermometer, 0)) Serial.println("Unable to find address for Device 0");     
       
 //버스에서 발견한 첫번째 장치의 주소 출력    
 Serial.print("Device 0 Address: ");    
 printAddress(insideThermometer);    
 Serial.println();    
     
 //데이터시트에서 확인결과 9~12비트까지 설정 가능    
 sensors.setResolution(insideThermometer, 10);    
      
 Serial.print("Device 0 Resolution: ");    
 Serial.print(sensors.getResolution(insideThermometer), DEC);     
 Serial.println();    
}    
     
     
// 온도를 출력하는 함수    
void printTemperature(DeviceAddress deviceAddress)    
{    
 //섭씨 온도를 가져옴    
 float  tempC = sensors.getTempC(deviceAddress);    
       
 Serial.print("Temp C: ");    
 Serial.print(tempC);    
 Serial.print(" Temp F: ");    
       
 //화씨 온도로 변환    
 Serial.println(DallasTemperature::toFahrenheit(tempC));  
 
 
 
    String cmd = "AT+CIPSTART=\"TCP\",\"";  
    cmd += SERVERIP;  
    cmd += "\",80";  
    Serial.println(cmd);  
    mySerial.println(cmd);  
    if(mySerial.find("Error"))  
   {
     Serial.println( "TCP connect error" );  
     return;  
   }
    
 
   char test[20];  
   String temp(floatToString(test,tempC, 2, 0));  
     
    cmd = "GET /insert_data.php?num="+temp+"\r\n";  
    mySerial.print("AT+CIPSEND=");  
    mySerial.println(cmd.length());  
         
      
    Serial.println(cmd);  
      
      
    if(mySerial.find(">"))  
    {
      Serial.print(">");  
      }else  
      {
        mySerial.println("AT+CIPCLOSE");  
        Serial.println("connect timeout");  
        delay(1000);  
        return;  
      }
        
      mySerial.print(cmd);  
      delay(2000);  
      //Serial.find("+IPD");  
      while (Serial.available())  
      {
        char c = Serial.read();  
        mySerial.write(c);  
        if(c=='\r') mySerial.print('\n');  
      }
      Serial.println("====");  
      delay(1000);  
}    
     
//디바이스 주소를 출력하는 함수    
void printAddress(DeviceAddress deviceAddress)    
{    
 for (uint8_t i = 0; i < 8; i++)    
 {   
   if (deviceAddress[i] < 16) Serial.print("0");    
       Serial.print(deviceAddress[i], HEX);    
 }   
}    
     
     
void loop(void)    
{     
 Serial.print("Requesting temperatures...");    
 //sensors.requestTemperatures();   //연결되어 있는 전체 센서의 온도 값을 읽어옴
 sensors.requestTemperaturesByIndex(0); //첫번째 센서의 온도값 읽어옴    
 Serial.println("DONE");    
      
 //센서에서 읽어온 온도를 출력    
 printTemperature(insideThermometer);    
}    
 
 
boolean connectWiFi()  
{  
  //mySerial.println("AT+CWMODE=1");  
    
  String cmd="AT+CWJAP=\"";  
  cmd+=SSID;  
  cmd+="\",\"";  
  cmd+=PASSWORD;  
  cmd+="\"";  
  mySerial.println(cmd);  
  Serial.println(cmd);  
  delay(3000);  
   
  if(mySerial.find("OK"))  
  {
    Serial.println("OK, Connected to WiFi.");  
    return true;  
  }
  else  
  {
    Serial.println("Can not connect to the WiFi.");  
    return false;  
  }
}  





6. 아두이노 IDE에서 툴 > 시리얼모니터를 선택합니다.


공유기에 접속 성공하면 “OK, Connected to WiFi” 메시지가 보입니다.

아두이노에 연결된 DB18B20를 찾았다면 “Found 1 devices”라는 메시지가 보입니다.


이후 온도를 가져오고(Requesting temperatures...Done)

웹서버에 온도를 저장하는 것(GET /insert_data.php?num=온도)을 반복합니다.





7. MySQL을 확인해보면 데이터가 입력되는 것을 볼 수 있습니다.



출처: 제4차 산업혁명 미래기술과 기업보안



사물인터넷 환경에서의 보안 패러다임



1) 사물인터넷 환경에서 보호 대상의 변화


(현재)    

 (사물인터넷 환경)

PC, 모바일 중심 

PC, 모바일 + 가전, 자동차, 웨어러블 기기등 모든 사물 


                                


2) 사물인터넷 환경에서 보호대상의 특성 변화


(현재)    

 (사물인터넷 환경)

고성능, 고가용성

초경량, 저전력



3) 사물인터넷 환경에서 보호 방법의 변화


(현재)    

 (사물인터넷 환경)

보안 시스템 구성 ( IDS, IPS, F/W 등) 

설계 단계부터 보안 내재화 +개발 보안



4) 사물인터넷 환경에서 보호 주체의 변화


(현재)    

 (사물인터넷 환경)

보안 전문업체,