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

de.sciss.lucre.expr.graph.GPIO.scala Maven / Gradle / Ivy

/*
 *  GPIO.scala
 *  (LucrePi)
 *
 *  Copyright (c) 2020-2024 Hanns Holger Rutz. All rights reserved.
 *
 *	This software is published under the GNU Affero General Public License v3+
 *
 *
 *	For further information, please contact Hanns Holger Rutz at
 *	[email protected]
 */

package de.sciss.lucre.expr.graph

import com.pi4j.io.gpio.event.{GpioPinDigitalStateChangeEvent, GpioPinListenerDigital}
import com.pi4j.io.gpio.{GpioController, GpioFactory, GpioPinDigitalInput, GpioPinDigitalOutput, GpioProvider, PinPullResistance, PinState, Pin => JPin}
import com.pi4j.io.i2c.{I2CBus, I2CDevice, I2CFactory}
import de.sciss.equal.Implicits._
import de.sciss.lucre.Txn.peer
import de.sciss.lucre.expr.ExElem.{ProductReader, RefMapIn}
import de.sciss.lucre.expr.impl.IActionImpl
import de.sciss.lucre.expr.{Context, ExElem, IAction, IControl, ITrigger}
import de.sciss.lucre.impl.{IChangeEventImpl, IChangeGeneratorEvent, IEventImpl, IGeneratorEvent}
import de.sciss.lucre.{Cursor, Disposable, IChangeEvent, IEvent, IExpr, IPull, ITargets, Txn}
import de.sciss.model.Change
import de.sciss.proc.SoundProcesses
import pi4j.component.servo.impl.PCA9685GpioServoProvider
import pi4j.gpio.extension.pca.{PCA9685GpioProvider, PCA9685Pin}

import scala.concurrent.stm.{Ref, TArray, TxnLocal}

object GPIO {
  private lazy val provider: GpioProvider   = GpioFactory.getDefaultProvider()

  private lazy val instance: GpioController = {
    try {
      GpioFactory.getInstance()
    } catch {
      case ex: UnsatisfiedLinkError =>  // e.g. trying to run on a desktop
        Console.err.println(s"GPIO - cannot obtain controller: ${ex.getMessage}")
        null
    }
  }

  private final class ExpandedDigitalOut[T <: Txn[T]](pin: IExpr[T, JPin], state: IExpr[T, Boolean] /*, tx0: T*/)
    extends IControl[T] {

    private[this] val local   = TxnLocal[Boolean](afterCommit = setState)
    private[this] val obsRef  = Ref(Disposable.empty[T])

    @volatile
    private[this] var out     = null: GpioPinDigitalOutput

//    local.set(state.value(tx0))(tx0.peer)

    private def setState(value: Boolean): Unit = {
      val _out = out
      if (_out != null) {
        _out.setState(value)
      }
    }

    def initControl()(implicit tx: T): Unit = {
      val jPin    = pin   .value
      val state0  = state .value
      tx.afterCommit {
        // XXX TODO --- is this good? does this always occur before TxnLocal update?
        val _instance = instance
        if (_instance != null) {
          out = _instance.provisionDigitalOutputPin(provider, jPin, if (state0) PinState.HIGH else PinState.LOW)
        }
      }
//      local() = state.value
      val obs = state.changed.react { implicit tx => upd =>
        local() = upd.now
      }
      obsRef() = obs
    }

    def dispose()(implicit tx: T): Unit = {
      obsRef().dispose()
      tx.afterCommit {
        val _out = out
        if (_out != null) {
          instance.unprovisionPin(_out)
        }
      }
    }
  }

  object DigitalOut extends ProductReader[DigitalOut] {
    /** Creates a digital output on the given pin reflecting the given low/high state. */
    def apply(pin: Pin, state: Ex[Boolean]): DigitalOut = Impl(pin, state)

    override def read(in: ExElem.RefMapIn, key: String, arity: Int, adj: Int): DigitalOut = {
      require (arity == 2 && adj == 0)
      val _pin      = in.readProductT[Pin]()
      val _state    = in.readEx[Boolean]()
      DigitalOut(_pin, _state)
    }

    private final case class Impl(pin: Pin, state: Ex[Boolean]) extends DigitalOut {
      override def productPrefix: String = s"GPIO$$DigitalOut" // serialization

      type Repr[T <: Txn[T]] = IControl[T]

      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): Repr[T] = {
        new ExpandedDigitalOut[T](pin.expand[T], state.expand[T])
      }
    }
  }
  /** A pin configured for digital output. The low/high state is specified in the constructor. */
  trait DigitalOut extends Control

  private final class ExpandedDigitalIn[T <: Txn[T]](pin: IExpr[T, JPin], state0: Boolean,
                                                     pull: IExpr[T, Option[Boolean]],
                                                     debounce: IExpr[T, Int] /*, tx0: T*/)
                                                    (implicit protected val targets: ITargets[T],
                                                     cursor: Cursor[T])
    extends IControl[T] with IExpr[T, Boolean] with IChangeGeneratorEvent[T, Boolean] {

    @volatile
    private[this] var in = null: GpioPinDigitalInput

    private[this] val valueRef = Ref(state0)

    private[this] lazy val listener = new GpioPinListenerDigital {
      def handleGpioPinDigitalStateChangeEvent(e: GpioPinDigitalStateChangeEvent): Unit = {
        val now = e.getState.isHigh
        setState(now)
      }
    }

    private def setState(now: Boolean): Unit =
      SoundProcesses.step[T]("GPIO.DigitalIn.handle") { implicit tx =>
        setStateTx(now)
      }

    private def setStateTx(now: Boolean)(implicit tx: T): Unit = {
      val before = valueRef.swap(now)
      if (now != before) {
        fire(Change(before, now))
      }
    }

    def value(implicit tx: T): Boolean = valueRef()

    override def changed: IChangeEvent[T, Boolean] = this

    private[lucre] def pullChange(pull: IPull[T])(implicit tx: T, phase: IPull.Phase): Boolean =
      pull.resolveExpr(this)

    def initControl()(implicit tx: T): Unit = {
      val jPin    = pin     .value
      val pull0   = pull    .value
      val deb0    = debounce.value
      // setStateTx(value0) // valueRef()  = value0 // a bit of a hack; assume an 'opener' circuit
      tx.afterCommit {
        val _instance = instance
        if (_instance != null) {
          val resistance = pull0 match {
            case Some(true)   => PinPullResistance.PULL_UP
            case Some(false)  => PinPullResistance.PULL_DOWN
            case None         => PinPullResistance.OFF
          }
          val _in = _instance.provisionDigitalInputPin(provider, jPin, resistance)
          if (deb0 >= 0) _in.setDebounce(deb0)
          _in.addListener(listener)
          val state1 = _in.getState.isHigh
          if (state1 !== state0) setState(state1)
          in = _in
        }
      }
    }

    def dispose()(implicit tx: T): Unit = {
      tx.afterCommit {
        val _in = in
        if (_in != null) {
          _in.removeListener(listener)
          instance.unprovisionPin(_in)
        }
      }
    }
  }

  val PullUp  : Ex[Option[Boolean]] = Some(true)
  val PullDown: Ex[Option[Boolean]] = Some(false)
  val PullOff : Ex[Option[Boolean]] = None

  object DigitalIn extends ProductReader[DigitalIn] {
    /** Creates a digital input on the given pin. It can be used to attach and listen to
      * buttons on the GPIO, for example.
      *
      * ''Note:'' Because initialization takes place outside a transaction, the value of the pin
      * is initially unknown and thus can be given as `init`. When initialized, this pin is actually polled,
      * potentially triggering actions in the user code.
      *
      * @param  pin       the pin to poll
      * @param  pull      if defined, sets a pull-up (`true`) or pull-down (`false`) resistor. Defaults to `None`.
      * @param  init      the assumed initial state (defaults to `false`)
      * @param  debounce  if zero or positive, specifies a debounce option in milliseconds.
      *                   Debouncing is used when due to noise or imprecision multiple button
      *                   clicks are detected when they should be treated as one. Defaults to `-1`.
      */
    def apply(pin: Pin, pull: Ex[Option[Boolean]] = None, init: Ex[Boolean] = false, debounce: Ex[Int] = -1): DigitalIn =
      Impl(pin, pull, init, debounce)

    override def read(in: ExElem.RefMapIn, key: String, arity: Int, adj: Int): DigitalIn = {
      require (arity == 4 && adj == 0)
      val _pin      = in.readProductT[Pin]()
      val _pull     = in.readEx[Option[Boolean]]()
      val _init     = in.readEx[Boolean]()
      val _debounce = in.readEx[Int]()
      DigitalIn(_pin, _pull, _init, _debounce)
    }

    private final case class Impl(pin: Pin, pull: Ex[Option[Boolean]], init: Ex[Boolean], debounce: Ex[Int])
      extends DigitalIn {

      override def productPrefix: String = s"GPIO$$DigitalIn" // serialization

      type Repr[T <: Txn[T]] = IControl[T] with IExpr[T, Boolean]

      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): Repr[T] = {
        import ctx.{cursor, targets}
        new ExpandedDigitalIn[T](pin.expand[T], pull = pull.expand[T], debounce = debounce.expand[T],
          state0 = init.expand[T].value)
      }
    }
  }
  /** A pin configured for digital input. The expression reflects the pin's state
    * as low (`false`) or high (`true`).
    */
  trait DigitalIn extends Control with Ex[Boolean] {
    type Repr[T <: Txn[T]] <: IControl[T] with IExpr[T, Boolean]
  }

  trait Pin extends Ex[JPin]

  // ---------- ADS1X15 ----------

  object ADS1X15 extends ProductReader[ADS1X15] {
    /**
      * @param bus      i2c bus (default is 1)
      * @param address  i2c address (default is 0x48)
      * @param bits     16 for the ADS1115, 12 for the ADS1015 (default is 16)
      */
    def apply(bus: Ex[Int] = 1, address: Ex[Int] = 0x48, bits: Ex[Int] = 16): ADS1X15 =
      Impl(bus = bus, address = address, bits = bits)

    override def read(in: ExElem.RefMapIn, key: String, arity: Int, adj: Int): ADS1X15 = {
      require (arity == 3 && adj == 0)
      val _bus      = in.readEx[Int]()
      val _address  = in.readEx[Int]()
      val _bits     = in.readEx[Int]()
      ADS1X15(_bus, _address, _bits)
    }

//    object Received extends ProductReader[Received] {
//      override def read(in: RefMapIn, key: String, arity: Int, adj: Int): Received = {
//        require (arity == 1 && adj == 0)
//        val _a = in.readProductT[ADS1X15]()
//        new Received(_a)
//      }
//    }
//    final case class Received(a: ADS1X15) extends Trig {
//      type Repr[T <: Txn[T]] = ITrigger[T]
//
//      override def productPrefix = s"GPIO$$ADS1X15$$Received"   // serialization
//
//      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): Repr[T] = {
//        val ns = a.expand[T]
//        import ctx.targets
//        new ReceivedExpanded[T](ns, tx)
//      }
//    }

    object RunSingle extends ProductReader[RunSingle] {
      override def read(in: RefMapIn, key: String, arity: Int, adj: Int): RunSingle = {
        assert (arity == 2 && adj == 0)
        val _a    = in.readProductT[ADS1X15]()
        val _chan = in.readEx[Int]()
        new RunSingle(_a, _chan)
      }
    }
    final case class RunSingle(a: ADS1X15, chan: Ex[Int]) extends Act with Trig {
      type Repr[T <: Txn[T]] = IAction[T] with ITrigger[T]

      override def productPrefix: String = s"GPIO$$ADS1X15$$RunSingle" // serialization

      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): Repr[T] = {
        val ax      = a.expand[T]
        val chanEx  = chan.expand[T]
        import ctx.targets
        new ExpandedRunSingle[T](ax, chanEx, tx)
      }
    }

    private final class ExpandedRunSingle[T <: Txn[T]](a: ADS1X15.Repr[T], chan: IExpr[T, Int], tx0: T)
                                                      (implicit protected val targets: ITargets[T])
      extends IActionImpl[T] with ITrigger[T] with IEventImpl[T, Unit] {

      private final val chanRef = Ref(-1)

      a.received.--->(changed)(tx0)

      override def executeAction()(implicit tx: T): Unit = {
        val chanV = chan.value
        if (chanV >= 0 && chanV < 4) {
          chanRef() = chanV
          a.runSingle(chanV)
        }
      }

      override def dispose()(implicit tx: T): Unit = {
        a.received.-/->(changed)
        super.dispose()
      }

      override def changed: IEvent[T, Unit] = this

      override private[lucre] def pullUpdate(pull: IPull[T])(implicit tx: T): Option[Unit] = {
        val opt = pull(a.received)
        if (opt.isDefined) Trig.Some else None  // XXX TODO is this correct?
      }
    }

    object In extends ProductReader[In] {
      override def read(in: RefMapIn, key: String, arity: Int, adj: Int): In = {
        require (arity == 2 && adj == 0)
        val _a    = in.readProductT[ADS1X15]()
        val _chan = in.readEx[Int]()
        new In(_a, _chan)
      }
    }
    final case class In(a: ADS1X15, chan: Ex[Int]) extends Ex[Int] {
      type Repr[T <: Txn[T]] = IExpr[T, Int]

      override def productPrefix: String = s"GPIO$$ADS1X15$$In" // serialization

      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): Repr[T] = {
        val ax      = a.expand[T]
        val chanEx  = chan.expand[T]
        import ctx.targets
        new InExpanded(ax, chanEx, tx)
      }
    }

    private final class InExpanded[T <: Txn[T]](a: Repr[T], chan: IExpr[T, Int], tx0: T)
                                               (implicit protected val targets: ITargets[T])
      extends IExpr[T, Int] with IChangeEventImpl[T, Int] {

      a.received.--->(changed)(tx0)

      def value(implicit tx: T): Int = a.in(chan.value)

      private[lucre] def pullChange(pull: IPull[T])(implicit tx: T, phase: IPull.Phase): Int = {
        /*val opt =*/ pull(a.received)
        a.in(chan.value) // XXX TODO is this correct?
      }

      def dispose()(implicit tx: T): Unit =
        a.received.-/->(changed)

      def changed: IChangeEvent[T, Int] = this
    }

    private final case class Impl(bus: Ex[Int], address: Ex[Int], bits: Ex[Int]) extends ADS1X15 {
      self =>

      override def productPrefix: String = s"GPIO$$ADS1X15" // serialization

//      override def received         : Trig    = Received(this)

      override def runSingle(chan: Ex[Int]): Act with Trig = RunSingle(this, chan)

      override def in(chan: Ex[Int]): Ex[Int] = In(this, chan)

      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): self.Repr[T] = {
        import ctx.{cursor, targets}
        new ExpandedADS1X15[T](bus = bus.expand[T], address = address.expand[T], bits = bits.expand[T])
      }
    }

    trait Repr[T <: Txn[T]] extends IControl[T] {
      def in(chan: Int)(implicit tx: T): Int

      def runSingle(chan: Int)(implicit tx: T): Unit

      def received: IEvent[T, (Int, Int)]
    }
  }
  trait ADS1X15 extends Control {
    type Repr[T <: Txn[T]] = ADS1X15.Repr[T]

//    /** Triggers when a conversion is completed. */
//    def received(chan: Ex[Int]): Trig

    /** Runs a single conversion on a given channel. The trigger is fired when conversion is complete. */
    def runSingle(chan: Ex[Int]): Act with Trig

    /** The last conversion at a given analog input. */
    def in(chan: Ex[Int]): Ex[Int]
  }

  // -----
  // implementation based on `Adafruit_ADS1X15`, licensed under BSD License:
  // Copyright (c) 2012, Adafruit Industries, all rights reserved.
  //
  // We still have to figure out why waiting for conversion-completion does not work
  // -----

  private final val ADS1X15_REG_POINTER_CONFIG      = 0x01  // Configuration
  private final val ADS1X15_REG_POINTER_LOWTHRESH   = 0x02  // Low threshold
  private final val ADS1X15_REG_POINTER_HITHRESH    = 0x03  // High threshold
  private final val ADS1X15_REG_POINTER_CONVERT     = 0x00  // Conversion

  private final val ADS1X15_REG_CONFIG_CQUE_1CONV   = 0x0000 // Assert ALERT/RDY after one conversions
  private final val ADS1X15_REG_CONFIG_CLAT_NONLAT  = 0x0000 // Non-latching comparator (default)
  private final val ADS1X15_REG_CONFIG_CPOL_ACTVLOW = 0x0000 // ALERT/RDY pin is low when active (default)
  private final val ADS1X15_REG_CONFIG_CMODE_TRAD   = 0x0000 // Traditional comparator with hysteresis (default)
  private final val ADS1X15_REG_CONFIG_MODE_CONTIN  = 0x0000 // Continuous conversion mode
  private final val ADS1X15_REG_CONFIG_MODE_SINGLE  = 0x0100 // Power-down single-shot mode (default)
  private final val ADS1X15_REG_CONFIG_OS_SINGLE    = 0x8000 // Write: Set to start a single-conversion

//  private final val ADS1X15_REG_CONFIG_MUX_SINGLE_0 = 0x4000 // Single-ended AIN0
//  private final val ADS1X15_REG_CONFIG_MUX_SINGLE_1 = 0x5000 // Single-ended AIN1
//  private final val ADS1X15_REG_CONFIG_MUX_SINGLE_2 = 0x6000 // Single-ended AIN2
//  private final val ADS1X15_REG_CONFIG_MUX_SINGLE_3 = 0x7000 // Single-ended AIN3

  private final val ADS1X15_REG_CONFIG_PGA_6_144V   = 0x0000 // +/-6.144V range = Gain 2/3

  private final val RATE_ADS1015_1600SPS            = 0x0080 // 1600 samples per second (default)
  private final val RATE_ADS1115_128SPS             = 0x0080 //  128 samples per second (default)

  private final val DEBUG = false

  private final class ExpandedADS1X15[T <: Txn[T]](bus: IExpr[T, Int], address: IExpr[T, Int], bits: IExpr[T, Int])
                                                  (implicit protected val targets: ITargets[T], cursor: Cursor[T])
    extends ADS1X15.Repr[T] with IGeneratorEvent[T, (Int, Int)] {

//    private[this] val obsRef  = Ref(Disposable.empty[T])

    private[this] val gain      = ADS1X15_REG_CONFIG_PGA_6_144V    // GAIN_TWOTHIRDS - +/- 6.144V range (limited to VDD +0.3V max!)
    private[this] var bitShift  = 0
    private[this] var dataRate  = 0

    @volatile
    private[this] var i2cBus    = null: I2CBus
    @volatile
    private[this] var i2cDev    = null: I2CDevice

    private[this] val _values   = TArray.ofDim[Int](4) // new Array[Int](4)

    override def in(chan: Int)(implicit tx: T): Int =
      if (chan >= 0 && chan < 4) _values(chan) else 0

    override def runSingle(chan: Int)(implicit tx: T): Unit =
      tx.afterCommit {
        val mux = (chan + 4) << 12  // 0x4000, 0x5000 etc.
        startADCReading(mux, continuous = false)
        // Wait for the conversion to complete

        // XXX TODO this causes I/O error
//        while (!conversionComplete()) ()
//        Thread.sleep(1) // XXX TODO -- this works "sometimes", but on the Pi, it seems the Thread is only woken up fast if the UI moves
        val t1 = System.currentTimeMillis()
        while ((System.currentTimeMillis() - t1) < 2) ()  // XXX TODO -- this "works" but is bad obviously

        val value = getLastConversionResults()
//        _values(chan) = value
        SoundProcesses.step[T]("ADS1X15.runSingle") { implicit tx =>
          _values(chan) = value
          fire((chan, value))
        }
      }

    override private[lucre] def pullUpdate(pull: IPull[T])(implicit tx: T): Option[(Int, Int)] =
      Some(pull.resolve[(Int, Int)])

    override def received: IEvent[T, (Int, Int)] = this

    private def getLastConversionResults(): Int = {
      // Read the conversion results
      val res = readRegister(ADS1X15_REG_POINTER_CONVERT) >> bitShift
      if (bitShift == 0 || res <= 0x07FF) {
        res
      } else {
        // Shift 12-bit results right 4 bits for the ADS1015,
        // making sure we keep the sign bit intact
        // negative number - extend the sign to 16th bit
        res | 0xF000
      }
    }

    private def startADCReading(mux: Int, continuous: Boolean): Unit = {
      // Start with default values
      val config = {
        ADS1X15_REG_CONFIG_CQUE_1CONV |   // Set CQUE to any value other than
          // None so we can use it in RDY mode
          ADS1X15_REG_CONFIG_CLAT_NONLAT |  // Non-latching (default val)
          ADS1X15_REG_CONFIG_CPOL_ACTVLOW | // Alert/Rdy active low   (default val)
          ADS1X15_REG_CONFIG_CMODE_TRAD     // Traditional comparator (default val)
      } | (if (continuous) ADS1X15_REG_CONFIG_MODE_CONTIN else ADS1X15_REG_CONFIG_MODE_SINGLE) | gain | dataRate | mux | ADS1X15_REG_CONFIG_OS_SINGLE

      if (DEBUG) println(s"startADCReading(0x${mux.toHexString}, $continuous)")
      // Write config register to the ADC
      writeRegister(ADS1X15_REG_POINTER_CONFIG    , config)

      // XXX TODO: this causes an I/O error

//      // Set ALERT/RDY to RDY mode.
//      writeRegister(ADS1X15_REG_POINTER_HITHRESH  , 0x8000)
//      writeRegister(ADS1X15_REG_POINTER_LOWTHRESH , 0x0000)
    }

    private def conversionComplete(): Boolean =
      (readRegister(ADS1X15_REG_POINTER_CONFIG) & 0x8000) != 0

    private def readRegister(reg: Int): Int = {
      if (DEBUG) println(s"readRegister($reg)")
      val arr = new Array[Byte](2)
      i2cDev.write(reg.toByte)
      val res = i2cDev.read (arr, 0, 2)
      if (DEBUG) println(s" -> res $res")
      ((arr(0) & 0xFF) << 8) | (arr(1) & 0xFF)
    }

    private def writeRegister(reg: Int, value: Int): Unit = {
      if (DEBUG) println(s"writeRegister($reg, 0x${value.toHexString})")
      val arr = new Array[Byte](3)
      arr(0) = reg.toByte
      arr(1) = (value >> 8).toByte
      arr(2) = (value & 0xFF).toByte
      i2cDev.write(arr, 0, 3)
    }

    def initControl()(implicit tx: T): Unit = {
      val busV      = bus     .value
      val addressV  = address .value
      val bitsV     = bits    .value

      bitShift    = 16 - bitsV
      if (bitsV == 12) {
        dataRate  = RATE_ADS1015_1600SPS
      } else {
        if (bitsV != 16) Console.err.println(s"Warning: ADS1X15 number of bits ($bitsV) must be 12 or 16")
        dataRate  = RATE_ADS1115_128SPS
      }

      tx.afterCommit {
        /*val gpio =*/ instance
//        gpio.provisionAnalogInputPin(adc, pin, s"analog-in-$i")
        if (DEBUG) println(s"Creating I2C for bus $busV and address 0x${addressV.toHexString}")
        i2cBus = I2CFactory.getInstance(busV)
        i2cDev = i2cBus.getDevice(addressV)
        if (DEBUG) println("I2C created")
        val res = i2cDev.read()
        if (DEBUG) println(s"I2C read() = $res")
      }
    }

    def dispose()(implicit tx: T): Unit = {
//      obsRef().dispose()
      tx.afterCommit {
        val _i2cBus = i2cBus
        if (_i2cBus != null) {
          _i2cBus.close()
        }
      }
    }
  }

  // ---------- PCA9685 ----------

  object PCA9685 extends ProductReader[PCA9685] {
    /**
      * @param bus      i2c bus (default is 1)
      * @param address  i2c address (default is 0x40)
      * @param freq     servo frequency in Hz (default is 50)
      */
    def apply(bus: Ex[Int] = 1, address: Ex[Int] = 0x40, freq: Ex[Double] = 50.0): PCA9685 =
      Impl(bus = bus, address = address, freq = freq)

    override def read(in: ExElem.RefMapIn, key: String, arity: Int, adj: Int): PCA9685 = {
      require (arity == 3 && adj == 0)
      val _bus      = in.readEx[Int]()
      val _address  = in.readEx[Int]()
      val _freq     = in.readEx[Double]()
      PCA9685(_bus, _address, _freq)
    }

    trait Repr[T <: Txn[T]] extends IControl[T] {
      /** The pulse-width-modulation in microseconds. Setting this to zero turns the servo off. */
      def setPwm(chan: Int, micros: Int)(implicit tx: T): Unit
    }

    private final case class Pwm(p: PCA9685, chan: Ex[Int]) extends Ex.Sink[Int] {
      override def productPrefix: String = s"GPIO$$PCA9685$$Pwm" // serialization -- not needed, though

      override def update(value: Ex[Int]): Unit = PwmUpdate(p, chan, value)
    }
    object PwmUpdate extends ProductReader[PwmUpdate] {
      override def read(in: RefMapIn, key: String, arity: Int, adj: Int): PwmUpdate = {
        assert (arity == 3 && adj == 0)
        val _p      = in.readProductT[PCA9685]()
        val _chan   = in.readEx[Int]()
        val _value  = in.readEx[Int]()
        new PwmUpdate(_p, _chan, _value)
      }
    }
    final case class PwmUpdate(p: PCA9685, chan: Ex[Int], value: Ex[Int]) extends Control {
      override def productPrefix: String = s"GPIO$$PCA9685$$PwmUpdate" // serialization

      override type Repr[T <: Txn[T]] = IControl[T]

      override protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): IControl[T] =
        new ExpandedPwmUpdate[T](p.expand[T], chan.expand[T], value.expand[T])
    }

    private final class ExpandedPwmUpdate[T <: Txn[T]](p: PCA9685.Repr[T], chan: IExpr[T, Int], value: IExpr[T, Int] /*, tx0: T*/)
      extends IControl[T] {

      private[this] val obsRef  = Ref(Disposable.empty[T])

      def initControl()(implicit tx: T): Unit = {
        val chanV   = chan  .value
        val value0  = value .value
        p.setPwm(chanV, value0)
        val obs = value.changed.react { implicit tx => upd =>
          p.setPwm(chanV, upd.now)
        }
        obsRef() = obs
      }

      def dispose()(implicit tx: T): Unit =
        obsRef().dispose()
    }

    private final case class Impl(bus: Ex[Int], address: Ex[Int], freq: Ex[Double]) extends PCA9685 {
      self =>

      override def productPrefix: String = s"GPIO$$PCA9685" // serialization

      override def pwm(chan: Ex[Int]): Ex.Sink[Int] = Pwm(this, chan)

      protected def mkRepr[T <: Txn[T]](implicit ctx: Context[T], tx: T): self.Repr[T] =
        new ExpandedPCA9685[T](bus = bus.expand[T], address = address.expand[T], freq = freq.expand[T])
    }
  }
  trait PCA9685 extends Control {
    type Repr[T <: Txn[T]] = PCA9685.Repr[T]

    /** The pulse-width-modulation in microseconds. Setting this to zero turns the servo off. */
    def pwm(chan: Ex[Int]): Ex.Sink[Int]
  }

  private final class ExpandedPCA9685[T <: Txn[T]](bus: IExpr[T, Int], address: IExpr[T, Int], freq: IExpr[T, Double])
    extends PCA9685.Repr[T] {

    @volatile
    private[this] var i2cBus    = null: I2CBus
    @volatile
    private[this] var i2cDev    = null: I2CDevice
    @volatile
    private[this] var servoProvider = null: PCA9685GpioServoProvider

    override def setPwm(chan: Int, micros: Int)(implicit tx: T): Unit =
      if (chan >= 0 && chan < 16) tx.afterCommit {
        if (servoProvider != null) {
          if (DEBUG) println(s"setPwm($chan, $micros)")
          val pin = PCA9685Pin.ALL(chan)
          val servoDriver = servoProvider.getServoDriver(pin)
          if (micros > 0) {
            servoDriver.setServoPulseWidth(micros)
          } else {
            servoDriver.getProvider.setAlwaysOff(servoDriver.getPin)
          }
        }
      }

    def initControl()(implicit tx: T): Unit = {
      val busV      = bus     .value
      val addressV  = address .value
      val freqV     = freq    .value

      tx.afterCommit {
        val gpio = instance
        //        gpio.provisionAnalogInputPin(adc, pin, s"analog-in-$i")
        if (DEBUG) println(s"Creating I2C for bus $busV and address 0x${addressV.toHexString}")
        i2cBus = I2CFactory.getInstance(busV)
        val gpioProvider = new PCA9685GpioProvider(i2cBus, addressV, new java.math.BigDecimal(freqV))
        i2cDev = i2cBus.getDevice(addressV)
        if (DEBUG) println("I2C created")

        for (pin <- PCA9685Pin.ALL) {
          val servoName = s"Servo_${pin.getAddress}"
          gpio.provisionPwmOutputPin(gpioProvider, pin, servoName)
        }
        servoProvider = new PCA9685GpioServoProvider(gpioProvider)
        // val servoDriver = servoProvider.getServoDriver(pin)
      }
    }

    def dispose()(implicit tx: T): Unit = {
      //      obsRef().dispose()
      tx.afterCommit {
        val _i2cBus = i2cBus
        if (_i2cBus != null) {
          _i2cBus.close()
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy