/tcp-server/src/main/java/be/abollaert/domotics/light/servers/tcp/api/EventDispatcher.java

https://github.com/abollaert/smartlights · Java · 360 lines · 227 code · 55 blank · 78 comment · 26 complexity · f0b42976f352b79a87de478108a3e257 MD5 · raw file

  1. package be.abollaert.domotics.light.servers.tcp.api;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.IOException;
  4. import java.net.DatagramPacket;
  5. import java.net.InetAddress;
  6. import java.net.MulticastSocket;
  7. import java.util.HashSet;
  8. import java.util.Set;
  9. import java.util.logging.Level;
  10. import java.util.logging.Logger;
  11. import be.abollaert.domotics.light.api.ChannelState;
  12. import be.abollaert.domotics.light.api.DigitalChannelStateChangeListener;
  13. import be.abollaert.domotics.light.api.DigitalModule;
  14. import be.abollaert.domotics.light.api.DigitalModuleConfigurationChangedListener;
  15. import be.abollaert.domotics.light.api.DimmerChannelStateChangeListener;
  16. import be.abollaert.domotics.light.api.DimmerModule;
  17. import be.abollaert.domotics.light.api.DimmerModuleConfigurationChangedListener;
  18. import be.abollaert.domotics.light.api.Driver;
  19. import be.abollaert.domotics.light.protocolbuffers.Eventing;
  20. import be.abollaert.domotics.light.protocolbuffers.Eventing.DigitalInputChannelStateChanged;
  21. import be.abollaert.domotics.light.protocolbuffers.Eventing.DigitalOutputChannelStateChanged;
  22. import be.abollaert.domotics.light.protocolbuffers.Eventing.DigitalInputChannelStateChanged.Builder;
  23. /**
  24. * Dispatches events received from the modules. It sends them to a multicast socket at port {@link #MULTICAST_PORT}.
  25. *
  26. * @author alex
  27. */
  28. final class EventDispatcher {
  29. /** Logger instance. */
  30. private static final Logger logger = Logger.getLogger(EventDispatcher.class.getName());
  31. /** The port used to send the events through. */
  32. private static final int MULTICAST_PORT = 5894;
  33. /** The address we will be sending messages to. This is the group. */
  34. private static final String MULTICAST_ADDRESS = "224.0.0.100";
  35. /** The socket. */
  36. private MulticastSocket socket;
  37. /** The group address. */
  38. private InetAddress groupAddress;
  39. /** The {@link Set} of {@link DigitalModule}s we are listening to. */
  40. private final Set<DigitalModuleStateListener> digitalModuleStateListeners = new HashSet<DigitalModuleStateListener>();
  41. /** The {@link Set} of {@link DimmerModule}s we are listening to. */
  42. private final Set<DimmerModuleStateListener> dimmerModuleStateListeners = new HashSet<DimmerModuleStateListener>();
  43. /**
  44. * Start the dispatcher.
  45. *
  46. * @throws IOException If an IO error occurs during the start.
  47. */
  48. void start(final Driver driver) throws IOException {
  49. // Open the socket first, then add ourselves as a listener.
  50. this.openMulticastSocket();
  51. for (final DigitalModule digitalModule : driver.getAllDigitalModules()) {
  52. final DigitalModuleStateListener stateListener = new DigitalModuleStateListener(digitalModule);
  53. this.digitalModuleStateListeners.add(stateListener);
  54. }
  55. for (final DimmerModule dimmerModule : driver.getAllDimmerModules()) {
  56. final DimmerModuleStateListener listener = new DimmerModuleStateListener(dimmerModule);
  57. this.dimmerModuleStateListeners.add(listener);
  58. }
  59. }
  60. /**
  61. * Stop the dispatcher.
  62. *
  63. * @throws IOException If an IO error occurs during the start.
  64. */
  65. void stop() throws IOException {
  66. if (this.socket == null) {
  67. throw new IllegalStateException("Do not have a multicast socket to send messages to. Have you started the dispatcher?");
  68. }
  69. for (final DigitalModuleStateListener digitalModuleStateListener : this.digitalModuleStateListeners) {
  70. digitalModuleStateListener.stop();
  71. }
  72. for (final DimmerModuleStateListener listener : this.dimmerModuleStateListeners) {
  73. listener.stop();
  74. }
  75. this.digitalModuleStateListeners.clear();
  76. this.dimmerModuleStateListeners.clear();
  77. this.socket.leaveGroup(this.groupAddress);
  78. this.socket.close();
  79. this.socket = null;
  80. this.groupAddress = null;
  81. }
  82. /**
  83. * Opens the multicast socket.
  84. *
  85. * @throws IOException If an IO error occurs during the open.
  86. */
  87. private final void openMulticastSocket() throws IOException {
  88. this.groupAddress = InetAddress.getByName(MULTICAST_ADDRESS);
  89. this.socket = new MulticastSocket(MULTICAST_PORT);
  90. this.socket.joinGroup(this.groupAddress);
  91. this.socket.setBroadcast(true);
  92. }
  93. /**
  94. * Sends the given message.
  95. *
  96. * @param message The message to send.
  97. *
  98. * @throws IOException If an IO error occurs while sending the message.
  99. */
  100. private final void sendMessage(final byte[] message) throws IOException {
  101. if (this.socket == null) {
  102. throw new IllegalStateException("Do not have a multicast socket to send messages to. Have you started the dispatcher?");
  103. }
  104. final DatagramPacket packet = new DatagramPacket(message, message.length, this.groupAddress, MULTICAST_PORT);
  105. this.socket.send(packet);
  106. }
  107. /**
  108. * State listener for a {@link DigitalModule}.
  109. *
  110. * @author alex
  111. */
  112. private final class DigitalModuleStateListener implements DigitalChannelStateChangeListener, DigitalModuleConfigurationChangedListener {
  113. /** The {@link DigitalModule} we are listening to. */
  114. private final DigitalModule module;
  115. /**
  116. * Create a new instance specifying the module.
  117. *
  118. * @param module The module.
  119. */
  120. private DigitalModuleStateListener(final DigitalModule module) {
  121. this.module = module;
  122. this.module.addChannelStateListener(this);
  123. this.module.addModuleConfigurationListener(this);
  124. }
  125. /**
  126. * Stop listening.
  127. */
  128. private final void stop() {
  129. this.module.removeChannelStateListener(this);
  130. this.module.removeModuleConfigurationListener(this);
  131. }
  132. /**
  133. * {@inheritDoc}
  134. */
  135. @Override
  136. public final void inputChannelStateChanged(final int channelNumber, final ChannelState newState) {
  137. if (logger.isLoggable(Level.INFO)) {
  138. logger.log(Level.INFO, "Sending input state change event for module [" + this.module.getId() + "], channel [" + channelNumber + "], state [" + newState + "]");
  139. }
  140. final Builder stateChangebuilder = DigitalInputChannelStateChanged.newBuilder();
  141. ByteArrayOutputStream outputStream = null;
  142. try {
  143. stateChangebuilder.setModuleId(this.module.getId());
  144. stateChangebuilder.setChannelNumber(channelNumber);
  145. stateChangebuilder.setNewState(newState == ChannelState.ON ? true : false);
  146. final be.abollaert.domotics.light.protocolbuffers.Eventing.Event.Builder eventBuilder = Eventing.Event.newBuilder();
  147. eventBuilder.setDigitalInputChannelStateChangedEvent(stateChangebuilder.build());
  148. eventBuilder.setType(Eventing.Event.Type.DIGITAL_INPUT_CHANNEL_STATE_CHANGED);
  149. outputStream = new ByteArrayOutputStream();
  150. eventBuilder.build().writeTo(outputStream);
  151. sendMessage(outputStream.toByteArray());
  152. } catch (IOException e) {
  153. if (logger.isLoggable(Level.WARNING)) {
  154. logger.log(Level.WARNING, "IO error while generating digital input channel state event : [" + e.getMessage() + "]", e);
  155. }
  156. } finally {
  157. if (outputStream != null) {
  158. try {
  159. outputStream.close();
  160. } catch (IOException e) {
  161. if (logger.isLoggable(Level.WARNING)) {
  162. logger.log(Level.WARNING, "IO error while closing output stream : [" + e.getMessage() + "]", e);
  163. }
  164. }
  165. }
  166. }
  167. }
  168. /**
  169. * {@inheritDoc}
  170. */
  171. @Override
  172. public final void outputChannelStateChanged(final int channelNumber, final ChannelState newState) {
  173. if (logger.isLoggable(Level.INFO)) {
  174. logger.log(Level.INFO, "Sending output state change event for module [" + this.module.getId() + "], channel [" + channelNumber + "], state [" + newState + "]");
  175. }
  176. final be.abollaert.domotics.light.protocolbuffers.Eventing.DigitalOutputChannelStateChanged.Builder stateChangebuilder = DigitalOutputChannelStateChanged.newBuilder();
  177. ByteArrayOutputStream outputStream = null;
  178. try {
  179. stateChangebuilder.setModuleId(this.module.getId());
  180. stateChangebuilder.setChannelNumber(channelNumber);
  181. stateChangebuilder.setNewState(newState == ChannelState.ON ? true : false);
  182. final be.abollaert.domotics.light.protocolbuffers.Eventing.Event.Builder eventBuilder = Eventing.Event.newBuilder();
  183. eventBuilder.setDigitalOutputChannelStateChangedEvent(stateChangebuilder);
  184. eventBuilder.setType(Eventing.Event.Type.DIGITAL_OUTPUT_CHANNEL_STATE_CHANGED);
  185. outputStream = new ByteArrayOutputStream();
  186. eventBuilder.build().writeTo(outputStream);
  187. sendMessage(outputStream.toByteArray());
  188. } catch (IOException e) {
  189. if (logger.isLoggable(Level.WARNING)) {
  190. logger.log(Level.WARNING, "IO error while generating digital output channel state event : [" + e.getMessage() + "]", e);
  191. }
  192. } finally {
  193. if (outputStream != null) {
  194. try {
  195. outputStream.close();
  196. } catch (IOException e) {
  197. if (logger.isLoggable(Level.WARNING)) {
  198. logger.log(Level.WARNING, "IO error while closing output stream : [" + e.getMessage() + "]", e);
  199. }
  200. }
  201. }
  202. }
  203. }
  204. /**
  205. * {@inheritDoc}
  206. */
  207. @Override
  208. public final void digitalModuleConfigurationChanged(int moduleId) {
  209. final Eventing.DigitalModuleConfigurationChanged.Builder eventBuilder = Eventing.DigitalModuleConfigurationChanged.newBuilder();
  210. eventBuilder.setModuleId(moduleId);
  211. try {
  212. final Eventing.Event.Builder builder = Eventing.Event.newBuilder();
  213. builder.setType(Eventing.Event.Type.DIGITAL_MODULE_CONFIG_CHANGE);
  214. builder.setDigitalModuleConfigChanged(eventBuilder);
  215. sendMessage(builder.build().toByteArray());
  216. } catch (IOException e) {
  217. if (logger.isLoggable(Level.WARNING)) {
  218. logger.log(Level.WARNING, "IO error while sending event (digital configuration change) : [" + e.getMessage() + "]", e);
  219. }
  220. }
  221. }
  222. }
  223. /**
  224. * Listener that listens for dimmer state changes.
  225. *
  226. * @author alex
  227. */
  228. private final class DimmerModuleStateListener implements DimmerChannelStateChangeListener, DimmerModuleConfigurationChangedListener {
  229. /** The module we are listening on. */
  230. private final DimmerModule module;
  231. /**
  232. * Create a new instance.
  233. *
  234. * @param module The mpdule.
  235. */
  236. private DimmerModuleStateListener(final DimmerModule module) {
  237. this.module = module;
  238. this.module.addChannelStateListener(this);
  239. this.module.addModuleConfigurationListener(this);
  240. }
  241. /**
  242. * Stop the state listener.
  243. */
  244. final void stop() {
  245. this.module.removeChannelStateListener(this);
  246. this.module.removeModuleConfigurationListener(this);
  247. }
  248. @Override
  249. public final void inputChannelStateChanged(final int channelNumber, final ChannelState newState) {
  250. final Eventing.DimmerInputChannelStateChanged.Builder eventBuilder = Eventing.DimmerInputChannelStateChanged.newBuilder();
  251. eventBuilder.setModuleId(this.module.getId());
  252. eventBuilder.setNewState(newState == ChannelState.ON? true : false);
  253. eventBuilder.setChannelNumber(channelNumber);
  254. final Eventing.Event.Builder messageBuilder = Eventing.Event.newBuilder();
  255. messageBuilder.setType(Eventing.Event.Type.DIMMER_MODULE_INPUT_CHANNEL_STATE_CHANGED);
  256. messageBuilder.setDimmerInputStateChanged(eventBuilder);
  257. try {
  258. sendMessage(messageBuilder.build().toByteArray());
  259. } catch (IOException e) {
  260. if (logger.isLoggable(Level.WARNING)) {
  261. logger.log(Level.WARNING, "Could not send dimmer input channel state change event due to an IO error [" + e.getMessage() + "]", e);
  262. }
  263. }
  264. }
  265. /**
  266. * {@inheritDoc}
  267. */
  268. @Override
  269. public final void outputChannelStateChanged(final int channelNumber, final ChannelState newState, final int percentage) {
  270. final Eventing.DimmerOutputChannelStateChanged.Builder eventBuilder = Eventing.DimmerOutputChannelStateChanged.newBuilder();
  271. eventBuilder.setModuleId(this.module.getId());
  272. eventBuilder.setChannelNumber(channelNumber);
  273. eventBuilder.setDimmerPercentage(percentage);
  274. eventBuilder.setOn(newState == ChannelState.ON? true : false);
  275. final Eventing.Event.Builder messageBuilder = Eventing.Event.newBuilder();
  276. messageBuilder.setType(Eventing.Event.Type.DIMMER_MODULE_OUTPUT_CHANNEL_STATE_CHANGED);
  277. messageBuilder.setDimmerOutputStateChanged(eventBuilder);
  278. try {
  279. sendMessage(messageBuilder.build().toByteArray());
  280. } catch (IOException e) {
  281. if (logger.isLoggable(Level.WARNING)) {
  282. logger.log(Level.WARNING, "Could not send dimmer output channel state change event due to an IO error [" + e.getMessage() + "]", e);
  283. }
  284. }
  285. }
  286. /**
  287. * {@inheritDoc}
  288. */
  289. @Override
  290. public final void dimmerModuleConfigurationChanged(final int moduleId) {
  291. final Eventing.DimmerModuleConfigurationChanged.Builder eventBuilder = Eventing.DimmerModuleConfigurationChanged.newBuilder();
  292. eventBuilder.setModuleId(this.module.getId());
  293. final Eventing.Event.Builder messageBuilder = Eventing.Event.newBuilder();
  294. messageBuilder.setType(Eventing.Event.Type.DIMMER_MODULE_CONFIG_CHANGE);
  295. messageBuilder.setDimmerModuleConfigChanged(eventBuilder);
  296. try {
  297. sendMessage(messageBuilder.build().toByteArray());
  298. } catch (IOException e) {
  299. if (logger.isLoggable(Level.WARNING)) {
  300. logger.log(Level.WARNING, "Could not send dimmer configuration change event due to an IO error [" + e.getMessage() + "]", e);
  301. }
  302. }
  303. }
  304. }
  305. }