001package jmri.jmrix.anyma; 002 003import static java.lang.System.arraycopy; 004 005import java.util.Arrays; 006import java.util.concurrent.Executors; 007import java.util.concurrent.ScheduledExecutorService; 008import java.util.concurrent.TimeUnit; 009import javax.usb.UsbConst; 010import jmri.util.MathUtil; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * Traffic controller for Anyma DMX. 016 * 017 * @author George Warner Copyright (c) 2017-2018 018 * @since 4.9.6 019 */ 020public class AnymaDMX_TrafficController { 021 022 private byte[] old_data = new byte[512]; 023 private byte[] new_data = new byte[512]; 024 private ScheduledExecutorService execService = null; 025 private AnymaDMX_UsbPortAdapter controller = null; 026 027 /** 028 * Create a new AnymaTrafficController instance. 029 */ 030 public AnymaDMX_TrafficController() { 031 // this forces first pass to transmit everything 032 Arrays.fill(old_data, (byte) -1); 033 034 execService = Executors.newScheduledThreadPool(5); 035 execService.scheduleAtFixedRate(() -> { 036 // if the new_data has changed... 037 if (!Arrays.equals(old_data, new_data)) { 038 // find indexes to first/last bytes that are different 039 int from = old_data.length; 040 int to = 0; 041 for (int i = 0; i < old_data.length; i++) { 042 if (old_data[i] != new_data[i]) { 043 from = Math.min(from, i); 044 to = i; 045 } 046 } 047 if (from <= to) { 048 int len = to - from + 1; 049 byte[] buf = new byte[len]; 050 System.arraycopy(new_data, from, buf, 0, len); 051 if (sendChannelRangeValues(from, to, buf)) { 052 arraycopy(new_data, from, old_data, from, len); 053 } 054 } 055 } 056 }, 0, 100L, TimeUnit.MILLISECONDS); // 10 times per second 057 } 058 059 /** 060 * Make connection to existing PortController (adapter) object. 061 * 062 * @param p the AnymaDMX_UsbPortAdapter we're connecting to 063 */ 064 public void connectPort(AnymaDMX_UsbPortAdapter p) { 065 if (controller != null) { 066 log.warn("connectPort called when already connected"); 067 } else { 068 log.debug("connectPort invoked"); 069 } 070 controller = p; 071 } 072 073 /** 074 * set a channel's value 075 * 076 * @param channel the channel (1 - 512 inclusive) 077 * @param value the value 078 */ 079 public void setChannelValue(int channel, byte value) { 080 if ((1 <= channel) && (channel <= 512)) { 081 new_data[channel - 1] = value; 082 } 083 } 084 085 /** 086 * set the values for a range of channels 087 * 088 * @param from the beginning index (inclusive) 089 * @param to the ending index (inclusive) 090 * @param buf the data to send 091 * note: the from/to indexes are 1-512 (inclusive) 092 */ 093 public void setChannelRangeValues(int from, int to, byte buf[]) { 094 if ((1 <= from) && (from <= 512) && (1 <= to) && (to <= 512)) { 095 int len = to - from + 1; 096 if (len == buf.length) { 097 arraycopy(new_data, from - 1, buf, 0, len); 098 } else { 099 log.error("range does not match buffer size"); 100 } 101 } else { 102 log.error("channel(s) out of range (1-512): {from: {}, to: {}}.", 103 from, to); 104 } 105 } 106 107 /** 108 * send the values for a range of channels (to the controller) 109 * 110 * @param from the beginning index (inclusive) 111 * @param to the ending index (inclusive) 112 * @param buf the data to send 113 * @return true if successful 114 * note: the from/to indexes are 0-511 (inclusive) 115 */ 116 private boolean sendChannelRangeValues(int from, int to, byte buf[]) { 117 boolean result = false; // assume failure (pessimist!) 118 if (controller != null) { 119 from = MathUtil.pin(from, 0, 511); 120 to = MathUtil.pin(to, from, 511); 121 int len = to - from + 1; 122 byte requestType = UsbConst.REQUESTTYPE_TYPE_VENDOR 123 | UsbConst.REQUESTTYPE_RECIPIENT_DEVICE 124 | UsbConst.ENDPOINT_DIRECTION_OUT; 125 byte request = 0x02; // anyma dmx cmd_SetChannelRange 126 result = controller.sendControlTransfer( 127 requestType, request, len, from, buf); 128 } 129 return result; 130 } 131 132 /** 133 * Clean up threads and local storage. 134 */ 135 public void dispose(){ 136 // modified from the javadoc for ExecutorService 137 execService.shutdown(); // Disable new tasks from being submitted 138 try { 139 // Wait a while for existing tasks to terminate 140 if (!execService.awaitTermination(60, TimeUnit.SECONDS)) { 141 execService.shutdownNow(); // Cancel currently executing tasks 142 // Wait a while for tasks to respond to being cancelled 143 if (!execService.awaitTermination(60, TimeUnit.SECONDS)) 144 log.error("Pool did not terminate"); 145 } 146 } catch (InterruptedException ie) { 147 // (Re-)Cancel if current thread also interrupted 148 execService.shutdownNow(); 149 // Preserve interrupt status 150 Thread.currentThread().interrupt(); 151 } 152 } 153 154 private final static Logger log 155 = LoggerFactory.getLogger(AnymaDMX_TrafficController.class); 156 157}