반응형

 

개인적으로 좋아하는 자연 기화식 가습기에 필터가 아닌 디스크 방식을 사용하는

샤오미 2세대 가습기입니다.

 

어느날 물이 차있음에도 물이 없다고 인식을 하고 동작을 안하게 되었습니다.

인터넷에서 찾아본 물통의 센서 부분을 분리해서 청소해도 문제점은 동일했습니다.

측정해보니 꽉 찼을때에는 M단위 저항값이 나오는것 같은데

본체에 악어 집게로 여러 종류의 저항을 물려봐도 반응은 없었습니다.

 

일단 무작정 분해 시작

상단 부분은 안쪽에서 손바닥으로 밀어서 빼내면 편합니다.

 

 

내부를 보니 12v 스탭 모터가 사용되는것 같았습니다.

팬과 디스크를 회전시켜주는 4선과 5선짜리 모터 2개가 있었습니다. 

 

 

아두이노를 사용하고 L298N같은 모듈을 이용해서 기존 기판을 대신할 계획을 세웠습니다.

 

그러다 발견한 유튜브에서 더 좋은 해답?을 찾았습니다.

Xiaomi Humidifier 2: fixing "no water". Replacing water level module with Arduino (youtube.com)

GitHub - fakcior/xiaomi-humidifier-water-sensor: Xiaomi SmartMi Evaporative Humidifier water level sensor

 

GitHub - fakcior/xiaomi-humidifier-water-sensor: Xiaomi SmartMi Evaporative Humidifier water level sensor

Xiaomi SmartMi Evaporative Humidifier water level sensor - GitHub - fakcior/xiaomi-humidifier-water-sensor: Xiaomi SmartMi Evaporative Humidifier water level sensor

github.com

러시아어로 말해서 뭔 말인지 못알아 들었지만...

중요한건 저 핀맵과 깃허브 코드면 충분합니다.

(지금 보니 영어 자막이 있네요)

 

 

가지고 있던 아두이노에 잘 컴파일해서 업로드되는지 확인하고

(CapacitiveSensor 라이브러리 추가 필요)

흥분한 나머지 핀을 부러트렸지만 파워 모듈쪽을 분해합니다.

파란 사각 부품을 제거해야 합니다.

방수 코팅같은게 있어 제거하기 약간 어렵습니다.

일반 납을 듬뿍 덮어씌우고 계속 열을 가하면서 들어냈습니다.

 

열을 가하면서 핀을 집어넣으려다가 패턴을 날려먹었습니다...

조금 남아있어 보여서 좀더 시도해보다가 난장판을 만들었습니다.

 

 핀맵을 보니 GND니 윗쪽 GND와 연결되있을거야 하며 위로하며 넘어갔습니다.

 

본체에 조립해보니 아두이노에서 통신을 하는듯 보이고 잘 작동해 보이지만

여전히 물이 비었다고 인식을 했습니다.

마지막에 센서쪽을 좀 건드렸을때 된적도 있는데

이미 잘 못하는 납땜으로 씨름하느라 지쳐서 꼼수를 쓰기로 하였습니다.

 

 

잘 안되는 원인이

-GND쪽을 날려먹어서

or

-센서쪽 고장

or

-calibration 필요(컴터랑 시리얼 모니터 연결하고 서서히 물 부어봐야함)

 

여러가지 문제의 원인이 있을 수 있는데

 

센서 부분에서 읽어오는 부분을 주석처리하고

무조건 물이 꽉차있다고 신호를 보내도록 코드를 수정했습니다.

 

 

물통에서 분리해도 풀로 인식하고 모터를 동작시켜주는 가습기!

 

------------------------------------------------------

이런식으로 고치면 물을 과하게 넣거나 물이 없어도 계속 돌아가는 문제가 발생하는데

저의 사용 패턴은 매일 하루 한번 물통을 통째로 들고가서 가볍게 청소하고 물을 가득 채워서 다음날까지 사용하기에

이런 방식으로도 크게 문제가 없었습니다.

(수리후 정상적일 때와 달라진 점은 물통에 분리했을때에도 멈추지 않고 돌아가는 정도?)  

------------------------------------------------------

 

처음에 브레드 보드용 핀 선을 사용했는데

이미 납땜을 해버려서 그냥 글루건으로 고정시켜버렸습니다.

 

 

케이스에 드릴로 구멍을 뚫고 선을 빼고 또 글루건으로 마감

 

본체에 넣을려고 하니 선이 너무 짧아서 utp선으로 연장을...

 

혹시 몰라 방수처리를 위해 투명 네일을 발라주고

아두이노를 옆면에 글루건으로 고정했습니다.

 

마지막으로 케이스 조립하고 완성

보통 수리를 해보려고 하다가 완전히 고장내고 버리는게 일상인데

오랜만에 잘 고쳐졌습니다.

 

수리후 아쉬운점으로는

유튜브 영상에서도 아두이노 나노를 사용하고

저도 다른곳에서 사용하다 빼온 이미 핀을 박아놓은 나노를 사용했는데

복잡한 일을 하는게 아니니 5v 프로 보드같은 더 작고 저렴한걸 사용해도 좋아보입니다.

 

브레드보드용 선이 아니라 처음부터 제대로 된 선을 쓰고 납땜하면 더 깔끔하고 쉽게 되었을 텐데

납땜 실력이 나빠서 선 연장하느라 고생했습니다.

 

 

 

 

 

반응형

'아두이노' 카테고리의 다른 글

아두이노에서 사용가능한 소형 물 펌프  (0) 2017.04.13
반응형

`javax.security.auth.login.AccountExpiredException` 클래스는 인증 과정에서 사용되며, 인증 제공자가 제공하는 사용자 계정의 만료를 나타냅니다.

만료된 계정으로 인증하려고 할 때 이 예외가 발생합니다
예를 들어, 사용자가 비밀번호를 변경하지 않고 만료일이 지나면 계정이 만료됩니다
만료된 계정은 로그인 할 수 없으며, 일반적으로 관리자가 계정을 다시 활성화하거나 사용자가 새로운 계정을 만들어야합니다.

`AccountExpiredException` 클래스는 `LoginException` 클래스를 상속받으므로, `LoginException`과 함께 사용됩니다
예외 처리 코드에서는 보통 `LoginException`의 하위 클래스인 `AccountExpiredException`을 처리하도록 구현됩니다.

다음은 `AccountExpiredException` 클래스의 생성자와 메소드입니다.

### 생성자

- `AccountExpiredException()`: 기본 생성자입니다.
- `AccountExpiredException(String msg)`: 지정된 메시지를 사용하여 예외를 생성합니다.

### 메소드

`AccountExpiredException` 클래스는 `LoginException` 클래스를 상속받으므로, `LoginException` 클래스의 메소드를 상속받습니다
`LoginException` 클래스의 주요 메소드는 다음과 같습니다.

- `getMessage()`: 예외 메시지를 반환합니다.
- `getCause()`: 예외의 원인을 반환합니다.
- `printStackTrace()`: 예외 스택 트레이스를 출력합니다.

이 예외를 처리하는 예제 코드는 다음과 같습니다.

 

import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.LoginException;

public class Example {
    public static void main(String[] args) {
        try {
            // 로그인을 시도하는 코드
        } catch (AccountExpiredException e) {
            System.out.println("계정이 만료되었습니다.");
        } catch (LoginException e) {
            System.out.println("로그인 중 오류가 발생했습니다.");
        }
    }
}


이 예제에서는 `LoginException`과 `AccountExpiredException` 예외를 처리하는 try-catch 블록이 포함되어 있습니다
`AccountExpiredException`이 발생하면 "계정이 만료되었습니다."라는 메시지가 출력되고, `LoginException`이 발생하면 "로그인 중 오류가 발생했습니다."라는 메시지가 출력됩니다.
`javax.security.auth.login.AccountExpiredException`은 인증 과정에서 발생하는 예외 클래스 중 하나로, 계정이 만료되었음을 나타냅니다


보통 시스템에서는 일정 기간 이상 로그인하지 않은 사용자 계정을 만료 처리합니다
만료된 계정으로 로그인을 시도하면 `AccountExpiredException`이 발생합니다.

이 클래스는 `LoginException` 클래스를 상속합니다.

**주요 메서드**
- `public AccountExpiredException()`: `AccountExpiredException` 객체를 생성합니다.
- `public AccountExpiredException(String msg)`: 지정된 상세 메시지를 사용하여 `AccountExpiredException` 객체를 생성합니다.
- `public AccountExpiredException(String message, Throwable cause)`: 지정된 상세 메시지와 원인을 사용하여 `AccountExpiredException` 객체를 생성합니다.
- `public AccountExpiredException(Throwable cause)`: 지정된 원인을 사용하여 `AccountExpiredException` 객체를 생성합니다.

**예외 처리 예제 코드**

import javax.security.auth.login.AccountExpiredException;

public class Example {
    public static void main(String[] args) {
        try {
            // 예외 발생 시뮬레이션
            throw new AccountExpiredException("This account has expired.");
        } catch (AccountExpiredException e) {
            // 예외 처리
            System.out.println("AccountExpiredException: " + e.getMessage());
        }
    }
}
public class AccountExpiredExceptionExample {
    public static void main(String[] args) {
        try {
            // 로그인 시도
            login("user1", "password123");
        } catch (LoginException e) {
            // 로그인 실패 시 예외 처리
            System.out.println(e.getMessage());
        }
    }

    public static void login(String username, String password) throws LoginException {
        // 만료된 계정으로 로그인 시도
        throw new AccountExpiredException("계정이 만료되었습니다.");
    }
}


이 예제 코드는 로그인 시도 시 AccountExpiredException이 발생하도록 구성되어 있습니다
예외 처리 코드에서는 getMessage() 메소드를 사용하여 예외 메시지를 출력합니다.

반응형
반응형

javax.security.auth.login.AccountException은 인증 과정에서 발생할 수 있는 예외 중 하나로, 사용자 계정에 문제가 있을 때 발생합니다
이 예외는 javax.security.auth.login.LoginException을 확장한 것이며, 다음과 같은 서브클래스를 가집니다.

- CredentialExpiredException
- FailedLoginException
- AccountExpiredException
- AccountLockedException
- AccountNotFoundException

CredentialExpiredException은 비밀번호가 만료되었을 때 발생하고, FailedLoginException은 로그인 시도가 실패했을 때 발생합니다
AccountExpiredException은 계정이 만료되었을 때 발생하고, AccountLockedException은 계정이 잠겼을 때 발생합니다
마지막으로, AccountNotFoundException은 해당 이름의 계정을 찾을 수 없을 때 발생합니다.

AccountException의 일반적인 사용 예는 다음과 같습니다.

 

try {
    // 사용자 인증 처리
} catch (AccountException e) {
    // 사용자 계정 예외 처리
    e.printStackTrace();
}


위 코드에서는 사용자 인증 과정에서 AccountException 예외가 발생하면 예외를 처리하도록 합니다
예외 처리는 예외 정보를 출력하는 것으로 구현되어 있습니다.
javax.security.auth.login.AccountException은 인증 및 권한 부여 메커니즘에서 발생할 수 있는 예외 중 하나로, 계정 관련 문제를 나타내는 예외입니다


AccountException은 다음과 같은 상황에서 발생할 수 있습니다.

- 사용자가 올바르지 않은 자격 증명을 제공한 경우
- 사용자가 잠긴 계정을 사용하려고 시도한 경우
- 사용자가 만료된 자격 증명을 사용하려고 시도한 경우
- 사용자가 비활성화된 계정을 사용하려고 시도한 경우
- 기타 계정 관련 문제

AccountException은 java.lang.Exception 클래스를 확장하며, 일반적으로 다른 예외 클래스와 마찬가지로 try-catch 블록에서 처리됩니다


아래는 AccountException 예외를 처리하는 예제 코드입니다.

 

import javax.security.auth.login.AccountException;

public class AccountExceptionExample {

    public static void main(String[] args) {
        try {
            // 사용자 인증을 시도하는 코드
            throw new AccountException("Invalid credentials");
        } catch (AccountException e) {
            // 예외 처리
            System.out.println(e.getMessage());
        }
    }
}
try {
    // 로그인 처리
    loginContext.login();
} catch (AccountException e) {
    // 계정 정보가 잘못된 경우 처리
    System.out.println("계정 정보가 잘못되었습니다: " + e.getMessage());
} catch (LoginException e) {
    // 그 외의 로그인 예외 처리
    System.out.println("로그인 처리 중 오류가 발생했습니다: " + e.getMessage());
}


위의 코드에서 loginContext.login() 메서드를 호출할 때 AccountException이 발생하면, 해당 예외를 처리하는 catch 블록이 실행됩니다
이 예외에서 getMessage() 메서드를 호출하면 발생한 예외의 상세 메시지를 가져올 수 있습니다.

위의 코드에서는 LoginException도 처리하고 있습니다
LoginException은 javax.security.auth.login 패키지에 있는 일반적인 로그인 예외 중 하나로, 로그인 처리 중 예기치 않은 오류가 발생한 경우에 발생합니다.

반응형
반응형

com.sun.jdi.request.AccessWatchpointRequest 클래스는 Java Debug Interface (JDI)의 일부입니다
이 클래스는 디버깅을 위해 액세스 워치포인트를 설정하는 데 사용됩니다.

액세스 워치포인트는 특정 필드의 값을 검사하고 필드의 값을 읽거나 쓸 때마다 발생하는 이벤트입니다
따라서 이 클래스를 사용하면 애플리케이션에서 특정 필드의 값을 계속 모니터링하면서 필드의 값이 변경될 때마다 이벤트를 발생시킬 수 있습니다.

AccessWatchpointRequest 클래스는 다음과 같은 메서드를 포함합니다.

- addThreadFilter(): 액세스 워치포인트를 활성화할 스레드를 지정합니다.
- addClassFilter(): 액세스 워치포인트를 설정할 클래스를 지정합니다.
- addInstanceFilter(): 액세스 워치포인트를 설정할 인스턴스를 지정합니다.
- addFieldFilter(): 액세스 워치포인트를 설정할 필드를 지정합니다.
- enable(): 액세스 워치포인트를 활성화합니다.
- disable(): 액세스 워치포인트를 비활성화합니다.

다음은 AccessWatchpointRequest의 예제 코드입니다.

 

VirtualMachine vm = ...;
ReferenceType clazz = vm.classesByName("com.example.ClassName").get(0);
Field field = clazz.fieldByName("fieldName");
AccessWatchpointRequest watchpointRequest = vm.eventRequestManager().createAccessWatchpointRequest(field);
watchpointRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL);
watchpointRequest.enable();


이 예제에서는 ClassName 클래스의 fieldName 필드를 모니터링하고 필드의 값이 변경될 때마다 모든 스레드를 중지시킵니다.
`com.sun.jdi.request.AccessWatchpointRequest` 클래스는 JVM에서 모니터링 할 필드의 변경을 추적하기 위한 이벤트를 생성하는 데 사용됩니다
이 클래스는 `com.sun.jdi.request.EventRequest` 클래스의 하위 클래스입니다.

`AccessWatchpointRequest` 객체를 만들면 이벤트가 발생할 필드와 클래스의 식별자를 지정할 수 있습니다
이벤트가 발생하면 JVM은 `com.sun.jdi.event.AccessWatchpointEvent` 객체를 생성하고, 이벤트를 처리하기 위해 `com.sun.jdi.event.EventSet` 객체에 묶어서 `com.sun.jdi.event.EventQueue`에 추가합니다.

`AccessWatchpointRequest` 클래스의 생성자는 다음과 같습니다.

AccessWatchpointRequest AccessWatchpointRequest.addFieldWatch(String fieldName)
AccessWatchpointRequest AccessWatchpointRequest.addFieldWatch(String fieldName, String declaringClass)


첫 번째 생성자는 모니터링 할 필드의 이름만 지정하고, 두 번째 생성자는 필드가 속한 클래스의 이름도 함께 지정합니다
이벤트가 발생하면 JVM은 `AccessWatchpointRequest` 객체의 `isEnabled()` 메소드가 true를 반환하는지 확인하고, true를 반환하면 `AccessWatchpointEvent` 객체가 생성됩니다.

다음은 `AccessWatchpointRequest` 클래스를 사용한 예제 코드입니다.

 

import com.sun.jdi.*;
import com.sun.jdi.connect.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;

import java.util.*;

public class AccessWatchpointExample {
    public static void main(String[] args) throws Exception {
        // Attach to a running JVM with the specified process ID.
        VirtualMachine vm = VirtualMachine.attach(args[0]);

        // Create a reference to the com.example.Employee class.
        ReferenceType employeeType = vm.classesByName("com.example.Employee").get(0);

        // Create a new access watchpoint request for the "salary" field of the Employee class.
        AccessWatchpointRequest request = vm.eventRequestManager().createAccessWatchpointRequest(employeeType.fieldByName("salary"));

        // Enable the access watchpoint request.
        request.enable();

        // Resume the VM to start processing events.
        vm.resume();

        // Wait for an access watchpoint event to occur.
        EventSet eventSet = vm.eventQueue().remove();

        // Get the access watchpoint event from the event set.
        AccessWatchpointEvent event = (AccessWatchpointEvent) eventSet.iterator().next();

        // Print some information about the event.
        System.out.println("Access watchpoint event:");
        System.out.println("Field: " + event.field().name());
        System.out.println("Object: " + event.object().toString());
        System.out.println("Thread: " + event.thread().name());

        // Disconnect from the VM.
        vm.dispose();
    }
}


이 예제 코드에서는 `AccessWatchpointRequest` 클래스를 사용하여 `com.example.Employee` 클래스의 `salary` 필드를 모니터링합니다
이벤트가 발생하면 이벤트 정보를 출력합니다
이 코드는 디버거 도구나 프로파일링 도구 등에서 유용하게 사용될 수 있습니다.

반응형
반응형

com.sun.jdi.event.AccessWatchpointEvent는 Java Debug Interface(JDI)의 이벤트 중 하나로, 객체의 필드에 접근할 때 발생하는 이벤트를 나타냅니다.

이벤트가 발생하면, JVM은 AccessWatchpointEvent 객체를 생성하고, 해당 이벤트에 대한 정보를 담아서 이 객체를 JDI 이벤트 큐에 넣습니다
디버거는 JDI 이벤트 큐에서 이벤트를 꺼내서 처리합니다.

AccessWatchpointEvent 객체는 다음과 같은 정보를 담고 있습니다.

- Field : 이벤트가 발생한 필드를 나타냅니다.
- ObjectReference : 필드가 속한 객체의 참조를 나타냅니다.
- ThreadReference : 이벤트가 발생한 스레드를 나타냅니다.
- Location : 이벤트가 발생한 코드 위치를 나타냅니다.

AccessWatchpointEvent 클래스는 com.sun.jdi.event.WatchpointEvent 클래스의 하위 클래스입니다
WatchpointEvent 클래스는 필드의 값을 변경할 때와 접근할 때 모두 발생하는 이벤트를 처리합니다
AccessWatchpointEvent 클래스는 이 중에서 필드에 접근할 때 발생하는 이벤트만 처리합니다.

AccessWatchpointEvent 클래스에는 다음과 같은 메서드가 있습니다.

- field() : 이벤트가 발생한 필드를 반환합니다.
- object() : 필드가 속한 객체의 참조를 반환합니다.
- thread() : 이벤트가 발생한 스레드를 반환합니다.
- location() : 이벤트가 발생한 코드 위치를 반환합니다.
- valueCurrent() : 필드의 현재 값을 반환합니다.
- toString() : AccessWatchpointEvent 객체를 문자열로 반환합니다.

다음은 AccessWatchpointEvent 객체를 사용한 간단한 예제 코드입니다.

 

import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;

public class AccessWatchpointEventExample {
    public static void main(String[] args) throws Exception {
        VirtualMachine vm = // 가상 머신 객체를 가져옵니다.

        // 필드에 접근할 때 이벤트를 생성하는 요청을 만듭니다.
        EventRequestManager erm = vm.eventRequestManager();
        AccessWatchpointRequest request = erm.createAccessWatchpointRequest(field, size);

        // 이벤트 핸들러를 등록합니다.
        vm.eventQueue().setExceptionHandler(new ExceptionHandler());
        EventSet eventSet = vm.eventQueue().remove();
        AccessWatchpointEvent event = (AccessWatchpointEvent)eventSet.iterator().next();

        // 이벤트 정보를 출력합니다.
        System.out.println("Field: " + event.field());
        System.out.println("ObjectReference: " + event.object());
        System.out.println("ThreadReference: " + event.thread());
        System.out.println("Location: " + event.location());
        System.out.println("Value: " + event.valueCurrent());
    }
}


이 코드는 가상 머신에서 필드에 접근할 때 발생하는 이벤트를 대기하다가, 이벤트가 발
`com.sun.jdi.event.AccessWatchpointEvent`는 Java Debug Interface(JDI)의 이벤트 중 하나입니다
이 이벤트는 디버깅 중에 변수나 객체의 필드에 접근할 때 발생합니다


일반적으로, 디버깅 중에 변수나 객체의 필드에 접근하면 해당 필드에 접근하기 위해 명령어가 실행됩니다
이 때, AccessWatchpointEvent는 이 명령어가 실행된 후 발생합니다
즉, 필드에 접근하는 과정에서 발생한 이벤트를 가리킵니다.

AccessWatchpointEvent 객체는 이벤트가 발생한 스레드, 필드가 속한 객체, 필드의 이름과 값 등을 포함합니다


이벤트 리스너를 등록하여 이벤트가 발생할 때마다 처리할 수 있습니다
JDI를 사용하여 디버깅 중에 AccessWatchpointEvent를 처리하면 변수나 객체의 필드에 대한 디버깅 정보를 수집하고 디버깅 세션을 보다 쉽게 관리할 수 있습니다.

다만, `com.sun.jdi.event` 패키지는 내부 구현을 위한 것으로 공식적인 API는 아니므로, 해당 패키지를 사용할 경우 내부 구현이 변경될 가능성이 있습니다
따라서, 공식적인 API를 사용하는 것이 좋습니다

 

import com.sun.jdi.*;
import com.sun.jdi.connect.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.util.*;

public class AccessWatchpointExample {

    public static void main(String[] args) throws Exception {

        VirtualMachine vm = null;

        try {
            // JVM에 연결하기
            vm = new VirtualMachineManager().defaultConnector().attach(args[0], Integer.parseInt(args[1]));
            System.out.println("Attached to JVM: " + vm.name());

            // 이벤트 요청 만들기
            EventRequestManager requestManager = vm.eventRequestManager();
            AccessWatchpointRequest accessWatchpointRequest = requestManager.createAccessWatchpointRequest(vm.classesByName("java.lang.String").get(0).fieldByName("value"));

            // 이벤트 핸들러 설정
            vm.eventQueue().setNextEvent(null);
            vm.setDebugTraceMode(VirtualMachine.TRACE_NONE);
            EventSet eventSet = vm.eventQueue().remove();
            for (Event event : eventSet) {
                if (event instanceof AccessWatchpointEvent) {
                    AccessWatchpointEvent accessWatchpointEvent = (AccessWatchpointEvent) event;
                    System.out.println("Access watchpoint triggered on field " + accessWatchpointEvent.field().name());
                    System.out.println("Old value: " + accessWatchpointEvent.valueCurrent());
                    System.out.println("New value: " + accessWatchpointEvent.valueToBe());
                }
            }

            // 이벤트 요청 활성화
            accessWatchpointRequest.enable();

            // 다음 이벤트를 기다리기
            vm.eventQueue().remove();
            System.out.println("Access watchpoint created successfully");

        } finally {
            if (vm != null) {
                vm.dispose();
            }
        }
    }
}


위 예제는 JVM에 연결하고 java.lang.String 클래스의 value 필드에 대한 접근 지점 이벤트를 만들고 핸들링하는 방법을 보여줍니다
이벤트가 트리거될 때마다 새로운 값을 출력합니다.

이 예제를 실행하기 위해서는 먼저 명령줄에서 프로세스 ID와 포트 번호를 입력해야 합니다
예를 들어 다음과 같이 입력할 수 있습니다.

 

java AccessWatchpointExample 1234 9999



이 예제는 1234라는 프로세스 ID를 가진 JVM에 연결하고 9999번 포트를 사용합니다
디버그 모드에서 실행되어야 하며, JVM에 디버깅 모드를 활성화하려면 -agentlib:jdwp 옵션을 사용해야 합니다.

이 예제에서는 AccessWatchpointRequest 클래스를 사용하여 접근 지점 이벤트를 만들고, EventQueue 클래스를 사용하여 이벤트를 핸들링합니다

반응형

+ Recent posts