diff --git a/config.json.example b/config.json.example index 8239021..f3df6a4 100644 --- a/config.json.example +++ b/config.json.example @@ -13,12 +13,39 @@ "password": "" }, + "piir": { + "exec": "/home/iptv/.local/bin/piir", + "gpio": 17, + "binds": [ + { + "data": "45 03", + "menuAction": "up" + }, + { + "data": "45 5E", + "menuAction": "down" + }, + { + "data": "45 02", + "menuAction": "left" + }, + { + "data": "45 40", + "menuAction": "right" + }, + { + "data": "45 5F", + "menuAction": "open" + } + ] + }, + "sources": [ { "type": "m3u", "url": "https://example.com/list.m3u", "cookies": null, - "rootCategory": null + "rootCategory": test }, { "type": "m3u-local", @@ -30,7 +57,7 @@ "tasks": [ { "name": "crawler", - "interval": 60 + "interval": 86400 }, { "name": "processService", diff --git a/src/main/java/com/mykola2312/mptv/Main.java b/src/main/java/com/mykola2312/mptv/Main.java index 8a0d505..e3b51e9 100644 --- a/src/main/java/com/mykola2312/mptv/Main.java +++ b/src/main/java/com/mykola2312/mptv/Main.java @@ -2,10 +2,10 @@ package com.mykola2312.mptv; import com.mykola2312.mptv.crawler.Crawler; import com.mykola2312.mptv.db.DB; +import com.mykola2312.mptv.piir.PiIR; import com.mykola2312.mptv.task.ProcessService; import com.mykola2312.mptv.task.TaskDispatcher; import com.mykola2312.mptv.ui.MainFrame; -import com.mykola2312.mptv.ui.MenuAction; import org.apache.commons.cli.*; import org.flywaydb.core.Flyway; @@ -18,6 +18,7 @@ public class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); public static ProcessService processService = new ProcessService(); + public static MainFrame frame; public static void main(String[] args) { // parse command line @@ -82,27 +83,28 @@ public class Main { // task dispatcher TaskDispatcher dispatcher = new TaskDispatcher(); dispatcher.updateTaskConfig(config.tasks); - //dispatcher.registerTask(crawler); // TODO: enable + dispatcher.registerTask(crawler); dispatcher.registerTask(processService); new Thread(dispatcher).start(); // initialize ui - MainFrame frame = new MainFrame(); + frame = new MainFrame(); frame.create(config.frame); - logger.info("mptv started"); + // start PiIR + PiIR piir = new PiIR(config.piir); + try { + piir.spawn(); - // TODO: remove this mock thread test - new Thread(new Runnable() { - @Override() - public void run() { - try { Thread.sleep(5000L); } catch (InterruptedException e) {} - - frame.action(MenuAction.ACTION_RIGHT); - } - }).start(); + processService.registerProcess(piir); + } catch (IOException e) { + logger.error("failed to spawn piir. fatal. exiting", e); + System.exit(1); + } + + logger.info("mptv started"); frame.loop(); } diff --git a/src/main/java/com/mykola2312/mptv/mpv/MPV.java b/src/main/java/com/mykola2312/mptv/mpv/MPV.java index 6829bb8..ee5172e 100644 --- a/src/main/java/com/mykola2312/mptv/mpv/MPV.java +++ b/src/main/java/com/mykola2312/mptv/mpv/MPV.java @@ -120,7 +120,7 @@ public class MPV implements TaskProcess { @Override public boolean spawn() throws IOException { process = Runtime.getRuntime().exec(new String[] { - "mpv", url, "--input-ipc-server=" + MPV_SOCKET_PATH + "mpv", "--vo=gpu", "--ao=pulse", "--fullscreen", "--input-ipc-server=" + MPV_SOCKET_PATH, url }); waitForConnection(MPV_SOCKET_PATH); @@ -130,7 +130,7 @@ public class MPV implements TaskProcess { @Override public boolean isAlive() { - return process.isAlive(); + return process != null ? process.isAlive() : false; } @Override diff --git a/src/main/java/com/mykola2312/mptv/piir/PiIR.java b/src/main/java/com/mykola2312/mptv/piir/PiIR.java index 3280542..ef337eb 100644 --- a/src/main/java/com/mykola2312/mptv/piir/PiIR.java +++ b/src/main/java/com/mykola2312/mptv/piir/PiIR.java @@ -1,24 +1,132 @@ -// package com.mykola2312.mptv.piir; +package com.mykola2312.mptv.piir; -// import java.io.IOException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; -// import com.mykola2312.mptv.task.TaskProcess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -// public class PiIR implements TaskProcess { -// public PiIR(PiIRConfig config) { +import com.fasterxml.jackson.core.JsonProcessingException; +import com.mykola2312.mptv.Main; +import com.mykola2312.mptv.task.TaskProcess; +import com.mykola2312.mptv.ui.MenuAction; -// } +public class PiIR implements TaskProcess { + private static final Logger logger = LoggerFactory.getLogger(PiIR.class); -// @Override -// public boolean spawn() throws IOException { -// } + private final String exec; + private final int gpio; + private final HashMap binds = new HashMap<>(); -// @Override -// public boolean isAlive() { -// } + private Process process = null; + private InputStream input = null; -// @Override -// public void stop() { -// } + private class PiIRReader implements Runnable { + private final PiIR piir; + private final InputStream input; + public boolean running = true; + + public PiIRReader(PiIR piir, InputStream input) { + this.piir = piir; + this.input = input; + } + + private static final int BUFFER_SIZE = 512; + + @Override + public void run() { + byte[] buf = new byte[BUFFER_SIZE]; + try { + while (running && !Thread.currentThread().isInterrupted()) { + // reader loop + int len = input.read(buf, 0, buf.length); + if (len < 0) { + logger.warn("reading error. exiting"); + running = false; + return; + } + + String line = new String( + Arrays.copyOfRange(buf, 0, len), + StandardCharsets.UTF_8); + try { + PiIRDump dump = PiIRDump.deserialize(line); + + piir.handleDump(dump); + } catch (JsonProcessingException e) { + logger.warn("failed to deserialize dump!", e); + } + } + } catch (IOException e) { + logger.error("failed to read. exiting piir reader", e); + } + } + } + + private PiIRReader reader = null; + private Thread readerThread = null; + + private static String formatBindKey(String preData, String data) { + return preData != null ? preData + " " + data : data; + } + + public PiIR(PiIRConfig config) { + this.exec = config.exec; + this.gpio = config.gpio; + + for (var bind : config.binds) { + binds.put(formatBindKey(bind.preData, bind.data), bind.menuAction); + } + } + + @Override + public boolean spawn() throws IOException { + process = Runtime.getRuntime().exec(new String[] { + exec, "dump", "--gpio", String.valueOf(gpio) + }); + input = process.getInputStream(); + + reader = new PiIRReader(this, input); + readerThread = new Thread(reader); + readerThread.start(); + + return isAlive(); + } + + @Override + public boolean isAlive() { + return process != null ? process.isAlive() : false; + } + + @Override + public void stop() { + if (reader != null) reader.running = false; + if (readerThread != null) readerThread.interrupt(); + if (input != null) { + try { + input.close(); + } catch (IOException e) { + logger.warn("failed to close input", e); + } + } + if (process != null) process.destroyForcibly(); + reader = null; + readerThread = null; + process = null; + } -// } + public void handleDump(PiIRDump dump) { + String key = formatBindKey(dump.pre_data, dump.data); + MenuAction action = binds.get(key); + if (action != null) { + Main.frame.action(action); + } else { + logger.warn(String.format( + "unknown piir dump pre_data \"%s\" data \"%s\"", + dump.pre_data, dump.data)); + } + } +} diff --git a/src/main/java/com/mykola2312/mptv/piir/PiIRDump.java b/src/main/java/com/mykola2312/mptv/piir/PiIRDump.java new file mode 100644 index 0000000..cefc14d --- /dev/null +++ b/src/main/java/com/mykola2312/mptv/piir/PiIRDump.java @@ -0,0 +1,15 @@ +package com.mykola2312.mptv.piir; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class PiIRDump { + public String pre_data; + public String data; + + public static PiIRDump deserialize(String data) throws JsonProcessingException { + return new ObjectMapper() + .readerFor(PiIRDump.class) + .readValue(data); + } +}