Getting a DIGITAL_IN callback to work

Can’t get the callback buried in Jaws class to work.
[Note the IOExtender class controlling an MCP23008 I2C I/O Extender and BlinkM code removed for brevity.]

Circuit consists of a wtv020m01 Sound playback module controlled by the Voice class.
A servo is controlled by the Jaws class. The eventual goal is to have the servo sweep when it sees the busy (playing a sound) signal of the sound module.

What works:
Servo by itself can be positioned at will via the agent.
Sound module works and using an LED I can see the busy signal go high and low.

What doesn’t work:
Feeding the busy signal to the pin
Putting a high signal on the pin I use for detecting the busy signal.
I see the high signal at the pin measured with a multimeter

What I have tried in the code:
Moving the pin around - look for the “earPin” variable
Changing DIGITAL_IN to DIGITAL_IN_PULLDOWN

What I observed which is weird:
After downlading new code, it appears the lipSync call back is called. I never see any other debug from that callback again:
2014-06-08 17:55:09 UTC-4 [Status] Device booting; 17.39% program storage used ...SNIP... 2014-06-08 17:55:11 UTC-4 [Device] Lip Syncing 0 2014-06-08 17:55:20 UTC-4 [Device] Servo Position: 0.11375 2014-06-08 17:55:33 UTC-4 [Device] Servo Position: 0.04 2014-06-08 17:56:01 UTC-4 [Device] Sound Command Sent 13 2014-06-08 17:56:01 UTC-4 [Device] Sync Played Voice 13 2014-06-08 17:56:37 UTC-4 [Device] Servo Position: 0.091625 2014-06-08 17:56:53 UTC-4 [Device] Sound Command Sent 13 2014-06-08 17:56:55 UTC-4 [Device] Sync Played Voice 13 2014-06-08 17:56:55 UTC-4 [Device] Servo Position: 0.091625

`
// Class for the sound board.
class Voice {

_clockPin = null
_dataPin = null
_resetPin = null

constructor(clockPin, dataPin, resetPin) {
_clockPin = clockPin
_clockPin.configure(DIGITAL_OUT)
_clockPin.write(1); // Initialize clock high to avoid false reading data
_dataPin = dataPin
_dataPin.configure(DIGITAL_OUT)
_resetPin = resetPin
_resetPin.configure(DIGITAL_OUT)
}

// SOUNDCARD Related Controls
function reset() {
server.log(“Reset Sound”)
_resetPin.write(1)
imp.sleep(0.100) // Pulse the Reset Pin for 100 ms
_resetPin.write(0)
}

function playVoice(voiceNumber) {
sendCommand(voiceNumber)
server.log("Sync Played Voice " + voiceNumber)
}

function asyncPlayVoice(voiceNumber){
server.log("Async Playing Voice " + voiceNumber)
}

function stopVoice(){
sendCommand(STOP)
server.log(“Stopped Voice”)
}

function pauseVoice(){
sendCommand(PLAY_PAUSE)
server.log(“Paused Voice”)
}

function mute(){
sendCommand(VOLUME_MIN)
server.log(“Muted Voice”)
}

function unmute(){
sendCommand(VOLUME_MAX)
server.log(“Unmuted Voice”)
}

// The guts of the interface is the timing related to sending
// the voice or special case command
function sendCommand(command){
local iCommand = command.tointeger()
//Start bit Low level pulse.
_clockPin.write(0);
imp.sleep(0.020); // 20 milli seconds

for (local mask = 0x8000; mask > 0; mask = mask >> 1) {
  //Clock low level pulse.
  _clockPin.write(0)
  imp.sleep(0.000050) // 50 micro seconds
  //Write data setup.
  if (iCommand & mask) {
    _dataPin.write(1)
  }
  else {
    _dataPin.write(0)
  }
  
  //Write data hold.
  imp.sleep(0.000050) // 50 micro seconds

  //Clock high level pulse.
  _clockPin.write(1)
  imp.sleep(0.000100) // 100 micro seconds

  if (mask>0x0001){
    //Stop bit high level pulse.
    imp.sleep(0.002) // 2 milli seconds
  }
}
//Busy active high from last data bit latch.
imp.sleep(0.020) // 20 milli seconds

server.log("Sound Command Sent " + command);

}

}

// Class for the servo motor
class Jaws {
_mouthPin = null // Controls the Server
_earPin = null // Listens for when sounds is being generated
_position = 20

_resetPin = null
_clockPin = null
_dataPin = null
constructor(mouthPin, earPin) {
_mouthPin = mouthPin
_mouthPin.configure(PWM_OUT, 0.02, SERVO_MIN)
_earPin = earPin
_earPin.configure(DIGITAL_IN, this.lipSync())
}

//SERVO Related Commands

// Servo position function that expects a value between 0 and 100
function SetServoPercent(value) {
local scaledValue = ((value.tofloat() / 100.0) * (SERVO_MAX-SERVO_MIN)) + SERVO_MIN
_mouthPin.write(scaledValue)
server.log("Servo Position: " + scaledValue)
}

// Sweep the servo back and forward until the fat monkey stops singing
function lipSync() {
local busyState = _earPin.read()
server.log("Lip Syncing "+busyState)
while ( busyState == 1) {
// Invert the position
_position = 100.0 - _position
SetServoPercent(_position)
// Wait:
imp.sleep(0.5)
busyState = _earPin.read()
server.log("Lip cycle "+busyState)
}
}

}

// Class for BlinkM I2C RGB Device control
class BlinkM {
// Snipped//
}

// Configure i2c bus that will control the BlinkM RGB LED
hardware.i2c89.configure(CLOCK_SPEED_100_KHZ)

// Create BlinkM object using I2C communications and the BlinkM bus address
blinkM <- BlinkM(hardware.i2c89, 0x09)

// Create i/o port instances (note: each device on the same bus should have a different device address)
IOExtender <- MCP23008(hardware.i2c89, 0) // pinstrapped to device address 0

// Create the output address for the cymbols motor
cymbals <- IOExtender.pin5
cymbals.configure(DIGITAL_OUT)

// Create the addresses used by the sound board
clockPin <- hardware.pin2
dataPin <- hardware.pin5
resetPin <- IOExtender.pin4
monkeyVoice <- Voice(clockPin,dataPin,resetPin)

// Create the addresses used by the servo
mouthPin <- hardware.pin1
earPin <- hardware.pin7
monkeyMouth <- Jaws(mouthPin, earPin)

// Function to turn on the ChaosMonkey for a period of seconds
function BangCymbols(chaosTime) {
server.log(“Create chaos for: " + chaosTime + " seconds”)
cymbals.write(1)
imp.sleep(chaosTime)
cymbals.write(0)
}

// Function to play a voice on the sound board
function Play(voiceNumber) {
monkeyVoice.playVoice(voiceNumber);
}

// Function to set the color of the ChaosMonkey’s rear end
function SetColor(colorString) {
blinkM.set(colorString)
}

// Servo position function that expects a value between 0 and 100
function SetServoPercent(value) {
monkeyMouth.SetServoPercent(value)
}

// Register a handler for messages from the agent.
agent.on(“color”, SetColor)
agent.on(“chaosTime”, BangCymbols)
agent.on(“mouth”, SetServoPercent)
agent.on(“play”,Play)

`

_earPin.configure(DIGITAL_IN, this.lipSync())

That line calls the function libSync and assigns its result as the callback. (Which should give an error, “Callback must be a function” or suchlike.) What you meant is

_earPin.configure(DIGITAL_IN, this.libSync.bindenv(this))

which assigns the function as the callback. There’s also going to be a problem with the implementation of lipSync: if the busyState stays high for too long, the lengthy blocking loop in that function will cause the imp to lose contact with the server (maybe that’s why you didn’t see the error message). You should do your servo sweeps in an imp.wakeup() handler instead.

Peter

Peter,
Thanks! Obviously I’m using an old mental model of function pointers. Thanks for pointing out the timer issue. After the hacking phase (should be at an end thanks to you) I will probably need to rethink to minimize power all over the place.

Regards
Guy

This worked but no idea how I would have figured that out from the IMP documentation. I guess you have to study more on Squirrel’s default behavior when it sees an expression that is a function.

Thanks again!

The important bit is the difference in behaviour between an expression that calls a function: this.lipSync() and an expression which just names a function: this.lipSync. This difference exists in both C and Javascript too: in C, you have to call qsort(…,myfunc); and not qsort(…,myfunc()).

It’s slightly complicated in Squirrel in that if you’d just used this.lipSync, your function would have got called but with the wrong environment, the wrong “this” object. To arrange that the caller’s “this” is also used as the callback’s “this”, you need to use bindenv, which works like bind() in Javascript or std::bind in C++11.

Peter