Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow for different power off detection, and add tx2 power control override #90

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/features/power/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ For controlling the power on/off state of the DUT.

- `autokitRelay`
- `crelay`
- `jetsonTx2` (specifically for use with jetson Tx2 dev kit)
- `dummyPower`: for use when no device is connected
3 changes: 2 additions & 1 deletion lib/features/power/implementations/autokit-relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ They used a HID interface and are controllable via:

- `POWER_RELAY_SERIAL`: the serial of the relay. An example of how to obtain that is here: https://github.com/darrylb123/usbrelay?tab=readme-ov-file#usage
- `POWER_RELAY_NUM`: for specifying which channel of the relay is being used for the power control of the DUT in the case of multiple channels being on the relay. Default is `0` which leads to all channels being toggled - actual channel numbers start at `1`
- `USB_RELAY_CONN`: `NO` or `NC` - for selecting if the normally open or closed configuration is used - default is `NO` and recommended
- `USB_RELAY_CONN`: `NO` or `NC` - for selecting if the normally open or closed configuration is used - default is `NO` and recommended
- `GPIO_POWER_DET` : (Tx2 specific) - for selecting the PGIO pin to be used for power state detection
8 changes: 4 additions & 4 deletions lib/features/power/implementations/autokit-relay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
const USBRelay = require("@balena/usbrelay");

export class AutokitRelay implements Power {
private relayId: string
private powerRelay: any
private conn: boolean
private relayNum: number
public relayId: string
public powerRelay: any
public conn: boolean
public relayNum: number
constructor(){
this.relayId = process.env.POWER_RELAY_SERIAL || 'HURTM'
this.relayNum = Number(process.env.POWER_RELAY_NUM || '0')
Expand Down
66 changes: 66 additions & 0 deletions lib/features/power/implementations/autokit-relay/jetson-tx2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { delay } from 'bluebird';
import { AutokitRelay } from "./";
import { exec } from 'mz/child_process';

// Device specific override for Tx2 power on / off sequencing
// Assumes this USB relay is connected to J6, NO
export class JetsonTx2Power extends AutokitRelay {
// Only way of determining Tx2 power state is checking the ethernet carrier state
public wiredIface: string;

constructor(){
super();
this.wiredIface = process.env.WIRED_IF || 'eth1';
}

async checkDutPower() {
const [stdout, _stderr] = await exec(`cat /sys/class/net/${this.wiredIface}/carrier`);
const file = stdout.toString();
if (file.includes('1')) {
console.log(`DUT is currently On`);
return true;
} else {
console.log(`DUT is currently Off`);
return false;
}
}

// Power on the DUT
async on(): Promise<void> {
// Trigger power on via a short button press - by toggling on, waiting, then toggling off
console.log(`Triggering TX2 power on sequence...`);
await delay(1000);
await super.on();
await delay(3000);
await super.off();
await delay(3000);
console.log(`Triggered power on sequence on TX2!`);
}

// Power off the DUT
async off(): Promise<void> {
// Trigger power off via a short button press - by toggling on, waiting, then toggling off
// If the DUT is already off, this will actually power the board back on - leading to potentially strange states during the provisioning
console.log(`powerOff - Will turn off TX2`);
const dutIsOn = await this.checkDutPower();
if (dutIsOn) {
console.log('TX2 is booted, trigger normal shutdown');
// Simulate short press on the power button
await delay(1000);
await super.on();
await delay(10 * 1000);
await super.off()

console.log(`Triggered power off sequence on TX2`);
await delay(1000);

/* Ensure device is off */
const dutIsOn = await this.checkDutPower();
if (dutIsOn) {
console.log('WARN: Triggered force shutdown but TX2 did not power off');
}
} else {
console.log('TX2 is not booted, no power toggle needed');
}
}
}
2 changes: 2 additions & 0 deletions lib/features/power/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { AutokitRelay } from "./implementations/autokit-relay";
import { DummyPower } from "./implementations/dummy-power";
import { Crelay } from "./implementations/crelay";
import { JetsonTx2Power } from "./implementations/autokit-relay/jetson-tx2";

const powerImplementations: {[key: string]: Type<Power> } = {
autokitRelay: AutokitRelay,
dummyPower: DummyPower,
crelay: Crelay,
jetsonTx2: JetsonTx2Power
};

export { powerImplementations }
20 changes: 16 additions & 4 deletions lib/features/serial/implementations/ftdi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ const SerialPort = require('serialport');

export class Ftdi implements Serial {
public DEV_SERIAL = '/dev/ttyUSB0' || process.env.DEV_SERIAL;
public BAUD_RATE = Number(process.env.BAUD_RATE || 115200);
public serial : any;
constructor(baudRate = 115200){
constructor(){
this.serial = new SerialPort(
this.DEV_SERIAL,
{
baudRate: baudRate,
baudRate: this.BAUD_RATE,
autoOpen: false,
});

Expand All @@ -18,9 +19,20 @@ export class Ftdi implements Serial {

async open(){
if(this.serial.isOpen){
console.log(`Serial already open!`)
console.log(`Serial already open!`);
return this.serial;
} else {
this.serial.open();
try{
console.log(`Opening Serial port ${this.DEV_SERIAL} with baud rate: ${this.BAUD_RATE}`)
this.serial.open(() => {
console.log('DUT serial is opened');
this.serial?.flush();
})
return this.serial;
} catch(e){
console.log(e)
}

}
}

Expand Down
9 changes: 9 additions & 0 deletions lib/flashing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ There are multiple possibilities here:
- If you do that, use `generic-flasher-boot-switch`.
- if testing balenaOS - flash the internal storage of the device once manually. Then leave the switch in the internal storage position. Then connect it to the autokit. Future boots, the device will automatically boot from a flasher image on the SD card if present, or internal storage otherwise. This method requires no automated boot switch toggling, but may be weak for testing BSP updates

#### Power of detection for flasher images

There are currently 2 ways to detect that a device running the flasher image has powered down after internal flashing:

1. `ethernet`: (default) The ethernet carrier signal is used - the autokit will use the interface configured via the `WIRED_IF` env variable. `POWER_OFF_TIMEOUT` can be used to control the number of cycles to wait and check the signal.
2. `serial`: The serial output of the DUT is monitored for a message `reboot: Power down` , configurable via the `POWER_OFF_MESSAGE` env variable. The interface defined via the `DEV_SERIAL` will be used, and this requires the `ftdi` serial implementation to be used. `POWER_OFF_TIMEOUT` can be used to set a timeout in ms (default 5 mins).

The method is selectable via `POWER_OFF_SELECTOR`, with `ethernet` or `serial`

### USB-BOOT / raspberry pi compute module devices (CM3/CM4)

For raspberry pi compute module devices, the autokit can bring them up into mass storage mode then write the OS image.
Expand Down
100 changes: 3 additions & 97 deletions lib/flashing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as sdk from 'etcher-sdk';
import * as fs from 'fs/promises';
import { BlockDeviceAdapter } from 'etcher-sdk/build/scanner/adapters';
import { Autokit } from '../';
import { waitForPowerOff } from './powerDetection';

/**
* Flash an image to a disk - this is the low level function used to flash a disk (SD card, USD storage device etc)
Expand Down Expand Up @@ -129,22 +130,6 @@ async function flashSD(filename: string, autoKit: Autokit){

}

/**
* Checks whether the DUT is powered using Ethernet carrier detection
**/
async function checkDutPower(autoKit:Autokit) {
const [stdout, _stderr] = await exec(`cat /sys/class/net/${autoKit.network.wiredIface}/carrier`);
const file = stdout.toString();
if (file.includes('1')) {
console.log(`DUT is currently On`);
return true;
} else {
console.log(`DUT is currently Off`);
return false;
}
}


const KEY_DELAY = Number(process.env.KEY_DELAY) || 500;
async function keyboardSequence(autoKit: Autokit, keyboard: [string]){
for(let key of keyboard){
Expand Down Expand Up @@ -181,53 +166,8 @@ async function flashFlasher(filename: string, autoKit: Autokit, jumper: boolean,

await autoKit.power.on();


await waitForPowerOff(autoKit);

// dut will now internally flash - need to wait until we detect DUT has powered off
// can be done through ethernet carrier signal, or through current measurement (or something else...)
// FOR NOW: use the network
// check if the DUT is on first
let dutOn = false;
while (!dutOn) {
console.log(`waiting for DUT to be on`);
dutOn = await checkDutPower(autoKit);
await delay(1000 * 5); // 5 seconds between checks
}
// once we confirmed the DUT is on, we wait for it to power down again, which signals the flashing has finished
// wait initially for 60s and then every 10s before checking if the board performed a shutdown after flashing the internal storage
const POLL_INTERVAL = 1000; // 1 second
const POLL_TRIES = 20; // 20 tries
const TIMEOUT_COUNT = 30;
let attempt = 0;
await delay(1000 * 60);
while (dutOn) {
await delay(1000 * 10); // 10 seconds between checks
console.log(`waiting for DUT to be off`);
dutOn = await checkDutPower(autoKit);
// occasionally the DUT might appear to be powered down, but it isn't - we want to confirm that the DUT has stayed off for an interval of time
if (!dutOn) {
let offCount = 0;
console.log(`detected DUT has powered off - confirming...`);
for (let tries = 0; tries < POLL_TRIES; tries++) {
await delay(POLL_INTERVAL);
dutOn = await checkDutPower(autoKit);
if (!dutOn) {
offCount += 1;
}
}
console.log(
`DUT stayted off for ${offCount} checks, expected: ${POLL_TRIES}`,
);
if (offCount !== POLL_TRIES) {
// if the DUT didn't stay off, then we must try the loop again
dutOn = true;
}
}
attempt += 1;
if (attempt === TIMEOUT_COUNT){
throw new Error(`Timed out while trying to flash internal storage!!`)
}
}
console.log('Internally flashed - powering off DUT');
// power off and toggle mux.
await delay(1000*10);
Expand Down Expand Up @@ -527,41 +467,7 @@ async function flashJetson(filename: string, autoKit: Autokit, deviceType: strin

if(nvme){
// wait for jetson to power off
const POLL_INTERVAL = 1000; // 1 second
const POLL_TRIES = 20; // 20 tries
const TIMEOUT_COUNT = 30000;
let attempt = 0;
let dutOn = true;
await delay(1000 * 60);
while (dutOn) {
await delay(1000 * 10); // 10 seconds between checks
console.log(`waiting for DUT to be off`);
dutOn = await checkDutPower(autoKit);
// occasionally the DUT might appear to be powered down, but it isn't - we want to confirm that the DUT has stayed off for an interval of time
if (!dutOn) {
let offCount = 0;
console.log(`detected DUT has powered off - confirming...`);
for (let tries = 0; tries < POLL_TRIES; tries++) {
await delay(POLL_INTERVAL);
dutOn = await checkDutPower(autoKit);
if (!dutOn) {
offCount += 1;
}
}
console.log(
`DUT stayted off for ${offCount} checks, expected: ${POLL_TRIES}`,
);
if (offCount !== POLL_TRIES) {
// if the DUT didn't stay off, then we must try the loop again
dutOn = true;
}
}
attempt += 1;
if (attempt === TIMEOUT_COUNT){
await autoKit.power.off();
throw new Error(`Timed out while trying to flash internal storage!!. Powered off DUT.`)
}
}
await waitForPowerOff(autoKit);
console.log('Internally flashed - powering off DUT');
}

Expand Down
67 changes: 67 additions & 0 deletions lib/flashing/powerDetection/ethernet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Autokit } from '../../';
import { exec } from 'mz/child_process';
import { delay } from 'bluebird';


const TIMEOUT_COUNT = Number(process.env.POWER_OFF_TIMEOUT) || 30

/**
* Checks whether the DUT is powered using Ethernet carrier detection
**/
async function checkDutPower(autoKit:Autokit) {
const [stdout, _stderr] = await exec(`cat /sys/class/net/${autoKit.network.wiredIface}/carrier`);
const file = stdout.toString();
if (file.includes('1')) {
console.log(`DUT is currently On`);
return true;
} else {
console.log(`DUT is currently Off`);
return false;
}
}

export async function waitForPowerOffEthernet(autoKit:Autokit):Promise<void>{
// dut will now internally flash - need to wait until we detect DUT has powered off
// FOR NOW: use the network
// check if the DUT is on first
let dutOn = false;
while (!dutOn) {
console.log(`waiting for DUT to be on`);
dutOn = await checkDutPower(autoKit);
await delay(1000 * 5); // 5 seconds between checks
}

const POLL_INTERVAL = 1000; // 1 second
const POLL_TRIES = 20; // 20 tries
let attempt = 0;
await delay(1000 * 60);
while (dutOn) {
await delay(1000 * 10); // 10 seconds between checks
console.log(`waiting for DUT to be off`);
dutOn = await checkDutPower(autoKit);
// occasionally the DUT might appear to be powered down, but it isn't - we want to confirm that the DUT has stayed off for an interval of time
if (!dutOn) {
let offCount = 0;
console.log(`detected DUT has powered off - confirming...`);
for (let tries = 0; tries < POLL_TRIES; tries++) {
await delay(POLL_INTERVAL);
dutOn = await checkDutPower(autoKit);
if (!dutOn) {
offCount += 1;
}
}
console.log(
`DUT stayed off for ${offCount} checks, expected: ${POLL_TRIES}`,
);
if (offCount !== POLL_TRIES) {
// if the DUT didn't stay off, then we must try the loop again
dutOn = true;
}
}
attempt += 1;
if (attempt === TIMEOUT_COUNT){
await autoKit.power.off();
throw new Error(`Timed out while trying to flash internal storage!!. Powered off DUT.`)
}
}
}
15 changes: 15 additions & 0 deletions lib/flashing/powerDetection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Autokit } from "../..";
import { waitForPowerOffEthernet } from "./ethernet";
import { waitForPowerOffSerial } from "./serial";

const powerOffSelector = process.env.POWER_OFF_SELECTOR || 'ethernet'

const powerOffFunctions: {[key: string]: (autoKit: Autokit) => Promise<void> } = {
ethernet: waitForPowerOffEthernet,
serial: waitForPowerOffSerial,
};

const waitForPowerOff = powerOffFunctions[powerOffSelector];


export { waitForPowerOff }
Loading