001package jmri.jmrix.loconet;
002
003import jmri.*;
004import jmri.implementation.DefaultMeter;
005import jmri.implementation.MeterUpdateTask;
006import jmri.jmrix.loconet.duplexgroup.swing.LnIPLImplementation;
007
008/**
009 * Provide access to current and voltage meter from some LocoNet command stations
010 *
011 * @author Steve G           Copyright (C) 2019
012 * @author Bob Jacobsen      Copyright (C) 2019
013 * @author Egbert Boerse     Copyright (C) 2019
014 * @author Daniel Bergqvist  Copyright (C) 2020
015 * @author B. Milhaupt       Copyright (C) 2020
016 */
017public class LnPredefinedMeters implements LocoNetListener, Disposable {
018
019    private SlotManager sm = null;
020    private LnTrafficController tc = null;
021    private final MeterUpdateTask updateTask;
022    private final LnMeterInitTask initializationTask;
023
024    /**
025     * Create a LnPredefinedMeters object
026     *
027     * @param scm  connection memo
028     */
029    public LnPredefinedMeters(LocoNetSystemConnectionMemo scm) {
030        this.sm = scm.getSlotManager();
031        this.tc = scm.getLnTrafficController();
032
033        updateTask = new MeterUpdateTask(LnConstants.METER_INTERVAL_MS) {
034            @Override
035            public void requestUpdateFromLayout() {
036                sm.sendReadSlot(249);
037            }
038        };
039
040        tc.addLocoNetListener(~0, this);
041
042        updateTask.initTimer();
043
044        // a work-around to ensure that the LocoNet transmit path is established
045        // before making an initial query-mode request
046        initializationTask = new LnMeterInitTask(sm.tc, 85);
047        initializationTask.initTimer();
048        initializationTask.enable();
049    }
050
051    @Override
052    public void message(LocoNetMessage msg) {
053        if (msg.getNumDataElements() != 21
054                || msg.getOpCode() != LnConstants.OPC_EXP_RD_SL_DATA
055                || msg.getElement(1) != 21
056                || msg.getElement(2) != 1
057                || msg.getElement(3) != 0x79) {
058            return;
059        }
060        int srcDeviceType = msg.getElement(16);
061        if ((srcDeviceType == LnConstants.RE_IPL_DIGITRAX_HOST_BXP88)
062            || (srcDeviceType == LnConstants.RE_IPL_DIGITRAX_HOST_LNWI)
063            || (srcDeviceType == LnConstants.RE_IPL_DIGITRAX_HOST_BXPA1)) {
064            // these devices support Query Mode but always return 0s for
065            // voltage/current data
066            return;
067        }
068
069        float valAmps = msg.getElement(6)/10.0f;
070        float valVolts = msg.getElement(4)*2.0f/10.0f;
071
072        int srcSerNum = msg.getElement(18)+128*msg.getElement(19);
073
074        String voltSysName = createSystemName(srcDeviceType, srcSerNum, "Voltage"); // NOI18N
075        Meter m = InstanceManager.getDefault(MeterManager.class).getBySystemName(voltSysName);
076        updateAddMeter(m, voltSysName, valVolts, true);
077
078        String ampsSysName = createSystemName(srcDeviceType, srcSerNum, "InputCurrent"); // NOI18N
079        m = InstanceManager.getDefault(MeterManager.class).getBySystemName(ampsSysName);
080        updateAddMeter(m, ampsSysName, valAmps, false);
081    }
082
083    /**
084     * Dispose of the instance.
085     * Removes TC Listener.
086     * Disposes initialization task and individual update tasks.
087     */
088    @Override
089    public void dispose() {
090        tc.removeLocoNetListener(~0, this);
091        initializationTask.dispose();
092        var meters = new java.util.HashSet<>(InstanceManager.getDefault(MeterManager.class).getNamedBeanSet());
093        for (Meter m: meters) {
094            if (m.getSystemName().startsWith(sm.getSystemPrefix()+"V")) { // NOI18N
095                updateTask.disable(m);
096                InstanceManager.getDefault(MeterManager.class).deregister(m);
097                updateTask.dispose(m);
098            }
099        }
100    }
101
102    public void requestUpdateFromLayout() {
103        log.debug("sending request for voltmeter/ammeter information");
104        sm.sendReadSlot(249);
105    }
106
107    private String createSystemName(int device, int sn, String typeString) {
108        String devName = LnIPLImplementation.getDeviceName(0, device,0,0);
109        if (devName == null) {
110            devName="["+device+"]"; // NOI18N
111        }
112        return sm.getSystemPrefix()+"V"+ devName + "(s/n"+sn+")"+typeString; // NOI18N
113    }
114
115    private void updateAddMeter(Meter m, String sysName, float value, boolean typeVolt ) {
116        if (m == null) {
117            Meter newMeter;
118            if (typeVolt) {
119                // voltMeter not (yet) registered
120                newMeter = new DefaultMeter.DefaultVoltageMeter(sysName,
121                    Meter.Unit.NoPrefix, 0, 25.4, 0.2, updateTask);
122            } else {
123                            // ammeter not (yet) registered
124                newMeter = new DefaultMeter.DefaultCurrentMeter(sysName,
125                    Meter.Unit.NoPrefix, 0, 12.7, 0.1, updateTask);
126            }
127            try {
128                newMeter.setCommandedAnalogValue(value);
129            } catch (JmriException e) {
130                log.debug("Exception setting {}Meter {} to value {}",
131                        (typeVolt?"volt":"current"), // NOI18N
132                        sysName, value, e);
133            }
134            InstanceManager.getDefault(MeterManager.class).register(newMeter);
135            log.debug("Added new {}Meter {} with value {}",
136                        (typeVolt?"volt":"current"), // NOI18N
137                    sysName, value);
138        } else {
139            try {
140                m.setCommandedAnalogValue(value);
141            } catch (JmriException e) {
142                log.debug("Exception setting {}Meter {} to value {}",
143                        (typeVolt?"volt":"current"), // NOI18N
144                        sysName, value, e);
145            }
146            log.debug("Updating currentMeter {} with value {}",
147                    sysName, value);
148       }
149    }
150
151    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LnPredefinedMeters.class);
152}