[Raspberry pi] - 1편 스마트 콘센트? Smart Plug!
[Raspberry pi] - 2편 스마트 콘센트? Smart Plug!
[Raspberry pi] - 3편 스마트 콘센트? Smart Plug!
[Raspberry pi] - 4편 스마트 콘센트? Smart Plug! + 앱위젯
[Raspberry pi] - 5편 스마트 콘센트? Smart Plug! + 터치스위치
[Raspberry pi] - 6편 스마트 콘센트? Smart Plug! + Node.js Push 서버 (개요편)
[Raspberry pi] - 7편 스마트 콘센트? Smart Plug! + Node.js Push 서버 (Firebase 등록)
[Raspberry pi] - 8편 스마트 콘센트? Smart Plug! + Node.js Push 서버 (서버편)
[Raspberry pi] - 9편 스마트 콘센트? Smart Plug! + Node.js Push 서버 (App편)
코드 몇줄짜리로 구현해 놓은 스마트콘센트(Smart Plug) 앱을 손보았다.
음성인식 앱에 위젯을 추가하여, 클릭 시마다 전원 on/off가 되고, 현재 상태를 알수 있도록 위젯 이미지가 바뀌도록 했다.
기능요약
1. 위젯을 바탕화면에 배치한다.
2. 위젯을 클릭하면 라즈베리파이(Raspberry Pi)에 전원 on/off를 요청한다.
3. 토글 방식으로 요청하고, 현재의 상태를 리턴받는다.
4. 리턴받은 값(on/off)에 따라 이미지 버튼을 변경한다.
5. Update 주기마다 현재 상태를 요청하여 리턴받는다.
6. 리턴받은 값(on/off)에 따라 이미지 버튼을 변경한다.
구현
1. 매니패스트 수정
앱위젯은 UI가 달린 브로드캐스트 리시버이므로, 매니패스트에도 receiver로 추가된다.
여기에 받고자 하는 브로드캐스트 메시지를 정의하고, 앱 내에서 주고받으면 된다.
아래와 같이 PLUGTOG 이라는 커스텀 메시지를 정의했다.
<receiver
android:name=".MyAppWidget"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.viewise.home.voiceassistance.PLUGTOG" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_app_widget_info" />
</receiver>
2. onUpdate
onUpdate는 앱위젯이 배치되는 초기, 그리고 셋팅된 업데이트 주기마다 호출된다.
이때에 할 일은 매니패스트에 정의한 커스텀 메시지를 인텐트에 넣어서, 버튼에 등록해 놓는 것이다.
버튼을 클릭하면 커스텀 메시지가 브로드캐스팅 된다.
imgBtn은 버튼 위젯이 아니고, ImageView 이다.
final static String ACTION_CLICK = "com.viewise.home.voiceassistance.PLUGTOG";
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
// Construct the RemoteViews object
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
Intent in = new Intent(context, MyAppWidget.class);
in.setAction(ACTION_CLICK);
in.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pending = PendingIntent.getBroadcast(context, appWidgetId, in, 0);
views.setOnClickPendingIntent(R.id.imgBtn, pending);
appWidgetManager.updateAppWidget(appWidgetId, views);
Log.d(TAG, "updateAppWidget.appWidgetId: " + String.valueOf(appWidgetId));
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
} }
3. onReceive
onReceive는 브로드캐스팅 된 메시지를 수신할 때에 호출된다.
이때에 발생한 메시지 중 UPDATE와 PLUGTOG 에 대해서만 반응하도록 했다.
UPDATE 시에는 라즈베리파이에 현재 콘센트 상태를 요청하여 받아오고, PLUGTOG 시에는 on/off 동작을 요청하고 현재 상태를 리턴받는다.
RequestPI는 스레드이고, 편의상 Http 요청을 하여 결과가 나올때까지 대기하도록 thread join을 시켰다.
Http 요청이 완료되면 isOn 이라는 전역변수에 값을 셋팅하고, 그 값을 읽어서 ImageView의 이미지소스를 on 또는 off 모양으로 바뀌도록 했다.
@Override public void onReceive(Context context, Intent intent){
Log.d(TAG, intent.getAction());
RequestPI requestPi = null;
if(intent.getAction().equals(ACTION_CLICK)){
requestPi = new RequestPI(strPlugTog);
requestPi.start();
} else if(intent.getAction().equals(ACTION_UPDATE)) {
requestPi = new RequestPI(strPlugStat);
requestPi.start();
}
try {
if(requestPi != null)
requestPi.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(intent.getAction().equals(ACTION_CLICK) || intent.getAction().equals(ACTION_UPDATE)) {
Log.d(TAG, "onReceive.Icon : " + String.valueOf(isOn));
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_app_widget);
if (isOn)
views.setImageViewResource(R.id.imgBtn, R.drawable.plugon);
else
views.setImageViewResource(R.id.imgBtn, R.drawable.plugoff);
AppWidgetManager manager = AppWidgetManager.getInstance(context);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
if(intent.getAction().equals(ACTION_UPDATE)) {//위젯 내 모든 컨트롤 업데이트
int[] appWidgetIds = manager.getAppWidgetIds(new ComponentName(context, getClass()));
for(int id:appWidgetIds) {
Log.d(TAG, "onReceive.appWidgetId : " + String.valueOf(id));
//updateAppWidget(context, manager, appWidgetId);
manager.updateAppWidget(id, views);//ImageViewResource 변경 UI반영을 위해
}
}else {
Log.d(TAG, "onReceive.appWidgetId : " + String.valueOf(appWidgetId));
//updateAppWidget(context, manager, appWidgetId);
manager.updateAppWidget(appWidgetId, views);//ImageViewResource 변경 UI반영을 위해
}
}
super.onReceive(context, intent);
}
4. RequestPI
RequestPI는 JSON 타입으로 Http 요청을 하고 결과를 리턴받는 스레드이다.
여기서는 간단히 result:on 또는 off를 받는다.
private class RequestPI extends Thread {
private String strUrl;
public RequestPI(String value) {
this.strUrl = value;
}
public void run() {
HttpURLConnection conn = null;
try {
URL url = new URL(strUrl);
url.openConnection();
conn = (HttpURLConnection)url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String strJson = in.readLine();
in.close();
Log.d(TAG, strJson);
try {
JSONObject json = new JSONArray(strJson).getJSONObject(0);
String rt = json.getString("result");
if(rt.equals("on"))
isOn = true;
else
isOn = false;
}catch (JSONException e) {
e.printStackTrace();
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(conn != null)
conn.disconnect();
}
} };
5. 웹 서버
라즈베리파이(Raspberry Pi)에 구현된 node.js 웹서버는 그동안 여러 기능의 추가/수정이 있었다.
인터넷에서 소스를 찾아서 copy한 단순한 소스가 점점 복잡해지고 있는 관계로, 콘센트 기능 부분만 발췌했다.
PLUGTOG 발생 시 app.get('/plugtoggle', function(req, res)) 부분이 호출되고, 현재 상태를 읽어서 콘센트가 on 이면 off를, off 이면 on을 시킨다.
그리고 JSON 타입으로 result:on 또는 result:off 를 리턴한다.
UPDATE 발생 시 app.get('/plugstat', function(req, res)) 부분이 호출된다.
이후 동작은 콘센트 제어 없이 상태만 리턴한다.
app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended : false }));
app.get('/plug', function(req, res){
res.sendFile('/html/plug.html', {root : __dirname });
});
app.post('/plugdata', function(req, res){
var state = req.body.switch;
if (state == 'ON') {
plug.writeSync(0);
}
else {
plug.writeSync(1);
}
console.log(state + " " + plug.readSync());
res.sendFile('/html/plug.html', {root : __dirname });
});
app.get('/plugon', function(req, res) {
plug.writeSync(0);
res.send([{result:'ok'}]);
console.log('on ' + plug.readSync());
});
app.get('/plugoff', function(req, res) {
plug.writeSync(1);
res.send([{result:'ok'}]);
console.log('off '+ plug.readSync());
});
app.get('/plugtoggle', function(req, res) {
if(plug.readSync() == 1) {
plug.writeSync(0);
res.send([{result:'on'}]);
}
else {
plug.writeSync(1);
res.send([{result:'off'}]);
}
});
app.get('/plugstat', function(req, res) {
if(plug.readSync() == 1) {
res.send([{result:'off'}]);
}
else {
res.send([{result:'on'}]);
}
});
6. 동작영상
'Raspberry pi' 카테고리의 다른 글
5편 적외선 리모콘 (IR Remote Controller) + TV 스피커리시버 연동 (0) | 2017.04.19 |
---|---|
5편 스마트 콘센트? Smart Plug! + 터치스위치 (0) | 2017.04.19 |
4편 적외선 리모콘 (IR Remote Controller) + 음성인식 개선 (1) | 2017.04.17 |
3편 적외선 리모콘 (IR Remote Controller) + 음성인식 (2) | 2017.04.17 |
3편 스마트 콘센트? Smart Plug! (0) | 2017.04.16 |