001/* 002 * Copyright (C) Photon Vision. 003 * 004 * This program is free software: you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License as published by 006 * the Free Software Foundation, either version 3 of the License, or 007 * (at your option) any later version. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program. If not, see <https://www.gnu.org/licenses/>. 016 */ 017 018package org.photonvision.common.hardware.GPIO.pi; 019 020import static org.photonvision.common.hardware.GPIO.pi.PigpioException.*; 021 022import java.io.IOException; 023import java.nio.ByteBuffer; 024import java.nio.ByteOrder; 025import java.util.ArrayList; 026import org.photonvision.common.logging.LogGroup; 027import org.photonvision.common.logging.Logger; 028 029@SuppressWarnings({"SpellCheckingInspection", "unused"}) 030public class PigpioSocket { 031 private static final Logger logger = new Logger(PigpioSocket.class, LogGroup.General); 032 private static final int PIGPIOD_MESSAGE_SIZE = 12; 033 034 private PigpioSocketLock commandSocket; 035 private int activeWaveformID = -1; 036 037 /** Creates and starts a socket connection to a pigpio daemon on localhost */ 038 public PigpioSocket() { 039 this("127.0.0.1", 8888); 040 } 041 042 /** 043 * Creates and starts a socket connection to a pigpio daemon on a remote host with the specified 044 * address and port 045 * 046 * @param addr Address of remote pigpio daemon 047 * @param port Port of remote pigpio daemon 048 */ 049 public PigpioSocket(String addr, int port) { 050 try { 051 commandSocket = new PigpioSocketLock(addr, port); 052 } catch (IOException e) { 053 logger.error("Failed to create or connect to Pigpio Daemon socket", e); 054 } 055 } 056 057 /** 058 * Reconnects to the pigpio daemon 059 * 060 * @throws PigpioException on failure 061 */ 062 public void reconnect() throws PigpioException { 063 try { 064 commandSocket.reconnect(); 065 } catch (IOException e) { 066 logger.error("Failed to reconnect to Pigpio Daemon socket", e); 067 throw new PigpioException("reconnect", e); 068 } 069 } 070 071 /** 072 * Terminates the connection to the pigpio daemon 073 * 074 * @throws PigpioException on failure 075 */ 076 public void gpioTerminate() throws PigpioException { 077 try { 078 commandSocket.terminate(); 079 } catch (IOException e) { 080 logger.error("Failed to terminate connection to Pigpio Daemon socket", e); 081 throw new PigpioException("gpioTerminate", e); 082 } 083 } 084 085 /** 086 * Read the GPIO level 087 * 088 * @param pin Pin to read from 089 * @return Value of the pin 090 * @throws PigpioException on failure 091 */ 092 public boolean gpioRead(int pin) throws PigpioException { 093 try { 094 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_READ.value, pin); 095 if (retCode < 0) throw new PigpioException(retCode); 096 return retCode != 0; 097 } catch (IOException e) { 098 logger.error("Failed to read GPIO pin: " + pin, e); 099 throw new PigpioException("gpioRead", e); 100 } 101 } 102 103 /** 104 * Write the GPIO level 105 * 106 * @param pin Pin to write to 107 * @param value Value to write 108 * @throws PigpioException on failure 109 */ 110 public void gpioWrite(int pin, boolean value) throws PigpioException { 111 try { 112 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WRITE.value, pin, value ? 1 : 0); 113 if (retCode < 0) throw new PigpioException(retCode); 114 } catch (IOException e) { 115 logger.error("Failed to write to GPIO pin: " + pin, e); 116 throw new PigpioException("gpioWrite", e); 117 } 118 } 119 120 /** 121 * Clears all waveforms and any data added by calls to {@link #waveAddGeneric(ArrayList)} 122 * 123 * @throws PigpioException on failure 124 */ 125 public void waveClear() throws PigpioException { 126 try { 127 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WVCLR.value); 128 if (retCode < 0) throw new PigpioException(retCode); 129 } catch (IOException e) { 130 logger.error("Failed to clear waveforms", e); 131 throw new PigpioException("waveClear", e); 132 } 133 } 134 135 /** 136 * Adds a number of pulses to the current waveform 137 * 138 * @param pulses ArrayList of pulses to add 139 * @return the new total number of pulses in the current waveform 140 * @throws PigpioException on failure 141 */ 142 private int waveAddGeneric(ArrayList<PigpioPulse> pulses) throws PigpioException { 143 // pigpio wave message format 144 145 // I p1 0 146 // I p2 0 147 // I p3 pulses * 12 148 // ## extension ## 149 // III on/off/delay * pulses 150 151 if (pulses == null || pulses.isEmpty()) return 0; 152 153 try { 154 ByteBuffer bb = ByteBuffer.allocate(pulses.size() * 12); 155 bb.order(ByteOrder.LITTLE_ENDIAN); 156 for (var pulse : pulses) { 157 bb.putInt(pulse.gpioOn).putInt(pulse.gpioOff).putInt(pulse.delayMicros); 158 } 159 160 int retCode = 161 commandSocket.sendCmd( 162 PigpioCommand.PCMD_WVAG.value, 163 0, 164 0, 165 pulses.size() * PIGPIOD_MESSAGE_SIZE, 166 bb.array()); 167 if (retCode < 0) throw new PigpioException(retCode); 168 169 return retCode; 170 } catch (IOException e) { 171 logger.error("Failed to add pulse(s) to waveform", e); 172 throw new PigpioException("waveAddGeneric", e); 173 } 174 } 175 176 /** 177 * Creates pulses and adds them to the current waveform 178 * 179 * @param pulseTimeMillis Pulse length in milliseconds 180 * @param blinks Number of times to pulse. -1 for repeat 181 * @param pinNo Pin to pulse 182 */ 183 private void addBlinkPulsesToWaveform(int pulseTimeMillis, int blinks, int pinNo) { 184 boolean repeat = blinks == -1; 185 186 if (blinks == 0) return; 187 188 if (repeat) { 189 blinks = 1; 190 } 191 192 try { 193 ArrayList<PigpioPulse> pulses = new ArrayList<>(); 194 var startPulse = new PigpioPulse(pinNo, 0, pulseTimeMillis * 1000); 195 var endPulse = new PigpioPulse(0, pinNo, pulseTimeMillis * 1000); 196 197 for (int i = 0; i < blinks; i++) { 198 pulses.add(startPulse); 199 pulses.add(endPulse); 200 } 201 202 waveAddGeneric(pulses); 203 pulses.clear(); 204 } catch (Exception e) { 205 e.printStackTrace(); 206 } 207 } 208 209 /** 210 * Generates and sends a waveform to the given pins with the specified parameters. 211 * 212 * @param pulseTimeMillis Pulse length in milliseconds 213 * @param blinks Number of times to pulse. -1 for repeat 214 * @param pins Pins to pulse 215 * @throws PigpioException on failure 216 */ 217 public void generateAndSendWaveform(int pulseTimeMillis, int blinks, int... pins) 218 throws PigpioException { 219 if (pins.length == 0) return; 220 boolean repeat = blinks == -1; 221 if (blinks == 0) return; 222 223 // stop any active waves 224 waveTxStop(); 225 waveClear(); 226 227 if (activeWaveformID != -1) { 228 waveDelete(activeWaveformID); 229 activeWaveformID = -1; 230 } 231 232 for (int pin : pins) { 233 addBlinkPulsesToWaveform(pulseTimeMillis, blinks, pin); 234 } 235 236 int waveformId = waveCreate(); 237 238 if (waveformId >= 0) { 239 if (repeat) { 240 waveSendRepeat(waveformId); 241 } else { 242 waveSendOnce(waveformId); 243 } 244 } else { 245 logger.error("Failed to send wave: " + getMessageForError(waveformId)); 246 } 247 } 248 249 /** 250 * Stops the transmission of the current waveform 251 * 252 * @return success 253 * @throws PigpioException on failure 254 */ 255 public boolean waveTxStop() throws PigpioException { 256 try { 257 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WVHLT.value); 258 if (retCode < 0) throw new PigpioException(retCode); 259 return retCode == 0; 260 } catch (IOException e) { 261 logger.error("Failed to stop waveform", e); 262 throw new PigpioException("waveTxStop", e); 263 } 264 } 265 266 /** 267 * Creates a waveform from the data provided by the prior calls to {@link 268 * #waveAddGeneric(ArrayList)} Upon success a wave ID greater than or equal to 0 is returned 269 * 270 * @return ID of the created waveform 271 * @throws PigpioException on failure 272 */ 273 public int waveCreate() throws PigpioException { 274 try { 275 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WVCRE.value); 276 if (retCode < 0) throw new PigpioException(retCode); 277 return retCode; 278 } catch (IOException e) { 279 logger.error("Failed to create new waveform", e); 280 throw new PigpioException("waveCreate", e); 281 } 282 } 283 284 /** 285 * Deletes the waveform with specified wave ID 286 * 287 * @param waveId ID of the waveform to delete 288 * @throws PigpioException on failure 289 */ 290 public void waveDelete(int waveId) throws PigpioException { 291 try { 292 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WVDEL.value, waveId); 293 if (retCode < 0) throw new PigpioException(retCode); 294 } catch (IOException e) { 295 logger.error("Failed to delete wave: " + waveId, e); 296 throw new PigpioException("waveDelete", e); 297 } 298 } 299 300 /** 301 * Transmits the waveform with specified wave ID. The waveform is sent once 302 * 303 * @param waveId ID of the waveform to transmit 304 * @return The number of DMA control blocks in the waveform 305 * @throws PigpioException on failure 306 */ 307 public int waveSendOnce(int waveId) throws PigpioException { 308 try { 309 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WVTX.value, waveId); 310 if (retCode < 0) throw new PigpioException(retCode); 311 return retCode; 312 } catch (IOException e) { 313 throw new PigpioException("waveSendOnce", e); 314 } 315 } 316 317 /** 318 * Transmits the waveform with specified wave ID. The waveform cycles until cancelled (either by 319 * the sending of a new waveform or {@link #waveTxStop()} 320 * 321 * @param waveId ID of the waveform to transmit 322 * @return The number of DMA control blocks in the waveform 323 * @throws PigpioException on failure 324 */ 325 public int waveSendRepeat(int waveId) throws PigpioException { 326 try { 327 int retCode = commandSocket.sendCmd(PigpioCommand.PCMD_WVTXR.value, waveId); 328 if (retCode < 0) throw new PigpioException(retCode); 329 return retCode; 330 } catch (IOException e) { 331 throw new PigpioException("waveSendRepeat", e); 332 } 333 } 334 335 /** 336 * Starts hardware PWM on a GPIO at the specified frequency and dutycycle 337 * 338 * @param pin GPIO pin to start PWM on 339 * @param pwmFrequency Frequency to run at (1Hz-125MHz). Frequencies above 30MHz are unlikely to 340 * work 341 * @param pwmDuty Duty cycle to run at (0-1,000,000) 342 * @throws PigpioException on failure 343 */ 344 public void hardwarePWM(int pin, int pwmFrequency, int pwmDuty) throws PigpioException { 345 try { 346 ByteBuffer bb = ByteBuffer.allocate(4); 347 bb.order(ByteOrder.LITTLE_ENDIAN); 348 bb.putInt(pwmDuty); 349 350 int retCode = 351 commandSocket.sendCmd(PigpioCommand.PCMD_HP.value, pin, pwmFrequency, 4, bb.array()); 352 if (retCode < 0) throw new PigpioException(retCode); 353 } catch (IOException e) { 354 throw new PigpioException("hardwarePWM", e); 355 } 356 } 357}