001package jmri.jmrix.marklin; 002 003import java.util.Hashtable; 004import javax.annotation.Nonnull; 005import jmri.JmriException; 006import jmri.Sensor; 007import org.slf4j.Logger; 008import org.slf4j.LoggerFactory; 009 010/** 011 * Implement sensor manager for Marklin systems. The Manager handles all the 012 * state changes. 013 * <p> 014 * System names are "USnnn:yy", where U is the user configurable system prefix, 015 * nnn is the Marklin Object Number for a given s88 Bus Module and 016 * yy is the port on that module. 017 * 018 * @author Kevin Dickerson Copyright (C) 2009 019 */ 020public class MarklinSensorManager extends jmri.managers.AbstractSensorManager 021 implements MarklinListener { 022 023 public MarklinSensorManager(MarklinSystemConnectionMemo memo) { 024 super(memo); 025 tc = memo.getTrafficController(); 026 // connect to the TrafficManager 027 tc.addMarklinListener(this); 028 } 029 030 MarklinTrafficController tc; 031 //The hash table simply holds the object number against the MarklinSensor ref. 032 private Hashtable<Integer, Hashtable<Integer, MarklinSensor>> _tmarklin = new Hashtable<Integer, Hashtable<Integer, MarklinSensor>>(); // stores known Marklin Obj 033 034 /** 035 * {@inheritDoc} 036 */ 037 @Override 038 @Nonnull 039 public MarklinSystemConnectionMemo getMemo() { 040 return (MarklinSystemConnectionMemo) memo; 041 } 042 043 /** 044 * {@inheritDoc} 045 * <p> 046 * System name is normalized to ensure uniqueness. 047 * @throws IllegalArgumentException when SystemName can't be converted 048 */ 049 @Override 050 @Nonnull 051 protected Sensor createNewSensor(@Nonnull String systemName, String userName) throws IllegalArgumentException { 052 MarklinSensor s = new MarklinSensor(systemName, userName); 053 if (systemName.contains(":")) { 054 int board = 0; 055 int channel = 0; 056 057 String curAddress = systemName.substring(getSystemPrefix().length() + 1, systemName.length()); 058 int seperator = curAddress.indexOf(":"); 059 try { 060 board = Integer.parseInt(curAddress.substring(0, seperator)); 061 if (!_tmarklin.containsKey(board)) { 062 _tmarklin.put(board, new Hashtable<>()); 063 MarklinMessage m = MarklinMessage.sensorPollMessage(board); 064 tc.sendMarklinMessage(m, this); 065 } 066 } catch (NumberFormatException ex) { 067 throw new IllegalArgumentException("Unable to convert " + // NOI18N 068 curAddress + 069 " into the Module and port format of nn:xx"); // NOI18N 070 } 071 Hashtable<Integer, MarklinSensor> sensorList = _tmarklin.get(board); 072 try { 073 channel = Integer.parseInt(curAddress.substring(seperator + 1)); 074 if (!sensorList.containsKey(channel)) { 075 sensorList.put(channel, s); 076 } 077 } catch (NumberFormatException ex) { 078 throw new IllegalArgumentException("Unable to convert " + // NOI18N 079 curAddress + 080 " into the Module and port format of nn:xx"); // NOI18N 081 } 082 } 083 return s; 084 } 085 086 @Override 087 @Nonnull 088 public String createSystemName(@Nonnull String curAddress, @Nonnull String prefix) throws JmriException { 089 if (!curAddress.contains(":")) { 090 throw new JmriException("Hardware Address "+curAddress+"should be passed in the form 'Module:port'"); 091 } 092 093 //Address format passed is in the form of board:channel or T:turnout address 094 int seperator = curAddress.indexOf(":"); 095 try { 096 board = Integer.parseInt(curAddress.substring(0, seperator)); 097 } catch (NumberFormatException ex) { 098 throw new JmriException("First part of "+curAddress+" in front of : should be a number"); 099 } 100 try { 101 port = Integer.parseInt(curAddress.substring(seperator + 1)); 102 } catch (NumberFormatException ex) { 103 throw new JmriException("Second part of "+curAddress+" after : should be a number"); 104 } 105 106 if (port == 0 || port > 16) { 107 throw new JmriException("Port number "+port+" in "+curAddress+" must be between 1 and 16"); 108 } 109 StringBuilder sb = new StringBuilder(); 110 sb.append(getSystemPrefix()); 111 sb.append("S"); 112 sb.append(board); 113 sb.append(":"); 114 //Little work around to pad single digit address out. 115 padPortNumber(port, sb); 116 return sb.toString(); 117 } 118 119 private int board = 0; 120 private int port = 0; 121 122 @Override 123 public boolean allowMultipleAdditions(@Nonnull String systemName) { 124 return true; 125 } 126 127 void padPortNumber(int portNo, StringBuilder sb) { 128 if (portNo < 10) { 129 sb.append("0"); 130 } 131 sb.append(portNo); 132 } 133 134 // to listen for status changes from Marklin system 135 @Override 136 public void reply(MarklinReply r) { 137 if (r.getPriority() == MarklinConstants.PRIO_1 && r.getCommand() >= MarklinConstants.FEECOMMANDSTART && r.getCommand() <= MarklinConstants.FEECOMMANDEND) { 138 if (r.getCommand() == MarklinConstants.S88EVENT) { 139 int module = (r.getElement(MarklinConstants.CANADDRESSBYTE1)); 140 module = (module << 8) + (r.getElement(MarklinConstants.CANADDRESSBYTE2)); 141 int contact = (r.getElement(MarklinConstants.CANADDRESSBYTE3)); 142 contact = (contact << 8) + (r.getElement(MarklinConstants.CANADDRESSBYTE4)); 143 String sensorprefix = getSystemPrefix() + "S" + module + ":"; 144 Hashtable<Integer, MarklinSensor> sensorList = _tmarklin.get(module); 145 if (sensorList == null) { 146 //Module does not exist, so add it 147 sensorList = new Hashtable<Integer, MarklinSensor>(); 148 _tmarklin.put(module, sensorList); 149 MarklinMessage m = MarklinMessage.sensorPollMessage(module); 150 tc.sendMarklinMessage(m, this); 151 if (log.isDebugEnabled()) { 152 log.debug("New module added {}", module); 153 } 154 } 155 MarklinSensor ms = sensorList.get(contact); 156 if (ms == null) { 157 StringBuilder sb = new StringBuilder(); 158 sb.append(sensorprefix); 159 //Little work around to pad single digit address out. 160 padPortNumber(contact, sb); 161 if (log.isDebugEnabled()) { 162 log.debug("New sensor added {} : {}", contact, sb.toString()); 163 } 164 ms = (MarklinSensor) provideSensor(sb.toString()); 165 } 166 if (r.getElement(9) == 0x01) { 167 ms.setOwnState(Sensor.INACTIVE); 168 return; 169 } 170 if (r.getElement(10) == 0x01) { 171 ms.setOwnState(Sensor.ACTIVE); 172 return; 173 } 174 log.error("state not found {} {} {}", ms.getDisplayName(), r.getElement(9), r.getElement(10)); 175 log.error("for reply {}", r); 176 } else { 177 int s88Module = r.getElement(9); 178 if (_tmarklin.containsKey(s88Module)) { 179 int status = r.getElement(10); 180 status = (status << 8) + (r.getElement(11)); 181 decodeSensorState(s88Module, status); 182 return; 183 } 184 if (log.isDebugEnabled()) { 185 log.debug("State s88Module not registered {}", s88Module); 186 } 187 } 188 } 189 } 190 191 @Override 192 public void message(MarklinMessage m) { 193 // messages are ignored 194 } 195 196 private void decodeSensorState(int board, int intState) { 197 MarklinSensor ms; 198 int k = 1; 199 int result; 200 201 String sensorprefix = getSystemPrefix() + "S" + board + ":"; 202 Hashtable<Integer, MarklinSensor> sensorList = _tmarklin.get(board); 203 for (int portNo = 1; portNo < 17; portNo++) { 204 result = intState & k; 205 ms = sensorList.get(portNo); 206 if (ms == null) { 207 StringBuilder sb = new StringBuilder(); 208 sb.append(sensorprefix); 209 //Little work around to pad single digit address out. 210 padPortNumber(portNo, sb); 211 ms = (MarklinSensor) provideSensor(sb.toString()); 212 } 213 if (result == 0) { 214 ms.setOwnState(Sensor.INACTIVE); 215 } else { 216 ms.setOwnState(Sensor.ACTIVE); 217 } 218 k = k * 2; 219 } 220 } 221 222 private final static Logger log = LoggerFactory.getLogger(MarklinSensorManager.class); 223 224}