반응형

 

 

 

많은 분들이 자바 전체 소스코드를 원하셔서 이렇게라도 올려봅니다.

 

제가 프로젝트를 다 삭제해서 잠깐 코드로 적어놓고 테스트는 하지 않았습니다. 

 

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import app.akexorcist.bluetotohspp.library.BluetoothSPP;
import app.akexorcist.bluetotohspp.library.BluetoothState;
import app.akexorcist.bluetotohspp.library.DeviceList;

public class MainActivity extends AppCompatActivity {

    private BluetoothSPP bt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {


        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bt = new BluetoothSPP(this); //Initializing

        if (!bt.isBluetoothAvailable()) { //블루투스 사용 불가
            Toast.makeText(getApplicationContext()
                    , "Bluetooth is not available"
                    , Toast.LENGTH_SHORT).show();
            finish();
        }
        
        // ------------------------------ 데이터 수신부 ----------------------------- //
        bt.setOnDataReceivedListener(new BluetoothSPP.OnDataReceivedListener() { //데이터 수신
            TextView temp = findViewById(R.id.temp);
            TextView humd = findViewById(R.id.humd);

            public void onDataReceived(byte[] data, String message) {

                String[] array = message.split(",");
                temp.setText(array[0].concat("C"));
                humd.setText(array[1].concat("%") );
            }
        });
        // ------------------------------ 데이터 수신부 ----------------------------- //

        bt.setBluetoothConnectionListener(new BluetoothSPP.BluetoothConnectionListener() { //연결됐을 때
            public void onDeviceConnected(String name, String address) {
                Toast.makeText(getApplicationContext()
                        , "Connected to " + name + "\n" + address
                        , Toast.LENGTH_SHORT).show();
            }

            public void onDeviceDisconnected() { //연결해제
                Toast.makeText(getApplicationContext()
                        , "Connection lost", Toast.LENGTH_SHORT).show();
            }

            public void onDeviceConnectionFailed() { //연결실패
                Toast.makeText(getApplicationContext()
                        , "Unable to connect", Toast.LENGTH_SHORT).show();
            }
        });

        Button btnConnect = findViewById(R.id.btnConnect); //연결시도
        btnConnect.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                if (bt.getServiceState() == BluetoothState.STATE_CONNECTED) {
                    bt.disconnect();
                } else {
                    Intent intent = new Intent(getApplicationContext(), DeviceList.class);
                    startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE);
                }
            }
        });
    }

    public void onDestroy() {
        super.onDestroy();
        bt.stopService(); //블루투스 중지
    }

    public void onStart() {
        super.onStart();
        if (!bt.isBluetoothEnabled()) { //
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
        } else {
            if (!bt.isServiceAvailable()) {
                bt.setupService();
                bt.startService(BluetoothState.DEVICE_OTHER); //DEVICE_ANDROID는 안드로이드 기기 끼리
                setup();
            }
        }
    }

    public void setup() {
        Button btnSend = findViewById(R.id.btnSend); //데이터 전송
        btnSend.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                bt.send("Text", true);
            }
        });
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == BluetoothState.REQUEST_CONNECT_DEVICE) {
            if (resultCode == Activity.RESULT_OK)
                bt.connect(data);
        } else if (requestCode == BluetoothState.REQUEST_ENABLE_BT) {
            if (resultCode == Activity.RESULT_OK) {
                bt.setupService();
                bt.startService(BluetoothState.DEVICE_OTHER);
                setup();
            } else {
                Toast.makeText(getApplicationContext()
                        , "Bluetooth was not enabled."
                        , Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }
}


728x90
반응형
반응형

안녕하세요. 약 두 달만에 글을 씁니다.

 

공모전과 기사 시험의 지옥을 뒤로 잠시 여유가 생겨 시간을 냅니다.

 

오늘은 MQ 시리즈에서 공기질 센서를 PPM으로 변환하는 방법에 대해 글을 쓰겠습니다.

 

MQ 시리즈 센서를 사용할 때 PPM 변환이 설명이 친절하지 않아 작은 힘을 보태겠습니다.

 

프로젝트에 필요한 공기질 센서를 엑셀로 정리하다 보니 이렇게 블로그 글 쓰는데 도움이 되네요.

 

MQ 시리즈마다 측정할 수 있는 센서값이 굉장히 많습니다. 

 

Gas_Sensor.xlsx
0.01MB

 

MQ 시리즈 중 하나를 사용할 수 있다면, 다른 시리즈 또한 쉽게 사용할 수 있습니다. 

 

MQ 시리즈

글을 세 편정도로 나누어서 글을 쓸 예정인데, 

 

1편은 센서의 원리가 궁금한 것이 아닌, 아두이노로 PPM 변환 결과만 필요하신 분을 위한 글입니다.

2편은 데이터시트를 보면서 회로를 보고, 원리를 확인합니다.

3편은 C파일의 코드를 뜯어보면서 파이썬으로 PPM으로 변환하는 코드를 작성하겠습니다.

 

 

github.com/miguel5612/MQSensorsLib

 

miguel5612/MQSensorsLib

We present a unified library for MQ sensors, this library allows to read MQ signals easily from Arduino, Genuino, ESP8266, ESP-32 boards whose references are MQ2, MQ3, MQ4, MQ5, MQ6, MQ7, MQ8, MQ9,...

github.com

 

GitHub MQ 시리즈 주소 입니다.

 

MQSensorsLib-master.zip
0.50MB

 

다운받아서 압축을 풀고 아두이노 라이브러리에 추가해주면 됩니다. 

 

 

원리가 다 같기 때문에 MQ-2 예제를 보면서 코드 사용법을 설명하겠습니다.

 

 

모든 코드를 보지 않고, 아두이노로 바로 사용할 수 있게 중요한 코드만 설명하겠습니다.

 

 

 

 

 

1. Calibration - 칼리브레이션

 

다른 센서와 다르게 드론과 같은 칼리브레이션 단계가 있습니다. 

 

MQ 시리즈에서 말하는 칼리브레이션은 처음 센서의 값을 영점을 조절하기 때문에 이런 표현을 쓰는 것 같습니다. 

 

쉽게 말해서 센서를 처음 측정할 때, 공기가 깨끗한 곳을 기준으로 공기질을 상대적으로 측정합니다.

 

칼리브레이션

 

센서로 바로 측정하는 것이 아니라 10번 정도 깨끗한 공기를 측정하고, 평균을 내어 R0을 영점을 맞춥니다.

 

여기서 RatioMQ2CleanAir는 센서마다 정의한 값이 서로 다르기 때문에 예제가 정의한 값을 사용합니다.

 

Clean Air

MQ2는 9.83ppm을 깨끗한 공기라고 가정을 하고 시작합니다. 이는 개발자들이 여러 시행착오를 거쳐 최적의 값을 낸 것 이기 때문에 그대로 사용하시면 됩니다.

 

 

 

 

 

 

2. PPM 출력

 

 

칼리브레이션이 끝나면, 아두이노에서 MQ Lib에서 알아서 저항 값이 PPM값으로 변환되어 나옵니다. 

 

바로 예제를 돌려보시면 아시겠지만, PPM값 뿐만 아니라, 센서에 사용된 전압, 저항 등이 모두 출력 됩니다.

 

 

 

3. a, b 선언

 

그리고 중요한 것이 어떤 공기를 측정할 것인지 상수를 선언하는 부분입니다. 

 

최대한 쉽게 설명하기 위해 데이터 시트는 보고 싶지 않지만, Figure는 하나 보고 가시는걸 추천드립니다.

 

MQ 시리즈 센서는 상수 값에 따라, 지수 회귀식 기울기 정도가 크게 바뀌기 때문에 a, b를 제대로 선언해주지 않으면, 이상한 PPM 값이 나오며 값의 차이가 굉장히 커집니다.

 

ppm = a * ratio ^ b

ratio = rs / ro

 

ro = 칼리브레이션된 저항값 (고정값)

rs = 공기질이 측정되는 저항값 (가변값)

 

만약 H2의 PPM을 원하면 아래처럼  a, b 값을 바꾸면 됩니다.

 

MQ2.setA(987.99); 

MQ2.setB(-2.162);

 

참고로 Figure에 보이는 RL은 MQ 센서에 붙어 있는 가변 저항입니다. 센서의 예민 정도를 조정할 수 있습니다. 

 

/*
  MQUnifiedsensor Library - reading an MQ2

  Demonstrates the use a MQ2 sensor.
  Library originally added 01 may 2019
  by Miguel A Califa, Yersson Carrillo, Ghiordy Contreras, Mario Rodriguez
 
  Added example
  modified 23 May 2019
  by Miguel Califa 

  Updated library usage
  modified 26 March 2020
  by Miguel Califa 
  Wiring:
  https://github.com/miguel5612/MQSensorsLib_Docs/blob/master/static/img/MQ_Arduino.PNG
  Please take care, arduino A0 pin represent the analog input configured on #define pin

 This example code is in the public domain.

*/

//Include the library
#include <MQUnifiedsensor.h>
/************************Hardware Related Macros************************************/
#define         Board                   ("Arduino UNO")
#define         Pin                     (A2)  //Analog input 3 of your arduino
/***********************Software Related Macros************************************/
#define         Type                    ("MQ-2") //MQ2
#define         Voltage_Resolution      (5)
#define         ADC_Bit_Resolution      (10) // For arduino UNO/MEGA/NANO
#define         RatioMQ2CleanAir        (9.83) //RS / R0 = 9.83 ppm 

/*****************************Globals***********************************************/
MQUnifiedsensor MQ2(Board, Voltage_Resolution, ADC_Bit_Resolution, Pin, Type);
/*****************************Globals***********************************************/

void setup() {
  //Init the serial port communication - to debug the library
  Serial.begin(9600); //Init serial port

  //Set math model to calculate the PPM concentration and the value of constants
  MQ2.setRegressionMethod(1); //_PPM =  a*ratio^b
  MQ2.setA(574.25); MQ2.setB(-2.222); // Configurate the ecuation values to get LPG concentration
  /*
    Exponential regression:
    Gas    | a      | b
    H2     | 987.99 | -2.162
    LPG    | 574.25 | -2.222
    CO     | 36974  | -3.109
    Alcohol| 3616.1 | -2.675
    Propane| 658.71 | -2.168
  */

  /*****************************  MQ Init ********************************************/ 
  //Remarks: Configure the pin of arduino as input.
  /************************************************************************************/ 
  MQ2.init(); 
  /* 
    //If the RL value is different from 10K please assign your RL value with the following method:
    MQ2.setRL(10);
  */
  /*****************************  MQ CAlibration ********************************************/ 
  // Explanation: 
  // In this routine the sensor will measure the resistance of the sensor supposing before was pre-heated
  // and now is on clean air (Calibration conditions), and it will setup R0 value.
  // We recomend execute this routine only on setup or on the laboratory and save on the eeprom of your arduino
  // This routine not need to execute to every restart, you can load your R0 if you know the value
  // Acknowledgements: https://jayconsystems.com/blog/understanding-a-gas-sensor
  Serial.print("Calibrating please wait.");
  float calcR0 = 0;
  for(int i = 1; i<=10; i ++)
  {
    MQ2.update(); // Update data, the arduino will be read the voltage on the analog pin
    calcR0 += MQ2.calibrate(RatioMQ2CleanAir);
    Serial.print(".");
  }
  MQ2.setR0(calcR0/10);
  Serial.println("  done!.");
  
  if(isinf(calcR0)) {Serial.println("Warning: Conection issue founded, R0 is infite (Open circuit detected) please check your wiring and supply"); while(1);}
  if(calcR0 == 0){Serial.println("Warning: Conection issue founded, R0 is zero (Analog pin with short circuit to ground) please check your wiring and supply"); while(1);}
  /*****************************  MQ CAlibration ********************************************/ 

  MQ2.serialDebug(true);
}

void loop() {
  MQ2.update(); // Update data, the arduino will be read the voltage on the analog pin
  MQ2.readSensor(); // Sensor will read PPM concentration using the model and a and b values setted before or in the setup
  MQ2.serialDebug(); // Will print the table on the serial port
  delay(500); //Sampling frequency
}

 

코드를 보면 복잡해보이지만, 주석을 걷어내고 필요한 부분만 본다면 굉장히 짧은 코드임을 알 수 있습니다. 

 

MQ2를 예제를 확인했지만, 다른 MQ 시리즈 또한 이런 방식이기 때문에 쉽게 따라하실 수 있습니다.

 

 

다음 글은 데이터 시트를 보면서 회로를 보겠습니다.

 

coding-yoon.tistory.com/121

 

[아두이노] MQ 시리즈 공기질 센서 PPM으로 변환하기! (2) 회로도 (Schematic)

안녕하세요. MQ시리즈 두 번째 글을 작성합니다. 확실히 글은 바로 바로 작성하는 것이 중요한 것 같습니다. 글을 쓰려고 보니 기억이 안 나서 다시 새로 공부했습니다. 저번 글은 아두이노 라이

coding-yoon.tistory.com

 

728x90
반응형
반응형

안녕하세요. 오늘은 PWM에 대해 알아보도록 하겠습니다.

 

아두이노의 핀에는 I/O(input/output)핀의 종류 3가지가 있습니다.

 

1. Analog Input 

 

2. Digital Input 

 

3. Digital Output

 

하나 부족해보이지 않나요?

 

바로 Analog Output입니다.  

 

아두이노에는 Analog Output이 없습니다. 

 

아날로그 와 디지털의 차이부터 먼저 볼까요?

 

 

아날로그, 디지털

 

 

(a)아날로그

 

아날로그는 연속적으로 변화하는 숫자, 물리량을 의미합니다. 소리, 진동 등의 자연현상

 

(b) 디지털

 

0, 1로 이루어진 규칙적인 숫자, 컴퓨터와 소통하기 위해 

 

(어떻게 0, 1로 컴퓨터가 만들어졌는지는 나중에 글을 쓰도록 하겠습니다!)

 

 

자. 그럼 이제 생각해봅시다. 

 

아날로그 출력 : 연속적으로 변화하는 숫자를 출력한다.

 

그렇다면 우리는 연속적으로 변화하는 숫자를 출력할 수 없는 것일까요?

 

예를 들어 연속적인 가변저항의 값을 변화시켜 LED의 밝기를 제어할 수는 없을까요?

 

당연히 있습니다. 아날로그 출력을 설명하기 전에 아날로그 입력의 원리부터 알아보도록 합시다.

 

 

ADC(Analog-Digital-Converter) : 아날로그 신호를 디지털 신호로 변환

 

아두이노에 아날로그 입력은

 

아두이노 안에는  ADC가 아날로그 신호(0 ~ 5V)를 10bit로 데이터 신호를 샘플링(양자화)하여 부호화!!

 

 

쉽게 말해 아두이노는 보통 0 ~ 5V의 전압을 입력받을 수 있습니다.

 

아두이노의 10bit로 0 ~ 5V를 나눕니다.

 

10bit로 나눈다는 말은 10bit는 2^10(2의 10제곱)으로 0 ~ 1023의 값이 존재하며 

 

0 ~ 5V를 1024개로 나눈다는 뜻입니다. 

 

예를 들면 0V일 때는 02.5V512!,    5V1024의 값을 가집니다.

 

이렇게 디지털로 샘플링 된 아날로그 신호를 PWM을 통해 아날로그 신호를 제어할 수 있습니다.

 

 

PWM!  :  펄스의 폭을 변조 !

 

그림으로 설명하겠습니다. 아주 간단합니다.

 

 

약 25%의 duty cycle

Pulse는 파란 색으로 튀어나온 부분이고

Width는 빨간 색 선입니다. 

 

PWMDuty Cycle(신호가 시스템이 살아있는 특정 기간의 백분율)의 원리를 사용합니다. 

 

쉽게 예를 들면

 

1Cycle에 Pulse Width(펄스의 가로 길이)가 약 25%라고 한다면

아두이노의 5V25%1.25V만큼 전압을 출력합니다. 만약 비트로 연산한다면 255가 됩니다.

 

1Cycle에 Pulse Width(펄스의 가로 길이)가 약 50%라고 한다면

아두이노의 5V50%2.5V만큼 전압을 출력합니다. 만약 비트로 연산한다면 511가 됩니다.

 

1Cycle에 Pulse Width(펄스의 가로 길이)가 약 100%라고 한다면

아두이노의 5V100%5V만큼 전압을 출력합니다. 만약 비트로 연산한다면 1023가 됩니다.

 

PWM의 출력(디지털)은 마치 아날로그처럼 사용할수 있어 하나의 DAC로 볼 수 있습니다.

(엄연히 말하면 DAC는 아닙니다. 결국 디지털 신호로 제어하기 때문입니다.)

 

DAC(Digta-Analog-Converter) : 디지털 신호를 아날로그 신호로 변환

 

그림으로 표현하면 이렇게 ADC와 DAC는 짝꿍입니다.

 

다음 시간에는 어떻게 PWM을 이용해서 아날로그 신호, 연속적인 숫자를 제어할 수 있는지 아두이노 코딩을 이용해서 글을 쓰겠습니다.

728x90
반응형
반응형

안녕하세요. 오늘은 풀업, 풀다운 저항에 대해 알아 보도록 하겠습니다.

 

우선 플로팅 현상을 방지하는데 풀업, 풀다운 저항을 사용합니다.

 

플로팅 현상은 3.3V에서 동작한다고 했을 때, 3.0V와 3.5V 사이에서 전압이 애매하게 걸릴 경우 

 

디지털 회로상으로 0 인지 1인지 구별하지 못하게 되어 오작동을 일으킵니다.

 

 

 

1. 아두이노 기본 풀업 저항(소프트웨어)

아두이노는 기본적으로 풀업 저항을 소프트웨어적으로 제공합니다.

아두이노 풀업 

 

 

 

 

2. 풀업 저항(하드웨어)

 

풀업 저항

저항은 대게 10k ~ 100k옴을 사용합니다.

 

3. 풀다운 저항(하드웨어)

풀다운 저항

 

728x90
반응형
반응형

안녕하세요! 아두이노, 안드로이드 블루투스 실습 두번 째 시간입니다. 

 

오늘은 안드로이드 스튜디오로 센서 값을 받아오는 어플을 만들어 보도록 하겠습니다.

 

아마 많은 분들께서 Code-jun님의 블루투스 예제를 보셨을거라고 생각합니다. 

 

실제로도 라이브러리가 상당히 잘 만들어져 있어 사용하는데 어려움이 없다고 생각합니다.

 

저는 이번에 Code-jun님이 올리신 라이브러리 예제를 통해 블루투스 무선통신을 하도록 하겠습니다. 

 

https://blog.codejun.space/13

 

아두이노, 안드로이드 블루투스 통신하기

아두이노로 미세먼지 측정기를 만들던 도중 안드로이드와 연동하여 데이터를 주고 받고 싶어 제작하던 중 유용한 정보가 될 것 같아 포스팅 합니다. 이번 개발환경은 DESKTOP과 TEST DEVICE를 동시에 사용합니다...

blog.codejun.space

 

위 사이트 방법대로 라이브러리를 추가한 후 이제 제 방식대로 코딩을 해보도록 하겠습니다. 

 

 

 

1. 디자인

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

<TextView
android:layout_width="wrap_content"
android:layout_height="50dp"
android:textSize ="40sp"
android:layout_gravity = "center"
android:text="온습도센서('C, %)" />

<Button
android:id="@+id/btnConnect"
android:layout_gravity = "center"
android:layout_width="300dp"
android:layout_height="50dp"
android:text="연결"

/>

<TextView
android:id="@+id/temp"
android:layout_width="match_parent"
android:layout_height="80dp"
android:textSize ="50sp"
android:gravity="center_horizontal"
android:hint="온도" />

<TextView
android:id="@+id/humd"
android:layout_width="match_parent"
android:layout_height="80dp"
android:textSize="50sp"
android:gravity="center_horizontal"
android:hint="습도" />

</LinearLayout>

 

굉장히 간단한 어플 레이아웃

 

디자인에 신경쓰시는 분들께서는 자신의 입맛대로 고쳐 쓰시면 됩니다.

그리고 사용하시기 전에 주의할 점이 있습니다. 제가 굵게 표시한 android.hint 부분은 블루투스에서 값이 전달되기

시작하면 textview 부분이 바껴야 하기 때문에 android.text로 하시면 안됩니다.

값이 올바르게 가더라도 값이 변하지 않습니다. 

 

 

 

2. 데이터 수신

 

bt.setOnDataReceivedListener(new BluetoothSPP.OnDataReceivedListener() { //데이터 수신
       public void onDataReceived(byte[] data, String message) {
                 Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
                  }
});

출처: https://blog.codejun.space/13 [CodeJUN]

라이브러리 출처 : https://github.com/akexorcist/Android-BluetoothSPPLibrary

 

위 함수는 데이터를 수신받고 있습니다. 그렇다면 어떻게 해야할까요?

방법은 두 가지입니다. 아두이노에서 문자로 보내는 방법, 바이트로 보내는 방법. 두가지 입니다.

우리는 바이트로 계산을 해서 값을 보내는 것은 어려우니 저희는 문자로 보내도록 하겠습니다.

 

 하지만 생각해 봐야할 문제가 있습니다.

우리가 아두이노에서 안드로이드로 보낼 값은 온도, 습도 두 가지인데 어떻게 해야하나.

 

정답은 한 번에 문자열로 보낸 후, 수신부에서 쪼개면 됩니다. 

 

 

 

bt.setOnDataReceivedListener(new BluetoothSPP.OnDataReceivedListener() {
       TextView temp = findViewById(R.id.temp);
       TextView humd = findViewById(R.id.humd);


       public void onDataReceived(byte[] data, String message) {


                 String[] array = message.split(",");

                 temp.setText(array[0].concat("C"));
                 humd.setText(array[1].concat("%") );

                 }
});

 

그리고 문자열의 끝은 개행문자로 끝이 납니다. (중요 중요)

개행문자?? new line(띄어쓰기)을 말합니다.

 

 

그럼 아두이노 송신 스케치를 다시 한번 보도록 하겠습니다.

 

아두이노 온습도 송신

자! 다시 보니 이해가 가시나요?

아두이노에서는 블루투스로

[temp(value), humd(value) /n] 이러한 형태의 문자열로 1초에 한 번씩 블루투스에게 값을 보내줍니다. 

 

 

어? 만약 안드로이드 스튜디오에서 String으로 값을 받았네?

나는 숫자로 받아서 안드로이드 스튜디오에서 계산해서 값을 집어넣고 싶은데?

 

방법 : 안드로이드 스튜디오 : String에서 double형태로 바꾸기

 

double dTemp = Double.parseDouble(array[0]);

double dHumd = Double.parseDouble(array[1]);

 

간단하죠?

여러분들도 온습도 센서말고도 원하는 센서를 달아 보시면 상당히 재미있습니다.

저 같은 경우는 

 
     음속 = (331.5 + (0.6*celsius)) m/s 
      왕복거리 = 음속 * 시간

거리 = (음속 * 시간)/2

 

공식을 사용하여 온도에 따른 좀 더 정확한 초음파 센서를 만들어 보았습니다.

 

이것으로 블루투스 모듈에 관한 두 번째 글을 마치도록 하겠습니다.

 

Copyright (c) 2014 Akexorcist

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

728x90
반응형

+ Recent posts