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