001/*
002 * geordi
003 *
004 * Copyright (C) 2018 Richard "Shred" Körber
005 *   https://github.com/shred/geordi
006 *
007 * This program is free software: you can redistribute it and/or modify
008 * it under the terms of the GNU General Public License as
009 * published by the Free Software Foundation, either version 3 of the
010 * License, or (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
015 */
016package org.shredzone.geordi.device;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.math.BigDecimal;
021import java.net.MalformedURLException;
022import java.net.URL;
023import java.time.Instant;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.List;
027
028import javax.inject.Inject;
029
030import org.json.JSONArray;
031import org.json.JSONException;
032import org.json.JSONObject;
033import org.json.JSONTokener;
034import org.shredzone.geordi.GeordiException;
035import org.shredzone.geordi.data.Sample;
036import org.shredzone.geordi.sensor.Sensor;
037import org.shredzone.geordi.service.DatabaseService;
038
039/**
040 * A {@link Device} implementation that reads Aquaero fan controllers. It
041 * requires a running <a href="https://github.com/shred/pyquaero">Pyquaero</a> server that
042 * is connected to the Aquaero device.
043 *
044 * @see <a href="https://aquacomputer.de">Aqua Computer GmbH &amp; Co. KG</a>
045 */
046public class AquaeroDevice extends Device {
047
048    @Inject
049    private DatabaseService databaseService;
050
051    @Override
052    public List<Sample> readSensors() {
053        JSONObject json;
054        try (InputStream in = getServerUrl().openStream()) {
055            json = new JSONObject(new JSONTokener(in));
056        } catch (IOException | JSONException ex) {
057            throw new GeordiException("Could not read data for sensor " + getId(), ex);
058        }
059
060        Instant ts = Instant.parse(json.getString("time") + "Z");
061
062        List<Sample> result = new ArrayList<>();
063        for (Sensor sensor : databaseService.fetchSensors(this)) {
064            BigDecimal value = getSensorValue(json, sensor);
065
066            // If Pyquaero runs on a Raspberry Pi 1, there might be a misreading of
067            // the sensors due to a hardware bug. We will ignore the 0 value that is
068            // returned from a misreading.
069            if (BigDecimal.ZERO.equals(value)) {
070                return Collections.emptyList();
071            }
072
073            result.add(new Sample(sensor, ts, value));
074        }
075
076        return result;
077    }
078
079    /**
080     * Gets the {@link Sensor} value from the JSON data.
081     *
082     * @param json
083     *            JSON data of Pyquaero
084     * @param sensor
085     *            {@link Sensor} to read
086     * @return Value that was read
087     */
088    private BigDecimal getSensorValue(JSONObject json, Sensor sensor) {
089        JSONObject config = sensor.getConfig();
090        JSONArray data = locate(json, config.getString("type"));
091        JSONObject values = data.getJSONObject(config.getInt("index"));
092        return values.getBigDecimal(config.getString("value"));
093    }
094
095    /**
096     * Locates a JSON array containing sensor data.
097     *
098     * @param json
099     *            JSON data of pyquaero
100     * @param path
101     *            Path to the array
102     * @return JSON array
103     */
104    private JSONArray locate(JSONObject json, String path) {
105        String[] parts = path.split("[/.]");
106        JSONObject current = json;
107        for (int ix = 0; ix < parts.length - 1; ix++) {
108            current = current.getJSONObject(parts[ix]);
109        }
110        return current.getJSONArray(parts[parts.length - 1]);
111    }
112
113    /**
114     * Returns the URL of the Pyquaero server.
115     */
116    private URL getServerUrl() {
117        try {
118            return new URL(String.format("http://%s:%d/status",
119                    getConfig().getString("host"),
120                    getConfig().getInt("port")));
121        } catch (MalformedURLException | JSONException ex) {
122            throw new GeordiException("Bad host config", ex);
123        }
124    }
125
126}