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