Advertisement
enbyted

MdbWrapper

May 2nd, 2019
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import { exec, ChildProcess } from 'child_process';
  2. import { write } from 'fs';
  3. import { EventEmitter } from 'events';
  4.  
  5. const ERRORS = {
  6.     get PROCESS_NOT_RUNNING() { return new Error("Process not running"); },
  7.     get CANNOT_STOP_MDB() { return new Error("Cannot stop MDB"); },
  8.     get TIMEOUT() { return new Error("timeout"); },
  9.     get DEVICE_NOT_SELECTED() { return new Error("Device not selected"); },
  10.     get TOOL_NOT_SELECTED() { return new Error("Tool not selected"); },
  11.     get PROGRAMMING_FAILED() { return new Error("Programming failed"); },
  12.     get IMAGE_NOT_LOADED() { return new Error("Image not loaded"); },
  13.     get DEVICE_RUNNING() { return new Error("Device running"); },
  14. };
  15.  
  16. const MDB_COMMANDS = {
  17.     QUIT: () => 'quit',
  18.     HALT: () => 'halt',
  19.     DEVICE: (name: string) => `Device ${name}`,
  20.     HW_TOOL: (id: string, justProgram: boolean, index?: number) => `HwTool ${id}${justProgram?" -p":""}${index?` ${index}`:''}`,
  21.     PROGRAM: (imageName: string) => `Program "${imageName}"`,
  22.     CHANGE_DIR: (dir: string) => `cd "${dir}"`,
  23.     RESET: () => 'Reset',
  24.     CONTINUE: () => 'Continue',
  25.     RUN: () => 'Run',
  26.     NEXT: () => 'Next', // Advance to next program line, skipping subroutine calls
  27.     STEP: () => 'Step', // Advance to different program line, following subroutine calls (only thouse with source line information)
  28.     STEPI: (count?: number) => `Stepi${count?` ${count}`:''}`, // Step one instruction
  29.     BACKTRACE: (full?: boolean) => `Backtrace${full?' full':''}`,
  30.     PRINT: (variable: string, format?: 'x' | 'd' | 'a', datasize?: 1 | 2 | 4) => (
  31.         `print${format?` /${format}`:''}${datasize?` /datasize:${datasize}`:''} ${variable}`
  32.     ),
  33.     PRINT_PIN: (name: string) => `Print pin ${name}`,
  34.     WRITE: (addr: number, data: number[], type?: 'r' | 'p' | 'm' | 'e') => (
  35.         `write${type?` ${type}`:''} ${addr}${data.reduce((val, prev) => `${prev} ${val}`, '')}`
  36.     ),
  37.     WRITE_PIN: (name: string, value: 'low' | 'high' | number) => (
  38.         `write pin ${name} ${(typeof value === 'string')?value:`${value}v`}`
  39.     ),
  40.     BREAK_FILE: (name: string, line: number, passCount?: number) => `break ${name}:${line}${passCount?` ${passCount}`:''}`,
  41.     BREAK_ADDRESS: (address: number, passCount?: number) => `break *0x${address.toString(16)}${passCount?` ${passCount}`:''}`,
  42.     BREAK_FUNCTION: (name: string, passCount?: number) => `break ${name}${passCount?` ${passCount}`:''}`,
  43. };
  44.  
  45. interface CancellableHandler<T> {
  46.     promise: Promise<T>;
  47.     cancel: () => void;
  48. }
  49.  
  50. /**
  51.  * Resolves or rejects with the provided promise or rejects with `ERRORS.TIMEOUT` on timeout.
  52.  *
  53.  * @param promise Promise to wait for
  54.  * @param ms Timeout duration in miliseconds or 0 to never timeout
  55.  */
  56. function waitForPromiseOrTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {
  57.     let timeout: NodeJS.Timeout;
  58.     const timeoutPromise = new Promise<T>((_, reject) => {
  59.         timeout = setTimeout(
  60.             () => (ms > 0) ? reject(ERRORS.TIMEOUT) : null,
  61.             ms
  62.         );
  63.     });
  64.     promise.finally(() => clearTimeout(timeout));
  65.  
  66.     return Promise.race([timeoutPromise, promise]);
  67. }
  68.  
  69. /**
  70.  * Resolves or rejects with the first handler to finish or rejects with `ERRORS.TIMEOUT` on timeout and
  71.  * automatically cancells all handlers upon resolving.
  72.  *
  73.  * @param ms Timeout duration in miliseconds or 0 to never timeout
  74.  * @param handlers Handler objects to wait for
  75.  */
  76. function raceForCancellableHandlersOrTimeout(ms: number, ...handlers: CancellableHandler<any>[]): Promise<void> {
  77.     let timeout: NodeJS.Timeout | undefined;
  78.     const timeoutPromise = new Promise((_, reject) => {
  79.         timeout = setTimeout(
  80.             () => (ms > 0) ? reject(ERRORS.TIMEOUT) : null,
  81.             ms
  82.         );
  83.     });
  84.     handlers.forEach(p => p.promise.finally(() => {
  85.         if (timeout) {
  86.             handlers.forEach(h => h.cancel());
  87.             clearTimeout(timeout);
  88.             timeout = undefined;
  89.         }
  90.     }));
  91.  
  92.     return Promise.race([timeoutPromise, ...(handlers.map(h => h.promise))]);
  93. }
  94.  
  95. /**
  96.  * Creates `CancellableEventHandlerObject` which resolves when provided event name is fired.
  97.  * Promise is resolved with the first argument passed by event emitter.
  98.  *
  99.  * @param emitter `EventEmitter` object to listen to
  100.  * @param eventName Name of event to listen to
  101.  * @param errorFactory If provided created promise will reject with error returned by factory instead of resolving
  102.  */
  103. function cancellableEventHandler<T = any>(emitter: EventEmitter, eventName: string, errorFactory?: (...args: any[]) => Error): CancellableHandler<T> {
  104.     let cancel : () => void = () => null;
  105.  
  106.     const promise = new Promise<T>((resolve, reject) => {
  107.         const handler = (errorFactory) ? ((...args: any[]) => reject(errorFactory(...args))) : (arg: T) => resolve(arg);
  108.         emitter.once(eventName, handler);
  109.         cancel = () => emitter.removeListener(eventName, handler);
  110.     });
  111.  
  112.     return { cancel, promise };
  113. }
  114.  
  115. /**
  116.  * Waits for eventName to happen and resolves with the first argument
  117.  * passed to the eventName callback or rejects when timemout happens first.
  118.  *
  119.  * @param emitter eventName emitter to listen to
  120.  * @param eventName Name of eventName to wait to
  121.  * @param timeoutMs Number of miliseconds to wait before timeout or 0 to wait indefinetly
  122.  */
  123. async function waitForEventOrTimeout<T = any>(emitter: EventEmitter, eventName: string, timeoutMs: number) {
  124.     const awaiter = cancellableEventHandler(emitter, eventName);
  125.  
  126.     if (timeoutMs === 0) {
  127.         return awaiter.promise;
  128.     }
  129.  
  130.     try {
  131.         return await waitForPromiseOrTimeout<T>(awaiter.promise, timeoutMs);
  132.     } finally {
  133.         awaiter.cancel();
  134.     }
  135. }
  136.  
  137. export interface HwTool {
  138.     name: string;
  139.     id: string;
  140.     index?: number;
  141. }
  142.  
  143. export interface HaltInfo {
  144.     breakpoint?: boolean;
  145.     address?: number;
  146.     file?: string;
  147.     line?: number;
  148. }
  149.  
  150. export interface BreakpointInfo {
  151.     id: number;
  152.     address?: number;
  153.     function?: string;
  154.     file?: {
  155.         name: string;
  156.         line: number;
  157.     };
  158. }
  159.  
  160. export const MDB_EVENTS = {
  161.     CONSOLE_DATA: 'console:data',
  162.     CONSOLE_WARNING: 'console:warning',
  163.     CONSOLE_ERROR: 'console:error',
  164.     CONSOLE_WRITE: 'console:write',
  165.     MDB_CHANGE_DIRECTORY: 'mdb:change_directory',
  166.     MDB_INITIALIZED: 'mdb:initialized',
  167.     MDB_STOP: 'mdb:stop',
  168.     DEVICE_HALT: 'device:halt',
  169.     DEVICE_RUNNING: 'device:running',
  170.     DEVICE_BREAKPOINT_SET: 'device:breakpoint:set',
  171.     DEVICE_PRORGRAMING_START: 'device:programing:start',
  172.     DEVICE_PRORGRAMING_SUCCESS: 'device:programing:success',
  173.     DEVICE_PRORGRAMING_FAIL: 'device:programing:fail',
  174. };
  175.  
  176. export class MdbWrapper extends EventEmitter {
  177.     // TODO: Find this path from Windows Registry or some other means on Linux.
  178.     private _mdbPath = '"C:\\Program Files (x86)\\Microchip\\MPLABX\\v5.05\\mplab_platform\\bin\\mdb.bat';
  179.     private _process ?: ChildProcess = undefined;
  180.     private _running = false;
  181.     private _initialized = false;
  182.     private _deviceSelected = false;
  183.     private _toolMode : 'NONE' | 'PROGRAM' | 'DEBUG' = 'NONE';
  184.     private _imageLoaded = false;
  185.     private _haltInfo: HaltInfo = {};
  186.     private _lastBreakpoint = -1;
  187.  
  188.     /**
  189.      * Waits for eventName to happen and resolves with the first argument
  190.      * passed to the eventName callback or rejects when timemout happens first.
  191.      *
  192.      * @param eventName name of eventName to listen to
  193.      * @param ms Number of miliseconds to wait before timeout or 0 to wait indefinetly
  194.      */
  195.     public async waitForEventOrTimeout<T = any>(eventName: string, ms: number) {
  196.         return waitForEventOrTimeout<T>(this, eventName, ms);
  197.     }
  198.  
  199.     /**
  200.      * (re)Starts mdb.bat process
  201.      * If already running stops the process.
  202.      *
  203.      * @param timeoutMs Number of miliseconds to wait for MDB to start or 0 to not wait
  204.      */
  205.     public async start(timeoutMs: number = 60000) {
  206.         if (this._process) {
  207.             await this.stop(timeoutMs);
  208.         }
  209.  
  210.         console.log("MDB starting");
  211.         this._running = false;
  212.         this._initialized = false;
  213.         this._deviceSelected = false;
  214.         this._toolMode = 'NONE';
  215.         this._imageLoaded = false;
  216.         this._process = exec(this._mdbPath);
  217.         this._process.on("error", this.onError);
  218.         this._process.on("exit", this.onExit);
  219.         this._process.stdout.on("data", this.onData);
  220.         this._process.stdout.on("data", (data) => {
  221.             const str = String(data).trim();
  222.             if (str.length > 0) {
  223.                 this.emit(MDB_EVENTS.CONSOLE_DATA, str);
  224.             }
  225.         });
  226.         this._process.stderr.on("data", (data) => {
  227.             const str = String(data).trim();
  228.             if (str.length > 0) {
  229.                 this.emit(MDB_EVENTS.CONSOLE_ERROR, str);
  230.             }
  231.         });
  232.        
  233.         await this.writeLn("set system.yestoalldialog true");
  234.  
  235.         if (timeoutMs > 0) {
  236.             await this.waitUntilInitialized(timeoutMs);
  237.         }
  238.     }
  239.  
  240.     /**
  241.      * Stops currently running process, gracefully if possible
  242.      *
  243.      * @param timeoutMs Number of miliseconds to wait for process to gracefully stop or 0 to wait indefinetly
  244.      */
  245.     public async stop(timeoutMs: number = 60000) {
  246.         if (! this._process) { return; }
  247.  
  248.         // If program is currently running MDB tends to hang when just `quit` is written
  249.         if (this._running) {
  250.             await this.writeLn(MDB_COMMANDS.HALT());
  251.             await this.waitForEventOrTimeout(MDB_EVENTS.DEVICE_HALT, timeoutMs);
  252.         }
  253.         await this.writeLn(MDB_COMMANDS.QUIT());
  254.  
  255.         try {
  256.             await this.waitForEventOrTimeout(MDB_EVENTS.MDB_STOP, timeoutMs);
  257.         } catch(e) {
  258.             console.warn("Cannot gracefuly stop the process", e);
  259.             if (this._process) {
  260.                 this._process.kill();
  261.                 try {
  262.                     await this.waitForEventOrTimeout(MDB_EVENTS.MDB_STOP, 1000);
  263.                 } catch(e) {
  264.                     console.error("Cannot forcefully stop the process", e);
  265.                 }
  266.             }
  267.         }
  268.  
  269.         // By now the process should have been set to undefined by onExit handler
  270.         if (this._process) {
  271.             throw ERRORS.CANNOT_STOP_MDB;
  272.         }
  273.     }
  274.  
  275.     /**
  276.      * Selects provided MCU for programming and debugging operations.
  277.      *
  278.      * @param device Part number of device to program i.e. PIC16F18857
  279.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  280.      */
  281.     public async selectDevice(device: string, timeoutMs: number = 60000) {
  282.         // TODO: Find better way of ensuring that device is selected
  283.         await this.writeLnAndWaitForEvent(
  284.             MDB_COMMANDS.DEVICE(device),
  285.             MDB_EVENTS.CONSOLE_DATA,
  286.             timeoutMs
  287.         );
  288.         this._deviceSelected = true;
  289.     }
  290.  
  291.     /**
  292.      * Initializes privided tool for programming or debugging operation depending on `justProgram` flag.
  293.      * This operation might involve changing tool's firmware and communicating with target.
  294.      * This must be called after `selectDevice` has been called.
  295.      *
  296.      * @param tool Tool to initialize
  297.      * @param justProgram If true tool is initialized for production programming operation, otherwise for debugging
  298.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  299.      */
  300.     public async selectHwTool(tool: HwTool, justProgram: boolean = false, timeoutMs: number = 60000) {
  301.         if (! this._deviceSelected) {
  302.             throw ERRORS.DEVICE_NOT_SELECTED;
  303.         }
  304.  
  305.         // TODO: Find better way of ensuring that the tool is ready
  306.         // As with any real tool a lot of data is printed before it's actually ready
  307.         await this.writeLnAndWaitForEvent(
  308.             MDB_COMMANDS.HW_TOOL(tool.id, justProgram, tool.index),
  309.             MDB_EVENTS.CONSOLE_DATA,
  310.             timeoutMs
  311.         );
  312.  
  313.         this._toolMode = justProgram ? 'PROGRAM' : 'DEBUG';
  314.     }
  315.  
  316.     /**
  317.      * Change current working directory
  318.      *
  319.      * @param newDirectory Relative or absolute path to new working directory
  320.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  321.      */
  322.     public async changeDirectory(newDirectory: string, timeoutMs: number = 60000) {
  323.         await this.writeLnAndWaitForEvent(
  324.             MDB_COMMANDS.CHANGE_DIR(newDirectory),
  325.             MDB_EVENTS.MDB_CHANGE_DIRECTORY,
  326.             timeoutMs
  327.         );
  328.     }
  329.  
  330.     /**
  331.      * Programs the provided image file (ELF or Intel HEX) into device's memory
  332.      *
  333.      * @param imageName Relative or absolute path to image file
  334.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  335.      */
  336.     public async program(imageName: string, timeoutMs: number = 60000) {
  337.         if (this._toolMode === 'NONE') {
  338.             throw ERRORS.TOOL_NOT_SELECTED;
  339.         }
  340.  
  341.         await this.waitUntilInitialized(1000);
  342.         await this.writeLn(MDB_COMMANDS.PROGRAM(imageName));
  343.  
  344.         const successAwaiter = cancellableEventHandler(this, MDB_EVENTS.DEVICE_PRORGRAMING_SUCCESS);
  345.         const failureAwaiter = cancellableEventHandler(this, MDB_EVENTS.DEVICE_PRORGRAMING_FAIL, () => ERRORS.PROGRAMMING_FAILED);
  346.        
  347.         await raceForCancellableHandlersOrTimeout(timeoutMs, successAwaiter, failureAwaiter);
  348.     }
  349.  
  350.     /**
  351.      * Sets breakpoint at provided address or timeouts
  352.      *
  353.      * @param address Address in image to break at
  354.      * @param passCount Number of hits before halting
  355.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  356.      */
  357.     public async breakpoint_address(address: number, passCount?: number, timeoutMs: number = 60000): Promise<BreakpointInfo> {
  358.         if (! this._imageLoaded) {
  359.             throw ERRORS.IMAGE_NOT_LOADED;
  360.         }
  361.  
  362.         return this.writeLnAndWaitForEvent<BreakpointInfo>(
  363.             MDB_COMMANDS.BREAK_ADDRESS(address, passCount),
  364.             MDB_EVENTS.DEVICE_BREAKPOINT_SET,
  365.             timeoutMs
  366.         );
  367.     }
  368.  
  369.     /**
  370.      * Sets breakpoint at provided file and line number or timeouts
  371.      *
  372.      * @param file Filename to break at
  373.      * @param line Line number to break at
  374.      * @param passCount Number of hits before halting
  375.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  376.      */
  377.     public async breakpoint_line(file: string, line: number, passCount?: number, timeoutMs: number = 60000): Promise<BreakpointInfo> {
  378.         if (! this._imageLoaded) {
  379.             throw ERRORS.IMAGE_NOT_LOADED;
  380.         }
  381.  
  382.         return this.writeLnAndWaitForEvent<BreakpointInfo>(
  383.             MDB_COMMANDS.BREAK_FILE(file, line, passCount),
  384.             MDB_EVENTS.DEVICE_BREAKPOINT_SET,
  385.             timeoutMs
  386.         );
  387.     }
  388.  
  389.     /**
  390.      * Sets breakpoint at the beggining of provided funtion or timeouts
  391.      *
  392.      * @param name Name of the function to break on
  393.      * @param passCount Number of hits before halting
  394.      * @param timeoutMs Timeout after given number of miliseconds. Pass 0 to waint indefinetly
  395.      */
  396.     public async breakpoint_function(name: string, passCount?: number, timeoutMs: number = 60000): Promise<BreakpointInfo> {
  397.         if (! this._imageLoaded) {
  398.             throw ERRORS.IMAGE_NOT_LOADED;
  399.         }
  400.  
  401.         return this.writeLnAndWaitForEvent<BreakpointInfo>(
  402.             MDB_COMMANDS.BREAK_FUNCTION(name, passCount),
  403.             MDB_EVENTS.DEVICE_BREAKPOINT_SET,
  404.             timeoutMs
  405.         );
  406.     }
  407.  
  408.     public async reset(timeoutMs: number = 60000) {
  409.         if (! this._imageLoaded) {
  410.             throw ERRORS.IMAGE_NOT_LOADED;
  411.         }
  412.  
  413.         // TODO: Find a good way of detecting that device is reset. Apperently for each HwTool the output is different
  414.         // For now wait for MDB to output anything
  415.         this.writeLnAndWaitForEvent(MDB_COMMANDS.RESET(), MDB_EVENTS.CONSOLE_DATA, timeoutMs);
  416.     }
  417.  
  418.     public async run(timeoutMs: number = 60000) {
  419.         if (! this._imageLoaded) {
  420.             throw ERRORS.IMAGE_NOT_LOADED;
  421.         }
  422.  
  423.         if (this._running) {
  424.             throw ERRORS.DEVICE_RUNNING;
  425.         }
  426.  
  427.         this.writeLnAndWaitForEvent(MDB_COMMANDS.RUN(), MDB_EVENTS.DEVICE_RUNNING, timeoutMs);
  428.     }
  429.  
  430.     public async stepOver(timeoutMs: number = 60000) {
  431.         if (! this._imageLoaded) {
  432.             throw ERRORS.IMAGE_NOT_LOADED;
  433.         }
  434.  
  435.         if (this._running) {
  436.             throw ERRORS.DEVICE_RUNNING;
  437.         }
  438.  
  439.         this._running = true;
  440.         this.writeLnAndWaitForEvent(MDB_COMMANDS.NEXT(), MDB_EVENTS.DEVICE_HALT, timeoutMs);
  441.     }
  442.  
  443.     public async stepIn(timeoutMs: number = 60000) {
  444.         if (! this._imageLoaded) {
  445.             throw ERRORS.IMAGE_NOT_LOADED;
  446.         }
  447.  
  448.         if (this._running) {
  449.             throw ERRORS.DEVICE_RUNNING;
  450.         }
  451.  
  452.         this._running = true;
  453.         this.writeLnAndWaitForEvent(MDB_COMMANDS.STEP(), MDB_EVENTS.DEVICE_HALT, timeoutMs);
  454.     }
  455.  
  456.     public async step(count?: number, timeoutMs: number = 60000) {
  457.         if (! this._imageLoaded) {
  458.             throw ERRORS.IMAGE_NOT_LOADED;
  459.         }
  460.  
  461.         if (this._running) {
  462.             throw ERRORS.DEVICE_RUNNING;
  463.         }
  464.  
  465.         this._running = true;
  466.         this.writeLnAndWaitForEvent(MDB_COMMANDS.STEPI(), MDB_EVENTS.DEVICE_HALT, timeoutMs);
  467.     }
  468.  
  469.     public async continue(timeoutMs: number = 60000) {
  470.         if (! this._imageLoaded) {
  471.             throw ERRORS.IMAGE_NOT_LOADED;
  472.         }
  473.  
  474.         this.writeLnAndWaitForEvent(MDB_COMMANDS.CONTINUE(), MDB_EVENTS.DEVICE_RUNNING, timeoutMs);
  475.     }
  476.  
  477.     /**
  478.      * Currently this function is just a stub, it will query MDB for connected tools
  479.      *
  480.      * @param timeoutMs
  481.      */
  482.     public async getHwTools(timeoutMs: number = 60000): Promise<HwTool[]> {
  483.         return [
  484.             { id: "SIM", name: "Simulator" }
  485.         ];
  486.     }
  487.  
  488.     private async writeLnAndWaitForEvent<T = any>(value: string, eventName: string, timeoutMs: number) {
  489.         await this.waitUntilInitialized(1000);
  490.         await this.writeLn(value);
  491.  
  492.         return await this.waitForEventOrTimeout<T>(eventName, timeoutMs);
  493.     }
  494.  
  495.     private async writeLn(value: string) {
  496.         return new Promise((resolve, reject) => {
  497.             if (! this._process) {
  498.                 return reject(ERRORS.PROCESS_NOT_RUNNING);
  499.             }
  500.             this._process.stdin.write(value + "\n", (err) => {
  501.                 if (err) {
  502.                     return reject(err);
  503.                 }
  504.                 this.emit(MDB_EVENTS.CONSOLE_WRITE, value + "\n");
  505.                 resolve();
  506.             });
  507.         });
  508.     }
  509.  
  510.     private onData = (data: any) => {
  511.         type Resolver<T> = {
  512.             eventName: string,
  513.             args?: (data: T) => any[],
  514.         } | ((data: T) => void);
  515.  
  516.         interface ValueHandlerString {
  517.             key: string;
  518.             resolver: Resolver<string>;
  519.         }
  520.         interface ValueHandlerRegExp {
  521.             key: RegExp;
  522.             resolver: Resolver<RegExpExecArray>;
  523.         }
  524.  
  525.         const valueHandlers: Array<ValueHandlerString | ValueHandlerRegExp> = [
  526.             { key: '>', resolver: () => {
  527.                 if (! this._initialized) {
  528.                     this._initialized = true;
  529.                     this.emit(MDB_EVENTS.MDB_INITIALIZED);
  530.                 }
  531.             }},
  532.             { key: 'Programming target...', resolver: { eventName: MDB_EVENTS.DEVICE_PRORGRAMING_START } },
  533.             { key: 'Program succeeded.', resolver: () => this._imageLoaded = true },
  534.             { key: 'Program succeeded.', resolver: {eventName: MDB_EVENTS.DEVICE_PRORGRAMING_SUCCESS } },
  535.             { key: 'Running', resolver: { eventName: MDB_EVENTS.DEVICE_RUNNING }},
  536.             { key: 'Running', resolver: () => this._running = true },
  537.             { key: 'HALTED', resolver: {
  538.                 eventName: MDB_EVENTS.DEVICE_HALT,
  539.                 args: () => [{...this._haltInfo, breakpoint: this._haltInfo.address === this._lastBreakpoint}]
  540.             }},
  541.             { key: 'HALTED', resolver: () => { this._running = false; this._lastBreakpoint = -1; } },
  542.             { key: /^>?Working directory: (.*)$/, resolver: {eventName: MDB_EVENTS.MDB_CHANGE_DIRECTORY, args: (arr: RegExpExecArray) => [arr[1]]}},
  543.             { key: /^>?(W[0-9]{4}-[A-Z]{3}): .*$/, resolver: {eventName: MDB_EVENTS.CONSOLE_WARNING, args: (arr: RegExpExecArray) => [arr[0], arr[1], arr[2]]} },
  544.             { key: /^>?Single breakpoint: @0x([0-9A-F]+)$/, resolver: (arr: RegExpExecArray) => this._lastBreakpoint = parseInt(arr[1], 16) },
  545.             { key: /^>?Breakpoint ([0-9]+) at 0x([0-9a-f]+).$/, resolver: {
  546.                 eventName: MDB_EVENTS.DEVICE_BREAKPOINT_SET,
  547.                 args: (arr: RegExpExecArray) => [{ id: parseInt(arr[1], 10), address: parseInt(arr[2], 16) }]
  548.             }},
  549.             { key: /^>?Breakpoint ([0-9]+) at function (.+)\.$/, resolver: {
  550.                 eventName: MDB_EVENTS.DEVICE_BREAKPOINT_SET,
  551.                 args: (arr: RegExpExecArray) => [{ id: parseInt(arr[1], 10), function: arr[2] }]
  552.             }},
  553.             { key: /^>?Breakpoint ([0-9]+) at file (.+), line ([0-9]+).$/, resolver: {
  554.                 eventName: MDB_EVENTS.DEVICE_BREAKPOINT_SET,
  555.                 args: (arr: RegExpExecArray) => [{ id: parseInt(arr[1], 10), file: { name: arr[2], line: parseInt(arr[3], 10) } }]
  556.             }},
  557.             { key: 'Stop at', resolver: () => this._haltInfo = {} },
  558.             { key: /^>?address:0x([0-9a-f]+)$/, resolver: (matches: RegExpExecArray) => this._haltInfo.address = parseInt(matches[1], 16) },
  559.             { key: /^>?file:(.+)$/, resolver: (matches: RegExpExecArray) => this._haltInfo.file = matches[1] },
  560.             { key: /^>?source line:([0-9]+)$/, resolver: (matches: RegExpExecArray) => this._haltInfo.line = parseInt(matches[1], 10) },
  561.             { key: /^>?---debug/, resolver: () => null } /* Silence "---debug peripheral accessed" - provide interface for it later */,
  562.             { key: 'Simulator halted', resolver: () => null } /* Silence "Simulator halted" it's the same as HALTED */,
  563.             { key: 'Stepping', resolver: () => this._running = true } /* Silence "Stepping" - it's output after "stepi" command */,
  564.         ];
  565.  
  566.         const callResolver = <T>(resolver: Resolver<T>, arg: T) => {
  567.             if (typeof resolver === 'function') {
  568.                 resolver(arg);
  569.             } else {
  570.                 const args = resolver.args ? resolver.args(arg) : [];
  571.                 this.emit(resolver.eventName, ...args);
  572.             }
  573.         };
  574.  
  575.         const lines = String(data).split("\n").map(line => line.trim()).filter(line => line.length > 0);
  576.        
  577.         lines.forEach(line => {
  578.             const altLine = (line[0] === '>') ? line.substring(1) : line;
  579.  
  580.             let handled = false;
  581.             for (const handler of valueHandlers) {
  582.                 if (typeof handler.key === 'string') {
  583.  
  584.                     if (line !== handler.key) {
  585.                         if (altLine === handler.key) {
  586.                             callResolver((handler as ValueHandlerString).resolver, altLine);
  587.                             handled = true;
  588.                         }
  589.                     } else {
  590.                         callResolver((handler as ValueHandlerString).resolver, line);
  591.                         handled = true;
  592.                     }
  593.  
  594.                 } else {
  595.  
  596.                     const match = handler.key.exec(line);
  597.                     if (match) {
  598.                         callResolver((handler as ValueHandlerRegExp).resolver, match);
  599.                         handled = true;
  600.                     }
  601.                 }
  602.             }
  603.             if (! handled) {
  604.                 console.warn("Unhandled line", line);
  605.             }
  606.         });
  607.     }
  608.  
  609.     private onExit = (code: number, signal: string) => {
  610.         if (code !== 0) {
  611.             console.warn("MDB didn't exit cleanly, code:", code, ", signal:", signal);
  612.         }
  613.  
  614.         this._process = undefined;
  615.         this._running = false;
  616.         this._initialized = false;
  617.         this.emit(MDB_EVENTS.MDB_STOP);
  618.     }
  619.  
  620.     private onError = (err: any) => {
  621.         console.warn("Childprocess reported an error", err);
  622.     }
  623.  
  624.     private async waitUntilInitialized(timeoutMs: number) {
  625.         if (! this._process) {
  626.             throw ERRORS.PROCESS_NOT_RUNNING;
  627.         }
  628.  
  629.         if (this._initialized) {
  630.             return;
  631.         }
  632.  
  633.         const exitAwaiter = cancellableEventHandler(this._process, "exit", () => ERRORS.PROCESS_NOT_RUNNING);        
  634.         const dataAwaiter = cancellableEventHandler(this, MDB_EVENTS.MDB_INITIALIZED);
  635.  
  636.         await raceForCancellableHandlersOrTimeout(timeoutMs, exitAwaiter, dataAwaiter);
  637.     }
  638. }
  639.  
  640. const test = async () => {
  641.     const mdb = new MdbWrapper();
  642.     try {
  643.         let halting = false;
  644.         //mdb.on(MDB_EVENTS.CONSOLE_DATA, data => console.log(data));
  645.         mdb.on(MDB_EVENTS.CONSOLE_WRITE, data => console.log(data));
  646.         mdb.on(MDB_EVENTS.CONSOLE_ERROR, data => console.log("E:", data));
  647.         mdb.on(MDB_EVENTS.DEVICE_HALT, (haltInfo: HaltInfo) => {
  648.             console.log(`Device halted at ${haltInfo.breakpoint?'breakpoint ':''}"${haltInfo.file}":${haltInfo.line} (${haltInfo.address})`);
  649.             if (! halting) {
  650.                 setImmediate(() => {
  651.                     if (haltInfo.breakpoint) {
  652.                         mdb.stepIn();
  653.                     } else {
  654.                         mdb.continue();
  655.                     }    
  656.                 });
  657.             }
  658.         });
  659.         await mdb.start();
  660.         await mdb.selectDevice("PIC16F18857");
  661.         const tool = (await mdb.getHwTools())[0];
  662.         await mdb.selectHwTool(tool);
  663.         await mdb.changeDirectory('C:\\dev\\hipernet\\SterownikiOswietlenia\\PSS\\build');
  664.         await mdb.program('prosty-sterownik-schodow\\prosty-sterownik-schodow.elf');
  665.         await mdb.reset();
  666.         console.log(await mdb.breakpoint_address(3433));
  667.         console.log(await mdb.breakpoint_function("main"));
  668.         console.log(await mdb.breakpoint_line("main.c", 170));
  669.  
  670.         //await new Promise(resolve => setTimeout(resolve, 3000));
  671.         await mdb.run();
  672.         await new Promise(resolve => setTimeout(resolve, 3000));
  673.         halting = true;
  674.         //await new Promise(resolve => setTimeout(resolve, 3000));
  675.     } finally {
  676.         await mdb.stop();
  677.     }
  678. };
  679.  
  680. test().then(() => console.log("Finished"), (err) => console.error("Error", err));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement