void delay(int ms) {
   try {
     Thread.sleep(ms);
   } catch (InterruptedException e) {
     handleCapturedInterrupt(e);
   }
 }
Ejemplo n.º 2
0
    void updateStateMachines(UPDATE_STATE_MACHINE caller)
          // We've got quite the little state machine here!
        {
      synchronized (callbackLock) {
        // ----------------------------------------------------------------------------------
        // If we're calling from other than the callback (in which we *know* the port is
        // ready), we need to check whether things are currently busy. We defer until
        // later if they are.
        if (caller == UPDATE_STATE_MACHINE.FROM_USER_WRITE) {
          if (!i2cDevice.isI2cPortReady() || callbackThread == null) return;

          // Optimized calling from user mode is not yet implemented
          return;
        }

        // ----------------------------------------------------------------------------------
        // Some ancillary bookkeeping

        if (caller == UPDATE_STATE_MACHINE.FROM_CALLBACK) {
          // Capture the current callback thread if we haven't already
          if (callbackThread == null) {
            callbackThread = Thread.currentThread();
            callbackThreadOriginalPriority = callbackThread.getPriority();
          } else
            assertTrue(
                !BuildConfig.DEBUG || callbackThread.getId() == Thread.currentThread().getId());

          // Set the thread name to make the system more debuggable
          if (0 == hardwareCycleCount)
            Thread.currentThread().setName(String.format("RWLoop(%s)", i2cDevice.getDeviceName()));

          // Adjust the target thread priority. Note that we only ever adjust it upwards,
          // not downwards, because in reality the thread is shared by other I2C objects
          // on the same controller and we don't want to fight with their understanding
          // of what the priority should be.
          int targetPriority = callbackThreadOriginalPriority + callbackThreadPriorityBoost;
          if (callbackThread.getPriority() < targetPriority) {
            try {
              callbackThread.setPriority(targetPriority);
            } catch (Exception e) {
              /* ignore: just run as is */
            }
          }

          // Update cycle statistics
          hardwareCycleCount++;
        }

        // ----------------------------------------------------------------------------------
        // Initialize state for managing state transition

        setActionFlag = false;
        queueFullWrite = false;
        queueRead = false;
        heartbeatRequired =
            (msHeartbeatInterval > 0
                && milliseconds(timeSinceLastHeartbeat) >= msHeartbeatInterval);
        enabledReadMode = false;
        enabledWriteMode = false;

        prevReadCacheStatus = readCacheStatus;
        prevWriteCacheStatus = writeCacheStatus;
        prevModeCacheStatus = modeCacheStatus;

        // ----------------------------------------------------------------------------------
        // Handle the state machine

        if (caller == UPDATE_STATE_MACHINE.FROM_CALLBACK) {
          // --------------------------------------------------------------------------
          // Deal with the fact that we've completed any previous queueing operation

          if (modeCacheStatus == MODE_CACHE_STATUS.QUEUED) modeCacheStatus = MODE_CACHE_STATUS.IDLE;

          if (readCacheStatus == READ_CACHE_STATUS.QUEUED
              || readCacheStatus == READ_CACHE_STATUS.VALID_QUEUED) {
            readCacheStatus = READ_CACHE_STATUS.QUEUE_COMPLETED;
            nanoTimeReadCacheValid = System.nanoTime();
          }

          if (writeCacheStatus == WRITE_CACHE_STATUS.QUEUED) {
            writeCacheStatus = WRITE_CACHE_STATUS.IDLE;
            // Our write mode status should have been reported back to us
            assertTrue(!BuildConfig.DEBUG || i2cDevice.isI2cPortInWriteMode());
          }

          // --------------------------------------------------------------------------
          // That limits the number of states the caches can now be in

          assertTrue(
              !BuildConfig.DEBUG
                  || (readCacheStatus == READ_CACHE_STATUS.IDLE
                      || readCacheStatus == READ_CACHE_STATUS.SWITCHINGTOREADMODE
                      || readCacheStatus == READ_CACHE_STATUS.VALID_ONLYONCE
                      || readCacheStatus == READ_CACHE_STATUS.QUEUE_COMPLETED));
          assertTrue(
              !BuildConfig.DEBUG
                  || (writeCacheStatus == WRITE_CACHE_STATUS.IDLE
                      || writeCacheStatus == WRITE_CACHE_STATUS.DIRTY));

          // --------------------------------------------------------------------------
          // Complete any read mode switch if there is one

          if (readCacheStatus == READ_CACHE_STATUS.SWITCHINGTOREADMODE) {
            // We're trying to switch into read mode. Are we there yet?
            if (i2cDevice.isI2cPortInReadMode()) {
              // See also below XYZZY
              readCacheStatus = READ_CACHE_STATUS.QUEUED;
              setActionFlag = true; // actually do an I2C read
              queueRead = true; // read the I2C read results
            } else {
              queueRead = true; // read the mode byte
            }
          }

          // --------------------------------------------------------------------------
          // If there's a write request pending, and it's ok to issue the write, do so

          else if (writeCacheStatus == WRITE_CACHE_STATUS.DIRTY) {
            issueWrite();

            // Our ordering rules are that any reads after a write have to wait until
            // the write is actually sent to the hardware, so anything we've read before is junk.
            // Note that there's an analogous check in read().
            readCacheStatus = READ_CACHE_STATUS.IDLE;
          }

          // --------------------------------------------------------------------------
          // Initiate reading if we should. Be sure to honor the policy of the read mode

          else if (readCacheStatus == READ_CACHE_STATUS.IDLE || readWindowChanged) {
            if (readWindow != null && readWindow.isOkToRead()) {
              // We're going to read from this window. If it's an only-once, then
              // ensure we don't come down this path again with the same ReadWindow instance.
              readWindow.setReadIssued();

              // You know...we might *already* have set up the controller to read what we want.
              // Maybe the previous read was a one-shot, for example.
              if (readWindowSentToController != null
                  && readWindowSentToController.contains(readWindow)
                  && i2cDevice.isI2cPortInReadMode()) {
                // Lucky us! We can go ahead and queue the read right now!
                // See also above XYZZY
                readWindowActuallyRead = readWindowSentToController;
                readCacheStatus = READ_CACHE_STATUS.QUEUED;
                setActionFlag = true; // actually do an I2C read
                queueRead = true; // read the results of the read
              } else {
                // We'll start switching now, and queue the read later
                readWindowActuallyRead = readWindow;
                startSwitchingToReadMode(readWindow);
              }
            } else {
              // There's nothing to read. Make *sure* we are idle.
              readCacheStatus = READ_CACHE_STATUS.IDLE;
            }

            readWindowChanged = false;
          }

          // --------------------------------------------------------------------------
          // Reissue any previous read if we should. The only way we are here and
          // see READ_CACHE_STATUS.QUEUE_COMPLETED is if we completed a queuing operation
          // above.

          else if (readCacheStatus == READ_CACHE_STATUS.QUEUE_COMPLETED) {
            if (readWindow != null && readWindow.isOkToRead()) {
              readCacheStatus = READ_CACHE_STATUS.VALID_QUEUED;
              setActionFlag = true; // actually do an I2C read
              queueRead = true; // read the results of the read
            } else {
              readCacheStatus = READ_CACHE_STATUS.VALID_ONLYONCE;
            }
          }

          // --------------------------------------------------------------------------
          // Completing the possibilities:

          else if (readCacheStatus == READ_CACHE_STATUS.VALID_ONLYONCE) {
            // Just leave it there until someone reads it
          }

          // ----------------------------------------------------------------------------------
          // Ok, after all that we finally know what how we're required to
          // interact with the device controller according to what we've been
          // asked to read or write. But what, now, about heartbeats?

          if (!setActionFlag && heartbeatRequired) {
            if (heartbeatAction != null) {
              if (readWindowSentToController != null && heartbeatAction.rereadLastRead) {
                // Controller is in or is switching to read mode. If he's there
                // yet, then issue an I2C read; if he's not, then he soon will be.
                if (i2cDevice.isI2cPortInReadMode()) {
                  setActionFlag = true; // issue an I2C read
                } else {
                  assertTrue(
                      !BuildConfig.DEBUG
                          || readCacheStatus == READ_CACHE_STATUS.SWITCHINGTOREADMODE);
                }
              } else if (readWindowSentToControllerInitialized
                  && readWindowSentToController == null
                  && heartbeatAction.rewriteLastWritten) {
                // Controller is in write mode, and the write cache has what we last wrote
                queueFullWrite = true;
                setActionFlag = true; // issue an I2C write
              } else if (heartbeatAction.heartbeatReadWindow != null) {
                // The simplest way to do this is just to do a new read from the outside, as that
                // means it has literally zero impact here on our state machine. That unfortunately
                // introduces concurrency where otherwise none might exist, but that's ONLY if you
                // choose this flavor of heartbeat, so that's a reasonable tradeoff.
                final ReadWindow window =
                    heartbeatAction
                        .heartbeatReadWindow; // capture here while we still have the lock
                Thread thread =
                    new Thread(
                        new Runnable() {
                          @Override
                          public void run() {
                            try {
                              I2cDeviceClient.this.read(window.getIregFirst(), window.getCreg());
                            } catch (Exception e) // paranoia
                            {
                              // ignored
                            }
                          }
                        });
                // Start the thread a-going. It will run relatively quickly and then shut down
                thread.setName("I2C heartbeat read thread");
                thread.setPriority(heartbeatAction.explicitReadPriority);
                thread.start();
              }
            }
          }

          if (setActionFlag) {
            // We're about to communicate on I2C right now, so reset the heartbeat.
            // Note that we reset() *before* we talk to the device so as to do
            // conservative timing accounting.
            timeSinceLastHeartbeat.reset();
          }
        } else if (caller == UPDATE_STATE_MACHINE.FROM_USER_WRITE) {
          // There's nothing we know to do that would speed things up, so we
          // just do nothing here and wait until the next portIsReady() callback.
        }

        // ----------------------------------------------------------------------------------
        // Read, set action flag and / or queue to module as requested

        if (setActionFlag) i2cDevice.setI2cPortActionFlag();
        else clearActionFlag();

        if (setActionFlag && !queueFullWrite) {
          i2cDevice.writeI2cPortFlagOnlyToController();
        } else if (queueFullWrite) {
          i2cDevice.writeI2cCacheToController();
          //
          if (modeCacheStatus == MODE_CACHE_STATUS.DIRTY)
            modeCacheStatus = MODE_CACHE_STATUS.QUEUED;
        }

        // Queue a read after queuing any write for a bit of paranoia: if we're mode switching
        // to write, we want that write to go out first, THEN read the mode status. It probably
        // would anyway, but why not...
        if (queueRead) {
          i2cDevice.readI2cCacheFromController();
        }

        // ----------------------------------------------------------------------------------
        // Do logging

        if (loggingEnabled) {
          StringBuilder message = new StringBuilder();

          switch (caller) {
            case FROM_CALLBACK:
              message.append(String.format("cyc %d", hardwareCycleCount));
              break;
            case FROM_USER_WRITE:
              message.append(String.format("usr write"));
              break;
          }
          if (setActionFlag) message.append("|flag");
          if (setActionFlag && !queueFullWrite) message.append("|f");
          else if (queueFullWrite) message.append("|w");
          else message.append("|.");
          if (queueRead) message.append("|r");
          if (readCacheStatus != prevReadCacheStatus)
            message.append(
                "| R." + prevReadCacheStatus.toString() + "->" + readCacheStatus.toString());
          if (writeCacheStatus != prevWriteCacheStatus)
            message.append(
                "| W." + prevWriteCacheStatus.toString() + "->" + writeCacheStatus.toString());
          // if (modeCacheStatus != prevModeCacheStatus)   message.append("| M." +
          // prevModeCacheStatus.toString() + "->" + modeCacheStatus.toString());
          if (enabledWriteMode)
            message.append(String.format("| setWrite(0x%02x,%d)", iregWriteFirst, cregWrite));
          if (enabledReadMode)
            message.append(
                String.format(
                    "| setRead(0x%02x,%d)", readWindow.getIregFirst(), readWindow.getCreg()));

          log(Log.DEBUG, message.toString());
        }

        // ----------------------------------------------------------------------------------
        // Notify anyone blocked in read() or write()
        callbackLock.notifyAll();
      }
    }