commit bc0cef6fe30444b3de40156847f757dcbb7dd9ce Author: Evert Prants Date: Fri Sep 18 17:19:44 2020 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e540a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/bin/ +/.settings/ +/.classpath +/.project diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f22711 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# RedstoneOutput +Custom Spigot plugin for interacting with Raspberry Pi GPIO outputs. + +This plugin uses a client-server model for interacting with GPIO because the pi is too slow for Spigot. Will try native when I get my RasPi 4 8GB. `gpio-server.py` is to be run on the pi. diff --git a/gpio-server.py b/gpio-server.py new file mode 100644 index 0000000..cdd0a52 --- /dev/null +++ b/gpio-server.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +# Pins controllable by client +# Types: +# 1: switch on or off depending on power +# 2: toggle on or off with single power high event +# 3: addressable (TODO) +# 4: on/off event to client (TODO) +# 5: analog event to client (TODO) + +import socket, threading +import RPi.GPIO as GPIO +import configparser +import os, io + +# Initialize GPIO +# Pinout help: https://pinout.xyz/ +GPIO.setmode(GPIO.BOARD) +GPIO.setwarnings(False) + +configfile = "server.ini" +config = configparser.ConfigParser() + +# Check if there is already a configurtion file +if not os.path.isfile(configfile): + # Create the configuration file as it doesn't exist yet + cfgfile = open(configfile, "w") + + # Add content to the file + config.add_section("server") + config.set("server", "host", "0.0.0.0") + config.set("server", "port", "62002") + config.add_section("output") + config.set("output", "type", "1") + config.set("output", "pin", "8") + config.write(cfgfile) + cfgfile.close() +else: + # Load config from file + config = configparser.ConfigParser() + config.read(configfile) + +# Set up all pins +for section in config.sections(): + if section == "server": + continue + type = int(config.get(section, "type")) + pin = int(config.get(section, "pin")) + if type == 1: + GPIO.setup(pin, GPIO.OUT, initial=GPIO.LOW) + +# Client connection thread +class ClientThread(threading.Thread): + def __init__(self, ip, port, clientsocket): + threading.Thread.__init__(self) + self.ip = ip + self.port = port + self.csocket = clientsocket + print("[+] New thread started for %s:%s" % (ip, str(port))) + + def handler(self, power, key, val): + if not config.has_section(key): + return + + type = int(config.get(key, "type")) + pin = int(config.get(key, "pin")) + + if type == 1: + if power > 0: + GPIO.output(pin, GPIO.HIGH) + else: + GPIO.output(pin, GPIO.LOW) + + def srvcmd(self,line): + split = line[:-2].split(':') + if split[0] and split[1]: + self.handler(int(split[0]), split[1], split[2]) + + def run(self): + self.csocket.send(b'connected\r\n') + + # Receive messages from client + while True: + data = self.csocket.recv(1024) + if not data: + break + data = data.decode('utf-8') + + # Handle command from client + self.srvcmd(data) + + self.csocket.close() + print("Client at %s disconnected..." % (self.ip)) + +def run_sockets(): + HOST = config.get("server", "host") + PORT = int(config.get("server", "port")) + + # Start server socket + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((HOST,PORT)) + print("Bound to %s:%s" % (HOST,PORT)) + + while True: + s.listen(4) + print("Listening for incoming connections...") + (clientsock, (ip, port)) = s.accept() + + # New thread for new client + newthread = ClientThread(ip, port, clientsock) + newthread.start() + +run_sockets() diff --git a/plugin.yml b/plugin.yml new file mode 100644 index 0000000..90f4384 --- /dev/null +++ b/plugin.yml @@ -0,0 +1,8 @@ +name: RedstoneOutput +main: ee.lunasqu.redstoneoutput.Main +version: 1.0 +api-version: 1.13 +commands: + rspi: + description: Control the remote connection for RedstoneOutput + usage: / start|stop|status diff --git a/src/ee/lunasqu/redstoneoutput/Connection.java b/src/ee/lunasqu/redstoneoutput/Connection.java new file mode 100644 index 0000000..d0ac096 --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/Connection.java @@ -0,0 +1,90 @@ +package ee.lunasqu.redstoneoutput; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.Socket; + +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitRunnable; + +import ee.lunasqu.redstoneoutput.events.ConnectionEstablishedEvent; +import ee.lunasqu.redstoneoutput.events.DisconnectedEvent; + +public class Connection extends BukkitRunnable { + private Socket socket; + private BufferedWriter writer; + private BufferedReader reader; + + private boolean alive; + + public Connection(String addr, int port) { + try { + socket = new Socket(addr, port); + writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + } catch (IOException e) { + e.printStackTrace(); + Bukkit.getPluginManager().callEvent(new DisconnectedEvent()); + return; + } + // Successful connection + Bukkit.getPluginManager().callEvent(new ConnectionEstablishedEvent()); + alive = true; + } + + public boolean sendStateChange (int power, String key, String value) { + if (!alive) return false; + try { + writer.write(power + ":" + key + ":" + value + "\r\n"); + writer.flush(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public void die () { + this.alive = false; + if (!socket.isClosed()) { + try { + socket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public boolean isAlive() { + return this.alive; + } + + @Override + public void run() { + if (!this.alive) { + if (!this.isCancelled()) this.cancel(); + return; + } + + try { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + + if (!alive) break; + } + alive = false; + if (!socket.isClosed()) { + socket.close(); + } + } catch (Exception e) { + e.printStackTrace(); + } + Bukkit.getPluginManager().callEvent(new DisconnectedEvent()); + this.cancel(); + } + +} diff --git a/src/ee/lunasqu/redstoneoutput/EventListener.java b/src/ee/lunasqu/redstoneoutput/EventListener.java new file mode 100644 index 0000000..1fa21c4 --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/EventListener.java @@ -0,0 +1,70 @@ +package ee.lunasqu.redstoneoutput; + +import java.util.Arrays; +import java.util.List; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Sign; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockRedstoneEvent; + +import ee.lunasqu.redstoneoutput.events.ConnectionEstablishedEvent; +import ee.lunasqu.redstoneoutput.events.DisconnectedEvent; + +public class EventListener implements Listener { + private static Material[] SIGNS = { + Material.ACACIA_SIGN, Material.ACACIA_WALL_SIGN, + Material.BIRCH_SIGN, Material.BIRCH_WALL_SIGN, + Material.CRIMSON_SIGN, Material.CRIMSON_WALL_SIGN, + Material.DARK_OAK_SIGN, Material.DARK_OAK_WALL_SIGN, + Material.JUNGLE_SIGN, Material.JUNGLE_WALL_SIGN, + Material.OAK_SIGN, Material.OAK_WALL_SIGN, + Material.SPRUCE_SIGN, Material.SPRUCE_WALL_SIGN + }; + + @EventHandler + public void onRedstone(BlockRedstoneEvent e) { + Block b = e.getBlock(); + Location[] sides = { + b.getLocation().add(new Location(b.getWorld(), 0, 1.0, 0)), + b.getLocation().add(new Location(b.getWorld(), -1.0, 0, 0)), + b.getLocation().add(new Location(b.getWorld(), 1.0, 0, 0)), + b.getLocation().add(new Location(b.getWorld(), 0, 0, 1.0)), + b.getLocation().add(new Location(b.getWorld(), 0, 0, -1.0)) + }; + + Sign sign = null; + List checklist = Arrays.asList(EventListener.SIGNS); + for (Location p : sides) { + Block test = b.getWorld().getBlockAt(p); + if (checklist.contains(test.getType())) { + Sign i = (Sign) test.getState(); + String l = i.getLine(0); + if (l.equalsIgnoreCase("[Output]")) { + sign = i; + break; + } + } + } + + if (sign == null) return; + String key = sign.getLine(1); + String value = sign.getLine(2); + Connection t = Main.plugin.getConnection(); + if (t == null) return; + t.sendStateChange(e.getNewCurrent(), key, value); + } + + @EventHandler + public void onConnClose(DisconnectedEvent e) { + Main.plugin.getLogger().info("Connection to remote failed."); + } + + @EventHandler + public void onConnEstab(ConnectionEstablishedEvent e) { + Main.plugin.getLogger().info("Connection to remote established!"); + } +} diff --git a/src/ee/lunasqu/redstoneoutput/Main.java b/src/ee/lunasqu/redstoneoutput/Main.java new file mode 100644 index 0000000..eb49126 --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/Main.java @@ -0,0 +1,40 @@ +package ee.lunasqu.redstoneoutput; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +public class Main extends JavaPlugin { + private FileConfiguration config; + + public static Main plugin; + public Connection service; + + public Connection getConnection() { + if (service == null || !service.isAlive()) return null; + return service; + } + + @Override + public void onEnable() { + config = this.getConfig(); + config.addDefault("host", "127.0.0.1"); + config.addDefault("port", 62002); + config.options().copyDefaults(true); + this.saveConfig(); + + plugin = this; + getServer().getPluginManager().registerEvents(new EventListener(), this); + this.getCommand("rspi").setExecutor(new RemoteCommand()); + this.connect(); + } + + @Override + public void onDisable() { + if (service != null && service.isAlive()) service.die(); + } + + public void connect () { + service = new Connection(this.config.getString("host"), this.config.getInt("port")); + service.runTaskAsynchronously(this); + } +} diff --git a/src/ee/lunasqu/redstoneoutput/RemoteCommand.java b/src/ee/lunasqu/redstoneoutput/RemoteCommand.java new file mode 100644 index 0000000..fc17df5 --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/RemoteCommand.java @@ -0,0 +1,37 @@ +package ee.lunasqu.redstoneoutput; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class RemoteCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command arg1, String arg2, String[] arguments) { + switch (arguments[0]) { + case "status": + if (Main.plugin.getConnection() != null) { + sender.sendMessage("The connection is established."); + } else { + sender.sendMessage("The connection is closed."); + } + return true; + case "start": + if (Main.plugin.getConnection() != null) { + sender.sendMessage("The connection is already established!"); + return false; + } + sender.sendMessage("Connection has been attempted!"); + Main.plugin.connect(); + return true; + case "stop": + if (Main.plugin.getConnection() == null) { + sender.sendMessage("The connection is already closed!"); + return false; + } + sender.sendMessage("Connection has been closed!"); + Main.plugin.getConnection().die(); + return true; + } + return false; + } +} diff --git a/src/ee/lunasqu/redstoneoutput/events/BaseEvent.java b/src/ee/lunasqu/redstoneoutput/events/BaseEvent.java new file mode 100644 index 0000000..91888a8 --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/events/BaseEvent.java @@ -0,0 +1,21 @@ +package ee.lunasqu.redstoneoutput.events; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class BaseEvent extends Event { + private static final HandlerList HANDLERS = new HandlerList(); + + public BaseEvent () { + super(!Bukkit.getServer().isPrimaryThread()); + } + + public HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} \ No newline at end of file diff --git a/src/ee/lunasqu/redstoneoutput/events/ConnectionEstablishedEvent.java b/src/ee/lunasqu/redstoneoutput/events/ConnectionEstablishedEvent.java new file mode 100644 index 0000000..617537d --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/events/ConnectionEstablishedEvent.java @@ -0,0 +1,5 @@ +package ee.lunasqu.redstoneoutput.events; + +public class ConnectionEstablishedEvent extends BaseEvent { + +} diff --git a/src/ee/lunasqu/redstoneoutput/events/DisconnectedEvent.java b/src/ee/lunasqu/redstoneoutput/events/DisconnectedEvent.java new file mode 100644 index 0000000..87988a2 --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/events/DisconnectedEvent.java @@ -0,0 +1,5 @@ +package ee.lunasqu.redstoneoutput.events; + +public class DisconnectedEvent extends BaseEvent { + +} diff --git a/src/ee/lunasqu/redstoneoutput/events/InputEvent.java b/src/ee/lunasqu/redstoneoutput/events/InputEvent.java new file mode 100644 index 0000000..be8d17f --- /dev/null +++ b/src/ee/lunasqu/redstoneoutput/events/InputEvent.java @@ -0,0 +1,15 @@ +package ee.lunasqu.redstoneoutput.events; + +public class InputEvent extends BaseEvent { + private String key; + private String value; + + public InputEvent (String key, String value) { + super(); + this.key = key; + this.value = value; + } + + public String getKey () { return this.key; } + public String getValue () { return this.value; } +}