001package jmri.jmrix.can.cbus;
002
003import jmri.*;
004import jmri.implementation.DefaultMeter;
005import jmri.implementation.MeterUpdateTask;
006import jmri.jmrix.can.CanSystemConnectionMemo;
007import jmri.jmrix.can.CanListener;
008import jmri.jmrix.can.CanMessage;
009import jmri.jmrix.can.CanReply;
010import jmri.jmrix.can.TrafficController;
011import jmri.jmrix.can.cbus.node.CbusNode;
012import jmri.jmrix.can.cbus.node.CbusNodeTableDataModel;
013
014/**
015 * Provide access to current meter from a MERG CBUS Command Station
016 *
017 * @author Steve Young (C) 2019
018 * 
019 * @author Andrew Crosland 2020
020 * Added voltage capability to use with new jmrit.voltmeter class
021 * 
022 * @author Daniel Bergqvist Copyright (C) 2020
023  * 
024 * @author Andrew Crosland 2021
025 * Added extra current meter for systems with two track outputs
026*/
027public class CbusPredefinedMeters implements CanListener, Disposable {
028
029    private final TrafficController tc;
030    private int _nodeToListen;
031    private int _eventToListenCurrent;
032    private int _eventToListenCurrentExtra;
033    private int _eventToListenVoltage;
034    private final CanSystemConnectionMemo _memo;
035    final MeterUpdateTask updateTask;
036    final Meter currentMeter;
037    final Meter currentMeterExtra;
038    final Meter voltageMeter;
039    
040    public CbusPredefinedMeters(CanSystemConnectionMemo memo) {
041        
042        tc = memo.getTrafficController();
043        _memo = memo;
044        
045        updateTask = new UpdateTask(-1);
046        
047        currentMeter = new DefaultMeter.DefaultCurrentMeter(
048                memo.getSystemPrefix() + InstanceManager.getDefault(MeterManager.class).typeLetter() + "CBUSCurrentMeter",
049                Meter.Unit.Milli, 0, 65535.0, 1.0, updateTask);
050        
051        currentMeterExtra = new DefaultMeter.DefaultCurrentMeter(
052                memo.getSystemPrefix() + InstanceManager.getDefault(MeterManager.class).typeLetter() + "CBUSCurrentMeter2",
053                Meter.Unit.Milli, 0, 65535.0, 1.0, updateTask);
054        
055        voltageMeter = new DefaultMeter.DefaultVoltageMeter(
056                memo.getSystemPrefix() + InstanceManager.getDefault(MeterManager.class).typeLetter() + "CBUSVoltageMeter",
057                Meter.Unit.NoPrefix, 0, 6553.5, 0.1, updateTask);
058        
059        InstanceManager.getDefault(MeterManager.class).register(currentMeter);
060        InstanceManager.getDefault(MeterManager.class).register(currentMeterExtra);
061        InstanceManager.getDefault(MeterManager.class).register(voltageMeter);
062        
063        log.debug("CbusMultiMeter constructor called");
064    }
065    
066    /**
067     * Listen for CAN Frames sent by Command Station 0
068     * Typically sent every 4-5 seconds.
069     *
070     * {@inheritDoc}
071     */
072    @Override
073    public void reply(CanReply r) {
074        if ( r.extendedOrRtr()
075            || CbusMessage.getOpcode(r) != CbusConstants.CBUS_ACON2
076            || CbusMessage.getNodeNumber(r) != _nodeToListen 
077            || ((CbusMessage.getEvent(r) != _eventToListenCurrent) 
078                && (CbusMessage.getEvent(r) != _eventToListenVoltage)
079                && (CbusMessage.getEvent(r) != _eventToListenCurrentExtra))) {
080            return;
081        }
082        try {
083            if (CbusMessage.getEvent(r) == _eventToListenCurrent) {
084                int currentInt = ( r.getElement(5) * 256 ) + r.getElement(6);
085                currentMeter.setCommandedAnalogValue(currentInt * 1.0f );  // mA value, min 0, max 65535, NOT percentage
086            } else if (CbusMessage.getEvent(r) == _eventToListenCurrentExtra) {
087                int currentInt = ( r.getElement(5) * 256 ) + r.getElement(6);
088                currentMeterExtra.setCommandedAnalogValue(currentInt * 1.0f );  // mA value, min 0, max 65535, NOT percentage
089            } else {
090                // Voltage from the command station is scaled by a factor of 10 to allow one decimal place
091                int voltageInt = ( r.getElement(5) * 256 ) + r.getElement(6);
092                voltageMeter.setCommandedAnalogValue(voltageInt / 10.0f ); // V value, min 0, max 6553.5, NOT percentage
093            }
094        } catch (JmriException e) {
095            log.error("exception thrown by setCurrent or setVoltage", e);
096        }
097    }
098
099    /**
100     * Outgoing CAN Frames ignored
101     *
102     * {@inheritDoc}
103     */
104    @Override
105    public void message(CanMessage m) {
106    }
107    
108    @Override
109    public void dispose() {
110        updateTask.disable(currentMeter);
111        updateTask.disable(currentMeterExtra);
112        updateTask.disable(voltageMeter);
113        InstanceManager.getDefault(MeterManager.class).deregister(currentMeter);
114        InstanceManager.getDefault(MeterManager.class).deregister(currentMeterExtra);
115        InstanceManager.getDefault(MeterManager.class).deregister(voltageMeter);
116        updateTask.dispose(currentMeter);
117        updateTask.dispose(currentMeterExtra);
118        updateTask.dispose(voltageMeter);
119    }
120    
121    
122    private class UpdateTask extends MeterUpdateTask {
123    
124        protected UpdateTask(int interval) {
125            super(interval);
126        }
127    
128        /**
129         * Starts listening for ExData2 CAN Frames using the Node of the Master Command Station
130         *
131         * {@inheritDoc}
132         */
133        @Override
134        public void enable() {
135            _nodeToListen = 65534;
136            _eventToListenCurrent = 1; // hard coded at present
137            _eventToListenVoltage = 2; // hard coded at present
138            _eventToListenCurrentExtra = 3; // hard coded at present
139            CbusNodeTableDataModel cs =  _memo.get(CbusNodeTableDataModel.class);
140            if (cs != null) {
141                CbusNode csnode = cs.getCsByNum(0);
142                if (csnode!=null) {
143                    _nodeToListen = csnode.getNodeNumber();
144                }
145            } else {
146                log.info("Unable to fetch Master Command Station from Node Manager");
147            }
148            tc.addCanListener(CbusPredefinedMeters.this);
149            log.info("Enabled meter Long Ex2Data {} {} {}", 
150                new CbusNameService(_memo).getEventNodeString(_nodeToListen,_eventToListenCurrent), 
151                new CbusNameService(_memo).getEventNodeString(_nodeToListen,_eventToListenVoltage),
152                new CbusNameService(_memo).getEventNodeString(_nodeToListen,_eventToListenCurrentExtra));
153            
154            super.enable();
155        }
156
157        /**
158         * Stops listening for updates
159         *
160         * {@inheritDoc}
161         */
162        @Override
163        public void disable() {
164            super.disable();
165            tc.removeCanListener(CbusPredefinedMeters.this);
166            log.info("Disabled meter.");
167        }
168        
169        /**
170         * Adjust CBUS Command station settings to change frequency of updates
171         * No local action performed
172         *
173         * {@inheritDoc}
174         */
175        @Override
176        public void requestUpdateFromLayout() {
177        }
178    }
179    
180    
181    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusPredefinedMeters.class);
182}