commit 61274350d55dcf506d9517df22fea43a704f007e Author: fey Date: Tue Sep 15 23:47:12 2026 +0200 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d71ffd --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.kotlin + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +.idea/*.xml +.idea/* +.idea/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9b5cc27 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM amazoncorretto:17.0.14-alpine3.21 +LABEL authors="fey" +WORKDIR /app +COPY . /app/ +RUN apk add maven +RUN mvn dependency:resolve-plugins dependency:go-offline -B +RUN mvn compile package +EXPOSE 9400 +ENTRYPOINT mvn -o exec:java \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8285432 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Octonet Exporter + +This is a Java application that exports metrics from a Digital Devices OctopusNET to Prometheus. + +## How to run? + +* Option 1: Set the OCTONET_IP environment variable to the IP address of your OctopusNET device and run the application using `mvn exec:java`. +* Option 2: There's are pre-built binary supplied in the download archive. You can use this by setting the OCTONET_IP environment variable to the IP address of your OctopusNET device and then run the application using `java -jar octonet-exporter-1.0-SNAPSHOT-jar-with-dependencies.jar`. +* Option 3 (recommended): Use the provided Dockerfile to build a container and run this application in Docker or Podman. Don't forget to supply an `-e OCTONET_IP=` to the container. + +## How to compile a binary? + +To compile a binary, run `mvn clean compile assembly:single` in the project directory. This will create a JAR file in the `target` directory that you can run using `java -jar octonet-exporter-1.0-SNAPSHOT-jar-with-dependencies.jar`. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..441109e --- /dev/null +++ b/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + moe.yo3explorer + octonet-exporter + 1.0-SNAPSHOT + + + 11 + 11 + UTF-8 + + + + + org.apache.logging.log4j + log4j-core + 2.25.4 + + + + com.squareup.okhttp3 + okhttp + 5.0.0-alpha.12 + + + + io.prometheus + prometheus-metrics-core + 1.3.6 + + + io.prometheus + prometheus-metrics-exporter-httpserver + 1.3.6 + + + + com.fasterxml.jackson.core + jackson-databind + 2.17.0 + + + + + + + maven-assembly-plugin + + + + moe.yo3explorer.octonet_exporter.Main + + + + jar-with-dependencies + + + + + org.codehaus.mojo + exec-maven-plugin + 3.2.0 + + moe.yo3explorer.octonet_exporter.Main + + + + + + + \ No newline at end of file diff --git a/src/main/java/moe/yo3explorer/octonet_exporter/Main.java b/src/main/java/moe/yo3explorer/octonet_exporter/Main.java new file mode 100644 index 0000000..f03ffbc --- /dev/null +++ b/src/main/java/moe/yo3explorer/octonet_exporter/Main.java @@ -0,0 +1,121 @@ +package moe.yo3explorer.octonet_exporter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.prometheus.metrics.core.metrics.Gauge; +import io.prometheus.metrics.exporter.httpserver.HTTPServer; +import moe.yo3explorer.octonet_exporter.entities.FanspeedResponse; +import moe.yo3explorer.octonet_exporter.entities.Tuner; +import moe.yo3explorer.octonet_exporter.entities.TunerResponse; +import okhttp3.Call; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.Map; + +public class Main { + private static final Logger log = LogManager.getLogger(Main.class); + + private static boolean safeSleep() + { + try { + Thread.sleep(1000); + return true; + } catch (InterruptedException e) { + return false; + } + } + + public static void main(String[] args) throws IOException { + Logger logger = LogManager.getLogger(Main.class); + logger.info("Hello! This is octonet_exporter!"); + Map getenv = System.getenv(); + if (!getenv.containsKey("OCTONET_IP")) + { + logger.error("OCTONET_IP environment variable is not set"); + System.exit(1); + } + String octonetIp = getenv.get("OCTONET_IP"); + logger.info("Octonet IP: " + octonetIp); + + HTTPServer server = HTTPServer.builder() + .hostname("0.0.0.0") + .port(9401) + .buildAndStart(); + logger.info("Server started on port 9401"); + + String fanspeedUrl = "http:/%s/system/fanspeed?_=%d"; + String temperaturUrl = "http://%s/log/Temperatur.log?_=%d"; + String tunerstatusUrl = "http://%s/octoserve/tunerstatus.json"; + ObjectMapper objectMapper = new ObjectMapper(); + Gauge fanspeed = Gauge.builder().name("octonetExporter_fanspeed").help("Fan speed").register(); + Gauge temperature = Gauge.builder().name("octonetExporter_temperature").help("Temperature").register(); + Gauge locked = Gauge.builder().name("octonetExporter_locked").help("Is the tuner locked?").labelNames("input").register(); + Gauge strength = Gauge.builder().name("octonetExporter_strength").help("Signal strength").labelNames("input").register(); + Gauge snr = Gauge.builder().name("octonetExporter_snr").help("Signal to noise ratio").labelNames("input").register(); + Gauge quality = Gauge.builder().name("octonetExporter_quality").help("Signal quality").labelNames("input").register(); + Gauge level = Gauge.builder().name("octonetExporter_level").help("Signal level").labelNames("input").register(); + + OkHttpClient client = new OkHttpClient.Builder().build(); + int state = 1; + while (state != Integer.MAX_VALUE) + { + if (state == 1) + { + logger.info("Starting scraping loop..."); + state++; + } + Request request = new Request.Builder().url(String.format(fanspeedUrl,octonetIp,System.currentTimeMillis())).build(); + Call call = client.newCall(request); + Response execute = call.execute(); + String string = execute.body().string(); + FanspeedResponse fanspeedResponse = objectMapper.readValue(string, FanspeedResponse.class); + fanspeed.set(fanspeedResponse.speed); + if (!safeSleep()) + break; + + request = new Request.Builder().url(String.format(temperaturUrl,octonetIp,System.currentTimeMillis())).build(); + call = client.newCall(request); + execute = call.execute(); + string = execute.body().string(); + StringReader stringReader = new StringReader(string); + BufferedReader bufferedReader = new BufferedReader(stringReader); + String headLine = bufferedReader.readLine(); + String temperatureString = bufferedReader.readLine(); + bufferedReader.close(); + stringReader.close(); + temperature.set(Double.parseDouble(temperatureString)); + if (!safeSleep()) + break; + + request = new Request.Builder().url(String.format(tunerstatusUrl,octonetIp)).build(); + call = client.newCall(request); + execute = call.execute(); + string = execute.body().string(); + TunerResponse tunerResponse = objectMapper.readValue(string, TunerResponse.class); + for (Tuner tuner : tunerResponse.getTunerList()) + { + locked.labelValues(tuner.getInput()).set(tuner.isLock() ? 1 : 0); + strength.labelValues(tuner.getInput()).set(tuner.isLock() ? ((double)(tuner.getStrength() + 108750) / 1000.0) : 0.0); + snr.labelValues(tuner.getInput()).set((double)tuner.getSnr() / 1000.0); + quality.labelValues(tuner.getInput()).set(tuner.getQuality()); + level.labelValues(tuner.getInput()).set(tuner.getLevel()); + } + if (!safeSleep()) + break; + if (state == 2) + { + logger.info("Scraping loop successfully started."); + state++; + } + } + + logger.warn("Scraping loop stopped."); + + } +} \ No newline at end of file diff --git a/src/main/java/moe/yo3explorer/octonet_exporter/entities/FanspeedResponse.java b/src/main/java/moe/yo3explorer/octonet_exporter/entities/FanspeedResponse.java new file mode 100644 index 0000000..65d95ad --- /dev/null +++ b/src/main/java/moe/yo3explorer/octonet_exporter/entities/FanspeedResponse.java @@ -0,0 +1,9 @@ +package moe.yo3explorer.octonet_exporter.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FanspeedResponse +{ + @JsonProperty("speed") + public int speed; +} diff --git a/src/main/java/moe/yo3explorer/octonet_exporter/entities/Tuner.java b/src/main/java/moe/yo3explorer/octonet_exporter/entities/Tuner.java new file mode 100644 index 0000000..ac6e728 --- /dev/null +++ b/src/main/java/moe/yo3explorer/octonet_exporter/entities/Tuner.java @@ -0,0 +1,35 @@ +package moe.yo3explorer.octonet_exporter.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Tuner { + @JsonProperty("Input") + private String input; + + @JsonProperty("Status") + private String status; + + @JsonProperty("Lock") + private boolean lock; + + @JsonProperty("Strength") + private long strength; + + @JsonProperty("SNR") + private int snr; + + @JsonProperty("Quality") + private int quality; + + @JsonProperty("Level") + private int level; + + // Getters and Setters + public String getInput() { return input; } + public String getStatus() { return status; } + public boolean isLock() { return lock; } + public long getStrength() { return strength; } + public int getSnr() { return snr; } + public int getQuality() { return quality; } + public int getLevel() { return level; } +} \ No newline at end of file diff --git a/src/main/java/moe/yo3explorer/octonet_exporter/entities/TunerResponse.java b/src/main/java/moe/yo3explorer/octonet_exporter/entities/TunerResponse.java new file mode 100644 index 0000000..480f996 --- /dev/null +++ b/src/main/java/moe/yo3explorer/octonet_exporter/entities/TunerResponse.java @@ -0,0 +1,17 @@ +package moe.yo3explorer.octonet_exporter.entities; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +public class TunerResponse { + @JsonProperty("TunerList") + private List tunerList; + + public List getTunerList() { + return tunerList; + } + + public void setTunerList(List tunerList) { + this.tunerList = tunerList; + } +} \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..5244791 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file