diff --git a/lib/units/ios-device/plugins/touch/esp32-movement.js b/lib/units/ios-device/plugins/touch/esp32-movement.js new file mode 100644 index 0000000000..3130183ed0 --- /dev/null +++ b/lib/units/ios-device/plugins/touch/esp32-movement.js @@ -0,0 +1,34 @@ +export const SINGLE_STEP_SIZE = 1 +export const BIG_STEP_SIZE = 4 +export const HUGE_STEP_SIZE = 8 +export const POSITION_TOLERANCE = 1 + +export function getStep(diff) { + const distance = Math.abs(diff) + let magnitude = SINGLE_STEP_SIZE + if (distance >= BIG_STEP_SIZE) { + magnitude = BIG_STEP_SIZE + } + if (distance >= HUGE_STEP_SIZE) { + magnitude = HUGE_STEP_SIZE + } + + return Math.sign(diff) * magnitude +} + +export function getDirection(axis, step) { + const directions = { + x: {positive: ['R', 'r', 'k'], negative: ['L', 'l', 'g']}, + y: {positive: ['D', 'd', 'h'], negative: ['U', 'u', 'j']} + } + const magnitude = Math.abs(step) + let index = 0 + if (magnitude === BIG_STEP_SIZE) { + index = 1 + } + if (magnitude === HUGE_STEP_SIZE) { + index = 2 + } + + return (step > 0 ? directions[axis].positive : directions[axis].negative)[index] +} diff --git a/lib/units/ios-device/plugins/touch/esp32touch.js b/lib/units/ios-device/plugins/touch/esp32touch.js index 98483179e5..c3afc349f8 100644 --- a/lib/units/ios-device/plugins/touch/esp32touch.js +++ b/lib/units/ios-device/plugins/touch/esp32touch.js @@ -1,8 +1,7 @@ import {SerialPort} from 'serialport' import Logger from '../../../../util/logger.js' -import {promisify} from 'node:util' -import _ from 'lodash' import EventEmitter from 'node:events' +import {getDirection, getStep, SINGLE_STEP_SIZE, BIG_STEP_SIZE, HUGE_STEP_SIZE, POSITION_TOLERANCE} from './esp32-movement.js' const log = Logger.createLogger('esp32touch') @@ -47,9 +46,10 @@ function throttle(func, ms) { */ export class Esp32Touch extends EventEmitter { static MAX_BLE_NAME_LENGTH = 22 // Match the safe limit for advertising - static SINGLE_STEP_SIZE = 13 / 3 // The amount of pixels a single step does. - static BIG_STEP_SIZE = 70 / 3 // The amount of pixels a big step does. - static HUGE_STEP_SIZE = 178 / 3 // The amount of pixels a huge step does + static SINGLE_STEP_SIZE = SINGLE_STEP_SIZE + static BIG_STEP_SIZE = BIG_STEP_SIZE + static HUGE_STEP_SIZE = HUGE_STEP_SIZE + static POSITION_TOLERANCE = POSITION_TOLERANCE static async listPorts() { return (await SerialPort.list()).filter((dev) => dev.manufacturer === 'Espressif') @@ -167,7 +167,7 @@ export class Esp32Touch extends EventEmitter { } reset() { this.bufferCommand('0') - this.curposX = 14 * Esp32Touch.SINGLE_STEP_SIZE + this.curposX = Esp32Touch.SINGLE_STEP_SIZE * 14 this.curposY = 0 } @@ -186,56 +186,29 @@ export class Esp32Touch extends EventEmitter { return } - if(this.targetPosX === null || this.targetPoxY === null) { + if (this.targetPosX === null || this.targetPosY === null) { return } const targetStepsX = Math.round(this.targetPosX * this.width) - const targetStepsY = Math.round(this.targetPoxY * this.height) - const diffX = targetStepsX - this.curposX // Positive -> left, negative - right - const diffY = targetStepsY - this.curposY // Positive -> up, negative - down - /** @type {[string, number, number][]} */ - let commands = [] - if(diffX > Esp32Touch.HUGE_STEP_SIZE * 1.5) { - commands.push(['k', Esp32Touch.HUGE_STEP_SIZE, 0]) - } - if(diffX < -Esp32Touch.HUGE_STEP_SIZE * 1.5) { - commands.push(['g', -Esp32Touch.HUGE_STEP_SIZE, 0]) - } - if(diffY > Esp32Touch.HUGE_STEP_SIZE * 1.5) { - commands.push(['h', 0, Esp32Touch.HUGE_STEP_SIZE]) + const targetStepsY = Math.round(this.targetPosY * this.height) + const diffX = targetStepsX - this.curposX // Positive -> right, negative -> left + const diffY = targetStepsY - this.curposY // Positive -> down, negative -> up + + if (Math.abs(diffX) <= Esp32Touch.POSITION_TOLERANCE && Math.abs(diffY) <= Esp32Touch.POSITION_TOLERANCE) { + return } - if(diffY < -Esp32Touch.HUGE_STEP_SIZE * 1.5) { - commands.push(['j', 0, -Esp32Touch.HUGE_STEP_SIZE]) + + const moveX = Math.abs(diffX) >= Math.abs(diffY) + const step = getStep(moveX ? diffX : diffY) + // Move the axis with the larger remaining error first. Randomly + // choosing an axis makes the estimated position unstable. + let dir, addX, addY + if (moveX) { + [dir, addX, addY] = [getDirection('x', step), step, 0] } - if (commands.length === 0) { - if(diffX > Esp32Touch.BIG_STEP_SIZE * 1.5) { - commands.push(['r', Esp32Touch.BIG_STEP_SIZE, 0]) - } - if(diffX < -Esp32Touch.BIG_STEP_SIZE * 1.5) { - commands.push(['l', -Esp32Touch.BIG_STEP_SIZE, 0]) - } - if(diffY > Esp32Touch.BIG_STEP_SIZE * 1.5) { - commands.push(['d', 0, Esp32Touch.BIG_STEP_SIZE]) - } - if(diffY < -Esp32Touch.BIG_STEP_SIZE * 1.5) { - commands.push(['u', 0, -Esp32Touch.BIG_STEP_SIZE]) - } - if(commands.length === 0) { - if(diffX > Esp32Touch.SINGLE_STEP_SIZE) { - commands.push(['R', Esp32Touch.SINGLE_STEP_SIZE, 0]) - } - if(diffX < -Esp32Touch.SINGLE_STEP_SIZE) { - commands.push(['L', -Esp32Touch.SINGLE_STEP_SIZE, 0]) - } - if(diffY > Esp32Touch.SINGLE_STEP_SIZE) { - commands.push(['D', 0, Esp32Touch.SINGLE_STEP_SIZE]) - } - if(diffY < -Esp32Touch.SINGLE_STEP_SIZE) { - commands.push(['U', 0, -Esp32Touch.SINGLE_STEP_SIZE]) - } - } + else { + [dir, addX, addY] = [getDirection('y', step), 0, step] } - const [dir, addX, addY] = _.sample(commands) ?? ['', 0, 0] if (dir) { this.bufferCommand(dir) this.curposX += addX @@ -253,7 +226,7 @@ export class Esp32Touch extends EventEmitter { move(targetX, targetY) { this.startWorking() this.targetPosX = targetX - this.targetPoxY = targetY + this.targetPosY = targetY } /** diff --git a/test/unit/units/ios-device/plugins/touch/esp32-movement.test.ts b/test/unit/units/ios-device/plugins/touch/esp32-movement.test.ts new file mode 100644 index 0000000000..efe42fce7f --- /dev/null +++ b/test/unit/units/ios-device/plugins/touch/esp32-movement.test.ts @@ -0,0 +1,29 @@ +import {describe, expect, it} from 'vitest' +import {getDirection, getStep} from '../../../../../../lib/units/ios-device/plugins/touch/esp32-movement.js' + +describe('ESP32 movement commands', () => { + it('selects a firmware-supported step based on the remaining distance', () => { + expect(getStep(100)).toBe(8) + expect(getStep(-8)).toBe(-8) + expect(getStep(7)).toBe(4) + expect(getStep(-2)).toBe(-1) + expect(getStep(0)).toBe(0) + }) + + it.each([ + ['x', 1, 'R'], + ['x', 4, 'r'], + ['x', 8, 'k'], + ['x', -1, 'L'], + ['x', -4, 'l'], + ['x', -8, 'g'], + ['y', 1, 'D'], + ['y', 4, 'd'], + ['y', 8, 'h'], + ['y', -1, 'U'], + ['y', -4, 'u'], + ['y', -8, 'j'], + ] as const)('maps axis %s step %i to firmware command %s', (axis, step, command) => { + expect(getDirection(axis, step)).toBe(command) + }) +})