OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / webui / test / protocol.test.js

89 lines · javascript ref:
// Vectors generated from blender-addon/cthuloid_lights/protocol.py, so the web
// UI and the addon emit byte-identical commands for the same look.
import test from 'brittle'
import * as p from '../lib/protocol.js'

const RGBE = [
  [[1, 1, 1, 1000, 30000, false, 1000], [65535, 65535, 30000]],
  [[0.5, 0.25, 0.125, 1000, 30000, false, 1000], [8192, 16384, 15000]],
  [[1, 0, 0, 1000, 45000, false, 1000], [0, 0, 45000]],
  [[1, 1, 1, 500, 30000, true, 1000], [32768, 32768, 15000]],
  [[-0.3, 1.7, 0.02, 0, 30000, false, 1000], [1311, 65535, 9000]],
  [[1, 0.5, 0, 1000, 60000, false, 1000], [0, 32768, 45000]]
]

const PICK = [
  [0, [1, 1]], [1, [1, 1]], [60, [1, 60]], [999, [1, 999]], [1000, [1, 1000]],
  [1001, [500, 2]], [2500, [833, 3]], [59999, [999, 60]], [1000000, [1000, 1000]],
  [5000000, [1000, 1000]]
]

test('channels match protocol.py', (t) => {
  for (const [args, want] of RGBE) t.alike(p.channelsFromRGBE(...args), want, JSON.stringify(args))
})

test('pwm2 period split matches protocol.py', (t) => {
  for (const [ms, want] of PICK) t.alike(p.pickPwm2Args(ms), want, 'fade ' + ms)
})

test('command strings match protocol.py', (t) => {
  t.is(p.pwmCommand([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 65535]), 'pwm 0 1 2 3 4 5 6 7 8 9 10 65535\n')
  t.is(p.pwm2Command([1, 2, 3], 2500), 'pwm2 833 3 1 2 3\n')
  t.is(p.gammaCommand(0.1, 2.345, 9), 'gamma 0.50 2.35 5.00\n')
  t.alike([0, 1000, 99999].map(p.freqCommand), ['freq 1\n', 'freq 1000\n', 'freq 40000\n'])
  t.is(p.blackoutCommand(), 'stopfade\npwm 0 0 0 0 0 0 0 0 0 0 0 0\n')
})

test('halves round to even, as Python does', (t) => {
  t.is(p.roundHalfEven(2.5), 2)
  t.is(p.roundHalfEven(3.5), 4)
  t.is(p.roundHalfEven(32767.5), 32768)
  t.is(p.roundHalfEven(2.4999), 2)
})

test('unit values: slots in BGR order, red capped, empty slots dark', (t) => {
  const slots = [{ r: 1, g: 0, b: 0, level: 1 }, { r: 0, g: 0, b: 1, level: 0.5 }]
  t.alike(p.unitValues(slots, 30000), [0, 0, 30000, 32768, 0, 0, 0, 0, 0, 0, 0, 0])
})

test('state and greeting parsing', (t) => {
  const s = p.parseState('state 1 1000 1.00 2.00 1.50 1 2 3 4 5 6 7 8 9 10 11 65535\r\n')
  t.alike(s, { fading: true, freq: 1000, gamma: { b: 1, g: 2, r: 1.5 }, values: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 65535] })
  t.is(p.parseState('OK pwm 12'), null)
  t.is(p.parseState('state 0 1000 1 1 1 1 2'), null)
  t.is(p.parseGreeting('studiolight ready\r\n'), 'studiolight')
  t.is(p.parseGreeting('hostname: studiolight'), null)
})

// ---- proto 3 (firmware esp324xlight.ino, docs/DESIGN-collab.md §5) ----------

test('proto 3 lines parse', (t) => {
  t.alike(p.parseHello('hello proto=3 unit=studiolight epoch=22 pk=ab01\r\n'),
    { proto: 3, unit: 'studiolight', epoch: 22, pk: 'ab01' })
  t.is(p.parseHello('studiolight ready'), null)
  t.alike(p.parseHeld('held 2 alice 22 7'), { slot: 2, holder: 'alice', epoch: 22, n: 7 })
  t.alike(p.parseGranted('granted 0 22 1'), { slot: 0, epoch: 22, n: 1 })
  t.alike(p.parseBusy('busy 3 bob'), { slot: 3, holder: 'bob' })
  const sig = 'f'.repeat(128)
  t.alike(p.parseFree('free 1 22 9 100 200 300 idle carol ' + sig),
    { slot: 1, epoch: 22, n: 9, b: 100, g: 200, r: 300, why: 'idle', holder: 'carol', sig })
  t.alike(p.parseValues('values 1 2 3 4 5 6 7 8 9 10 11 12'), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
  t.is(p.parseValues('values 1 2'), null)
  t.is(p.parseHeld('held x alice 1 1'), null)
})

test('proto 3 commands', (t) => {
  t.is(p.grantCommand(2, 'alice'), 'grant 2 alice\n')
  t.is(p.grantCommand('all', 'web'), 'grant all web\n')
  t.is(p.slotCommand(1, 22, 7, [10, 20, 30], 60), 'slot 1 22 7 10 20 30 60\n')
  t.is(p.releaseCommand(1), 'release 1\n')
  t.is(p.freeMessage('studiolight', { slot: 2, epoch: 9, n: 41, b: 1, g: 2, r: 45000 }),
    'cthuloid/free/1 studiolight 2 9 41 1 2 45000')
})

test('holder ids are made safe for the firmware', (t) => {
  t.is(p.holderId('Alice Smith!'), 'Alice-Smith')
  t.is(p.holderId(''), 'web')
  t.is(p.holderId('x'.repeat(50)).length, 32)
})