001package jmri.jmrix.dccpp; 002 003import jmri.InstanceManager; 004import jmri.JmriException; 005import jmri.Meter; 006import jmri.MeterManager; 007import jmri.implementation.DefaultMeter; 008import jmri.implementation.MeterUpdateTask; 009 010import java.util.HashMap; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015/** 016 * Provide access to current and voltage meters from the DCC++ Base Station 017 * Creates meters based on values sent from command station 018 * User can create new meters in the sketch. 019 * 020 * @author Mark Underwood Copyright (C) 2015 021 * @author Daniel Bergqvist Copyright (C) 2020 022 */ 023public class DCCppPredefinedMeters implements DCCppListener { 024 025 private DCCppTrafficController tc = null; 026 private final MeterUpdateTask updateTask; 027 private String systemPrefix = null; 028 private char beanType; 029 private HashMap<String, Meter> meters = new HashMap<String, Meter>(2); //keep track of defined meters 030 031 public DCCppPredefinedMeters(DCCppSystemConnectionMemo memo) { 032 log.debug("Constructor called"); 033 034 systemPrefix = memo.getSystemPrefix(); 035 beanType = InstanceManager.getDefault(MeterManager.class).typeLetter(); 036 tc = memo.getDCCppTrafficController(); 037 038 updateTask = new MeterUpdateTask(10000, 10000) { 039 @Override 040 public void requestUpdateFromLayout() { 041 tc.sendDCCppMessage(DCCppMessage.makeReadTrackCurrentMsg(), DCCppPredefinedMeters.this); 042 } 043 }; 044 045 // TODO: For now this is OK since the traffic controller 046 // ignores filters and sends out all updates, but 047 // at some point this will have to be customized. 048 tc.addDCCppListener(DCCppInterface.CS_INFO, this); 049 050 updateTask.initTimer(); 051 052 //request one 'c' reply to set up the meters 053 tc.sendDCCppMessage(DCCppMessage.makeReadTrackCurrentMsg(), DCCppPredefinedMeters.this); 054 } 055 056 public void setDCCppTrafficController(DCCppTrafficController controller) { 057 tc = controller; 058 } 059 060 /* handle new Meter replies and original current replies 061 * creates meters if first time this name is encountered 062 * uses new MeterReply message format from DCC-EX 063 * also supports original "current percent" meter from 064 * older DCC++ */ 065 @Override 066 public void message(DCCppReply r) { 067 068 //bail if other message types received 069 if (!r.isCurrentReply() && !r.isMeterReply()) return; 070 071 log.debug("Handling reply: '{}'", r); 072 073 //assume old-style current message and default name and settings 074 String meterName = "CurrentPct"; 075 double meterValue = 0.0; 076 String meterType = DCCppConstants.CURRENT; 077 Meter.Unit meterUnit = Meter.Unit.Percent; 078 double minValue = 0.0; 079 double maxValue = 100.0; 080 double resolution = 0.1; 081 double warnValue = 100.0; //TODO: use when Meter updated to take advantage of it 082 083 //use settings from message if Meter reply 084 if (r.isMeterReply()) { 085 meterName = r.getMeterName(); 086 meterValue= r.getMeterValue(); 087 meterType = r.getMeterType(); 088 minValue = r.getMeterMinValue(); 089 maxValue = r.getMeterMaxValue(); 090 resolution= r.getMeterResolution(); 091 meterUnit = r.getMeterUnit(); 092 warnValue = r.getMeterWarnValue(); 093 } 094 095 //create, store and register the meter if not yet defined 096 if (!meters.containsKey(meterName)) { 097 log.debug("Adding new meter '{}' of type '{}' with unit '{}' {}", 098 meterName, meterType, meterUnit, warnValue); 099 Meter newMeter; 100 String sysName = systemPrefix + beanType + meterType + "_" + meterName; 101 if (meterType.equals(DCCppConstants.VOLTAGE)) { 102 newMeter = new DefaultMeter.DefaultVoltageMeter( 103 sysName, meterUnit, minValue, maxValue, resolution, updateTask); 104 } else { 105 newMeter = new DefaultMeter.DefaultCurrentMeter( 106 sysName, meterUnit, minValue, maxValue, resolution, updateTask); 107 } 108 //store meter by incoming name for lookup later 109 meters.put(meterName, newMeter); 110 InstanceManager.getDefault(MeterManager.class).register(newMeter); 111 } 112 113 //calculate percentage meter value if original current reply message type received 114 if (r.isCurrentReply()) { 115 meterValue = ((r.getCurrentInt() * 1.0f) / (DCCppConstants.MAX_CURRENT * 1.0f)) * 100.0f ; 116 } 117 118 //set the newValue for the meter 119 Meter meter = meters.get(meterName); 120 log.debug("Setting value for '{}' to {}" , meterName, meterValue); 121 try { 122 meter.setCommandedAnalogValue(meterValue); 123 } catch (JmriException e) { 124 log.error("exception thrown when setting meter '{}' value {}", meterName, meterValue, e); 125 } 126 } 127 128 @Override 129 public void message(DCCppMessage m) { 130 // Do nothing 131 } 132 133 /* dispose of all defined meters */ 134 /* NOTE: I don't know if this is ever called */ 135 public void dispose() { 136 meters.forEach((k, v) -> { 137 log.debug("disposing '{}'", k); 138 updateTask.disable(v); 139 InstanceManager.getDefault(MeterManager.class).deregister(v); 140 updateTask.dispose(v); 141 }); 142 } 143 144 // Handle message timeout notification, no retry 145 @Override 146 public void notifyTimeout(DCCppMessage msg) { 147 log.debug("Notified of timeout on message '{}', not retrying", msg); 148 } 149 150 private final static Logger log = LoggerFactory.getLogger(DCCppPredefinedMeters.class); 151 152}