/* * Copyright (C) 2013-2014 Sony Computer Science Laboratories, Inc. All Rights Reserved. * Copyright (C) 2014 Sony Corporation. All Rights Reserved. */ package com.sonycsl.Kadecot.wamp.echonetlite; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; import com.sonycsl.Kadecot.core.provider.KadecotCoreStore; import com.sonycsl.Kadecot.device.AccessException; import com.sonycsl.Kadecot.device.DeviceProperty; import com.sonycsl.Kadecot.device.echo.EchoDeviceDatabase; import com.sonycsl.Kadecot.wamp.KadecotProperty; import com.sonycsl.Kadecot.wamp.KadecotWampClient; import com.sonycsl.Kadecot.wamp.KadecotWampTopic; import com.sonycsl.Kadecot.wamp.echonetlite.ECHONETLiteWampSubscriber.OnTopicListener; import com.sonycsl.Kadecot.wamp.provider.KadecotProviderClient; import com.sonycsl.echo.Echo; import com.sonycsl.echo.eoj.EchoObject; import com.sonycsl.echo.node.EchoNode; import com.sonycsl.wamp.WampError; import com.sonycsl.wamp.WampPeer; import com.sonycsl.wamp.message.WampInvocationMessage; import com.sonycsl.wamp.message.WampMessage; import com.sonycsl.wamp.message.WampMessageFactory; import com.sonycsl.wamp.message.WampResultMessage; import com.sonycsl.wamp.role.WampCallee; import com.sonycsl.wamp.role.WampCaller; import com.sonycsl.wamp.role.WampPublisher; import com.sonycsl.wamp.role.WampRole; import com.sonycsl.wamp.util.WampRequestIdGenerator; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class ECHONETLiteClient extends KadecotWampClient { public static final String BASE_URI = "com.sonycsl.kadecot.echonetlite"; private ECHONETLiteWampCallee mCallee; private ECHONETLiteWampSubscriber mSubscriber; private ECHONETLiteManager mManager; // <deviceId, deviceData> private Map<Long, ECHONETLiteDeviceData> mDeviceMap; private Map<String, JSONObject> mTemporaryDeviceMap; private final Handler mHandler; public ECHONETLiteClient(Context context) { super(); mDeviceMap = new ConcurrentHashMap<Long, ECHONETLiteDeviceData>(); mTemporaryDeviceMap = new HashMap<String, JSONObject>(); ECHONETLiteManager.ECHONETLiteWampDevicePropertyChangedListener pListener = createPropetyChangedListener(); ECHONETLiteDiscovery.OnEchoDeviceInfoListener dListener = createDeviceInfoListener(); mManager = ECHONETLiteManager.getInstance(); mManager.setClient(this); mManager.setListener(pListener, dListener); mHandler = new Handler(); } private ECHONETLiteManager.ECHONETLiteWampDevicePropertyChangedListener createPropetyChangedListener() { return new ECHONETLiteManager.ECHONETLiteWampDevicePropertyChangedListener() { @Override public void OnPropertyChanged(ECHONETLiteDeviceData data, List<DeviceProperty> list) { publishOnPropertyChanged(data, list); } }; } private ECHONETLiteDiscovery.OnEchoDeviceInfoListener createDeviceInfoListener() { return new ECHONETLiteDiscovery.OnEchoDeviceInfoListener() { @Override public void onDeviceStateChanged(JSONObject data) { putDeviceInfo(data); } @Override public void onDeviceAdded(JSONObject data) { putDeviceInfo(data); } }; } @Override protected Set<WampRole> getClientRoleSet() { mSubscriber = new ECHONETLiteWampSubscriber(mManager, new OnTopicListener() { // TODO: 実験用の値なので変更可能にする private static final int DELAY_MILLIS = 5000; private Map<String, Runnable> mRunnables = new HashMap<String, Runnable>(); @Override public void onTopicStarted(String topic) { final String propertyName = topic.split("\\.", 7)[6]; Runnable r = new Runnable() { @Override public void run() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { synchronized (mDeviceMap) { for (Entry<Long, ECHONETLiteDeviceData> entry : mDeviceMap .entrySet()) { JSONObject property = new JSONObject(); List<DeviceProperty> response; try { property.put("propertyName", propertyName); response = mCallee.callGet(entry.getValue(), property); } catch (JSONException e) { e.printStackTrace(); continue; } catch (AccessException e) { e.printStackTrace(); continue; } JSONObject argsKw; try { argsKw = mCallee .createResponseArgumentsKw(response); } catch (JSONException e) { e.printStackTrace(); continue; } if (argsKw == null) { continue; } try { transmit(WampMessageFactory.createPublish( WampRequestIdGenerator.getId(), new JSONObject() .put(KadecotCoreStore.Devices.DeviceColumns.DEVICE_ID, entry.getKey()), ECHONETLiteTopicGenerator.getTopic(entry .getValue() .getClassCode(), ECHONETLitePropertyName .translate(propertyName)), new JSONArray(), argsKw)); } catch (JSONException e) { e.printStackTrace(); continue; } } } return null; } }.execute(); mHandler.postDelayed(this, DELAY_MILLIS); } }; mRunnables.put(topic, r); mHandler.post(r); } @Override public void onTopicStopped(String topic) { mHandler.removeCallbacks(mRunnables.remove(topic)); } }); mCallee = new ECHONETLiteWampCallee(); Set<WampRole> roleSet = new HashSet<WampRole>(); roleSet.add(new WampCaller()); roleSet.add(new WampPublisher()); roleSet.add(mSubscriber); roleSet.add(mCallee); return roleSet; } @Override protected void onConnected(WampPeer peer) { } @Override protected void onTransmitted(WampPeer peer, WampMessage msg) { if (msg.isGoodbyeMessage()) { if (mIdHolder < 0) { return; } transmit(WampMessageFactory .createUnsubscribe(WampRequestIdGenerator.getId(), mIdHolder)); } } @Override public Map<String, String> getSubscribableTopics() { Map<String, String> topics = new HashMap<String, String>(); for (String topic : ECHONETLiteTopicGenerator.getTopics()) { topics.put(topic, ""); } return topics; } @Override public Map<String, String> getRegisterableProcedures() { Map<String, String> procs = new HashMap<String, String>(); for (ECHONETLiteProcedure procedure : ECHONETLiteProcedure.values()) { procs.put(procedure.toString(), ""); } return procs; } @Override public Set<String> getTopicsToSubscribe() { Set<String> topics = new HashSet<String>(); topics.add(KadecotWampTopic.TOPIC_PRIVATE_SEARCH); topics.add(KadecotProviderClient.Topic.START.getUri()); topics.add(KadecotProviderClient.Topic.STOP.getUri()); return topics; } // TODO: 2014/6 release 向けの暫定措置 private int mIdHolder = -1; @Override protected void onReceived(WampMessage msg) { if (msg.isWelcomeMessage()) { mManager.start(); putTopics(); mIdHolder = WampRequestIdGenerator.getId(); transmit(WampMessageFactory.createSubscribe(mIdHolder, new JSONObject(), ECHONETLiteTopicGenerator.getTopic((short) 304, "0x85"))); } if (msg.isSubscribedMessage()) { if (mIdHolder == msg.asSubscribedMessage().getRequestId()) { mIdHolder = msg.asSubscribedMessage().getSubscriptionId(); } } if (msg.isGoodbyeMessage()) { removeTopics(); mManager.stop(); } if (msg.isResultMessage()) { WampResultMessage result = msg.asResultMessage(); if (!result.hasArgumentsKw()) { return; } JSONObject device = result.getArgumentsKw(); try { // add device data to data list ECHONETLiteDeviceData data = new ECHONETLiteDeviceData(device); // data.rename(echoData.nickname); mDeviceMap.put(data.getDeviceId(), data); } catch (JSONException e) { e.printStackTrace(); } } } private void putTopics() { try { // TODO: fix this workaround JSONObject topic = new JSONObject(); topic.put(KadecotCoreStore.Topics.TopicColumns.NAME, ECHONETLiteTopicGenerator.getTopic((short) 304, "0x85")); topic.put(KadecotCoreStore.Topics.TopicColumns.DESCRIPTION, "test"); transmit(WampMessageFactory.createCall(WampRequestIdGenerator.getId(), new JSONObject(), KadecotProviderClient.Procedure.PUT_TOPIC.getUri(), new JSONArray(), topic)); } catch (JSONException e) { e.printStackTrace(); } } private void removeTopics() { try { // TODO: fix this workaround JSONObject topic = new JSONObject(); topic.put(KadecotCoreStore.Topics.TopicColumns.NAME, ECHONETLiteTopicGenerator.getTopic((short) 304, "0x85")); transmit(WampMessageFactory.createCall(WampRequestIdGenerator.getId(), new JSONObject(), KadecotProviderClient.Procedure.REMOVE_TOPIC.getUri(), new JSONArray(), topic)); } catch (JSONException e) { e.printStackTrace(); } } private void putDeviceInfo(JSONObject data) { try { mTemporaryDeviceMap.put(data.getString(KadecotCoreStore.Devices.DeviceColumns.UUID), data); } catch (JSONException e) { e.printStackTrace(); } transmit(WampMessageFactory.createCall(WampRequestIdGenerator.getId(), new JSONObject(), KadecotProviderClient.Procedure.PUT_DEVICE.getUri(), new JSONArray(), data)); } private void publishOnPropertyChanged(ECHONETLiteDeviceData data, List<DeviceProperty> list) { try { for (DeviceProperty dp : list) { JSONObject options = new JSONObject(); options.put("deviceId", data.getDeviceId()); String topic = ECHONETLiteTopicGenerator.getTopic(data.getClassCode(), dp.name); if (topic == null) { return; } JSONArray arguments = new JSONArray(); arguments.put(dp.value); transmit(WampMessageFactory.createPublish(WampRequestIdGenerator.getId(), options, topic, arguments)); } } catch (JSONException e) { e.printStackTrace(); } } private class ECHONETLiteWampCallee extends WampCallee { @Override protected WampMessage invocation(String procedure, WampMessage msg) { ECHONETLiteProcedure enumProcedure = ECHONETLiteProcedure .getEnum(procedure); WampInvocationMessage invMsg = msg.asInvocationMessage(); if (enumProcedure == null) { return WampMessageFactory.createError(msg.getMessageType(), invMsg.getRequestId(), new JSONObject(), WampError.NO_SUCH_PROCEDURE); } JSONObject argumentKw = new JSONObject(); try { argumentKw = resolveInvocationMsg(enumProcedure, invMsg); } catch (JSONException e) { e.printStackTrace(); return WampMessageFactory.createError(msg.getMessageType(), invMsg.getRequestId(), new JSONObject(), WampError.INVALID_ARGUMENT); } catch (AccessException e) { e.printStackTrace(); return WampMessageFactory.createError(msg.getMessageType(), invMsg.getRequestId(), new JSONObject(), e.getClass().getName()); } catch (IllegalArgumentException e) { e.printStackTrace(); return WampMessageFactory.createError(msg.getMessageType(), invMsg.getRequestId(), new JSONObject(), WampError.INVALID_ARGUMENT); } catch (UnsupportedOperationException e) { e.printStackTrace(); return WampMessageFactory.createError(msg.getMessageType(), invMsg.getRequestId(), new JSONObject(), WampError.NO_SUCH_PROCEDURE); } return WampMessageFactory.createYield(invMsg.getRequestId(), new JSONObject(), new JSONArray(), argumentKw); } private JSONObject resolveInvocationMsg(ECHONETLiteProcedure procedure, WampInvocationMessage msg) throws JSONException, AccessException, IllegalArgumentException, UnsupportedOperationException { long deviceId = msg.getDetails().getLong( KadecotCoreStore.Devices.DeviceColumns.DEVICE_ID); JSONObject params = msg.getArgumentsKw(); List<DeviceProperty> response = new ArrayList<DeviceProperty>(); ECHONETLiteDeviceData data = mDeviceMap.get(deviceId); if (data == null) { throw new IllegalArgumentException("no such device : deviceId " + deviceId); } switch (procedure) { case GET: response = callGet(data, params); break; case SET: response = callSet(data, params); break; default: throw new UnsupportedOperationException(procedure.toString()); } return createResponseArgumentsKw(response); } /** * @param list size must be one. * @return * @throws JSONException */ private JSONObject createResponseArgumentsKw(List<DeviceProperty> list) throws JSONException { JSONObject json = new JSONObject(); DeviceProperty dp = list.get(0); if (dp.value == null) { return null; } String propName = ECHONETLitePropertyName.translate(dp.name); json.put(KadecotProperty.PROPERTY_NAME_KEY, propName); json.put(KadecotProperty.PROPERTY_VALUE_KEY, dp.value); return json; } /** * @param data * @param params [propName1, propName2, ...] * @return */ public List<DeviceProperty> callGet(ECHONETLiteDeviceData data, JSONObject params) throws JSONException, AccessException { List<DeviceProperty> propList = makePropertyList(params); EchoObject obj = null; try { obj = getEchoObject(data.getDeviceId()); } catch (UnknownHostException e) { e.printStackTrace(); } return mManager.get(obj, propList); } /** * @param data * @param params [[propName1, propValue1], [propName2, propValue2], ...] * @return */ private List<DeviceProperty> callSet(ECHONETLiteDeviceData data, JSONObject params) throws JSONException, AccessException { List<DeviceProperty> propertyList = makePropertyList(params); EchoObject obj = null; try { obj = getEchoObject(data.getDeviceId()); } catch (UnknownHostException e) { e.printStackTrace(); } return mManager.set(obj, propertyList); } /** * @param data * @param param {"propertyName" : name} * @return */ private List<DeviceProperty> makePropertyList(JSONObject param) throws JSONException { ArrayList<DeviceProperty> propertyList = new ArrayList<DeviceProperty>(); String propName = ECHONETLitePropertyName.translate(param .getString(KadecotProperty.PROPERTY_NAME_KEY)); Object propValue = null; // for set if (param.has(KadecotProperty.PROPERTY_VALUE_KEY)) { String paramValue = param.getString(KadecotProperty.PROPERTY_VALUE_KEY); JSONArray jarray = new JSONArray(); String val = ECHONETLitePropertyValue.getPropertyValue(paramValue).toString(); jarray.put(Integer.decode(val)); propValue = jarray; } DeviceProperty dp = new DeviceProperty(propName, propValue); propertyList.add(dp); return propertyList; } } private EchoObject getEchoObject(long deviceId) throws UnknownHostException { ECHONETLiteDeviceData data = mDeviceMap.get(deviceId); if (data == null) { return null; } String address = null; if (data.getAddress().equals(EchoDeviceDatabase.LOCAL_ADDRESS)) { // local address = Echo.getSelfNode().getAddressStr(); } else { // remote address = data.getAddress(); } EchoNode ne = Echo.getNode(address); if (ne == null) return null; EchoObject eoj = ne.getInstance(data.getClassCode(), data.getInstanceCode()); return eoj; } Map<Long, ECHONETLiteDeviceData> getDeviceMap() { return mDeviceMap; } }