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        for (Meter m: InstanceManager.getDefault(MeterManager.class).getNamedBeanSet()) {
087            if (m.getSystemName().startsWith(sm.getSystemPrefix()+"V")) { // NOI18N
088                updateTask.disable(m);
089                InstanceManager.getDefault(MeterManager.class).deregister(m);
090                updateTask.dispose(m);
091            }
092        }
093    }
094
095    public void requestUpdateFromLayout() {
096        log.debug("sending request for voltmeter/ammeter information");
097        sm.sendReadSlot(249);
098    }
099
100    private final String createSystemName(int device, int sn, String typeString) {
101        String devName = LnIPLImplementation.getDeviceName(0, device,0,0);
102        if (devName == null) {
103            devName="["+device+"]"; // NOI18N
104        }
105        return sm.getSystemPrefix()+"V"+ devName + "(s/n"+sn+")"+typeString; // NOI18N
106    }
107
108    private void updateAddMeter(Meter m, String sysName, float value, boolean typeVolt ) {
109        if (m == null) {
110            Meter newMeter;
111            if (typeVolt) {
112                // voltMeter not (yet) registered
113                newMeter = new DefaultMeter.DefaultVoltageMeter(sysName,
114                    Meter.Unit.NoPrefix, 0, 25.4, 0.2, updateTask);
115            } else {
116                            // ammeter not (yet) registered
117                newMeter = new DefaultMeter.DefaultCurrentMeter(sysName,
118                    Meter.Unit.NoPrefix, 0, 12.7, 0.1, updateTask);
119            }
120            try {
121                newMeter.setCommandedAnalogValue(value);
122            } catch (JmriException e) {
123                log.debug("Exception setting {}Meter {} to value {}",
124                        (typeVolt?"volt":"current"), // NOI18N
125                        sysName, value, e);
126            }
127            InstanceManager.getDefault(MeterManager.class).register(newMeter);
128            log.debug("Added new {}Meter {} with value {}",
129                        (typeVolt?"volt":"current"), // NOI18N
130                    sysName, value);
131        } else {
132            try {
133                m.setCommandedAnalogValue(value);
134            } catch (JmriException e) {
135                log.debug("Exception setting {}Meter {} to value {}",
136                        (typeVolt?"volt":"current"), // NOI18N
137                        sysName, value, e);
138            }
139            log.debug("Updating currentMeter {} with value {}",
140                    sysName, value);
141       }
142    }
143
144    private final static Logger log = LoggerFactory.getLogger(LnPredefinedMeters.class);
145}