Skip to content

Commit

Permalink
Implements Tessel.prototype.reboot(). Closes gh-188 (#202)
Browse files Browse the repository at this point in the history
* Implements Tessel.prototype.reboot(). Closes gh-188

- Closes ports before executing `reboot`

Signed-off-by: Rick Waldron <[email protected]>

* Adds SAMD21 resetting (#1)
  • Loading branch information
rwaldron authored Sep 14, 2016
1 parent 1c42cab commit a37b19c
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 64 deletions.
37 changes: 37 additions & 0 deletions node/tessel-export.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const PWM_MIN_FREQUENCY = 1;
const PWM_PRESCALARS = [1, 2, 4, 8, 16, 64, 256, 1024];
// Maximum number of unscaled ticks in a second (48 MHz)
const SAMD21_TICKS_PER_SECOND = 48000000;
// GPIO number of RESET pin
const SAMD21_RESET_GPIO = 39;

function Tessel(options) {
if (Tessel.instance) {
Expand Down Expand Up @@ -123,6 +125,41 @@ Tessel.prototype.open = function(portName) {
return this;
};

Tessel.prototype.reboot = function() {

this.close();

// When attempting to reboot, if the sockets
// are left open at the moment that `reboot`
// is executed, there will be a substantial
// delay before the actual reboot occurs.
// Polling for `destroyed` signals ensures
// that the sockets are closed before
// `reboot` is executed.
var pollUntilSocketsDestroyed = () => {
if (this.port.A.sock.destroyed &&
this.port.B.sock.destroyed) {

// Stop SPI communication between SAMD21 and MediaTek
childProcess.execSync('/etc/init.d/spid stop');

// Create a GPIO entry for the SAMD21 RESET pin
childProcess.execSync(`echo "${SAMD21_RESET_GPIO}" > /sys/class/gpio/export`);
// Make that GPIO an output
childProcess.execSync(`echo "out" > /sys/class/gpio/gpio${SAMD21_RESET_GPIO}/direction`);
// Pull the output low to reset the SAMD21
childProcess.execSync(`echo "0" > /sys/class/gpio/gpio${SAMD21_RESET_GPIO}/value`);

// Reboot the MediaTek
childProcess.execSync('reboot');
} else {
setImmediate(pollUntilSocketsDestroyed);
}
};

pollUntilSocketsDestroyed();
};

Tessel.prototype.pwmFrequency = function(frequency, cb) {
if (frequency < PWM_MIN_FREQUENCY || frequency > PWM_MAX_FREQUENCY) {
throw new RangeError(`pwmFrequency value must be between ${PWM_MIN_FREQUENCY} and ${PWM_MAX_FREQUENCY}`);
Expand Down
171 changes: 107 additions & 64 deletions node/test/unit/tessel.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,94 @@ exports['Tessel'] = {
test.done();
},

portsAliasToPort: function(test) {
test.expect(1);
test.equal(this.tessel.port, this.tessel.ports);
test.done();
},

twoPortsInitialized: function(test) {
test.expect(5);
test.equal(this.tessel.ports.A instanceof Tessel.Port, true);
test.equal(this.tessel.ports.B instanceof Tessel.Port, true);
test.equal(this.Port.callCount, 2);
test.deepEqual(this.Port.firstCall.args, ['A', '/var/run/tessel/port_a', this.tessel]);
test.deepEqual(this.Port.lastCall.args, ['B', '/var/run/tessel/port_b', this.tessel]);
test.done();
},

ledsLazyInitialization: function(test) {
test.expect(3);
test.equal(this.LED.callCount, 0);
// Trigger a [[Get]]
test.equal(this.tessel.led[0] instanceof Tessel.LED, true);
test.equal(this.LED.callCount, 1);
test.done();
},

ledsLazyInitializedAndOff: function(test) {
test.expect(5);
test.equal(this.tessel.led[0].value, 0);
test.equal(this.fsWrite.callCount, 1);
test.equal(this.fsWrite.lastCall.args[0], '/sys/devices/leds/leds/tessel:red:error/brightness');
test.equal(this.fsWrite.lastCall.args[1], '0');
test.equal(this.LED.callCount, 1);
test.done();
},

fourLEDsInitialized: function(test) {
test.expect(9);
test.equal(this.tessel.led[0] instanceof Tessel.LED, true);
test.equal(this.tessel.led[1] instanceof Tessel.LED, true);
test.equal(this.tessel.led[2] instanceof Tessel.LED, true);
test.equal(this.tessel.led[3] instanceof Tessel.LED, true);
test.equal(this.LED.callCount, 4);
test.deepEqual(
this.LED.getCall(0).args, ['red', '/sys/devices/leds/leds/tessel:red:error/brightness']
);
test.deepEqual(
this.LED.getCall(1).args, ['amber', '/sys/devices/leds/leds/tessel:amber:wlan/brightness']
);
test.deepEqual(
this.LED.getCall(2).args, ['green', '/sys/devices/leds/leds/tessel:green:user1/brightness']
);
test.deepEqual(
this.LED.getCall(3).args, ['blue', '/sys/devices/leds/leds/tessel:blue:user2/brightness']
);

test.done();
},

networkWifiInitialized: function(test) {
test.expect(1);
test.equal(this.tessel.network.wifi instanceof Tessel.Wifi, true);

test.done();
},

tesselVersion: function(test) {
test.expect(1);
test.equal(this.tessel.version, version);
test.done();
},
};

exports['Tessel.prototype'] = {
setUp: function(done) {
this.LED = sandbox.spy(Tessel, 'LED');
this.Port = sandbox.stub(Tessel, 'Port');
this.fsWrite = sandbox.stub(fs, 'writeFile');

this.tessel = new Tessel();
done();
},

tearDown: function(done) {
Tessel.instance = null;
sandbox.restore();
done();
},

closeBoth: function(test) {
test.expect(1);

Expand Down Expand Up @@ -183,74 +271,29 @@ exports['Tessel'] = {
test.done();
},

portsAliasToPort: function(test) {
test.expect(1);
test.equal(this.tessel.port, this.tessel.ports);
test.done();
},

twoPortsInitialized: function(test) {
test.expect(5);
test.equal(this.tessel.ports.A instanceof Tessel.Port, true);
test.equal(this.tessel.ports.B instanceof Tessel.Port, true);
test.equal(this.Port.callCount, 2);
test.deepEqual(this.Port.firstCall.args, ['A', '/var/run/tessel/port_a', this.tessel]);
test.deepEqual(this.Port.lastCall.args, ['B', '/var/run/tessel/port_b', this.tessel]);
test.done();
},

ledsLazyInitialization: function(test) {
test.expect(3);
test.equal(this.LED.callCount, 0);
// Trigger a [[Get]]
test.equal(this.tessel.led[0] instanceof Tessel.LED, true);
test.equal(this.LED.callCount, 1);
test.done();
},

ledsLazyInitializedAndOff: function(test) {
test.expect(5);
test.equal(this.tessel.led[0].value, 0);
test.equal(this.fsWrite.callCount, 1);
test.equal(this.fsWrite.lastCall.args[0], '/sys/devices/leds/leds/tessel:red:error/brightness');
test.equal(this.fsWrite.lastCall.args[1], '0');
test.equal(this.LED.callCount, 1);
test.done();
},

fourLEDsInitialized: function(test) {
test.expect(9);
test.equal(this.tessel.led[0] instanceof Tessel.LED, true);
test.equal(this.tessel.led[1] instanceof Tessel.LED, true);
test.equal(this.tessel.led[2] instanceof Tessel.LED, true);
test.equal(this.tessel.led[3] instanceof Tessel.LED, true);
test.equal(this.LED.callCount, 4);
test.deepEqual(
this.LED.getCall(0).args, ['red', '/sys/devices/leds/leds/tessel:red:error/brightness']
);
test.deepEqual(
this.LED.getCall(1).args, ['amber', '/sys/devices/leds/leds/tessel:amber:wlan/brightness']
);
test.deepEqual(
this.LED.getCall(2).args, ['green', '/sys/devices/leds/leds/tessel:green:user1/brightness']
);
test.deepEqual(
this.LED.getCall(3).args, ['blue', '/sys/devices/leds/leds/tessel:blue:user2/brightness']
);
reboot: function(test) {
test.expect(7);

test.done();
},
var close = sandbox.stub(this.tessel, 'close');
var execSync = sandbox.stub(childProcess, 'execSync');
var destroyed = true;

networkWifiInitialized: function(test) {
test.expect(1);
test.equal(this.tessel.network.wifi instanceof Tessel.Wifi, true);
this.tessel.port.A.sock = {
destroyed
};
this.tessel.port.B.sock = {
destroyed
};

test.done();
},
this.tessel.reboot();

tesselVersion: function(test) {
test.expect(1);
test.equal(this.tessel.version, version);
test.equal(close.callCount, 1);
test.equal(execSync.callCount, 5);
test.equal(execSync.getCall(0).args[0], '/etc/init.d/spid stop');
test.equal(execSync.getCall(1).args[0], 'echo "39" > /sys/class/gpio/export');
test.equal(execSync.getCall(2).args[0], 'echo "out" > /sys/class/gpio/gpio39/direction');
test.equal(execSync.getCall(3).args[0], 'echo "0" > /sys/class/gpio/gpio39/value');
test.equal(execSync.lastCall.args[0], 'reboot');
test.done();
},
};
Expand Down

0 comments on commit a37b19c

Please sign in to comment.