Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support grid size in protocols #2639

Merged
merged 33 commits into from
Nov 15, 2023
Merged
Changes from 1 commit
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f3438bc
chore: refactor existing http api to new service
Julusian Nov 1, 2023
2c9c18a
feat: new restful http api
Julusian Nov 1, 2023
74db486
wip: start of tests
Julusian Nov 1, 2023
ec3d7bc
wip: tests
Julusian Nov 2, 2023
86941c2
aaa
Julusian Nov 6, 2023
739b95d
wip: add endpoint to get custom variable value
Julusian Nov 6, 2023
77f27e7
wip: osc api
Julusian Nov 6, 2023
7fe28c3
wip: rename endpoints
Julusian Nov 6, 2023
9979a9b
wip
Julusian Nov 6, 2023
c9d97fa
wip
Julusian Nov 6, 2023
e09d880
wip
Julusian Nov 7, 2023
dddcb36
wip: osc
Julusian Nov 7, 2023
8d2eafe
wip: osc docs
Julusian Nov 8, 2023
b7515f0
wip: http docs
Julusian Nov 8, 2023
8d1a3f4
chore: refactor tcp-udp api instantiation
Julusian Nov 8, 2023
f19f340
Merge branch 'beta' into feat/grid-size-protocols
Julusian Nov 8, 2023
1796d53
fix
Julusian Nov 8, 2023
03e42e6
wip: rework http router binding
Julusian Nov 8, 2023
7f3f483
feat: add config fields to enable/disable api sections
Julusian Nov 8, 2023
9d9c4db
fix: emberplus api not disabling
Julusian Nov 8, 2023
d8fe3de
feat: add grid location structure to emberplus api
Julusian Nov 8, 2023
576fbe9
feat: add location addressing to rosstalk
Julusian Nov 8, 2023
16a4aa1
Revert "feat: add location addressing to rosstalk"
Julusian Nov 8, 2023
977ebfe
chore: restore reverted rosstalk test
Julusian Nov 8, 2023
e5319f5
feat: add step set to http and osc api
Julusian Nov 10, 2023
a2bbefa
feat: add grid location to tcp/udp api
Julusian Nov 10, 2023
d326abc
feat: add enable/disable configs
Julusian Nov 10, 2023
5280174
chore: docs
Julusian Nov 10, 2023
d9c1ae3
chore: docs
Julusian Nov 10, 2023
361ffeb
Merge branch 'beta' into feat/grid-size-protocols
Julusian Nov 11, 2023
2f3d761
fix: ensure emberplus api handles non-numeric internal colours
Julusian Nov 15, 2023
83c4d21
chore: fix types
Julusian Nov 15, 2023
9d90c1d
Merge branch 'beta' into feat/grid-size-protocols
Julusian Nov 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add grid location to tcp/udp api
Julusian committed Nov 10, 2023
commit a2bbefac6b97402b6aebacf50fdb83221f74eb7e
2 changes: 1 addition & 1 deletion lib/Service/Tcp.js
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ class ServiceTcp extends ServiceTcpBase {
constructor(registry) {
super(registry, 'tcp', 'Service/Tcp', 'tcp_enabled', 'tcp_listen_port')

this.#api = new ServiceTcpUdpApi(registry)
this.#api = new ServiceTcpUdpApi(registry, 'tcp')

this.graphics.on('button_drawn', (location, render) => {
const bgcolor = render.style?.bgcolor || 0
239 changes: 228 additions & 11 deletions lib/Service/TcpUdpApi.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import CoreBase from '../Core/Base.js'
import { parseColorToNumber } from '../Resources/Util.js'
import { formatLocation } from '../Shared/ControlId.js'
import RegexRouter from './RegexRouter.js'

/**
@@ -27,22 +29,39 @@ export class ServiceTcpUdpApi extends CoreBase {
* Message router
* @type {RegexRouter}
* @access private
* @readonly
*/
#router

/**
* Protocol name
* @type {string}
* @access private
* @readonly
*/
#protocolName

get router() {
return this.#router
}

/**
* @param {import('../Registry.js').default} registry - the core registry
* @param {stirng} protocolName - the protocol name
*/
constructor(registry) {
constructor(registry, protocolName) {
super(registry, 'api', 'Service/Api')

this.#router = new RegexRouter(() => {
throw new ApiMessageError('Syntax error')
})
this.#setupRoutes()
this.#protocolName = protocolName

this.#setupLegacyRoutes()
this.#setupNewRoutes()
}

#setupRoutes() {
#setupLegacyRoutes() {
this.#router.addPath('page-set :page(\\d+) :deviceId', (match) => {
const page = parseInt(match.page)
const deviceId = match.deviceId
@@ -73,13 +92,13 @@ export class ServiceTcpUdpApi extends CoreBase {

this.logger.info(`Got bank-press (trigger) ${controlId}`)

if (!this.controls.pressControl(controlId, true)) {
if (!this.controls.pressControl(controlId, true, this.#protocolName)) {
throw new ApiMessageError('Page/bank out of range')
}

setTimeout(() => {
this.logger.info(`Auto releasing bank-press ${controlId}`)
this.controls.pressControl(controlId, false)
this.controls.pressControl(controlId, false, this.#protocolName)
}, 20)
})

@@ -88,7 +107,7 @@ export class ServiceTcpUdpApi extends CoreBase {

this.logger.info(`Got bank-down (trigger) ${controlId}`)

if (!this.controls.pressControl(controlId, true)) {
if (!this.controls.pressControl(controlId, true, this.#protocolName)) {
throw new ApiMessageError('Page/bank out of range')
}
})
@@ -98,7 +117,7 @@ export class ServiceTcpUdpApi extends CoreBase {

this.logger.info(`Got bank-up (trigger) ${controlId}`)

if (!this.controls.pressControl(controlId, false)) {
if (!this.controls.pressControl(controlId, false, this.#protocolName)) {
throw new ApiMessageError('Page/bank out of range')
}
})
@@ -170,13 +189,211 @@ export class ServiceTcpUdpApi extends CoreBase {
throw new ApiMessageError('Scan failed')
}
})
}

#surfaceSetPage = (match) => {
const page = parseInt(match.page)
const surfaceId = match.surfaceId

this.surfaces.devicePageSet(surfaceId, page)

return `If ${surfaceId} is connected`
}
#surfacePageUp = (match) => {
const surfaceId = match.surfaceId

this.surfaces.devicePageUp(surfaceId)

return `If ${surfaceId} is connected`
}
#surfacePageDown = (match) => {
const surfaceId = match.surfaceId

this.surfaces.devicePageDown(surfaceId)

return `If ${surfaceId} is connected`
}

#locationPress = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location press at ${formatLocation(location)} (${controlId})`)

if (!controlId || !this.controls.pressControl(controlId, true, this.#protocolName)) {
throw new ApiMessageError('No control at location')
}

setTimeout(() => {
this.logger.info(`Auto releasing ${formatLocation(location)} (${controlId})`)
this.controls.pressControl(controlId, false, this.#protocolName)
}, 20)
}
#locationDown = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location down at ${formatLocation(location)} (${controlId})`)

if (!controlId || !this.controls.pressControl(controlId, true, this.#protocolName)) {
throw new ApiMessageError('No control at location')
}
}
#locationUp = (match) => {
const { location, controlId } = this.#locationParse(match)

this.#router.addPath('custom-variable :name set-value :value(.*)', async (match) => {
const result = this.instance.variable.custom.setValue(match.name, match.value)
if (result) {
throw new ApiMessageError(result)
this.logger.info(`Got location up at ${formatLocation(location)} (${controlId})`)

if (!controlId || !this.controls.pressControl(controlId, false, this.#protocolName)) {
throw new ApiMessageError('No control at location')
}
}
#locationRotateLeft = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location rotate-left at ${formatLocation(location)} (${controlId})`)

if (!controlId || !this.controls.rotateControl(controlId, false, this.#protocolName)) {
throw new ApiMessageError('No control at location')
}
}
#locationRotateRight = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location rotate-right at ${formatLocation(location)} (${controlId})`)

if (!controlId || !this.controls.rotateControl(controlId, true, this.#protocolName)) {
throw new ApiMessageError('No control at location')
}
}
#locationSetStep = (match) => {
const step = parseInt(match.step)
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location set-step at ${formatLocation(location)} (${controlId}) to ${step}`)
if (!controlId) {
throw new ApiMessageError('No control at location')
}

const control = this.controls.getControl(controlId)
if (!control || typeof control.stepMakeCurrent !== 'function') {
throw new ApiMessageError('No control at location')
}

if (!control.stepMakeCurrent(step)) throw new ApiMessageError('Step out of range')
}

#locationStyleText = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location style text at ${formatLocation(location)} (${controlId}) `)
if (!controlId) {
throw new ApiMessageError('No control at location')
}

const control = this.controls.getControl(controlId)
if (control && typeof control.styleSetFields === 'function') {
const text = match.text || ''

control.styleSetFields({ text: text })
} else {
throw new ApiMessageError('No control at location')
}
}

#locationStyleColor = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location style color at ${formatLocation(location)} (${controlId}) `)
if (!controlId) {
throw new ApiMessageError('No control at location')
}

const control = this.controls.getControl(controlId)
if (control && typeof control.styleSetFields === 'function') {
const color = parseColorToNumber(match.color)

control.styleSetFields({ color: color })
} else {
throw new ApiMessageError('No control at location')
}
}

#locationStyleBgcolor = (match) => {
const { location, controlId } = this.#locationParse(match)

this.logger.info(`Got location style bgcolor at ${formatLocation(location)} (${controlId}) `)
if (!controlId) {
throw new ApiMessageError('No control at location')
}

const control = this.controls.getControl(controlId)
if (control && typeof control.styleSetFields === 'function') {
const color = parseColorToNumber(match.bgcolor)

control.styleSetFields({ bgcolor: color })
} else {
throw new ApiMessageError('No control at location')
}
}

#setupNewRoutes() {
// surface pages
this.#router.addPath('surface :surfaceId page-set :page(\\d+)', this.#surfaceSetPage)
this.#router.addPath('surface :surfaceId page-up', this.#surfacePageUp)
this.#router.addPath('surface :surfaceId page-down', this.#surfacePageDown)

// control by location
this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) press', this.#locationPress)
this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) down', this.#locationDown)
this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) up', this.#locationUp)
this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) rotate-left', this.#locationRotateLeft)
this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) rotate-right', this.#locationRotateRight)
this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) set-step :step(\\d+)', this.#locationSetStep)

this.#router.addPath('location :page(\\d+)/:row(\\d+)/:column(\\d+) style text{ :text}?', this.#locationStyleText)
this.#router.addPath(
'location :page(\\d+)/:row(\\d+)/:column(\\d+) style color :color(.+)',
this.#locationStyleColor
)
this.#router.addPath(
'location :page(\\d+)/:row(\\d+)/:column(\\d+) style bgcolor :bgcolor(.+)',
this.#locationStyleBgcolor
)

// surfaces
this.#router.addPath('surfaces rescan', async () => {
this.logger.debug('Rescanning USB')

try {
await this.surfaces.triggerRefreshDevices()
} catch (e) {
throw new ApiMessageError('Scan failed')
}
})

// custom variables
this.#router.addPath('custom-variable :name set-value :value(.*)', this.#customVariableSetValue)
}

#customVariableSetValue = (match) => {
const result = this.instance.variable.custom.setValue(match.name, match.value)
if (result) {
throw new ApiMessageError(result)
}
}

#locationParse = (match) => {
const location = {
pageNumber: Number(match.page),
row: Number(match.row),
column: Number(match.column),
}

const controlId = this.registry.page.getControlIdAt(location)

return {
location,
controlId,
}
}

/**
2 changes: 1 addition & 1 deletion lib/Service/Udp.js
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ class ServiceUdp extends ServiceUdpBase {
constructor(registry) {
super(registry, 'udp', 'Service/Udp', 'udp_enabled', 'udp_listen_port')

this.#api = new ServiceTcpUdpApi(registry)
this.#api = new ServiceTcpUdpApi(registry, 'udp')

this.init()
}
3 changes: 0 additions & 3 deletions test/Service/OscApi.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { jest } from '@jest/globals'
import { mock } from 'jest-mock-extended'
import { ServiceOscApi } from '../../lib/Service/OscApi'
import express from 'express'
import supertest from 'supertest'
import bodyParser from 'body-parser'
import { rgb } from '../../lib/Resources/Util'

const mockOptions = {
790 changes: 790 additions & 0 deletions test/Service/TcpUdpApi.test.js

Large diffs are not rendered by default.