diff --git a/demo/src/useTethr.ts b/demo/src/useTethr.ts index 1673904..c91a6ee 100644 --- a/demo/src/useTethr.ts +++ b/demo/src/useTethr.ts @@ -162,6 +162,7 @@ export function useTethr(onSave: (object: TethrObject) => void) { facingMode: useTethrConfig(camera, 'facingMode'), focalLength: useTethrConfig(camera, 'focalLength'), focusDistance: useTethrConfig(camera, 'focusDistance'), + focusMeteringMode: useTethrConfig(camera, 'focusMeteringMode'), focusPeaking: useTethrConfig(camera, 'focusPeaking'), liveviewMagnifyRatio: useTethrConfig(camera, 'liveviewMagnifyRatio'), liveviewEnabled: useTethrConfig(camera, 'liveviewEnabled'), diff --git a/src/IFD.ts b/src/IFD.ts index 043f575..763a332 100644 --- a/src/IFD.ts +++ b/src/IFD.ts @@ -179,12 +179,22 @@ export function encodeIFD(data: IFDData): ArrayBuffer { dataView.writeUint32(count) switch (entry.type) { + case IFDType.Byte: { + if (count > 4) { + throw new Error('Not yet supported') + } else { + for (let i = 0; i < 4; i++) { + dataView.writeUint8(entry.value[i]) + } + } + break + } case IFDType.Short: { if (entry.value.length > 2) { throw new Error('Not yet supported') } else { for (let i = 0; i < 2; i++) { - dataView.writeUint16(entry.value[i] ?? 0) + dataView.writeUint16(entry.value[i]) } } break diff --git a/src/TethrPTPUSB/TethrSigma.ts b/src/TethrPTPUSB/TethrSigma.ts index e15f394..0a3cb2c 100644 --- a/src/TethrPTPUSB/TethrSigma.ts +++ b/src/TethrPTPUSB/TethrSigma.ts @@ -11,6 +11,7 @@ import { computeShutterSpeedSeconds, ConfigName, ExposureMode, + FocusMeteringMode, FocusPeaking, ISO, WhiteBalance, @@ -109,6 +110,7 @@ enum SnapCaptureMode { const ConfigListSigma: ConfigName[] = [ 'aperture', + 'batteryLevel', 'iso', 'canRunAutoFocus', 'colorTemperature', @@ -116,6 +118,7 @@ const ConfigListSigma: ConfigName[] = [ 'destinationToSave', 'focalLength', 'focusDistance', + 'focusMeteringMode', 'focusPeaking', 'exposureComp', 'exposureMode', @@ -139,18 +142,22 @@ export class TethrSigma extends TethrPTPUSB { async open() { await super.open() - const {data} = await this.device.receiveData({ + await this.device.receiveData({ label: 'SigmaFP ConfigApi', opcode: OpCodeSigma.ConfigApi, parameters: [0x0], }) + /** + * Don't need this IFD data + * decodeIFD(data, { cameraModel: {tag: 1, type: IFDType.Ascii}, serialNumber: {tag: 2, type: IFDType.Ascii}, firmwareVersion: {tag: 3, type: IFDType.Ascii}, communiationVersion: {tag: 5, type: IFDType.Float}, }) + */ const checkPropChangedInterval = () => { if (!this.opened) return @@ -276,7 +283,9 @@ export class TethrSigma extends TethrPTPUSB { // NOTE: the colorModeOptions lacks Warm Gold (0xf0). // it must be manually added only if the firmware is 5.0, // but configAPI doesn't return the version information by some reason... - colorModeOptions.push(0x11) + if (!colorModeOptions.includes(0x11)) { + colorModeOptions.push(0x11) + } return { writable: colorModeOptions.length > 0, @@ -515,15 +524,11 @@ export class TethrSigma extends TethrPTPUSB { } } - async setFocusDistance( - value: number - ): Promise<{status: OperationResultStatus}> { + async setFocusDistance(value: number): Promise { const {focusPosition: range} = await this.getCamCanSetInfo5() const focusPosition = scalar.round(scalar.lerp(range[1], range[0], value)) - console.error(value, focusPosition) - const data = encodeIFD({ focusPosition: {tag: 81, type: IFDType.Short, value: [focusPosition]}, }) @@ -541,8 +546,50 @@ export class TethrSigma extends TethrPTPUSB { return {status: 'ok'} } + async getFocusMeteringModeDesc(): Promise> { + const {focusArea: ids} = await this.getCamCanSetInfo5() + + const id = (await this.getCamStatus()).focusArea[0] + + const value = this.focusAreaTable.get(id) as FocusMeteringMode + + const values = ids.map(n => + this.focusAreaTable.get(n) + ) as FocusMeteringMode[] + + return { + writable: values.length > 0, + value, + option: { + type: 'enum', + values: values, + }, + } + } + + async setFocusMeteringMode( + value: FocusMeteringMode + ): Promise { + const id = this.focusAreaTable.getKey(value) as number + const data = encodeIFD({ + focusArea: {tag: 10, type: IFDType.Byte, value: [id]}, + }) + + try { + await this.device.sendData({ + label: 'SigmaFP SetCamDataGroupFocus', + opcode: OpCodeSigma.SetCamDataGroupFocus, + data, + }) + } catch (err) { + return {status: 'invalid parameter'} + } + + return {status: 'ok'} + } + async getFocusPeakingDesc(): Promise> { - // const value = (await this.getCamDataGroup5()).focusPeaking + // TODO: There's no way to retrieve and configure this value in the latest firmware const value = 0 const {focusPeaking: values} = await this.getCamCanSetInfo5() @@ -606,10 +653,10 @@ export class TethrSigma extends TethrPTPUSB { let jpegQuality: string | null = null let dngBitDepth: number | null = null - const hasDngMatch = imageQuality.match(/^raw (12|14)bit(?:,([a-z]+))?/i) + const pattern = imageQuality.match(/^raw (12|14)bit(?:,([a-z]+))?/i) - if (hasDngMatch) { - const [, dngBitDepthStr, jpegQualityStr] = hasDngMatch + if (pattern) { + const [, dngBitDepthStr, jpegQualityStr] = pattern jpegQuality = jpegQualityStr ?? null dngBitDepth = parseInt(dngBitDepthStr) } else { @@ -655,40 +702,31 @@ export class TethrSigma extends TethrPTPUSB { } async getImageQualityDesc() { - type ImageQualityConfig = { - jpegQuality: string | null - hasDNG: boolean - } + const {imageQuality, dngImageQuality: dngBitDepth} = + await this.getCamStatus() - const imageQuality: ImageQualityConfig = await (async () => { - const {imageQuality} = await this.getCamStatus() - - let jpegQuality: string | null = null - switch (imageQuality & 0x0f) { - case 0x02: - jpegQuality = 'fine' - break - case 0x04: - jpegQuality = 'standard' - break - case 0x08: - jpegQuality = 'low' - break - } + let jpegQuality: string | null = null + switch (imageQuality & 0x0f) { + case 0x02: + jpegQuality = 'fine' + break + case 0x04: + jpegQuality = 'standard' + break + case 0x08: + jpegQuality = 'low' + break + } - const hasDNG = !!(imageQuality & 0x10) + const hasDNG = !!(imageQuality & 0x10) - return { - jpegQuality, - hasDNG, - } - })() - - const {dngImageQuality} = await this.getCamStatus() + const value = [hasDNG ? `raw ${dngBitDepth}bit` : null, jpegQuality] + .filter(Boolean) + .join(',') return { writable: true, - value: stringifyImageQuality(imageQuality, dngImageQuality), + value: value, option: { type: 'enum', values: [ @@ -703,25 +741,6 @@ export class TethrSigma extends TethrPTPUSB { ], }, } as ConfigDesc - - function stringifyImageQuality( - quality: ImageQualityConfig, - dngBitDepth: number - ) { - if (quality.hasDNG) { - if (quality.jpegQuality) { - return `raw ${dngBitDepth}bit,${quality.jpegQuality}` - } else { - return `raw ${dngBitDepth}bit` - } - } else { - if (quality.jpegQuality) { - return quality.jpegQuality - } else { - throw new Error('Invalid ImageQualityConfig') - } - } - } } async setImageSize(imageSize: string): Promise { @@ -733,10 +752,14 @@ export class TethrSigma extends TethrPTPUSB { async getImageSizeDesc(): Promise> { const {resolution} = await this.getCamStatus() + const {stillImageResolution} = await this.getCamCanSetInfo5() const value = this.imageSizeTable.get(resolution) + const values = stillImageResolution.map(v => + this.imageSizeTableIFD.get(v) + ) as string[] - if (!value) { + if (!value || values.length === 0) { return { writable: false, value: null, @@ -748,7 +771,7 @@ export class TethrSigma extends TethrPTPUSB { value, option: { type: 'enum', - values: ['low', 'medium', 'high'], + values, }, } } @@ -832,7 +855,7 @@ export class TethrSigma extends TethrPTPUSB { const {lvMagnifyRatio} = await this.getCamStatus() const value = this.liveviewMagnifyRatioTable.get(lvMagnifyRatio) ?? null - const {lvMagnifyRatio: values} = await this.getCamCanSetInfo5() + const {lvMagnificationRate: values} = await this.getCamCanSetInfo5() return { writable: values.length > 0, @@ -1044,14 +1067,17 @@ export class TethrSigma extends TethrPTPUSB { } async runAutoFocus(): Promise { - const succeed = await this.executeSnapCommand(SnapCaptureMode.StartAF) - await this.clearImageDBAll() + const startAFSucceed = await this.executeSnapCommand( + SnapCaptureMode.StartAF + ) - if (succeed !== null) { - return {status: 'ok'} - } else { - return {status: 'general error'} - } + if (!startAFSucceed) return {status: 'general error'} + + const stopAFSucceed = await this.executeSnapCommand(SnapCaptureMode.StopAF) + + if (!stopAFSucceed) return {status: 'general error'} + + return {status: 'ok'} } async getLiveViewImage(): Promise> { @@ -1354,11 +1380,11 @@ export class TethrSigma extends TethrPTPUSB { afLock: {tag: 2, type: IFDType.Byte}, afFaceEyePriorMode: {tag: 3, type: IFDType.Byte}, afFaceEyePriorDetectionStatus: {tag: 4, type: IFDType.Byte}, - afAreaSelect: {tag: 10, type: IFDType.Byte}, - afAreaMode: {tag: 11, type: IFDType.Byte}, - afFrameSize: {tag: 12, type: IFDType.Byte}, - // afFramePosition: {tag: 13, type: IFDType.Byte}, - // afFrameFaceFocusDetection: {tag: 14, type: IFDType.Byte}, + focusArea: {tag: 10, type: IFDType.Byte}, + onePointSelectionMethod: {tag: 11, type: IFDType.Byte}, + distanceMeasurementFrameSize: {tag: 12, type: IFDType.Byte}, + // distanceMeasurementFramePosition: {tag: 13, type: IFDType.Short}, + // distanceMeasurementFrame: {tag: 14, type: IFDType.Byte}, preAlwaysAf: {tag: 51, type: IFDType.Byte}, afLimit: {tag: 52, type: IFDType.Byte}, focusPosition: {tag: 81, type: IFDType.Short}, @@ -1397,9 +1423,18 @@ export class TethrSigma extends TethrPTPUSB { colorTemerature: {tag: 302, type: IFDType.Short}, colorMode: {tag: 320, type: IFDType.Byte}, focusMode: {tag: 600, type: IFDType.Byte}, + + focusArea: {tag: 610, type: IFDType.Byte}, + onePointSelectionMethod: {tag: 611, type: IFDType.Byte}, + focusOverallArea: {tag: 612, type: IFDType.Short}, + focusValidArea: {tag: 613, type: IFDType.Short}, + distanceMeasurementFrameSize: {tag: 614, type: IFDType.Byte}, + eachDistanceMesasurementFrameSize: {tag: 615, type: IFDType.Short}, + distanceMeasurementFrameMovementAmount: {tag: 616, type: IFDType.Byte}, + focusPosition: {tag: 658, type: IFDType.Short}, lvImageTransfer: {tag: 700, type: IFDType.Byte}, - lvMagnifyRatio: {tag: 701, type: IFDType.Byte}, + lvMagnificationRate: {tag: 701, type: IFDType.Byte}, focusPeaking: {tag: 702, type: IFDType.Byte}, shutterSound: {tag: 801, type: IFDType.Byte}, afVolume: {tag: 802, type: IFDType.Byte}, @@ -1953,6 +1988,14 @@ export class TethrSigma extends TethrPTPUSB { [0x1, 'high'], [0x3, 'medium'], [0x4, 'low'], + [0xff, 'raw'], + ]) + + private imageSizeTableIFD = new BiMap([ + [0x1, 'high'], + [0x2, 'medium'], + [0x3, 'low'], + [0xff, 'raw'], ]) private destinationToSaveTable = new BiMap([ @@ -1961,4 +2004,10 @@ export class TethrSigma extends TethrPTPUSB { [0x02, 'pc'], [0x03, 'camera,pc'], ]) + + private focusAreaTable = new BiMap([ + [1, 'multi spot'], + [2, 'vendor:1 point selection'], + [3, 'vendor:tracking'], + ]) } diff --git a/src/configs.ts b/src/configs.ts index d2ad21a..892778c 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -36,7 +36,10 @@ export type FocusMode = 'af' | 'mf' export type FunctionalMode = 'standard' | 'sleep' -export type FocusMeteringMode = 'center-spot' | 'multi-spot' +export type FocusMeteringMode = + | 'center spot' + | 'multi spot' + | `vendor:${string}` /** * Focus peaking mode. `false` means disabled.