initial commit
This commit is contained in:
commit
bc0cef6fe3
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/bin/
|
||||||
|
/.settings/
|
||||||
|
/.classpath
|
||||||
|
/.project
|
4
README.md
Normal file
4
README.md
Normal file
@ -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.
|
114
gpio-server.py
Normal file
114
gpio-server.py
Normal file
@ -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()
|
8
plugin.yml
Normal file
8
plugin.yml
Normal file
@ -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: /<command> start|stop|status
|
90
src/ee/lunasqu/redstoneoutput/Connection.java
Normal file
90
src/ee/lunasqu/redstoneoutput/Connection.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
src/ee/lunasqu/redstoneoutput/EventListener.java
Normal file
70
src/ee/lunasqu/redstoneoutput/EventListener.java
Normal file
@ -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<Material> 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!");
|
||||||
|
}
|
||||||
|
}
|
40
src/ee/lunasqu/redstoneoutput/Main.java
Normal file
40
src/ee/lunasqu/redstoneoutput/Main.java
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
37
src/ee/lunasqu/redstoneoutput/RemoteCommand.java
Normal file
37
src/ee/lunasqu/redstoneoutput/RemoteCommand.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
21
src/ee/lunasqu/redstoneoutput/events/BaseEvent.java
Normal file
21
src/ee/lunasqu/redstoneoutput/events/BaseEvent.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package ee.lunasqu.redstoneoutput.events;
|
||||||
|
|
||||||
|
public class ConnectionEstablishedEvent extends BaseEvent {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package ee.lunasqu.redstoneoutput.events;
|
||||||
|
|
||||||
|
public class DisconnectedEvent extends BaseEvent {
|
||||||
|
|
||||||
|
}
|
15
src/ee/lunasqu/redstoneoutput/events/InputEvent.java
Normal file
15
src/ee/lunasqu/redstoneoutput/events/InputEvent.java
Normal file
@ -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; }
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user