From fcb4bba983d9b3e8913d819793824c06719d2a73 Mon Sep 17 00:00:00 2001 From: johntalton Date: Sun, 26 May 2024 19:11:24 -0400 Subject: [PATCH] =?UTF-8?q?pot=20updates=20=F0=9F=AA=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +- public/css/app.css | 26 +- public/css/ds1841.css | 114 ++++++ public/css/themes.css | 18 +- public/custom-elements/ds1841.html | 98 +++++ public/devices-i2c/device-factory.js | 4 +- public/devices-i2c/ds1841.js | 560 +++++++++++++++++++-------- public/index.css | 1 + public/index.html | 4 +- public/util/range.js | 6 +- 10 files changed, 644 insertions(+), 193 deletions(-) create mode 100644 public/css/ds1841.css create mode 100644 public/custom-elements/ds1841.html diff --git a/package.json b/package.json index c788446..f0dafc8 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,11 @@ "@johntalton/adxl375": "^1.0.0", "@johntalton/aht20": "^1.1.1", "@johntalton/am2320": "^1.0.0", - "@johntalton/and-other-delights": "^7.0.1", + "@johntalton/and-other-delights": "../and-other-delights", "@johntalton/bitsmush": "^1.0.1", "@johntalton/boschieu": "^6.0.1", - "@johntalton/ds1841": "^1.0.1", - "@johntalton/ds3231": "^1.1.0", + "@johntalton/ds1841": "../ds1841", + "@johntalton/ds3231": "^1.1.1", "@johntalton/ds3502": "^4.0.0", "@johntalton/excamera-i2cdriver": "^1.0.0", "@johntalton/ht16k33": "^1.0.3", diff --git a/public/css/app.css b/public/css/app.css index f159ab2..404ffbe 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -4,21 +4,25 @@ @import url('app-main.css'); body { - font-family: 'Tahoma'; - /* font-family: fantasy; */ - /* font-family: 'Brush Script MT',cursive; */ + font-family: 'Tahoma'; + /* font-family: fantasy; */ + /* font-family: 'Brush Script MT',cursive; */ } body { - display: grid; + display: grid; - grid-template-areas: - "logo nav" - "aside main"; + grid-template-areas: + "logo nav" + "aside main"; - grid-template-rows: 5em 1fr; - grid-template-columns: minmax(300px, 20%) 1fr; + grid-template-rows: 5em 1fr; + grid-template-columns: 0 1fr; - background-color: var(--base-accent-background-color, red); - color: var(--base-accent-text-color, red); + background-color: var(--base-accent-background-color, red); + color: var(--base-accent-text-color, red); } + +body[data-view ^= "aside"] { + grid-template-columns: 20% 1fr; +} \ No newline at end of file diff --git a/public/css/ds1841.css b/public/css/ds1841.css new file mode 100644 index 0000000..2a13936 --- /dev/null +++ b/public/css/ds1841.css @@ -0,0 +1,114 @@ +ds1841-config { + + & [data-shadow-warning] { + display: none; + } + + &[data-enable ~= "shadow"] { + + & [data-shadow-warning] { + display: flex; + grid-column: -2 / -1; + + font-weight: bold; + + /* background: repeating-linear-gradient( + -45deg, + #0000000f, + #ffffff 5px, + transparent 5px, + transparent 25px + ); */ + } + } + + & ol[data-lut-list] { + grid-column: 1 / -1; + + display: grid; + grid-template-columns: repeat(auto-fill, 30%); + /* justify-items: center; */ + justify-self: stretch; + + + gap: 1em; + list-style: none; + + & li { + display: grid; + gap: 1em; + grid-template-columns: 1fr 1fr; + align-items: center; + + /* margin-block: 0.25em; */ + + padding: .5em; + padding-inline-start: 1em; + background-color: var(--color-accent--lighter, red); + border-radius: 1em; + } + } + + + + & [data-number-range] { + display: flex; + gap: 1em; + } + + + & form { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1em; + + align-items: center; + align-self: stretch; + + & fieldset { + grid-column: 1 / -1; + + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1em; + + padding: 2em; + margin-block-end: 1em; + border-radius: 1em; + border-color: var(--color-accent--darker, red); + + justify-self: stretch; + justify-items: start; + align-items: center; + + & legend { + font-weight: bold; + padding-inline: 1ch; + } + } + + & select { + background-color: var(--color-accent--lighter, red); + color: var(--color-accent--lighter-text, red); + } + + & input[type="checkbox"] { + accent-color: var(--color-accent--darker, red); + width: 1.25em; + height: 1.25em; + margin: 0.5em; + } + + & input[type="number"] { + min-width: 7ch; + } + & input[type="number"]:not(:where(:focus-visible, :focus)) { + background-color: var(--color-accent--lighter, red); + } + & input[type="number"]:where(:focus-visible, :focus) { + background-color: var(--color-white, red); + color: var(--color-black, red); + } + + } +} diff --git a/public/css/themes.css b/public/css/themes.css index 1bea8f5..ff5937e 100644 --- a/public/css/themes.css +++ b/public/css/themes.css @@ -239,4 +239,20 @@ --color-accent--dark-text: black; --color-accent--darker: oklab(0.68 -0.1 -0.17); --color-accent--darker-text: white; -} \ No newline at end of file + + @media (prefers-color-scheme: dark) { + --color-accent: oklab(0.27 0 0); + --color-accent-text: white; + --color-accent--light: oklab(0.53 -0.07 -0.17); + --color-accent--light-text: white; + --color-accent--lighter: oklab(0.31 0 0); + --color-accent--lighter-text: white; + --color-accent--lightest: oklab(0.43 0 0); + --color-accent--lightest-text: white; + --color-accent--dark: oklab(0.27 0 0); + --color-accent--dark-text: white; + --color-accent--darker: oklab(0.68 -0.1 -0.17); + --color-accent--darker-text: white; + } +} + diff --git a/public/custom-elements/ds1841.html b/public/custom-elements/ds1841.html new file mode 100644 index 0000000..685cfb8 --- /dev/null +++ b/public/custom-elements/ds1841.html @@ -0,0 +1,98 @@ + + + + + +
+ + + +
+ +
+
+ + + + +
+ +
+ + + + + + + + +

ADC and Summation Shadowed

+
+ +
+ Lookup Table Mode + + + + + + +
+
+ +
+
+ + +
+ +
+ + +
+

Initial Value Shadowed

+
+ + +
+ + +
+ + + + + +
+ + + + + +
+ + +
+
+ +
+
+
    + + + +
+
+
+
+ + + diff --git a/public/devices-i2c/device-factory.js b/public/devices-i2c/device-factory.js index 66b973f..f910b3d 100644 --- a/public/devices-i2c/device-factory.js +++ b/public/devices-i2c/device-factory.js @@ -2,7 +2,7 @@ import { TCA9548Builder } from './tca9548a.js' import { DS3502Builder } from './ds3502.js' import { DS1841Builder } from './ds1841.js' -import { BoschIEUBuilder } from './boschieu.js' +// import { BoschIEUBuilder } from './boschieu.js' // import { BNO08XBuilder } from './bno08x.js' // import { SSD1306Builder } from './ssd1306.js' import { PCA9536Builder} from './pca9536.js' @@ -50,7 +50,7 @@ const BY_NAME = { [PCA_9536_INFO.name]: (definition, ui) => PCA9536Builder.builder(definition, ui), [TCA9548_INFO.name]: (definition, ui) => TCA9548Builder.builder(definition, ui), [DS3502_INFO.name]: (definition, ui) => DS3502Builder.builder(definition, ui), - [BOSCH_IEU_INFO.name]: (definition, ui) => BoschIEUBuilder.builder(definition, ui), + // [BOSCH_IEU_INFO.name]: (definition, ui) => BoschIEUBuilder.builder(definition, ui), // [BNO_08X_INFO.name]: (definition, ui) => BNO08XBuilder.builder(definition, ui), // [SSD1306_INFO.name]: (definition, ui) => SSD1306Builder.builder(definition, ui), [PCF_8574_INFO.name]: (definition, ui) => PCF8574Builder.builder(definition, ui), diff --git a/public/devices-i2c/ds1841.js b/public/devices-i2c/ds1841.js index 20079d5..15909b7 100644 --- a/public/devices-i2c/ds1841.js +++ b/public/devices-i2c/ds1841.js @@ -1,40 +1,116 @@ import { I2CAddressedBus } from '@johntalton/and-other-delights' -import { DS1841, MODE, ADC, ADDITION_MODE, ACCESS_CONTROL, LUT_MODE } from '@johntalton/ds1841' +import { DS1841, LUT_BYTE_PER_ENTRY, LUT_BYTE_SIZE, LUT_TABLE_SIZE } from '@johntalton/ds1841' +import { range } from '../util/range.js' + +const SINGLE_SPACE = ' ' +const EMPTY_SPACE = '' + +export class NotDOMTokenList { + #set + #attr + constructor(attr) { + this.#set = new Set(attr.value.split(SINGLE_SPACE).filter(v => v !== EMPTY_SPACE)) + this.#attr = attr + } + _update() { + this.#attr.value = this.value + } -async function fetchOutputs(device) { - const lutIndex = await device.getLUTIndex() - const lutValue = await device.getLUTByIndex(lutIndex) - const ivr = await device.getIVR() - const temp = await device.getTemperature() - const volt = await device.getVoltage() + toString() { + return [ ...this.#set.values() ].join(SINGLE_SPACE) + } - const { wiperAccessControl, lutIndexMode } = await device.getCR2() + get value() { return this.toString() } - const controlFunctionality = (wiperAccessControl === ACCESS_CONTROL.MANUAL) ? 'WIPER' - : (lutIndexMode === LUT_MODE.FROM_DIRECT_VALUE) ? 'LUTAR' : 'TEMP' + contains(token) { + return this.#set.has(token) + } - return { temp, volt, ivr, lutIndex, lutValue, controlFunctionality } -} + add(...tokens) { + for(const token of tokens) { + if(token === EMPTY_SPACE) { throw new SyntaxError('token empty') } + if(token.includes(SINGLE_SPACE)) { throw new InvalidCharacterError('token whitespace') } + } + + for(const token of tokens) { + this.#set.add(token) + } + + this._update() + } + + + remove(...tokens) { + for(const token of tokens) { + if(token === EMPTY_SPACE) { throw new SyntaxError('token empty') } + if(token.includes(SINGLE_SPACE)) { throw new InvalidCharacterError('token whitespace') } + } + + for(const token of tokens) { + this.#set.delete(token) + } + + this._update() + } + + toggle(token, force) { + if(token === EMPTY_SPACE) { throw new SyntaxError('token empty') } + if(token.includes(SINGLE_SPACE)) { throw new InvalidCharacterError('token whitespace') } + + if(this.#set.has(token)) { + if(force === undefined || force === false) { + this.#set.delete(token) + this._update() + return false + } + + return true + } -async function updateOutputs({ - temp, volt, ivr, lutIndex, lutValue, controlFunctionality -}) { - const outputTemp = document.getElementById('outputTemp') - const outputVoltage = document.getElementById('outputVoltage') - const outputIVR = document.getElementById('outputIVR') - const outputLUTIndex = document.getElementById('outputLUTIndex') - const outputLUTValue = document.getElementById('outputLUTValue') - const outputFunctionality = document.getElementById('outputFunctionality') - - outputTemp.innerText = temp - outputVoltage.innerText = volt - outputIVR.innerText = ivr - outputLUTIndex.innerText = lutIndex - outputLUTValue.innerText = lutValue - outputFunctionality.innerText = controlFunctionality + if(force === undefined || force === true) { + this.#set.add(token) + this._update() + return true + } + + return false + } } +// async function fetchOutputs(device) { +// const lutIndex = await device.getLUTIndex() +// const lutValue = await device.getLUTByIndex(lutIndex) +// const ivr = await device.getIVR() +// const temp = await device.getTemperature() +// const volt = await device.getVoltage() + +// const { wiperAccessControl, lutIndexMode } = await device.getCR2() + +// const controlFunctionality = (wiperAccessControl === ACCESS_CONTROL.MANUAL) ? 'WIPER' +// : (lutIndexMode === LUT_MODE.FROM_DIRECT_VALUE) ? 'LUTAR' : 'TEMP' + +// return { temp, volt, ivr, lutIndex, lutValue, controlFunctionality } +// } + +// async function updateOutputs({ +// temp, volt, ivr, lutIndex, lutValue, controlFunctionality +// }) { +// const outputTemp = document.getElementById('outputTemp') +// const outputVoltage = document.getElementById('outputVoltage') +// const outputIVR = document.getElementById('outputIVR') +// const outputLUTIndex = document.getElementById('outputLUTIndex') +// const outputLUTValue = document.getElementById('outputLUTValue') +// const outputFunctionality = document.getElementById('outputFunctionality') + +// outputTemp.innerText = temp +// outputVoltage.innerText = volt +// outputIVR.innerText = ivr +// outputLUTIndex.innerText = lutIndex +// outputLUTValue.innerText = lutValue +// outputFunctionality.innerText = controlFunctionality +// } + export class DS1841Builder { #abus #device @@ -63,223 +139,365 @@ export class DS1841Builder { signature() { } async buildCustomView(selectionElem) { - const root = document.createElement('ds1841-config') + const response = await fetch('./custom-elements/ds1841.html') + if (!response.ok) { throw new Error('no html for view') } + const view = await response.text() + const doc = (new DOMParser()).parseFromString(view, 'text/html') - root.addEventListener('change', e => { + const root = doc?.querySelector('ds1841-config') + if (root === null) { throw new Error('no root for template') } - if(e.target.id === 'wiperSlider') { - e.target.disabled = true - return - } + const configForm = root.querySelector('form[data-config]') + const valuesForm = root.querySelector('form[data-values]') + const lutForm = root.querySelector('form[data-lut]') - const updateModeNVRAM = e.target.id === 'updateNVRAMMode' + const enableShadowSelect = configForm?.querySelector('select[name="enableShadowRegisters"]') + const enableADCCheckbox = configForm?.querySelector('input[name="enableADC"]') + const enableSumCheckbox = configForm?.querySelector('input[name="enableLUTSummation"]') + const enableLUTAddrCheckbox = configForm?.querySelector('input[name="enableTemperatureUpdates"]') + const enableLUTValueCheckbox = configForm?.querySelector('input[name="enableIndexUpdates"]') - if(updateModeNVRAM || e.target.id === 'updateOnlyMode') { + const ivrNumber = valuesForm?.querySelector('input[name="ivrValue"]') + const ivrRange = valuesForm?.querySelector('input[name="ivrValueAlt"]') + const lutValueNumber = valuesForm?.querySelector('input[name="lutValue"]') + const lutValueRange = valuesForm?.querySelector('input[name="lutValueAlt"]') + const lutIndexNumber = valuesForm?.querySelector('input[name="lutIndex"]') + const temperatureOutput = valuesForm?.querySelector('output[name="temperature"]') + const voltageOutput = valuesForm?.querySelector('output[name="voltage"]') - const group = document.getElementById('updateModeGroup') - group.disabled = true + const lutList = lutForm?.querySelector('ol[data-lut-list]') + const lustListTemplate = lutList?.querySelector(':scope > template') - const mode = updateModeNVRAM ? MODE.SET_AND_UPDATE : MODE.UPDATE_ONLY + const refreshButton = root.querySelector('button[data-refresh]') - console.log('set mode', mode) + const tokens = new NotDOMTokenList(root.getAttributeNode('data-enable')) - this.#device.setCR0({ mode }) - .then(() => { - group.disabled = false - }) - .catch(e => console.log(e)) - return - } + const revalidateValues = async => { + const adc = tokens.contains('adc') + const addr = adc && !tokens.contains('address') + const value = adc && !tokens.contains('value') + + lutValueNumber.disabled = !value + lutValueRange.disabled = !value + + lutIndexNumber.disabled = !addr + + temperatureOutput.disabled = !adc + voltageOutput.disabled = !adc + } + + const delayMs = ms => new Promise(resolve => setTimeout(resolve, ms)) + const SHADOW_DELAY_MS = 20 + const delayEEPROM = () => delayMs(SHADOW_DELAY_MS) + + const refreshLUTValue = async () => { + // await delayMs(1000) + const lutValue = await this.#device.getLUTValue() + lutValueNumber.value = lutValue + lutValueRange.value = lutValue + } + + const refreshValues = async () => { + const ivr = await this.#device.getIVR() + const lutIndex = await this.#device.getLUTIndex() + const lutValue = await this.#device.getLUTValue() + const temperature = await this.#device.getTemperature() + const voltage = await this.#device.getVoltage() + + + const t = `${Math.trunc(temperature * 100.0) / 100.0} ℃` + const v = `${Math.trunc(voltage / 1000.0 * 100.0) / 100.0} mV` + + ivrNumber.value = ivr + ivrRange.value = ivr + + lutValueNumber.value = lutValue + lutValueRange.value = lutValue + + lutIndexNumber.value = lutIndex - if(e.target.id === 'enableADC') { - const sumElem = document.getElementById('enableSummation') - const controlsGroup = document.getElementById('functionalityControl') + temperatureOutput.value = t + voltageOutput.value = v + } - console.log(e, e.target.checked) - const enableControlsOnSuccess = e.target.checked - controlsGroup.disabled = true + const refreshControl0 = async () => { + const { + enableShadowEE + } = await this.#device.getCR0() - e.target.disabled = true + enableShadowSelect.value = enableShadowEE - const additionMode = sumElem.checked ? ADDITION_MODE.SUMMED : ADDITION_MODE.DIRECT - const updateMode = e.target.checked ? ADC.ON : ADC.OFF + // implications + tokens.toggle('shadow', enableShadowEE) + } - this.#device.setCR1({ - updateMode, - additionMode + const refreshControl1 = async () => { + const { + enableSummation, + enableADC + } = await this.#device.getCR1() + + enableADCCheckbox.checked = enableADC + enableSumCheckbox.checked = enableSummation + + // implications + tokens.toggle('adc', enableADC) + tokens.toggle('sum', enableSummation) + } + + const refreshControl2 = async () => { + const { + enableLUTValueUpdate, + enableLUTAddressUpdate + } = await this.#device.getCR2() + + enableLUTAddrCheckbox.checked = enableLUTAddressUpdate + enableLUTValueCheckbox.checked = enableLUTValueUpdate + + // implications + tokens.toggle('address', enableLUTAddressUpdate) + tokens.toggle('value', enableLUTValueUpdate) + } + + const refreshControls = async () => { + await refreshControl0() + await refreshControl1() + await refreshControl2() + } + + + function temperatureRangeForLUTIndex(index) { + if(index === 0) { return [ -Infinity, -40 ] } + if(index >= 71) { return [ 101, Infinity]} + + const temp = (index * 2) + -40 + return [ temp - 1, temp ] + } + + + const refreshLUTBulk = async () => { + // as most i2c does not allow infinite reads, an upper bound + // must be set, the LUT is 72 bytes, however, our current + // but driver can only support upto 64 bytes. + // Choosing a 'random' value here to make things work + const SAFE_READ_SIZE = 24 + const maxCount = Math.floor(SAFE_READ_SIZE / LUT_BYTE_PER_ENTRY) + + for(const begin of range(0, LUT_TABLE_SIZE, maxCount)) { + const size = Math.min(begin + maxCount, LUT_TABLE_SIZE) - begin + if(size === 0) { break } + + const lut = await this.#device.getLUT(begin, size) + + lut.forEach((value, current) => { + const index = begin + current + + const li = lutForm?.querySelector(`li[data-index="${index}"]`) + const lutEntryNumber = li?.querySelector('input') + const lutEntryLabel = li?.querySelector('label') + + const [low, high] = temperatureRangeForLUTIndex(index) + + lutEntryNumber.value = value + lutEntryLabel.innerText = `${low} - ${high} ℃` }) - .then(() => { - console.log('cr1 updated', enableControlsOnSuccess) - controlsGroup.disabled = !enableControlsOnSuccess - e.target.disabled = false - }) - .catch(e => console.log(e)) - - return } + } + const refreshLUT = async (index) => { - if(e.target.name === 'controlFunctionalityGroup') { - const group = document.getElementById('functionalityControl') + } - const selected = document.querySelector('input[name="controlFunctionalityGroup"]:checked') - group.disabled = true - const wiperAccessControl = (selected.value === 'WIPER') ? ACCESS_CONTROL.MANUAL : ACCESS_CONTROL.ADC_CONTROL - const lutIndexMode = (selected.value === 'LUTAR') ? LUT_MODE.FROM_DIRECT_VALUE : LUT_MODE.FROM_ADC_TEMPERATURE + configForm?.addEventListener('change', async event => { + const whatChanged = event.target.closest('[data-what]').getAttribute('data-what') - this.#device.setCR2({ - wiperAccessControl, - lutIndexMode + if(whatChanged.includes('control0')) { + await this.#device.setCR0({ + enableShadowEE: enableShadowSelect.value === 'true' }) - .then(() => { - group.disabled = false - }) - .then(() => fetchOutputs(this.#device)) - .then(updateOutputs) - .catch(e => console.log(e)) + await delayEEPROM() - return + await refreshControl0() + await refreshControl1() } - if(e.target.id.startsWith('lutValueIndex')) { - const value = parseInt(e.target.value, 10) - const lutIndex = parseInt(e.target.getAttribute('data-index'), 10) + if(whatChanged.includes('control1')) { + await this.#device.setCR1({ + enableADC: enableADCCheckbox.checked, + enableSummation: enableSumCheckbox.checked + }) + + if(!tokens.contains('shadow')) { + await delayEEPROM() + } + + await refreshControl1() + } + + if(whatChanged.includes('control2')) { + await this.#device.setCR2({ + enableLUTAddressUpdate: enableLUTAddrCheckbox.checked, + enableLUTValueUpdate: enableLUTValueCheckbox.checked + }) + + await refreshControl2() + } - console.log('set lut index', lutIndex, value) - this.#device.setLUTByIndex(lutIndex, value) - .then(() => { - console.log('lut index set') - }) - .catch(e => console.log(e)) + await revalidateValues() + }) - return + + ivrNumber?.addEventListener('change', async event => { + event.preventDefault() + + ivrNumber.disabled = true + ivrRange.disabled = true + + ivrRange.value = ivrNumber.value + + await this.#device.setIVR(parseInt(ivrNumber.value)) + + if(!tokens.contains('shadow')) { + await delayEEPROM() } + await refreshLUTValue() + + ivrNumber.disabled = false + ivrRange.disabled = false + }) + ivrRange?.addEventListener('change', async event => { + event.preventDefault() - console.warn('change unhandled', e) + ivrNumber.disabled = true + ivrRange.disabled = true - }, {}) + ivrNumber.value = ivrRange.value + await this.#device.setIVR(parseInt(ivrRange.value)) + + if(!tokens.contains('shadow')) { + await delayEEPROM() + } + await refreshLUTValue() - // await this.#device.setIVR(3) - const wiper = await this.#device.getWIPER() + ivrNumber.disabled = false + ivrRange.disabled = false + }) - const { mode } = await this.#device.getCR0() - const { updateMode, additionMode } = await this.#device.getCR1() + lutValueNumber?.addEventListener('change', async event => { + event.preventDefault() - const { - temp, volt, ivr, lutIndex, lutValue, controlFunctionality - } = await fetchOutputs(this.#device) + lutValueNumber.disabled = true + lutValueRange.disabled = true - const lut = await this.#device.getLUT() + lutValueRange.value = lutValueNumber.value + await this.#device.setLUTValue(parseInt(lutValueNumber.value)) + lutValueNumber.disabled = false + lutValueRange.disabled = false + }) + lutValueRange?.addEventListener('change', async event => { + event.preventDefault() - const template = ` + lutValueNumber.disabled = true + lutValueRange.disabled = true -
- - ${temp} + lutValueNumber.value = lutValueRange.value - - ${volt} + await this.#device.setLUTValue(parseInt(lutValueRange.value)) - - ${ivr} + lutValueNumber.disabled = false + lutValueRange.disabled = false + }) - - ${lutIndex} + lutIndexNumber?.addEventListener('change', async event => { + event.preventDefault() - - ${lutValue} + lutIndexNumber.disabled = true - - ${wiper} + await this.#device.setLUTIndex(parseInt(lutIndexNumber.value)) + await refreshLUTValue() - - ${controlFunctionality} -
+ lutIndexNumber.disabled = false + }) -
- Mode - - + for(const lutIndex of range(0, LUT_TABLE_SIZE - 1)) { + const liDoc = lustListTemplate.content.cloneNode(true) + const li = liDoc.querySelector('li') - - -
+ li.setAttribute('data-index', lutIndex) -
- + lutList.append(li) - - -
+ const lutEntryNumber = li.querySelector('input') + lutEntryNumber.addEventListener('change', async event => { + event.preventDefault() -
- + const value = pasrseInt(lutEntryNumber.value) + await this.#device.setLUT(lutIndex, value) - - + }) + } + refreshButton?.addEventListener('click', async event => { + event.preventDefault() - - + refreshButton.disabled = true - - + // await refreshControls() + // await revalidateValues() - - + await refreshValues() -
+ refreshButton.disabled = false + }) -
- - - + const tabButtons = root.querySelectorAll('button[data-tab]') + for (const tabButton of tabButtons) { + tabButton.addEventListener('click', event => { + event.preventDefault() - - -
+ const { target } = event + const parent = target?.parentNode + const grandParent = parent.parentNode + const tabName = target.getAttribute('data-tab') -
- + // remove content active + const activeOthers = grandParent.querySelectorAll(':scope > [data-active]') + activeOthers.forEach(ao => ao.toggleAttribute('data-active', false)) - - -
+ // remove tab button active + const activeOthersTabsButtons = parent.querySelectorAll(':scope > button[data-tab]') + activeOthersTabsButtons.forEach(ao => ao.toggleAttribute('data-active', false)) -
    - ${Object.entries(lut).map(([key, value]) => { - const index = parseInt(key, 10) - const selected = lutIndex === index + const tabContentElem = grandParent.querySelector(`:scope > [data-for-tab="${tabName}"]`) + if(tabContentElem === null) { console.warn('tab content not found', tabName) } + else { + tabContentElem.toggleAttribute('data-active', true) + } - const from = (index * 2 - 1) - 40 - const to = from + 1 + tabButton.toggleAttribute('data-active', true) + }) + } - return ` -
  1. - - -
  2. - ` - }).join('')} -
- ` - root.innerHTML = template + await refreshControls() + await refreshValues() + await revalidateValues() + await refreshLUTBulk() return root } diff --git a/public/index.css b/public/index.css index cab8bbf..910f3a7 100644 --- a/public/index.css +++ b/public/index.css @@ -28,6 +28,7 @@ @import url('./css/pcf8574.css') layer(app); @import url('./css/aw9523.css') layer(app); @import url('./css/mcp23.css') layer(app); +@import url('./css/ds1841.css') layer(app); @import url('./css/gpio.css') layer(app); diff --git a/public/index.html b/public/index.html index 78a62f9..3dc5a8c 100644 --- a/public/index.html +++ b/public/index.html @@ -11,7 +11,7 @@ - + diff --git a/public/util/range.js b/public/util/range.js index 5f1661b..0cee640 100644 --- a/public/util/range.js +++ b/public/util/range.js @@ -1,5 +1,5 @@ -export function* range(start, end) { +export function* range(start, end, step = 1) { yield start - if (start === end) return - yield* range(start + 1, end) + if (start >= end) return + yield* range(start + step, end, step) }