/* * Copyright (C) 2013-2014 Sony Computer Science Laboratories, Inc. All Rights Reserved. * Copyright (C) 2014 Sony Corporation. All Rights Reserved. */ package com.sonycsl.Kadecot.device.echo; import android.content.Context; import com.sonycsl.Kadecot.call.ErrorResponse; import com.sonycsl.Kadecot.core.Dbg; import com.sonycsl.Kadecot.device.AccessException; import com.sonycsl.Kadecot.device.DeviceData; import com.sonycsl.Kadecot.device.DeviceDatabase; import com.sonycsl.Kadecot.device.DeviceInfo; import com.sonycsl.Kadecot.device.DeviceManager; import com.sonycsl.Kadecot.device.DeviceProperty; import com.sonycsl.Kadecot.device.DeviceProtocol; import com.sonycsl.Kadecot.device.echo.generator.EchoDeviceAgent; import com.sonycsl.Kadecot.device.echo.generator.EchoDeviceGenerator; import com.sonycsl.Kadecot.log.Logger; import com.sonycsl.echo.Echo; import com.sonycsl.echo.EchoFrame; import com.sonycsl.echo.EchoProperty; import com.sonycsl.echo.EchoSocket; import com.sonycsl.echo.EchoUtils; import com.sonycsl.echo.eoj.EchoObject; import com.sonycsl.echo.eoj.device.DeviceObject; import com.sonycsl.echo.eoj.device.housingfacilities.PowerDistributionBoardMetering; import com.sonycsl.echo.node.EchoNode; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.net.InetAddress; 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.concurrent.ConcurrentHashMap; public class EchoManager implements DeviceProtocol { public static final String PROTOCOL_TYPE_ECHO = "echonetlite"; private static EchoManager sInstance = null; private final Context mContext; private final MyNodeProfile mNodeProfile; private final MyController mController; private final Map<String, Callback> mCallbacks; static final long CALLBACK_TIME_OUT = 1000 * 10; static final long ACCESS_INTERVAL_TIME = 0; private final Map<String, EchoDeviceGenerator> mGenerators; private final EchoDiscovery mEchoDiscovery; private EchoDeviceDatabase mEchoDeviceDatabase; private DeviceManager mDeviceManager; private DeviceDatabase mDeviceDatabase; private final Map<InetAddress, Long> mLastAccessTimes; private EchoManager(Context context) { mContext = context.getApplicationContext(); mNodeProfile = new MyNodeProfile(); mController = new MyController(); mCallbacks = new ConcurrentHashMap<String, Callback>(); mGenerators = new ConcurrentHashMap<String, EchoDeviceGenerator>(); mEchoDiscovery = new EchoDiscovery(mContext); mLastAccessTimes = new ConcurrentHashMap<InetAddress, Long>(); setup(); } public static synchronized EchoManager getInstance(Context context) { if (sInstance == null) { sInstance = new EchoManager(context); } return sInstance; } public static synchronized EchoManager getInstance() { if (sInstance == null) { // TODO: throw error return null; } return sInstance; } private EchoDeviceDatabase getEchoDeviceDatabase() { if (mEchoDeviceDatabase == null) { mEchoDeviceDatabase = EchoDeviceDatabase.getInstance(mContext); } return mEchoDeviceDatabase; } private DeviceDatabase getDeviceDatabase() { if (mDeviceDatabase == null) { mDeviceDatabase = DeviceDatabase.getInstance(mContext); } return mDeviceDatabase; } private DeviceManager getDeviceManager() { if (mDeviceManager == null) { mDeviceManager = DeviceManager.getInstance(mContext); } return mDeviceManager; } private void setup() { Echo.addEventListener(new Echo.EventListener() { @Override public void onNewDeviceObject(DeviceObject device) { mEchoDiscovery.onDiscover(device); } @Override public void receiveEvent(EchoFrame frame) { // System.err.println(frame); EchoObject eoj = Echo.getNode(frame.getSrcEchoAddress()).getInstance( frame.getSrcEchoClassCode(), frame.getSrcEchoInstanceCode()); EchoProperty[] properties = frame.getProperties(); short tid = frame.getTID(); final String callbackId = getCallbackId(tid, eoj); switch (frame.getESV()) { case EchoFrame.ESV_SET_RES: case EchoFrame.ESV_SETI_SNA: case EchoFrame.ESV_SETC_SNA: case EchoFrame.ESV_GET_RES: case EchoFrame.ESV_GET_SNA: Dbg.print("receive:" + callbackId); Callback callback = mCallbacks.get(callbackId); // synchronized(EchoSocket.class) { if (callback != null) { Dbg.print("callback:" + callbackId); mCallbacks.remove(callbackId); callback.run(properties); } // } break; case EchoFrame.ESV_INF: case EchoFrame.ESV_INF_SNA: case EchoFrame.ESV_INFC: onReceiveInformerFrame(eoj, properties); break; } } public void onReceiveInformerFrame(EchoObject eoj, EchoProperty[] properties) { EchoDeviceData data = getEchoDeviceDatabase().getDeviceData(eoj); if (data == null) { return; } List<DeviceProperty> list = new ArrayList<DeviceProperty>(); for (EchoProperty p : properties) { boolean success = p.edt != null; DeviceProperty prop = new DeviceProperty(toPropertyName(p.epc), success ? toPropertyValue(p.edt) : null, success); list.add(prop); } getDeviceManager().onPropertyChanged(data, list); } @Override public void onGetProperty(EchoObject eoj, short tid, byte esv, EchoProperty property, boolean success) { super.onGetProperty(eoj, tid, esv, property, success); if (success && (property.epc == DeviceObject.EPC_GET_PROPERTY_MAP)) { byte[] properties = EchoUtils.propertyMapToProperties(property.edt); HashSet<DeviceProperty> watchingPropertySet = new HashSet<DeviceProperty>(); switch (eoj.getEchoClassCode()) { case PowerDistributionBoardMetering.ECHO_CLASS_CODE: watchingPropertySet .add(new DeviceProperty( toPropertyName(PowerDistributionBoardMetering.EPC_MEASURED_CUMULATIVE_AMOUNT_OF_ELECTRIC_ENERGY_NORMAL_DIRECTION))); watchingPropertySet .add(new DeviceProperty( toPropertyName(PowerDistributionBoardMetering.EPC_MEASURED_CUMULATIVE_AMOUNT_OF_ELECTRIC_ENERGY_REVERSE_DIRECTION))); watchingPropertySet .add(new DeviceProperty( toPropertyName(PowerDistributionBoardMetering.EPC_UNIT_FOR_CUMULATIVE_AMOUNTS_OF_ELECTRIC_ENERGY))); for (byte p : properties) { int i = p & 0xFF; if (i >= (PowerDistributionBoardMetering.EPC_MEASUREMENT_CHANNEL1 & 0xFF) && i <= (PowerDistributionBoardMetering.EPC_MEASUREMENT_CHANNEL32 & 0xFF)) { watchingPropertySet.add(new DeviceProperty(toPropertyName(p))); } } EchoDeviceData data = getEchoDeviceDatabase().getDeviceData(eoj); if (data != null) { long delay = (Logger.DEFAULT_INTERVAL_MILLS) - (System.currentTimeMillis() % (Logger.DEFAULT_INTERVAL_MILLS)); Logger.getInstance(mContext).watch(data.nickname, watchingPropertySet, Logger.DEFAULT_INTERVAL_MILLS, delay); } break; } } } }); } @Override public synchronized void start() { if (Echo.getSelfNode() != null) { try { Echo.restart(); } catch (IOException e) { e.printStackTrace(); } return; } else { ArrayList<DeviceObject> deviceList = new ArrayList<DeviceObject>(); deviceList.add(mController); for (String protocolName : mGenerators.keySet()) { EchoDeviceGenerator gen = mGenerators.get(protocolName); List<EchoDeviceData> agentDataList = getEchoDeviceDatabase().getDeviceDataList(protocolName); gen.onInitGenerator(agentDataList); for (EchoDeviceData data : agentDataList) { EchoDeviceAgent agent = new EchoDeviceAgent(data, gen); deviceList.add(agent); } } try { Echo.start(mNodeProfile, deviceList.toArray(new DeviceObject[] {})); } catch (IOException e) { e.printStackTrace(); } } mEchoDiscovery.startDiscovering(); } @Override public synchronized void stop() { try { Echo.stop(); } catch (IOException e) { e.printStackTrace(); } } @Override public synchronized void refreshDeviceList() { mEchoDiscovery.clearActiveDevices(); mEchoDiscovery.startDiscovering(); } @Override public synchronized void deleteAllDeviceData() { try { Echo.stop(); Echo.clear(); } catch (IOException e) { e.printStackTrace(); } mEchoDiscovery.clearActiveDevices(); getEchoDeviceDatabase().deleteAllDeviceData(); for (String protocolName : mGenerators.keySet()) { mGenerators.get(protocolName).onDeleteAllEchoDevice(); } setup(); } // public String getCallbackId(short tid, final EchoObject eoj, final byte // epc) { // return tid+","+eoj.getNode().getAddress()+","+eoj.getEchoObjectCode(); // } private String getCallbackId(short tid, final EchoObject eoj) { return tid + "," + eoj.getNode().getAddress() + "," + eoj.getEchoObjectCode(); } private class Callback { public volatile EchoProperty[] properties = null; public void run(EchoProperty[] properties) { this.properties = properties; } } public static String toPropertyName(byte epc) { return "0x" + EchoUtils.toHexString(epc); } private static JSONArray toPropertyValue(byte[] edt) { JSONArray edtAry = new JSONArray(); if (edt != null) { for (int i = 0; i < edt.length; i++) { edtAry.put(edt[i] & 0xff); } } return edtAry; } @Override public int getAllowedPermissionLevel() { return 1; } public static JSONObject convertPropertyAsJSON(String nickname, String propertyName, Object propertyData) { JSONObject jsonObj = new JSONObject(); try { jsonObj.put("nickname", nickname); jsonObj.put("property", propertyName); jsonObj.put("data", propertyData); } catch (JSONException e) { e.printStackTrace(); } return jsonObj; } @Override public void deleteDeviceData(long deviceId) { Dbg.print("deleteDeviceData:" + deviceId); EchoDeviceData data = getEchoDeviceDatabase().getDeviceData(deviceId); if (data == null) { return; } mEchoDiscovery.removeActiveDevices(deviceId); getEchoDeviceDatabase().deleteDeviceData(deviceId); if (data.parentId == null) { return; } DeviceData parentData = getDeviceDatabase().getDeviceData(data.parentId); if (parentData == null) { return; } for (String protocolName : mGenerators.keySet()) { if (protocolName.equals(parentData.protocolName)) { mGenerators.get(protocolName).onDeleteEchoDevice(data); break; } } return; } public EchoObject getEchoObject(long deviceId) throws UnknownHostException { EchoDeviceData data = getEchoDeviceDatabase().getDeviceData(deviceId); if (data == null) { return null; } String address = null; if (data.address.equals(EchoDeviceDatabase.LOCAL_ADDRESS)) { // local address = Echo.getSelfNode().getAddressStr(); } else { // remote address = data.address; } EchoNode ne = Echo.getNode(address); if (ne == null) return null; EchoObject eoj = ne.getInstance(data.echoClassCode, data.instanceCode); return eoj; } @Override public List<DeviceProperty> set(long deviceId, List<DeviceProperty> propertyList) throws AccessException { EchoObject eoj = null; try { eoj = getEchoObject(deviceId); } catch (UnknownHostException e) { e.printStackTrace(); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, "unknown host")); } if (eoj == null) { throw new AccessException(new ErrorResponse(ErrorResponse.INVALID_PARAMS_CODE, "Not found echo object")); } ArrayList<EchoProperty> list = new ArrayList<EchoProperty>(); try { for (DeviceProperty p : propertyList) { byte epc = Integer.decode(p.name).byteValue(); if (p.value instanceof Integer) { JSONArray ja = new JSONArray(); ja.put(((Integer) p.value).intValue()); p.value = ja; } if (!(p.value instanceof JSONArray)) { throw new AccessException(new ErrorResponse(ErrorResponse.INVALID_PARAMS_CODE)); } JSONArray value = (JSONArray) p.value; byte[] edt = new byte[value.length()]; for (int i = 0; i < value.length(); i++) { edt[i] = (byte) value.getInt(i); } list.add(new EchoProperty(epc, edt)); } } catch (Exception e) { throw new AccessException(new ErrorResponse(ErrorResponse.INVALID_PARAMS_CODE, e)); } waitForAccess(eoj.getNode().getAddress()); return setProperty(eoj, list); } private List<DeviceProperty> setProperty(EchoObject eoj, List<EchoProperty> propertyList) throws AccessException { try { final Thread current = Thread.currentThread(); Callback callback = new Callback() { @Override public void run(EchoProperty[] properties) { super.run(properties); current.interrupt(); } }; HashMap<Byte, byte[]> map = new HashMap<Byte, byte[]>(); // send // synchronized(EchoSocket.class) { EchoObject.Setter setter = eoj.set(); for (EchoProperty p : propertyList) { setter.reqSetProperty(p.epc, p.edt); map.put((Byte) p.epc, p.edt); } String id = send(eoj, setter, callback); /* * short nextTid = EchoSocket.getNextTIDNoIncrement(); id = * getCallbackId(nextTid, eoj); mCallbacks.put(id, callback); short * tid = setter.send(); if (nextTid != tid) { mCallbacks.remove(id); * Dbg.print("fault"); throw new AccessException(new ErrorResponse( * ErrorResponse.INTERNAL_ERROR_CODE, "fault")); } */ // } // sleep try { Dbg.print("send:" + id); Thread.sleep(CALLBACK_TIME_OUT); } catch (InterruptedException e) { } if (mCallbacks.containsKey(id)) { mCallbacks.remove(id); // timeout Dbg.print("ECHONET Lite Timeout:" + id); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, "ECHONET Lite Timeout")); } else { if (callback.properties != null) { List<DeviceProperty> list = new ArrayList<DeviceProperty>(); for (EchoProperty p : callback.properties) { DeviceProperty prop = new DeviceProperty(toPropertyName(p.epc), toPropertyValue(map .get(p.epc)), p.edt == null); list.add(prop); } return list; } else { throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE)); } } } catch (IOException e) { e.printStackTrace(); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, e)); } } @Override public List<DeviceProperty> get(long deviceId, List<DeviceProperty> propertyList) throws AccessException { EchoObject eoj = null; try { eoj = getEchoObject(deviceId); } catch (UnknownHostException e) { e.printStackTrace(); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, "unknown host")); } if (eoj == null) { throw new AccessException(new ErrorResponse(ErrorResponse.INVALID_PARAMS_CODE, "Not found echo object")); } ArrayList<Byte> list = new ArrayList<Byte>(); try { for (DeviceProperty dp : propertyList) { byte epc = Integer.decode(dp.name).byteValue(); list.add(epc); } } catch (Exception e) { throw new AccessException(new ErrorResponse(ErrorResponse.INVALID_PARAMS_CODE, e)); } waitForAccess(eoj.getNode().getAddress()); return getProperty(eoj, list); } private void waitForAccess(InetAddress address) { long currentTime = System.currentTimeMillis(); Long time = mLastAccessTimes.get(address); long lastAccessTime; if (time == null) { lastAccessTime = 0; } else { lastAccessTime = time; } long interval = currentTime - lastAccessTime; if (interval < ACCESS_INTERVAL_TIME) { Dbg.print("waitForAccess:" + (ACCESS_INTERVAL_TIME - interval)); try { Thread.sleep(ACCESS_INTERVAL_TIME - interval); } catch (InterruptedException e) { e.printStackTrace(); } } mLastAccessTimes.put(address, System.currentTimeMillis()); } private List<DeviceProperty> getProperty(EchoObject eoj, List<Byte> epcList) throws AccessException { try { final Thread current = Thread.currentThread(); Callback callback = new Callback() { @Override public void run(EchoProperty[] properties) { super.run(properties); current.interrupt(); } }; // send // synchronized(EchoSocket.class) { EchoObject.Getter getter = eoj.get(); for (Byte b : epcList) { getter.reqGetProperty(b); } String id = send(eoj, getter, callback); /* * short nextTid = EchoSocket.getNextTIDNoIncrement(); id = * getCallbackId(nextTid, eoj); mCallbacks.put(id, callback); short * tid = getter.send(); if (nextTid != tid) { mCallbacks.remove(id); * Dbg.print("fault"); throw new AccessException(new ErrorResponse( * ErrorResponse.INTERNAL_ERROR_CODE, "fault")); } */ // } // sleep try { Dbg.print("send:" + id + "," + eoj.getClass().getSimpleName()); Thread.sleep(CALLBACK_TIME_OUT); } catch (InterruptedException e) { } if (mCallbacks.containsKey(id)) { mCallbacks.remove(id); // timeout Dbg.print("ECHONET Lite Timeout:" + id); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, "ECHONET Lite Timeout")); } else { if (callback.properties != null) { List<DeviceProperty> list = new ArrayList<DeviceProperty>(); for (EchoProperty p : callback.properties) { DeviceProperty prop = new DeviceProperty(toPropertyName(p.epc)); if (p.edt != null) { prop.value = toPropertyValue(p.edt); } else { prop.value = null; } prop.success = (p.edt != null); list.add(prop); } return list; } else { throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE)); } } } catch (IOException e) { e.printStackTrace(); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, e)); } } private synchronized String send(EchoObject eoj, Object sender, Callback callback) throws AccessException, IOException { short nextTid = EchoSocket.getNextTIDNoIncrement(); String id = getCallbackId(nextTid, eoj); mCallbacks.put(id, callback); short tid = (short) (nextTid - 1); if (sender instanceof EchoObject.Setter) { tid = ((EchoObject.Setter) sender).send().getTID(); } else if (sender instanceof EchoObject.Getter) { tid = ((EchoObject.Getter) sender).send().getTID(); } if (nextTid != tid) { mCallbacks.remove(id); Dbg.print("fault"); throw new AccessException(new ErrorResponse(ErrorResponse.INTERNAL_ERROR_CODE, "fault")); } return id; } public void addEchoDeviceGenerator(EchoDeviceGenerator generator) { mGenerators.put(generator.getProtocolName(), generator); } @Override public DeviceInfo getDeviceInfo(long deviceId, String locale) { EchoDeviceData data = getEchoDeviceDatabase().getDeviceData(deviceId); if (data == null) { return null; } boolean active = mEchoDiscovery.isActiveDevice(data.address, data.echoClassCode, data.instanceCode); String parent = ""; if (data.parentId != null) { JSONObject obj = getDeviceManager().getDeviceInfo(data.parentId, 0); if (obj != null) { try { parent = obj.getString("parent"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } DeviceInfo info = new DeviceInfo(active, EchoDeviceUtils.getClassName(data.echoClassCode), "0x" + EchoUtils.toHexString(data.echoClassCode), parent); Dbg.print("(echo device info)nickname:" + data.nickname + ",address:" + data.address + ",instanceCode:" + data.instanceCode + ",active:" + active); return info; } public EchoDeviceData getDeviceData(String nickname) { return mEchoDeviceDatabase.getDeviceData(nickname); } public void changeNickname() { } @Override public String getProtocolName() { return PROTOCOL_TYPE_ECHO; } public synchronized byte generateDevice(short echoClassCode, long parentId) { Dbg.print(); int instanceCode; EchoNode node = Echo.getSelfNode(); EchoDeviceData data; if (node == null) { return 0; } DeviceData parentData = getDeviceDatabase().getDeviceData(parentId); if (parentData == null) { return 0; } EchoDeviceGenerator gen = mGenerators.get(parentData.protocolName); if (gen == null) { return 0; } // synchronized(mEchoDeviceDatabase) { // / instance codeを決める List<Integer> instanceCodeList = getEchoDeviceDatabase().getLocalDeviceInstanceCodeList(echoClassCode); if (instanceCodeList.size() == 0) { instanceCode = EchoDeviceDatabase.MIN_INSTANCE_CODE; } else { instanceCode = instanceCodeList.get(instanceCodeList.size() - 1) + 1; if (instanceCode >= EchoDeviceDatabase.MAX_INSTANCE_CODE) { instanceCode = EchoDeviceDatabase.MIN_INSTANCE_CODE; } if (instanceCodeList.contains(instanceCode)) { for (int code : instanceCodeList) { instanceCode = code + 1; if (!instanceCodeList.contains(instanceCode)) { break; } } } if (instanceCode >= EchoDeviceDatabase.MAX_INSTANCE_CODE) { return 0; } } Dbg.print("new device instance code:" + instanceCode); // / data = getEchoDeviceDatabase().addLocalDeviceData(echoClassCode, (byte) instanceCode, parentId); if (data == null) { return 0; } // } Dbg.print("nickname:" + data.nickname + ",instanceCode:" + data.instanceCode); node.addDevice(new EchoDeviceAgent(data, gen)); try { node.getNodeProfile().inform().reqInformSelfNodeInstanceListS().send(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } Dbg.print(instanceCode); return (byte) instanceCode; } }