// 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)
})