Initial commit.

This commit is contained in:
fey 2026-09-15 23:47:12 +02:00
commit 61274350d5
9 changed files with 338 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -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

9
Dockerfile Normal file
View File

@ -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

13
README.md Normal file
View File

@ -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=<your_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`.

75
pom.xml Normal file
View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>moe.yo3explorer</groupId>
<artifactId>octonet-exporter</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.25.4</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>5.0.0-alpha.12</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-core</artifactId>
<version>1.3.6</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>prometheus-metrics-exporter-httpserver</artifactId>
<version>1.3.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>moe.yo3explorer.octonet_exporter.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<mainClass>moe.yo3explorer.octonet_exporter.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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<String, String> 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.");
}
}

View File

@ -0,0 +1,9 @@
package moe.yo3explorer.octonet_exporter.entities;
import com.fasterxml.jackson.annotation.JsonProperty;
public class FanspeedResponse
{
@JsonProperty("speed")
public int speed;
}

View File

@ -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; }
}

View File

@ -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<Tuner> tunerList;
public List<Tuner> getTunerList() {
return tunerList;
}
public void setTunerList(List<Tuner> tunerList) {
this.tunerList = tunerList;
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-config-2.xsd">
<Appenders>
<Console name="CONSOLE">
<PatternLayout pattern="%p - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="CONSOLE" level="DEBUG"/>
</Root>
</Loggers>
</Configuration>