///
/**
* Async TypeScript MicroPython interface (for serial and network connections, REPL & WebREPL)
*
* - License: MIT
* - Repository: https://github.com/metachris/micropython-ctl
* - Author: chris@linuxuser.at / https://twitter.com/metachris
*/
import WebSocket from 'isomorphic-ws';
import { Buffer } from 'buffer/';
import { InvalidPassword, CouldNotConnect, ScriptExecutionError } from './errors';
import { WEBSERVER_PORT } from './settings';
export { InvalidPassword, CouldNotConnect, ScriptExecutionError, WEBSERVER_PORT };
export declare enum ConnectionMode {
SERIAL = "SERIAL",
NETWORK = "NETWORK",
PROXY = "PROXY"
}
export declare enum ConnectionState {
CONNECTING = "CONNECTING",
OPEN = "OPEN",
CLOSED = "CLOSED"
}
export declare enum ReplMode {
TERMINAL = "TERMINAL",
SCRIPT_RAW_MODE = "SCRIPT_RAW_MODE",
GETVER_WAITING_RESPONSE = "GETVER_WAITING_RESPONSE",
PUTFILE_WAITING_FIRST_RESPONSE = "PUTFILE_WAITING_FIRST_RESPONSE",
PUTFILE_WAITING_FINAL_RESPONSE = "PUTFILE_WAITING_FINAL_RESPONSE"
}
export declare enum RawReplState {
WAITING_FOR_SCRIPT = "WAITING_FOR_SCRIPT",
SCRIPT_SENT = "SCRIPT_SENT",
SCRIPT_RECEIVING_RESPONSE = "SCRIPT_RECEIVING_RESPONSE",
SCRIPT_EXECUTED = "SCRIPT_EXECUTED"
}
declare enum RawReplReceivingResponseSubState {
SCRIPT_RECEIVING_OUTPUT = "SCRIPT_RECEIVING_OUTPUT",
SCRIPT_RECEIVING_ERROR = "SCRIPT_RECEIVING_ERROR",
SCRIPT_WAITING_FOR_END = "SCRIPT_WAITING_FOR_END"
}
declare type promiseResolve = (value: string | PromiseLike) => void;
declare type promiseReject = (reason: any) => void;
export interface DeviceState {
connectionMode: ConnectionMode;
connectionPath: string | null;
port: any;
ws: WebSocket | null;
wsConnectTimeout: NodeJS.Timeout | undefined;
wsConnectTimeoutTriggered: boolean;
connectionState: ConnectionState;
replMode: ReplMode;
replPassword: string;
replPromise: Promise | null;
replPromiseResolve: promiseResolve | null;
replPromiseReject: promiseReject | null;
rawReplState: RawReplState;
lastCommand: string;
inputBuffer: string;
errorBuffer: string;
broadcastCommandOutputAsTerminalData: boolean;
dataRawBuffer: Buffer;
isReadingUntil: boolean;
readUntilData: Buffer;
readUntilBuffer: Buffer;
readUntilTimeout: any;
readUntilPromise: Promise | null;
readUntilPromiseResolve: promiseResolve | null;
readUntilPromiseReject: promiseReject | null;
lastRunScriptTimeNeeded: number;
receivingResponseSubState: RawReplReceivingResponseSubState;
putFileSize: number;
putFileData: Uint8Array;
putFileName: string;
putFileDest: string;
}
export interface FileListEntry {
filename: string;
isDir: boolean;
size: number;
mTime: number;
sha256?: string;
}
/**
* Main class for a MicroPython device connection.
*
* See also https://github.com/metachris/micropython-ctl
*
* ```
* const micropython = new MicroPythonDevice()
*
* // Connect to micropython device over network
* await micropython.connectNetwork('DEVICE_IP', 'WEBREPL_PASSWORD')
*
* // Or connect to micropython device over serial interface
* await micropython.connectSerial('/dev/ttyUSB0')
*
* // Run a Python script and capture the output
* const output = await micropython.runScript('import os; print(os.listdir())')
*
* // List all files in the root
* const files = await micropython.listFiles()
* ```
*/
export declare class MicroPythonDevice {
private state;
/**
* Callback that is triggered when the connection is lost or closed.
*
* ```typescript
* micropython.onclose = () => console.log('connection closed')
* ```
*/
onclose: () => void;
/**
* Callback to receive terminal (REPL) data
*
* ```typescript
* micropython.onTerminalData = (data) => process.stdout.write(data)
* ```
*/
onTerminalData: (data: string) => void;
constructor();
/**
* Whether currently connected to a device.
*/
isConnected(): boolean;
isSerialDevice(): boolean;
isProxyConnection(): boolean;
isTerminalMode(): boolean;
/**
* Get the state object. Mostly used for debugging purposes.
*/
getState(): DeviceState;
private createReplPromise;
/** The internal webserver is used to proxy runScript commands over an existing connection */
startInternalWebserver(): Promise;
private connectProxy;
/**
* Connect to a device over the serial interface
*
* @param path Serial interface (eg. `/dev/ttyUSB0`, `/dev/tty.SLAB_USBtoUART`, ...)
* @throws {CouldNotConnect} Connection failed
*/
connectSerial(path: string): Promise;
/**
* Connect to a device over the network (requires enabled WebREPL)
*
* @param host IP address or hostname
* @param password webrepl password
* @param timeoutSec Connection timeout (default: 5 sec). To disable, set to 0
* @throws {CouldNotConnect} Connection failed
*/
connectNetwork(host: string, password: string, timeoutSec?: number): Promise;
/**
* Handle special WebREPL only commands data
*
* getver, putfile, getfile
*/
private handlProtocolSpecialCommandsOutput;
private handleWebsocketMessage;
private clearBuffer;
/**
* Returns a promise that is resolved if `data` is received within `timeout` seconds,
* otherwise rejected
*/
private readUntil;
/**
* Handle incoming data
*/
private handleProtocolData;
sendData(data: string | Buffer | ArrayBuffer): void;
private serialSendData;
private wsSendData;
disconnect(): Promise;
private closeWebsocket;
/**
* Execute a Python script on the device, wait and return the output.
*
* @param script the python code
* @param options
*
* @throws {ScriptExecutionError} on Python code execution error. Includes the Python traceback.
*/
runScript(script: string, options?: RunScriptOptions): Promise;
private enterRawRepl;
private exitRawRepl;
/**
* GET_VER webrepl command. Returns the micropython version.
* Only works with network connections, not with serial.
*/
getVer(): Promise;
listFiles(directory?: string, options?: ListFilesOptions): Promise;
/**
* Get the contents of a file.
*
* ```typescript
* const data = await micropython.getFile('boot.py')
* ```
*
* @returns {Buffer} contents of the file in a Buffer
* @param filename filename of file to download
* @throws {ScriptExecutionError} if not found: "`OSError: [Errno 2] ENOENT`"
*/
getFile(filename: string): Promise;
statPath(path: string): Promise<{
exists: boolean;
isDir: boolean;
size: number;
}>;
/**
*
* @param name
* @throws {ScriptExecutionError}
*/
mkdir(name: string): Promise;
/**
* Uploading data to the device, saving as a file.
*
* We break the buffer into multiple chunks, and execute a raw repl command for each of them
* in order to not fill up the device RAM.
*
* See also:
* - https://github.com/dhylands/rshell/blob/master/rshell/main.py#L1079
* - https://github.com/scientifichackers/ampy/blob/master/ampy/files.py#L209
*
* @param targetFilename
* @param data
*/
putFile(targetFilename: string, data: Buffer, options?: PutFileOptions): Promise;
/**
* Remove a file or directory. Optional recursively
*
* @param path
* @param recursive default: false
*
* @throws {ScriptExecutionError} if not found: "OSError: [Errno 2] ENOENT"
* @throws {ScriptExecutionError} if directory not empty: "OSError: 39"
*/
remove(path: string, recursive?: boolean): Promise;
/**
* Rename a file or directory (uos.rename)
*
* @throws {ScriptExecutionError} if not found: "OSError: [Errno 2] ENOENT"
* @throws {ScriptExecutionError} if directory not empty: "OSError: 39"
*/
rename(oldPath: string, newPath: string): Promise;
/**
* Reset a device.
*/
reset(options?: ResetOptions): Promise;
/**
* Get SHA256 hash of a file contents.
*
* ```typescript
* const data = await micropython.getFileHash('boot.py')
* ```
*
* @param filename filename of target file
* @returns sha256 hash, hexlified
* @throws {ScriptExecutionError} if not found: "`OSError: [Errno 2] ENOENT`"
*/
getFileHash(filename: string): Promise;
/**
* Check whether a file is the same as provided, within a single `runScript` execution.
* Does not work in browser.
*
* - If filesize is different, then file is different
* - If filesize equal then compare sha256 hash
*
* This is a helper for bulk uploading directories, but only if they have changed.
*
* @throws {ScriptExecutionError} if not found: "OSError: [Errno 2] ENOENT"
*/
isFileTheSame(filename: string, data: Buffer): Promise;
/**
* Get information about the board.
*
* ```typescript
* const boardInfo = await micropython.getBoardInfo()
* console.log(boardInfo)
*
* {
* sysname: 'esp32',
* nodename: 'esp32',
* release: '1.13.0',
* version: 'v1.13 on 2020-09-02',
* machine: 'ESP32 module with ESP32',
* uniqueId: 'c44f3312f529',
* memFree: 108736,
* fsBlockSize: 4096,
* fsBlocksTotal: 512,
* fsBlocksFree: 438
* }
* ```
*/
getBoardInfo(): Promise;
gcCollect(): Promise;
}
export interface ListFilesOptions {
recursive?: boolean;
includeSha256?: boolean;
}
export interface RunScriptOptions {
/** Whether unnecessary indentation should be kept in the script (default: `false`) */
disableDedent?: boolean;
/** Whether to stay in RAW repl mode after execution. Useful for large scripts that need to get executed piece by piece (uploading files, etc.) (default: `false`) */
stayInRawRepl?: boolean;
/** Whether to broadcast the received data to the {@linkCode MicroPythonDevice.onTerminalData} callback while receiving (default: `false`) */
broadcastOutputAsTerminalData?: boolean;
/** Whether to resolve the promise before waiting for the result (default: `false`) */
resolveBeforeResult?: boolean;
runGcCollectBeforeCommand?: boolean;
}
export interface ResetOptions {
softReset?: boolean;
broadcastOutputAsTerminalData?: boolean;
}
export interface PutFileOptions {
checkIfSimilarBeforeUpload?: boolean;
}
export interface BoardInfo {
sysname: string;
nodename: string;
release: string;
version: string;
machine: string;
uniqueId: string;
memFree: number;
fsBlockSize: number;
fsBlocksTotal: number;
fsBlocksFree: number;
}