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}