001package jmri.jmrit.logixng.expressions;
002
003import java.beans.*;
004import java.io.*;
005import java.util.*;
006
007import jmri.*;
008import jmri.jmrit.logixng.*;
009import jmri.jmrit.logixng.util.*;
010import jmri.jmrit.logixng.util.parser.*;
011import jmri.util.TimerUtil;
012
013/**
014 * Check the status of battery and power supply.
015 *
016 * @author Daniel Bergqvist Copyright (C) 2023
017 */
018public class ExpressionLinuxLinePower extends AbstractDigitalExpression
019        implements PropertyChangeListener {
020
021    private Is_IsNot_Enum _is_IsNot = Is_IsNot_Enum.Is;
022    private ProtectedTimerTask _timerTask;
023    private final int _delay = 5;
024    private boolean _lastResult;
025    private JmriException _thrownException;
026
027    public ExpressionLinuxLinePower(String sys, String user)
028            throws BadUserNameException, BadSystemNameException {
029        super(sys, user);
030
031        try {
032            _lastResult = internalEvaluate();
033        } catch (JmriException e) {
034            _thrownException = e;
035        }
036    }
037
038    @Override
039    public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws ParserException {
040        DigitalExpressionManager manager = InstanceManager.getDefault(DigitalExpressionManager.class);
041        String sysName = systemNames.get(getSystemName());
042        String userName = userNames.get(getSystemName());
043        if (sysName == null) sysName = manager.getAutoSystemName();
044        ExpressionLinuxLinePower copy = new ExpressionLinuxLinePower(sysName, userName);
045        copy.setComment(getComment());
046        copy.set_Is_IsNot(_is_IsNot);
047        return manager.registerExpression(copy);
048    }
049
050    public void set_Is_IsNot(Is_IsNot_Enum is_IsNot) {
051        _is_IsNot = is_IsNot;
052    }
053
054    public Is_IsNot_Enum get_Is_IsNot() {
055        return _is_IsNot;
056    }
057
058    /** {@inheritDoc} */
059    @Override
060    public Category getCategory() {
061        return Category.LINUX;
062    }
063
064    private List<String> getLinuxPowerSupplies() throws IOException, NoPowerSuppliesException {
065        List<String> powerSupplies = new ArrayList<>();
066
067        Process process = Runtime.getRuntime().exec(new String[]{"upower","-e"});
068        try (BufferedReader buffer = new BufferedReader(new InputStreamReader(process.getInputStream())))  {
069            String line;
070            while ((line = buffer.readLine()) != null) {
071                powerSupplies.add(line);
072            }
073        }
074
075//        powerSupplies.clear();  // For testing only
076
077        if (powerSupplies.isEmpty()) throw new NoPowerSuppliesException();
078
079        return powerSupplies;
080    }
081
082    public boolean isLinePowerOnline() throws IOException, JmriException {
083        boolean isPowerOnline = false;
084
085        for (String powerSupply : getLinuxPowerSupplies()) {
086            Process process = Runtime.getRuntime().exec(new String[]{"upower", "-i", powerSupply});
087            try (BufferedReader buffer = new BufferedReader(new InputStreamReader(process.getInputStream())))  {
088                String line;
089                boolean linePowerFound = false;
090                while ((line = buffer.readLine()) != null) {
091                    if (line.isBlank()) continue;
092
093                    if (line.startsWith("  ")) {
094                        line = line.substring(2);
095                    } else {
096                        throw new JmriException("Unknown string. It doesn't start with two spaces.");
097                    }
098
099//                    System.out.format("'%s'%n", line);
100
101                    if ("line-power".equals(line)) {
102//                        System.out.format("Line power found%n");
103                        linePowerFound = true;
104                    } else if (line.startsWith("  ")) {
105//                        System.out.format("Spaces found%n");
106                        line = line.substring(2);
107                        if (linePowerFound && line.startsWith("online:")) {
108//                            System.out.format("Online found%n");
109                            String[] parts = line.split("\\s+");
110//                            System.out.format("Line: '%s', part0: '%s', part1: '%s'%n", line, parts[0], parts[1]);
111                            if ("yes".equals(parts[1])) {
112                                isPowerOnline = true;
113                            }
114                        }
115                    } else {
116//                        System.out.format("Other: %s%n", line);
117                        linePowerFound = false;
118                    }
119                }
120            }
121        }
122
123        return isPowerOnline;
124    }
125
126    private boolean internalEvaluate() throws JmriException {
127
128        try {
129            if (_is_IsNot == Is_IsNot_Enum.Is) {
130                return isLinePowerOnline();
131            } else {
132                return !isLinePowerOnline();
133            }
134        } catch (java.io.IOException e) {
135            throw new JmriException("IO Exception: " + e.getMessage(), e);
136        }
137    }
138
139    /** {@inheritDoc} */
140    @Override
141    public boolean evaluate() throws JmriException {
142
143        if (_thrownException != null) {
144            JmriException e = _thrownException;
145            _thrownException = null;
146            throw e;
147        }
148
149        // Check this every ?? seconds
150        return internalEvaluate();
151    }
152
153    @Override
154    public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException {
155        throw new UnsupportedOperationException("Not supported.");
156    }
157
158    @Override
159    public int getChildCount() {
160        return 0;
161    }
162
163    @Override
164    public String getShortDescription(Locale locale) {
165        return Bundle.getMessage(locale, "LinuxLinePower_Short");
166    }
167
168    @Override
169    public String getLongDescription(Locale locale) {
170        return Bundle.getMessage(locale, "LinuxLinePower_Long", _is_IsNot.toString());
171    }
172
173    /** {@inheritDoc} */
174    @Override
175    public void setup() {
176        // Do nothing
177    }
178
179    /** {@inheritDoc} */
180    @Override
181    public void registerListenersForThisClass() {
182        if (!_listenersAreRegistered) {
183            _timerTask = new ProtectedTimerTask() {
184                @Override
185                public void execute() {
186                    try {
187                        boolean _lastLastResult = _lastResult;
188                        _lastResult = internalEvaluate();
189                        if (_lastResult != _lastLastResult) {
190                            getConditionalNG().execute();
191                        }
192                    } catch (JmriException e) {
193                        _thrownException = e;
194                    }
195                }
196            };
197
198            TimerUtil.schedule(_timerTask, _delay*1000, _delay*1000);
199            _listenersAreRegistered = true;
200        }
201    }
202
203    /** {@inheritDoc} */
204    @Override
205    public void unregisterListenersForThisClass() {
206        if (_listenersAreRegistered) {
207            _timerTask.cancel();
208            _listenersAreRegistered = false;
209        }
210    }
211
212    /** {@inheritDoc} */
213    @Override
214    public void propertyChange(PropertyChangeEvent evt) {
215        getConditionalNG().execute();
216    }
217
218    /** {@inheritDoc} */
219    @Override
220    public void disposeMe() {
221    }
222
223
224    public static class NoPowerSuppliesException extends JmriException {
225
226        /**
227         * Creates a new instance of <code>NoPowerSuppliesException</code>.
228         */
229        public NoPowerSuppliesException() {
230            super(Bundle.getMessage("LinuxLinePower_NoPowerSuppliesException"));
231        }
232    }
233
234//    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ExpressionLinuxLinePower.class);
235
236}