All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.pigpioj.PigpioSocket Maven / Gradle / Ivy

package uk.pigpioj;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.ReplayingDecoder;

public class PigpioSocket implements PigpioInterface {
	static final Logger LOGGER = Logger.getLogger(PigpioSocket.class.getName());
	private static final int DEFAULT_TIMEOUT_MS = 30_000;
	private static final int NOTIFICATION_HANDLE_NOT_SET = -1;

	static final int DEFAULT_PORT = 8888;

	// Notification flags
	private static final int PI_NTFY_FLAGS_WDOG = 1 << 5;
	private static final int PI_NTFY_FLAGS_ALIVE = 1 << 6;
	private static final int PI_NTFY_FLAGS_EVENT = 1 << 7;

	// Commands
	/**
	 * GPIO mode set
* Request: gpio mode 0
* Response: - - 0 - */ private static final int PI_CMD_MODES = 0; /** * GPIO mode get
* Request: gpio 0 0 -
* Response: - - mode - */ private static final int PI_CMD_MODEG = 1; /** * GPIO set pull up/down
* Request: gpio pud 0 -
* Response: - - 0 - */ private static final int PI_CMD_PUD = 2; /** * GPIO read
* Request: gpio 0 0 =
* Response: - - level - */ private static final int PI_CMD_READ = 3; /** * GPIO write
* Response: gpio level 0 -
* Response: - - 0 - */ private static final int PI_CMD_WRITE = 4; /** * PWM set duty cycle
* Request: gpio dutycycle 0 -
* Response: - - 0 - */ private static final int PI_CMD_PWM = 5; /** * PWM set range
* Request: gpio range 0 -
* Response: - - 0 - */ private static final int PI_CMD_PRS = 6; /** * PWM set frequency
* Request: gpio frequency 0 -
* Response: - - 0 - */ private static final int PI_CMD_PFS = 7; /** * Servo set pulse width
* Request: gpio pulsewidth 0 -
* Response: - - 0 - */ private static final int PI_CMD_SERVO = 8; /** * Set GPIO watchdog
* Request: gpio timeout 0 -
* Response: - - 0 - */ private static final int PI_CMD_WDOG = 9; /** * Read GPIO bank 1, i.e. GPIOs 0-31
* Request: 0 0 0 -
* Response: - - bits - */ private static final int PI_CMD_BR1 = 10; /** * Read GPIO bank 2, i.e. GPIOs 32-63
* Request: 0 0 0 -
* Response: - - bits - */ private static final int PI_CMD_BR2 = 11; private static final int PI_CMD_BC1 = 12; // bits 0 0 - (Clear GPIO bank 1) private static final int PI_CMD_BC2 = 13; // bits 0 0 - (Clear GPIO bank 2) private static final int PI_CMD_BS1 = 14; // bits 0 0 - (Set GPIO bank 1) private static final int PI_CMD_BS2 = 15; // bits 0 0 - (Set GPIO bank 2) private static final int PI_CMD_TICK = 16; // 0 0 0 - (?) private static final int PI_CMD_HWVER = 17; // 0 0 0 - (Get Pi Hardware Revision) private static final int PI_CMD_NO = 18; // 0 0 0 - (Notify Open) private static final int PI_CMD_NB = 19; // handle bits 0 - (Notify Begin) private static final int PI_CMD_NP = 20; // handle 0 0 - (Notify Pause) private static final int PI_CMD_NC = 21; // handle 0 0 - (Notify Close) private static final int PI_CMD_PRG = 22; // gpio 0 0 - (PWM get range) private static final int PI_CMD_PFG = 23; // gpio 0 0 - (PWM get frequency) private static final int PI_CMD_PRRG = 24; // gpio 0 0 - (PWM get real range) private static final int PI_CMD_HELP = 25; // N/A N/A N/A N/A private static final int PI_CMD_PIGPV = 26; // 0 0 0 - (Get pigpio version) private static final int PI_CMD_WVCLR = 27; // 0 0 0 - private static final int PI_CMD_WVAG = 28; // 0 0 12*X gpioPulse_t pulse[X] private static final int PI_CMD_WVAS = 29; // gpio baud 12+X uint32_t databits uint32_t stophalfbits uint32_t offset // uint8_t data[X] private static final int PI_CMD_WVGO = 30; // 0 0 0 - private static final int PI_CMD_WVGOR = 31; // 0 0 0 - private static final int PI_CMD_WVBSY = 32; // 0 0 0 - private static final int PI_CMD_WVHLT = 33; // 0 0 0 - private static final int PI_CMD_WVSM = 34; // subcmd 0 0 - private static final int PI_CMD_WVSP = 35; // subcmd 0 0 - private static final int PI_CMD_WVSC = 36; // subcmd 0 0 - private static final int PI_CMD_TRIG = 37; // gpio pulselen 4 uint32_t level private static final int PI_CMD_PROC = 38; // 0 0 X uint8_t text[X] private static final int PI_CMD_PROCD = 39; // script_id 0 0 - private static final int PI_CMD_PROCR = 40; // script_id 0 4*X uint32_t pars[X] private static final int PI_CMD_PROCS = 41; // script_id 0 0 - private static final int PI_CMD_SLRO = 42; // gpio baud 4 uint32_t databits private static final int PI_CMD_SLR = 43; // gpio count 0 - private static final int PI_CMD_SLRC = 44; // gpio 0 0 - /** * Get script status
* Request: script_id 0 0 -
* Response: - - X+4 uint32_t status; uint8_t data[X] */ private static final int PI_CMD_PROCP = 45; // private static final int PI_CMD_MICS = 46; // micros 0 0 - private static final int PI_CMD_MILS = 47; // millis 0 0 - private static final int PI_CMD_PARSE = 48; // N/A N/A N/A N/A private static final int PI_CMD_WVCRE = 49; // 0 0 0 private static final int PI_CMD_WVDEL = 50; // wave_id 0 0 private static final int PI_CMD_WVTX = 51; // wave_id 0 0 private static final int PI_CMD_WVTXR = 52; // wave_id 0 0 private static final int PI_CMD_WVNEW = 53; // 0 0 0 - // I2C Commands /** * I2C open
* Request: bus device 4 uint32_t flags-
* Response: - - handle - */ private static final int PI_CMD_I2CO = 54; /** * I2C close
* Request: handle 0 0 -
* Response: - - 0 - */ private static final int PI_CMD_I2CC = 55; /** * I2C read device
* Request: handle count 0 -
* Response: - - X uint8_t data[X] */ private static final int PI_CMD_I2CRD = 56; /** * I2C write device
* Request: handle 0 X uint8_t data[X]
* Response: - - 0 - */ private static final int PI_CMD_I2CWD = 57; /** * I2C write quick
* Request: handle bit 0 -
* Response: - - 0 - */ private static final int PI_CMD_I2CWQ = 58; /** * I2C read byte
* Request: handle 0 0 -
* Response: - - byte value - */ private static final int PI_CMD_I2CRS = 59; /** * I2C write byte
* Request: handle byte 0 -
* Response: - - 0 - */ private static final int PI_CMD_I2CWS = 60; /** * I2C read byte data
* Request: handle register 0 -
* Response: - - byte value - */ private static final int PI_CMD_I2CRB = 61; /** * I2C write byte data
* Request: handle register 4 uint32_t byte
* Response: - - 0 - */ private static final int PI_CMD_I2CWB = 62; /** * I2C read word data
* Request: handle register 0 -
* Response: - - word value - */ private static final int PI_CMD_I2CRW = 63; /** * I2C write word data
* Request: ?handle register 4 uint32_t word
* Response: - - 0 - */ private static final int PI_CMD_I2CWW = 64; /** * I2C read block data
* Request: handle register 0 -
* Response: - - X uint8_t data[X] */ private static final int PI_CMD_I2CRK = 65; /** * I2C write block data
* Request: handle register X uint8_t bvs[X]
* Response: - - 0 - */ private static final int PI_CMD_I2CWK = 66; /** * I2C read I2C block data
* Response: handle register 4 uint32_t num
* Response: - - X uint8_t data[X] */ private static final int PI_CMD_I2CRI = 67; /** * I2C write I2C block data
* Request: handle register X uint8_t bvs[X]
* Response: - - 0 - */ private static final int PI_CMD_I2CWI = 68; /** * I2C process call
* Request: handle register 4 uint32_t word
* Response: - - word value - */ private static final int PI_CMD_I2CPC = 69; /** * I2C block process call
* Request: handle register X uint8_t data[X]
* Response: - - X uint8_t data[X] */ private static final int PI_CMD_I2CPK = 70; // SPI Commands /** * SPI open
* Request: channel baud 4 uint32_t flags
* Response: - - handle - */ private static final int PI_CMD_SPIO = 71; /** * SPI clode
* Request: handle 0 0 -
* Response: - - 0 - */ private static final int PI_CMD_SPIC = 72; /** * SPI read
* Request: handle count 0 -
* Response: - - X uint8_t data[X] */ private static final int PI_CMD_SPIR = 73; /** * SPI write
* Request: handle 0 X uint8_t data[X]
* Response: - - 0 - */ private static final int PI_CMD_SPIW = 74; /** * SPI Transfer
* Request: handle 0 X uint8_t data[X]
* Response: - - X uint8_t data[X] */ private static final int PI_CMD_SPIX = 75; private static final int PI_CMD_SERO = 76; // baud flags X uint8_t device[X] private static final int PI_CMD_SERC = 77; // handle 0 0 - private static final int PI_CMD_SERRB = 78; // handle 0 0 - private static final int PI_CMD_SERWB = 79; // handle byte 0 - private static final int PI_CMD_SERR = 80; // handle count 0 - private static final int PI_CMD_SERW = 81; // handle 0 X uint8_t data[X] private static final int PI_CMD_SERDA = 82; // handle 0 0 - private static final int PI_CMD_GDC = 83; // gpio 0 0 - private static final int PI_CMD_GPW = 84; // gpio 0 0 - private static final int PI_CMD_HC = 85; // gpio frequency 0 - private static final int PI_CMD_HP = 86; // gpio frequency 4 uint32_t dutycycle private static final int PI_CMD_CF1 = 87; // arg1 arg2 X uint8_t argx[X] private static final int PI_CMD_CF2 = 88; // arg1 retMax X uint8_t argx[X] private static final int PI_CMD_BI2CC = 89; // sda 0 0 - private static final int PI_CMD_BI2CO = 90; // sda scl 4 uint32_t baud private static final int PI_CMD_BI2CZ = 91; // sda 0 X uint8_t data[X] private static final int PI_CMD_I2CZ = 92; // handle 0 X uint8_t data[X] private static final int PI_CMD_WVCHA = 93; // 0 0 X uint8_t data[X] private static final int PI_CMD_SLRI = 94; // gpio invert 0 - private static final int PI_CMD_CGI = 95; // 0 0 0 - private static final int PI_CMD_CSI = 96; // config 0 0 - private static final int PI_CMD_FG = 97; // gpio steady 0 - (Set glitch filter) private static final int PI_CMD_FN = 98; // gpio steady 4 uint32_t active (Set noise filter) private static final int PI_CMD_NOIB = 99; // 0 0 0 - (Notify Open In Band) private static final int PI_CMD_WVTXM = 100; // wave_id mode 0 - private static final int PI_CMD_WVTAT = 101; // - - 0 - private static final int PI_CMD_PADS = 102; // pad strength 0 - private static final int PI_CMD_PADG = 103; // pad 0 0 - private static final int PI_CMD_FO = 104; // mode 0 X uint8_t file[X] private static final int PI_CMD_FC = 105; // handle 0 0 - (File close) private static final int PI_CMD_FR = 106; // handle count 0 - private static final int PI_CMD_FW = 107; // handle 0 X uint8_t data[X] private static final int PI_CMD_FS = 108; // handle offset 4 uint32_t from private static final int PI_CMD_FL = 109; // count 0 X uint8_t pattern[X] private static final int PI_CMD_SHELL = 110; // len(name) 0 len(name)+1+len(string) uint8_t name[len(name)] uint8_t // null (0) uint8_t string[len(string)] private static final int PI_CMD_BSPIC = 111; // CS 0 0 - private static final int PI_CMD_BSPIO = 112; // CS 0 20 uint32_t MISO uint32_t MOSI uint32_t SCLK uint32_t baud // uint32_t spi_flags private static final int PI_CMD_BSPIX = 113; // CS 0 X uint8_t data[X] /** * I2C/SPI as slave transfer
* Request: control 0 X uint8_t data[X]
* Response: - - X+4 uint32_t status; uint8_t data[X] */ private static final int PI_CMD_BSCX = 114; private static final int PI_CMD_EVM = 115; // handle bits 0 - (Event Monitor) private static final int PI_CMD_EVT = 116; // event 0 0 - (Event Trigger) private static final int PI_CMD_PROCU = 117; private static final int PI_CMD_WVCAP = 118; /* * pigpiod_if2 Error Codes typedef enum { pigif_bad_send = -2000, pigif_bad_recv * = -2001, pigif_bad_getaddrinfo = -2002, pigif_bad_connect = -2003, * pigif_bad_socket = -2004, pigif_bad_noib = -2005, pigif_duplicate_callback = * -2006, pigif_bad_malloc = -2007, pigif_bad_callback = -2008, * pigif_notify_failed = -2009, pigif_callback_not_found = -2010, * pigif_unconnected_pi = -2011, pigif_too_many_pis = -2012, } pigifError_t; */ private Queue messageQueue; private Lock lock; private Condition condition; private EventLoopGroup workerGroup; private Channel messageChannel; private Channel notificationChannel; private ChannelFuture lastWriteFuture; private int timeoutMs; private int monitorMask; private int notificationHandle = NOTIFICATION_HANDLE_NOT_SET; private Map callbacks; private int lastGpioLevelMask; public PigpioSocket() { this(DEFAULT_TIMEOUT_MS); } public PigpioSocket(int timeoutMs) { this.timeoutMs = timeoutMs; messageQueue = new LinkedList<>(); lock = new ReentrantLock(); condition = lock.newCondition(); callbacks = new HashMap<>(); } public void connect(String host) throws InterruptedException { connect(host, DEFAULT_PORT); } public void connect(String host, int port) throws InterruptedException { workerGroup = new NioEventLoopGroup(); ResponseHandler rh = new ResponseHandler(this::messageReceived); Bootstrap b1 = new Bootstrap(); b1.group(workerGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ResponseDecoder(), new MessageEncoder(), rh); } }); Bootstrap b2 = new Bootstrap(); b2.group(workerGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new NotificationDecoder(), new MessageEncoder(), rh, new NotificationHandler(PigpioSocket.this::notificationReceived)); } }); // Connect messageChannel = b1.connect(host, port).sync().channel(); // Connect notificationChannel = b2.connect(host, port).sync().channel(); // Enable the notification channel notificationChannel.writeAndFlush(new Message(PI_CMD_NOIB, 0, 0)); LOGGER.fine("Connected to " + host + " using port " + port); } @Override public void close() { if (messageChannel == null || !messageChannel.isOpen()) { return; } messageChannel.close(); notificationChannel.close(); try { messageChannel.closeFuture().sync(); // Wait until all messages are flushed before closing the channel. if (lastWriteFuture != null) { lastWriteFuture.sync(); } } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Error: " + e, e); } finally { workerGroup.shutdownGracefully(); } } void messageReceived(ResponseMessage msg) { LOGGER.finer("messageReceived(" + msg + ")"); // A hack as the notification handle is sent via the notification // channel which has a different message structure if (msg.cmd == PI_CMD_NOIB) { notificationHandle = (int) msg.res; return; } lock.lock(); try { messageQueue.add(msg); condition.signalAll(); } finally { lock.unlock(); } } void notificationReceived(NotificationMessage msg) { if (msg.flags == 0) { int changed_level_mask = lastGpioLevelMask ^ msg.level; LOGGER.fine("changed_level_mask: " + changed_level_mask); LOGGER.finer(() -> "changed_level_mask: \n" // + decodeMask(changed_level_mask) + "\n" + "...|....;....|....;....|....;....|....;....|....;....|....;....|"); lastGpioLevelMask = msg.level; callbacks.entrySet().stream().filter(entry -> (1 << entry.getKey().intValue() & changed_level_mask) != 0) .forEach(entry -> entry.getValue().callback(entry.getKey().intValue(), (1 << entry.getKey().intValue() & msg.level) != 0, msg.epochTime, msg.nanoTime)); } else { if ((msg.flags & PI_NTFY_FLAGS_WDOG) != 0) { LOGGER.finer("WDOG notification message: " + msg); } if ((msg.flags & PI_NTFY_FLAGS_ALIVE) != 0) { LOGGER.finer("ALIVE notification message: " + msg); } if ((msg.flags & PI_NTFY_FLAGS_EVENT) != 0) { LOGGER.finer("EVENT notification message: " + msg); } } } private synchronized ResponseMessage sendMessage(Message message) { ResponseMessage rm = null; lock.lock(); try { lastWriteFuture = messageChannel.writeAndFlush(message); // FIXME Should really loop until we get the expected response message while (true) { if (condition.await(timeoutMs, TimeUnit.MILLISECONDS)) { rm = messageQueue.remove(); if (rm.cmd == message.cmd) { break; } // Shouldn't happen LOGGER.warning("Unexpected response: " + rm + ". Was expecting " + message.cmd); } else { String msg = "Timeout waiting for response to command " + message.cmd; LOGGER.severe(msg); throw new TimeoutException(msg); } } } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Interrupted: " + e, e); } finally { lock.unlock(); } return rm; } @Override public int enableListener(int gpio, int edge, PigpioCallback callback) { if (notificationHandle == NOTIFICATION_HANDLE_NOT_SET) { LOGGER.warning("Error, notification handle not set"); } int monitor_bit = 1 << gpio; if ((monitorMask & monitor_bit) != 0) { LOGGER.warning("GPIO " + gpio + " is already being monitored"); return PigpioConstants.SUCCESS; } if (sendMessage(new Message(PI_CMD_NB, notificationHandle, monitorMask | monitor_bit)) == null) { return PigpioConstants.ERROR; } monitorMask |= monitor_bit; callbacks.put(Integer.valueOf(gpio), callback); return PigpioConstants.SUCCESS; } @Override public int disableListener(int gpio) { if (notificationHandle == NOTIFICATION_HANDLE_NOT_SET) { LOGGER.warning("Error, notification handle not set"); } int monitor_bit = 1 << gpio; if ((monitorMask & monitor_bit) == 0) { LOGGER.warning("GPIO " + gpio + " isn't being monitored"); return PigpioConstants.SUCCESS; } if (sendMessage(new Message(PI_CMD_NB, notificationHandle, monitorMask & ~monitor_bit)) == null) { return PigpioConstants.ERROR; } monitorMask &= ~monitor_bit; callbacks.remove(Integer.valueOf(gpio)); return PigpioConstants.SUCCESS; } @Override public int getVersion() { ResponseMessage message = sendMessage(new Message(PI_CMD_PIGPV, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int getHardwareRevision() { ResponseMessage message = sendMessage(new Message(PI_CMD_HWVER, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int getMode(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_MODEG, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int setMode(int gpio, int mode) { if (sendMessage(new Message(PI_CMD_MODES, gpio, mode)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int read(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_READ, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int write(int gpio, boolean level) { if (sendMessage( new Message(PI_CMD_WRITE, gpio, level ? PigpioConstants.PI_ON : PigpioConstants.PI_OFF)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int getPWMDutyCycle(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_GDC, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int setPWMDutyCycle(int gpio, int dutyCycle) { if (sendMessage(new Message(PI_CMD_PWM, gpio, dutyCycle)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int getPWMRange(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_PRG, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int setPWMRange(int gpio, int range) { if (sendMessage(new Message(PI_CMD_PRS, gpio, range)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int getPWMRealRange(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_PRRG, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int getPWMFrequency(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_PFG, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int setPWMFrequency(int gpio, int frequency) { if (sendMessage(new Message(PI_CMD_PFS, gpio, frequency)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int setPullUpDown(int gpio, int pud) { if (sendMessage(new Message(PI_CMD_PUD, gpio, pud)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int getServoPulseWidth(int gpio) { ResponseMessage message = sendMessage(new Message(PI_CMD_GPW, gpio, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int setServoPulseWidth(int gpio, int pulseWidth) { if (sendMessage(new Message(PI_CMD_SERVO, gpio, pulseWidth)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int noiseFilter(int gpio, int steadyMs, int activeMs) { if (sendMessage(new Message(PI_CMD_FN, gpio, steadyMs, new UIntMessageExtension(activeMs))) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int glitchFilter(int gpio, int steadyMs) { if (sendMessage(new Message(PI_CMD_FG, gpio, steadyMs)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int hardwareClock(int gpio, int clockFreq) { if (sendMessage(new Message(PI_CMD_HC, gpio, clockFreq)) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } @Override public int hardwarePwm(int gpio, int pwmFreq, int pwmDuty) { if (sendMessage(new Message(PI_CMD_HP, gpio, pwmFreq, new UIntMessageExtension(pwmDuty))) == null) { return PigpioConstants.ERROR; } return PigpioConstants.SUCCESS; } // I2C @Override public int i2cOpen(int i2cBus, int i2cAddr, int i2cFlags) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CO, i2cBus, i2cAddr, new UIntMessageExtension(i2cFlags))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cClose(int handle) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CC, handle)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cWriteQuick(int handle, int bit) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CWQ, handle, bit)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cReadByte(int handle) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CRS, handle, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cWriteByte(int handle, int bVal) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CWS, handle, bVal)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cReadByteData(int handle, int i2cReg) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CRB, handle, i2cReg)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cWriteByteData(int handle, int i2cReg, int bVal) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CWB, handle, i2cReg, new UIntMessageExtension(bVal))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cReadWordData(int handle, int i2cReg) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CRW, handle, i2cReg)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cWriteWordData(int handle, int i2cReg, int wVal) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CWW, handle, i2cReg, new UIntMessageExtension(wVal))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cProcessCall(int handle, int i2cReg, int wVal) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CPC, handle, i2cReg, new UIntMessageExtension(wVal))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cReadBlockData(int handle, int i2cReg, byte[] buf) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CRK, handle, i2cReg)); if (message == null) { return PigpioConstants.ERROR; } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, buf, 0, bam.data.length); return (int) message.res; } @Override public int i2cWriteBlockData(int handle, int i2cReg, byte[] buf, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CWK, handle, i2cReg, new ByteArrayMessageExtension(count, buf))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cBlockProcessCall(int handle, int i2cReg, byte[] buf, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CPK, handle, i2cReg, new ByteArrayMessageExtension(count, buf))); if (message == null) { return PigpioConstants.ERROR; } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, buf, 0, bam.data.length); return (int) message.res; } @Override public int i2cReadI2CBlockData(int handle, int i2cReg, byte[] buf, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CRI, handle, i2cReg, new UIntMessageExtension(count))); if (message == null) { return PigpioConstants.ERROR; } if (!(message instanceof ByteArrayResponseMessage)) { LOGGER.severe("Expected ByteArrayResponseMessage, got " + message.getClass().getName() + ": " + message); throw new RuntimeException( "Expected ByteArrayResponseMessage, got " + message.getClass().getName() + ": " + message); } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, buf, 0, bam.data.length); return (int) message.res; } @Override public int i2cWriteI2CBlockData(int handle, int i2cReg, byte[] buf, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CWI, handle, i2cReg, new ByteArrayMessageExtension(count, buf))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int i2cReadDevice(int handle, byte[] buffer, int count) { ResponseMessage message = sendMessage(new Message(PI_CMD_I2CRD, handle, count)); if (message == null) { return PigpioConstants.ERROR; } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, buffer, 0, bam.data.length); return (int) message.res; } @Override public int i2cWriteDevice(int handle, byte[] buffer, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_I2CWD, handle, 0, new ByteArrayMessageExtension(count, buffer))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } // SPI @Override public int spiOpen(int spiChan, int baud, int spiFlags) { ResponseMessage message = sendMessage( new Message(PI_CMD_SPIO, spiChan, baud, new UIntMessageExtension(spiFlags))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int spiClose(int handle) { ResponseMessage message = sendMessage(new Message(PI_CMD_SPIC, handle)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int spiRead(int handle, byte[] buf, int count) { ResponseMessage message = sendMessage(new Message(PI_CMD_SPIR, handle, count)); if (message == null) { return PigpioConstants.ERROR; } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, buf, 0, bam.data.length); return (int) message.res; } @Override public int spiWrite(int handle, byte[] buffer, int offset, int length) { ResponseMessage message = sendMessage( new Message(PI_CMD_SPIW, handle, 0, new ByteArrayMessageExtension(offset, length, buffer))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int spiXfer(int handle, byte[] txBuf, byte[] rxBuf, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_SPIX, handle, 0, new ByteArrayMessageExtension(count, txBuf))); if (message == null) { return PigpioConstants.ERROR; } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, rxBuf, 0, bam.data.length); return (int) message.res; } @Override public int gpioWaveClear() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVCLR, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveAddNew() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVNEW, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveAddGeneric(GpioPulse[] pulses) { // TODO Warning untested! ResponseMessage message = sendMessage( new Message(PI_CMD_WVAG, 0, pulses.length, new GpioPulseArrayMessageExtension(pulses))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveAddSerial(int userGpio, int baud, int dataBits, int stopBits, int offset, byte[] str) { /* * // TODO Create a message extension to carry dataBits, stopBits, offset, str * length and str ResponseMessage message = sendMessage(new Message(PI_CMD_WVAG, * userGpio, baud, new ByteArrayMessageExtension())); if (message == null) { * return PigpioConstants.ERROR; } * * return (int) message.res; */ throw new UnsupportedOperationException(); } @Override public int gpioWaveCreate() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVCRE, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveCreatePad(int pctCB, int pctBOOL, int pctTOOL) { ResponseMessage message = sendMessage( new Message(PI_CMD_WVCAP, pctCB, pctBOOL, new UIntMessageExtension(pctTOOL))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveDelete(int waveId) { ResponseMessage message = sendMessage(new Message(PI_CMD_WVDEL, waveId, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveTxSend(int waveId, int waveMode) { ResponseMessage message = sendMessage(new Message(PI_CMD_WVTXM, waveId, waveMode)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveChain(byte[] buf) { ResponseMessage message = sendMessage(new Message(PI_CMD_WVCHA, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveTxAt() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVTAT, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveTxBusy() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVBSY, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveTxStop() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVHLT, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetMicros() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSM, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetHighMicros() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSM, 1, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetMaxMicros() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSM, 2, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetPulses() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSP, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetHighPulses() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSP, 1, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetMaxPulses() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSP, 2, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetCbs() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSC, 0, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetHighCbs() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSC, 1, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int gpioWaveGetMaxCbs() { ResponseMessage message = sendMessage(new Message(PI_CMD_WVSC, 2, 0)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int serOpen(String sertty, int baud, int serFlags) { ResponseMessage message = sendMessage( new Message(PI_CMD_SERO, baud, serFlags, new ByteArrayMessageExtension(sertty.getBytes()))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int serClose(int handle) { ResponseMessage message = sendMessage(new Message(PI_CMD_SERC, handle)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int serWriteByte(int handle, int bVal) { ResponseMessage message = sendMessage(new Message(PI_CMD_SERWB, handle, bVal)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int serReadByte(int handle) { ResponseMessage message = sendMessage(new Message(PI_CMD_SERRB, handle)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int serWrite(int handle, byte[] buf, int count) { ResponseMessage message = sendMessage( new Message(PI_CMD_SERW, handle, 0, new ByteArrayMessageExtension(count, buf))); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } @Override public int serRead(int handle, byte[] buf, int count) { ResponseMessage message = sendMessage(new Message(PI_CMD_SERR, handle, count)); if (message == null) { return PigpioConstants.ERROR; } ByteArrayResponseMessage bam = (ByteArrayResponseMessage) message; System.arraycopy(bam.data, 0, buf, 0, bam.data.length); return (int) message.res; } @Override public int serDataAvailable(int handle) { ResponseMessage message = sendMessage(new Message(PI_CMD_SERDA, handle)); if (message == null) { return PigpioConstants.ERROR; } return (int) message.res; } public static String decodeMask(int mask) { StringBuilder b = new StringBuilder(64); for (int i = 63; i >= 0; --i) { b.append(((1 << i) & mask) != 0 ? "1" : "0"); } return b.toString(); } /* * typedef struct { uint32_t cmd; uint32_t p1; uint32_t p2; union { uint32_t p3; * uint32_t ext_len; uint32_t res; }; } cmdCmd_t; */ static class Message { int cmd; long p1; long p2; long p3; MessageExtension extension; Message(int cmd, long p1) { this.cmd = cmd; this.p1 = p1; } Message(int cmd, long p1, long p2) { this.cmd = cmd; this.p1 = p1; this.p2 = p2; } Message(int cmd, long p1, long p2, MessageExtension extension) { this.cmd = cmd; this.p1 = p1; this.p2 = p2; this.p3 = extension.numBytes; this.extension = extension; } @Override public String toString() { return "Message [cmd=" + cmd + ", p1=" + p1 + ", p2=" + p2 + ", p3=" + p3 + ", extension=" + extension + "]"; } } static class ResponseMessage { int cmd; long p1; long p2; long res; ResponseMessage(int cmd, long p1, long p2, long res) { this.cmd = cmd; this.p1 = p1; this.p2 = p2; this.res = res; } @Override public String toString() { return "ResponseMessage [cmd=" + cmd + ", p1=" + p1 + ", p2=" + p2 + ", res=" + res + "]"; } } static class ByteArrayResponseMessage extends ResponseMessage { byte[] data; public ByteArrayResponseMessage(int cmd, long p1, long p2, long res, byte[] data) { super(cmd, p1, p2, res); this.data = data; } } static class ScriptStatusResponseMessage extends ResponseMessage { long status; long[] pars; public ScriptStatusResponseMessage(int cmd, long p1, long p2, long res, long status, long[] pars) { super(cmd, p1, p2, res); this.status = status; this.pars = pars; } } static class BscXferResponseMessage extends ResponseMessage { long status; byte[] data; public BscXferResponseMessage(int cmd, long p1, long p2, long res, long status, byte[] data) { super(cmd, p1, p2, res); this.status = status; this.data = data; } } static abstract class MessageExtension { int numBytes; MessageExtension(int numBytes) { this.numBytes = numBytes; } abstract void encode(ByteBuf out); } static class UByteMessageExtension extends MessageExtension { short val; public UByteMessageExtension(short val) { super(1); this.val = val; } @Override public void encode(ByteBuf out) { out.writeByte(val); } @Override public String toString() { return "UByteMessageExtension [numBytes=" + numBytes + ", val=" + val + "]"; } } static class UIntMessageExtension extends MessageExtension { long val; public UIntMessageExtension(long val) { super(4); this.val = val; } @Override public void encode(ByteBuf out) { out.writeIntLE((int) val); } @Override public String toString() { return "UIntMessageExtension [numBytes=" + numBytes + ", val=" + val + "]"; } } static class ByteArrayMessageExtension extends MessageExtension { private int offset; byte[] data; public ByteArrayMessageExtension(byte[] data) { this(0, data.length, data); } public ByteArrayMessageExtension(int length, byte[] data) { this(0, length, data); } public ByteArrayMessageExtension(int offset, int length, byte[] data) { super(length); this.offset = offset; this.data = data; } @Override public void encode(ByteBuf out) { out.writeBytes(data, offset, numBytes); } @Override public String toString() { return "ByteArrayMessageExtension [numBytes=" + numBytes + ", data.length=" + data.length + "]"; } } static class GpioPulseArrayMessageExtension extends MessageExtension { private GpioPulse[] pulses; public GpioPulseArrayMessageExtension(GpioPulse[] pulses) { // A GPIO pulse object has 3 4-byte integers super(pulses.length * 3 * 4); this.pulses = pulses; } @Override void encode(ByteBuf out) { for (GpioPulse pulse : pulses) { out.writeInt((int) (pulse.getGpioOn() & 0xffffffff)); out.writeInt((int) (pulse.getGpioOff() & 0xffffffff)); out.writeInt((int) (pulse.getUsDelay() & 0xffffffff)); } } } static class NotificationMessage { int seq; // unsigned short short flags; // unsigned short (bit mask) long tick; // Number of microseconds since system boot (unsigned int) int level; // Bit mask indicating the level of all GPIOs (unsigned int) long epochTime; long nanoTime; public NotificationMessage(int seq, short flags, long tick, int level, long epochTime, long nanoTime) { this.seq = seq; this.flags = flags; this.tick = tick; this.level = level; this.epochTime = epochTime; this.nanoTime = nanoTime; } @Override public String toString() { return "NotificationMessage [seq=" + seq + ", flags=0x" + Integer.toHexString(flags) + ", tick=" + tick + ", level=0x" + Integer.toHexString(level) + ", epochTime=" + epochTime + ", nanoTime=" + nanoTime + "]"; } } @FunctionalInterface static interface MessageListener { void messageReceived(T message); } static class MessageEncoder extends MessageToByteEncoder { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception { out.writeIntLE(msg.cmd); out.writeIntLE((int) msg.p1); out.writeIntLE((int) msg.p2); out.writeIntLE((int) msg.p3); if (msg.extension != null) { msg.extension.encode(out); } } } static class ResponseDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext context, ByteBuf buf, List out) { if (buf.readableBytes() < 4 * 4) { return; } buf.markReaderIndex(); int cmd = buf.readIntLE(); long p1 = buf.readUnsignedIntLE(); long p2 = buf.readUnsignedIntLE(); long res = buf.readUnsignedIntLE(); ResponseMessage message = null; switch (cmd) { case PI_CMD_SLR: case PI_CMD_I2CRD: case PI_CMD_I2CRK: case PI_CMD_I2CRI: case PI_CMD_I2CPK: case PI_CMD_SPIR: case PI_CMD_SPIX: case PI_CMD_SERR: case PI_CMD_CF2: case PI_CMD_BI2CZ: case PI_CMD_I2CZ: case PI_CMD_FR: case PI_CMD_FL: case PI_CMD_BSPIX: if (buf.readableBytes() < res) { buf.resetReaderIndex(); } else { byte[] data = new byte[(int) res]; buf.readBytes(data); message = new ByteArrayResponseMessage(cmd, p1, p2, res, data); } break; case PI_CMD_PROCP: if (buf.readableBytes() < res) { buf.resetReaderIndex(); } else { long status = buf.readUnsignedIntLE(); long[] pars = new long[(int) ((res - 4) / 4)]; for (int i = 0; i < pars.length; i++) { pars[i] = buf.readUnsignedIntLE(); } message = new ScriptStatusResponseMessage(cmd, p1, p2, res, status, pars); } break; case PI_CMD_BSCX: if (buf.readableBytes() < res) { buf.resetReaderIndex(); } else { long status = buf.readUnsignedIntLE(); byte[] data = new byte[(int) (res - 4)]; buf.readBytes(data); message = new BscXferResponseMessage(cmd, p1, p2, res, status, data); } break; default: message = new ResponseMessage(cmd, p1, p2, res); } if (message != null) { out.add(message); } } } static class IntegerHeaderFrameDecoder extends ReplayingDecoder { private boolean readLength; private int length; @Override protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List out) throws Exception { if (!readLength) { length = buf.readInt(); readLength = true; checkpoint(); } if (readLength) { ByteBuf frame = buf.readBytes(length); readLength = false; checkpoint(); out.add(frame); } } } static class NotificationDecoder extends ByteToMessageDecoder { private boolean notificationHandleSet; @Override protected void decode(ChannelHandlerContext context, ByteBuf in, List out) { long nano_time = System.nanoTime(); long epoch_time = System.currentTimeMillis(); if (!notificationHandleSet) { if (in.readableBytes() < 4 * 4) { return; } out.add(new ResponseMessage((int) in.readUnsignedIntLE(), in.readUnsignedIntLE(), in.readUnsignedIntLE(), in.readUnsignedIntLE())); notificationHandleSet = true; return; } // 2 shorts and 2 ints if (in.readableBytes() < (2 * 2 + 2 * 4)) { return; } out.add(new NotificationMessage(in.readUnsignedShortLE(), in.readShortLE(), in.readUnsignedIntLE(), in.readIntLE(), epoch_time, nano_time)); } } @Sharable static class ResponseHandler extends SimpleChannelInboundHandler { private MessageListener listener; ResponseHandler(MessageListener listener) { this.listener = listener; } @Override protected void channelRead0(ChannelHandlerContext context, ResponseMessage msg) { listener.messageReceived(msg); } @Override public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { LOGGER.log(Level.SEVERE, "exceptionCaught: " + cause, cause); context.close(); } } static class NotificationHandler extends SimpleChannelInboundHandler { private MessageListener listener; NotificationHandler(MessageListener listener) { this.listener = listener; } @Override protected void channelRead0(ChannelHandlerContext context, NotificationMessage msg) { listener.messageReceived(msg); } @Override public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { LOGGER.log(Level.SEVERE, "exceptionCaught: " + cause, cause); context.close(); } } static class TimeoutException extends RuntimeException { private static final long serialVersionUID = 5767582299816127993L; public TimeoutException(String message) { super(message); } } }