diff --git a/src/PTPDevice.ts b/src/PTPDevice.ts index 9419f44..ef613d7 100644 --- a/src/PTPDevice.ts +++ b/src/PTPDevice.ts @@ -1,6 +1,7 @@ import EventEmitter from 'eventemitter3' import promiseTimeout from 'p-timeout' import PromiseQueue from 'promise-queue' +import sleep from 'sleep-promise' import {ResCode} from './PTPDatacode' import {PTPDataView} from './PTPDataView' @@ -15,6 +16,8 @@ enum PTPType { const PTPCommandMaxByteLength = 12 + 4 * 3 const PTPDefaultTimeoutMs = 10000 +const PTPTryCount = 30 +const PTPTryAgainIntervalMs = 500 interface PTPSendCommandOption { label?: string @@ -209,29 +212,45 @@ export class PTPDevice extends EventEmitter { expectedResCodes: [ResCode.OK], ...option, } - const id = this.generateTransactionId() - await this.transferOutCommand(opcode, id, parameters) + for (let i = 0; i < PTPTryCount; i++) { + const id = this.generateTransactionId() - const res = await this.waitBulkIn(id, PTPCommandMaxByteLength) + await this.transferOutCommand(opcode, id, parameters) - // Error checking - if (res.type !== PTPType.Response) { - throw new Error( - `Expected response type: ${PTPType.Response}, got: ${res.type}` - ) - } + const res = await this.waitBulkIn(id, PTPCommandMaxByteLength) - if (!expectedResCodes.includes(res.code)) { - const expected = expectedResCodes.map(toHexString) - const got = toHexString(res.code) - throw new Error(`Expected rescode=[${expected}], got= ${got}`) - } + // Error checking + if (res.type !== PTPType.Response) { + throw new Error( + `Expected response type: ${PTPType.Response}, got: ${res.type}` + ) + } - return { - resCode: res.code, - parameters: [...new Uint32Array(res.payload)], + // When the device is busy, try again + const tryAgain = + !expectedResCodes.includes(ResCode.DeviceBusy) && + res.code === ResCode.DeviceBusy + + if (tryAgain) { + sleep(PTPTryAgainIntervalMs) + continue + } + + // Check rescode + if (!expectedResCodes.includes(res.code)) { + const expected = expectedResCodes.map(toHexString) + const got = toHexString(res.code) + throw new Error(`Expected rescode=[${expected}], got= ${got}`) + } + + return { + resCode: res.code, + parameters: [...new Uint32Array(res.payload)], + } } + + throw new Error('Cannot send command') } private sendDataNow = async ( @@ -242,30 +261,46 @@ export class PTPDevice extends EventEmitter { expectedResCodes: [ResCode.OK], ...option, } - const id = this.generateTransactionId() - await this.transferOutCommand(opcode, id, parameters) - await this.transferOutData(opcode, id, data) + for (let i = 0; i < PTPTryCount; i++) { + const id = this.generateTransactionId() - const res = await this.waitBulkIn(id, PTPCommandMaxByteLength) + await this.transferOutCommand(opcode, id, parameters) + await this.transferOutData(opcode, id, data) - // Error checking - if (res.type !== PTPType.Response) { - throw new Error( - `Expected response type: ${PTPType.Response}, got: ${res.type}` - ) - } + const res = await this.waitBulkIn(id, PTPCommandMaxByteLength) - if (!expectedResCodes.includes(res.code)) { - const expected = expectedResCodes.map(toHexString) - const got = toHexString(res.code) - throw new Error(`Expected rescode=[${expected}], got=${got}`) - } + // Error checking + if (res.type !== PTPType.Response) { + throw new Error( + `Expected response type: ${PTPType.Response}, got: ${res.type}` + ) + } - return { - resCode: res.code, - parameters: [...new Uint32Array(res.payload)], + // When the device is busy, try again + const tryAgain = + !expectedResCodes.includes(ResCode.DeviceBusy) && + res.code === ResCode.DeviceBusy + + if (tryAgain) { + sleep(PTPTryAgainIntervalMs) + continue + } + + // Check rescode + if (!expectedResCodes.includes(res.code)) { + const expected = expectedResCodes.map(toHexString) + const got = toHexString(res.code) + throw new Error(`Expected rescode=[${expected}], got=${got}`) + } + + return { + resCode: res.code, + parameters: [...new Uint32Array(res.payload)], + } } + + throw new Error('Cannot send data') } private receiveDataNow = async ( @@ -277,39 +312,55 @@ export class PTPDevice extends EventEmitter { maxByteLength: 10_000, // = 10KB. Looks enough for non-media data transfer ...option, } - const id = this.generateTransactionId() - - await this.transferOutCommand(opcode, id, parameters) - const res1 = await this.waitBulkIn(id, maxByteLength) - - if (res1.type === PTPType.Response) { - if (expectedResCodes.includes(res1.code)) { - this.#console.groupEnd() - return { - resCode: res1.code, - parameters: [], - data: new ArrayBuffer(0), + + for (let i = 0; i < PTPTryCount; i++) { + const id = this.generateTransactionId() + + await this.transferOutCommand(opcode, id, parameters) + const res1 = await this.waitBulkIn(id, maxByteLength) + + if (res1.type === PTPType.Response) { + if (expectedResCodes.includes(res1.code)) { + this.#console.groupEnd() + return { + resCode: res1.code, + parameters: [], + data: new ArrayBuffer(0), + } } } - } - if (res1.type !== PTPType.Data) { - throw new Error(`Cannot receive data code=${toHexString(res1.code)}`) - } + if (res1.type !== PTPType.Data) { + throw new Error(`Cannot receive data code=${toHexString(res1.code)}`) + } - const res2 = await this.waitBulkIn(id, PTPCommandMaxByteLength) + const res2 = await this.waitBulkIn(id, PTPCommandMaxByteLength) - if (res2.type !== PTPType.Response) { - throw new Error( - `Expected response type: ${PTPType.Response}, but got: ${res2.type}` - ) - } + if (res2.type !== PTPType.Response) { + throw new Error( + `Expected response type: ${PTPType.Response}, but got: ${res2.type}` + ) + } + // When the device is busy, try again + const tryAgain = + !expectedResCodes.includes(ResCode.DeviceBusy) && + res2.code === ResCode.DeviceBusy + + if (tryAgain) { + sleep(PTPTryAgainIntervalMs) + continue + } - return { - resCode: res2.code, - parameters: [...new Uint32Array(res2.payload)], - data: res1.payload, + // Check rescode + + return { + resCode: res2.code, + parameters: [...new Uint32Array(res2.payload)], + data: res1.payload, + } } + + throw new Error('Cannot receive data') } waitEvent = async (code: number): Promise => { diff --git a/src/TethrPTPUSB/TethrSigma.ts b/src/TethrPTPUSB/TethrSigma.ts index 977f40b..dc75dd6 100644 --- a/src/TethrPTPUSB/TethrSigma.ts +++ b/src/TethrPTPUSB/TethrSigma.ts @@ -244,23 +244,36 @@ export class TethrSigma extends TethrPTPUSB { const [lvTop, lvBottom, lvLeft, lvRight] = focusValidArea + // Assume the margins are symmetrical return [lvLeft + lvRight, lvTop + lvBottom] as Vec2 } - async getAutoFocusFrameCenterDesc(): Promise> { - if (!(await this.get('canRunAutoFocus'))) { - return {writable: false, value: null} - } + async #enableSpotAutoFocus() { + await this.device.sendData({ + label: 'SigmaFP SetCamDataGroupFocus', + opcode: OpCodeSigma.SetCamDataGroupFocus, + data: encodeIFD({ + // 2 === 1-spot selection + focusArea: {tag: 10, type: IFDType.Byte, value: [2]}, + }), + }) - // First, enable the spot focus area await this.device.sendData({ label: 'SigmaFP SetCamDataGroupFocus', opcode: OpCodeSigma.SetCamDataGroupFocus, data: encodeIFD({ - focusArea: {tag: 10, type: IFDType.Short, value: [2]}, - onePointSelectionMethod: {tag: 11, type: IFDType.Short, value: [49]}, + // 49 === 49-point selection mode + onePointSelectionMethod: {tag: 11, type: IFDType.Byte, value: [49]}, }), }) + } + + async getAutoFocusFrameCenterDesc(): Promise> { + if (!(await this.get('canRunAutoFocus'))) { + return {writable: false, value: null} + } + + await this.#enableSpotAutoFocus() // Then, get the current position const {distanceMeasurementFramePosition} = await this.getCamStatus() @@ -300,46 +313,29 @@ export class TethrSigma extends TethrPTPUSB { return {status: 'invalid parameter'} } - // First, enable the spot focus area - await this.device.sendData({ - label: 'SigmaFP SetCamDataGroupFocus', - opcode: OpCodeSigma.SetCamDataGroupFocus, - data: encodeIFD({ - focusArea: {tag: 10, type: IFDType.Short, value: [2]}, - onePointSelectionMethod: {tag: 11, type: IFDType.Short, value: [49]}, - }), - }) + await this.#enableSpotAutoFocus() const lvSize = await this.#getLVCoordinateSize() - // Assume the margins are symmetrical const [x, y] = vec2.round(vec2.mul(center, lvSize)) - const data = encodeIFD({ - distanceMeasurementFramePosition: { - tag: 13, - type: IFDType.Short, - value: [y, x], - }, + const {resCode} = await this.device.sendData({ + label: 'SigmaFP SetCamDataGroupFocus', + opcode: OpCodeSigma.SetCamDataGroupFocus, + data: encodeIFD({ + distanceMeasurementFramePosition: { + tag: 13, + type: IFDType.Short, + value: [y, x], + }, + }), }) - // Try to set the focus position many times because it often fails - for (let i = 0; i < 20; i++) { - const {resCode} = await this.device.sendData({ - label: 'SigmaFP SetCamDataGroupFocus', - opcode: OpCodeSigma.SetCamDataGroupFocus, - expectedResCodes: [ResCode.OK, ResCode.DeviceBusy], - data, - }) - - if (resCode === ResCode.OK) { - return {status: 'ok'} - } - - await sleep(50) + if (resCode === ResCode.OK) { + return {status: 'ok'} + } else { + return {status: 'invalid parameter'} } - - return {status: 'invalid parameter'} } async getAutoFocusFrameSizeDesc(): Promise> {