001package com.pi4j.io.w1;
002
003import java.io.File;
004import java.io.FilenameFilter;
005import java.util.ArrayList;
006import java.util.Arrays;
007import java.util.Collection;
008import java.util.Collections;
009import java.util.Iterator;
010import java.util.LinkedHashMap;
011import java.util.List;
012import java.util.Map;
013import java.util.ServiceLoader;
014import java.util.concurrent.CopyOnWriteArrayList;
015import java.util.logging.Logger;
016
017/*
018 * #%L
019 * **********************************************************************
020 * ORGANIZATION  :  Pi4J
021 * PROJECT       :  Pi4J :: Java Library (Core)
022 * FILENAME      :  W1Master.java
023 *
024 * This file is part of the Pi4J project. More information about
025 * this project can be found here:  https://www.pi4j.com/
026 * **********************************************************************
027 * %%
028 * Copyright (C) 2012 - 2021 Pi4J
029 * %%
030 * This program is free software: you can redistribute it and/or modify
031 * it under the terms of the GNU Lesser General Public License as
032 * published by the Free Software Foundation, either version 3 of the
033 * License, or (at your option) any later version.
034 *
035 * This program is distributed in the hope that it will be useful,
036 * but WITHOUT ANY WARRANTY; without even the implied warranty of
037 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
038 * GNU General Lesser Public License for more details.
039 *
040 * You should have received a copy of the GNU General Lesser Public
041 * License along with this program.  If not, see
042 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
043 * #L%
044 */
045
046/**
047 * @author Peter Schuebl
048 */
049public class W1Master {
050
051    private final Logger log = Logger.getLogger(W1Master.class.getName());
052
053    private final List<W1DeviceType> deviceTypes = new ArrayList<>();
054
055    private final Map<String, W1DeviceType> deviceTypeMap = new LinkedHashMap<>();
056
057    private File masterDir = new File("/sys/bus/w1/devices");
058
059    private final List<W1Device> devices = new CopyOnWriteArrayList<>();
060
061    /**
062     * Create an instance of the W1 master. Typically there should only be one master.
063     * <p/>
064     * java.util.ServiceLoader is used to add device support for individual devices.
065     */
066    public W1Master() {
067        init(null);
068    }
069
070    /**
071     * Create an instance of the W1 master. Typically there should only be one master.
072     * <p/>
073     * java.util.ServiceLoader is used to add device support for individual devices.
074     *
075     * @param classloader This ClassLoader will be used for the ServiceLoader to determine supported device types.
076     */
077    public W1Master(final ClassLoader classloader) {
078        init(classloader);
079    }
080
081    /**
082     * Create a master with a different default dir e.g. for tests.
083     *
084     * @param masterDir
085     */
086    public W1Master(final String masterDir) {
087        this.masterDir = new File(masterDir);
088        init(null);
089    }
090
091    /**
092     * Create a master with a different default dir e.g. for tests.
093     *
094     * @param masterDir
095     * @param classloader This ClassLoader will be used for the ServiceLoader to determine supported device types.
096     */
097    public W1Master(final String masterDir, final ClassLoader classloader) {
098        this.masterDir = new File(masterDir);
099        init(classloader);
100    }
101
102    private void init(final ClassLoader classloader) {
103        final ServiceLoader<W1DeviceType> w1DeviceTypes = classloader == null ? ServiceLoader.load(W1DeviceType.class) : ServiceLoader.load(W1DeviceType.class, classloader);
104        final Iterator<W1DeviceType> w1DeviceTypeIterator = w1DeviceTypes.iterator();
105        while (w1DeviceTypeIterator.hasNext()) {
106            final W1DeviceType w1DeviceType = w1DeviceTypeIterator.next();
107            deviceTypes.add(w1DeviceType);
108            final String deviceFamily = Integer.toHexString(w1DeviceType.getDeviceFamilyCode()).toUpperCase();
109            deviceTypeMap.put(deviceFamily, w1DeviceType);
110        }
111        devices.addAll(readDevices());
112    }
113
114    public void checkDeviceChanges() {
115        final List<W1Device> refreshedDevices = new ArrayList<>();
116        final List<W1Device> removedDevices = new ArrayList<>();
117
118        refreshedDevices.addAll(readDevices());
119
120        for (final W1Device device : devices) {
121            if (!refreshedDevices.contains(device)) {
122                removedDevices.add(device);
123            }
124        }
125        refreshedDevices.removeAll(devices);
126
127        final int newCount = refreshedDevices.size();
128        final int removedCount = removedDevices.size();
129        if (newCount > 0) {
130            log.fine("found " + newCount + " new device(s): " + refreshedDevices);
131        }
132
133        if (removedCount > 0) {
134            log.fine("removed " + removedCount + " device(s): " + removedDevices);
135        }
136
137        devices.addAll(refreshedDevices);
138        devices.removeAll(removedDevices);
139    }
140
141    /**
142     * Gets a list of the available device types.
143     *
144     * @return
145     */
146    public Collection<W1DeviceType> getDeviceTypes() {
147        return deviceTypes;
148    }
149
150    public Map<String, W1DeviceType> getDeviceTypeMap() {
151        return deviceTypeMap;
152    }
153
154    private List<File> getDeviceDirs() {
155        final File[] slaveDevices = masterDir.listFiles(new FilenameFilter() {
156            @Override
157            public boolean accept(final File dir, final String name) {
158                return !name.contains("w1_bus_master");
159            }
160        });
161        if (slaveDevices != null) {
162            return Arrays.asList(slaveDevices);
163        }
164        return Collections.emptyList();
165    }
166
167    /**
168     * Gets a list of all registered slave device ids.
169     *
170     * @return list of slave ids, can be empty, never null.
171     */
172    public List<String> getDeviceIDs() {
173        final List<String> ids = new ArrayList<>();
174        for (final File deviceDir : getDeviceDirs()) {
175            ids.add(deviceDir.getName());
176        }
177        /*
178         * //for (final W1Device device: devices) { ids.add(device.getId());
179         */
180        return ids;
181    }
182
183    /**
184     * Get the list of available devices.
185     *
186     * @return returns an unmodifiable list of W1Devices.
187     */
188    public List<W1Device> getDevices() {
189        return Collections.unmodifiableList(devices);
190    }
191
192    @SuppressWarnings("unchecked")
193    public <T extends W1Device> List<T> getDevices(final int deviceFamilyId) {
194        final List<W1Device> filteredDevices = new ArrayList<>();
195        for (final W1Device device : devices) {
196            if (deviceFamilyId == device.getFamilyId()) {
197                filteredDevices.add(device);
198            }
199        }
200        return (List<T>) filteredDevices;
201    }
202
203    /**
204     * Get a single device by it's ID string
205     * @param id (string)
206     * @return W1 device instance
207     */
208    @SuppressWarnings("unchecked")
209    public <T extends W1Device> T getDeviceById(final String id) {
210        final List<W1Device> filteredDevices = new ArrayList<>();
211        for (final W1Device device : devices) {
212            if (device.getId().equalsIgnoreCase(id)) {
213                return (T)device;
214            }
215        }
216        return null;
217    }
218
219    @SuppressWarnings("unchecked")
220    <T extends W1Device> List<T> readDevices() {
221        final List<W1Device> devices = new ArrayList<>();
222        for (final File deviceDir : getDeviceDirs()) {
223            final String id = deviceDir.getName().substring(0, 2).toUpperCase();
224            final W1DeviceType w1DeviceType = deviceTypeMap.get(id);
225            if (w1DeviceType != null) {
226                final W1Device w1Device = w1DeviceType.create(deviceDir);
227                devices.add(w1Device);
228            } else {
229                log.info("no device type for [" + id + "] found - ignoring");
230            }
231        }
232        return (List<T>) devices;
233    }
234
235    /*
236    public <T extends W1Device> List<T> getDevices(final String deviceFamilyId) {
237        List<W1Device> devices = new ArrayList<>();
238        for (W1Device device : readDevices()) {
239
240            if (deviceFamilyId == null || deviceFamilyId.toUpperCase().equals(device.getId())) {
241                devices.add(device);
242            }
243        }
244        return (List<T>) devices;
245    }
246     */
247
248    /**
249     * Get a list of devices that implement a certain interface.
250     *
251     * @param type
252     * @param <T>
253     * @return
254     */
255    @SuppressWarnings("unchecked")
256    public <T> List<T> getDevices(final Class<T> type) {
257        final List<W1Device> allDevices = getDevices();
258        final List<T> filteredDevices = new ArrayList<>();
259        for (final W1Device device : allDevices) {
260            if (type.isAssignableFrom(device.getClass())) {
261                filteredDevices.add((T) device);
262            }
263        }
264        return filteredDevices;
265    }
266
267    @SuppressWarnings("unchecked")
268    public <T extends W1Device> List<T> getW1Devices(final Class<T> type) {
269        for (final W1DeviceType deviceType : deviceTypes) {
270            if (deviceType.getDeviceClass().equals(type)) {
271                return (List<T>) getDevices(deviceType.getDeviceFamilyCode());
272            }
273        }
274        return Collections.emptyList();
275    }
276
277    @Override
278    public String toString() {
279        final StringBuilder builder = new StringBuilder();
280        builder.append("W1 Master: ").append(masterDir).append("\n");
281        builder.append("Device Types: \n");
282        for (final W1DeviceType deviceType : deviceTypeMap.values()) {
283            builder.append(" - ").append(Integer.toHexString(deviceType.getDeviceFamilyCode()));
284            builder.append(" = ").append(deviceType.getDeviceClass());
285            builder.append("\n");
286        }
287        builder.append("Devices:\n");
288        for (final W1Device device : getDevices()) {
289            builder.append(" - ").append(device.getId()).append(": ").append(device.getName());
290            builder.append(" = ").append(device.getClass().getName()).append("\n");
291        }
292        return builder.toString();
293    }
294}