Skip to content

Commit

Permalink
Update to version with inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
codingjoe committed Oct 3, 2024
1 parent 23d21ab commit 2a82cca
Show file tree
Hide file tree
Showing 5 changed files with 523 additions and 223 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

A browser interface for the DP100 digital power supply by Alientek.

![screenshot](screenshot-UI-graph.png)
![screenshot-2024-10-03.png](screenshot-2024-10-03.png)

## Features

- 🌐 Connect to the DP100 using your browser (no installation required).
- 📈 Histogram of the voltage and current levels.
- 📈 Power diagram of the voltage and current levels.
- 📏 Set the voltage and current levels.
- ❤️ Free, open source and build with love!

## Usage
Expand Down
240 changes: 147 additions & 93 deletions assets/js/dp100.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const FUNCTIONS = Object.freeze({
* This class is used to interact with the DP100 power supply.
* @example
*
* class MyPSU extends DP100 {
* class MyPSU extends DP100() {
* receiveBasicInfo ({vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt}) {
* console.info('Input Voltage:', vIn, 'V')
* console.info('Output Voltage:', vOut, 'V')
Expand All @@ -64,108 +64,162 @@ const FUNCTIONS = Object.freeze({
*
* const psu = new MyPSU()
* await psu.connect()
*
* @param {*} Base - The base class to extend.
* @mixin
* @returns {Base} The new class.
*/
export class DP100 {
export function DP100 (Base) {
return class extends Base {

/** The connected DP100 device. */
device = null
/** The connected DP100 device. */
settingsQueue = []

/** Connect to the DP100 device. */
async connect () {
[this.device] = await navigator.hid.requestDevice({
filters: [{ vendorId, productId }]
})
await this.device.open()
this.device.addEventListener('inputreport', this.inputReportHandler.bind(this))
setInterval(
() => this.sendReport(FUNCTIONS.BASIC_INFO), 10
)
}
/** Connect to the DP100 device. */
async connect () {
[this.device] = await navigator.hid.requestDevice({
filters: [{ vendorId, productId }]
})
await this.device.open()
this.device.addEventListener('inputreport', this.inputReportHandler.bind(this))
this.getBasicSettings().then(() => setInterval(() => this.sendReport(FUNCTIONS.BASIC_INFO), 100))
}

/**
* Send a report to the DP100.
*
* @param {Number} functionId -- The function to call on the DP100.
* @param {Uint8Array} content -- The data to send to the DP100.
* @returns {Promise<void>} -- A promise that resolves when the report is sent.
*/
async sendReport (functionId, content = null) {
content = content || new Uint8Array(0)
const report = new Uint8Array([
deviceAddr,
functionId,
content.length,
content,
0, // checksum
0 // checksum
])
const reportView = new DataView(report.buffer, report.byteOffset, report.byteLength)
const checksum = crc16(report.buffer.slice(0, report.length - 2))
reportView.setUint16(report.length - 2, checksum, true)
console.debug('device.sendReport', reportView)
return await this.device.sendReport(0, report)
}
/**
* Send a report to the DP100.
*
* @param {Number} functionId -- The function to call on the DP100.
* @param {Uint8Array} content -- The data to send to the DP100.
* @param {Number} sequence -- The sequence number for the report.
* @returns {Promise<void>} -- A promise that resolves when the report is sent.
*/
async sendReport (functionId, content = null, sequence = null) {
content = content || new Uint8Array([0])
const header = [deviceAddr, functionId, sequence, // sequence, unused if there is no content
content.length, ...content, 0, // checksum
0 // checksum
]
if (sequence === null) {
header.splice(2, 1)
}
const report = new Uint8Array(header)
const reportView = new DataView(report.buffer, report.byteOffset, report.byteLength)
const checksum = crc16(report.buffer.slice(0, report.length - 2))
reportView.setUint16(report.length - 2, checksum, true)
console.debug('device.sendReport', reportView)
return await this.device.sendReport(0, report)
}

/** Handle input reports from the DP100
* @param {HIDInputReportEvent} event
*/
inputReportHandler (event) {
console.debug('device.inputreport', event)
const data = event.data
const headerLength = 4
const header = {
deviceAddr: data.getUint8(0),
functionType: event.data.getUint8(1),
sequence: event.data.getUint8(2),
contentLength: event.data.getUint8(3),
async getBasicSettings () {
console.info('getBasicSettings')
await this.sendReport(FUNCTIONS.BASIC_SET, new Uint8Array([0 | 0x80]), 0)
}
const contentView = new DataView(data.buffer.slice(headerLength, headerLength + header.contentLength))
const checksum = data.getUint16(headerLength + header.contentLength, true)
const computedChecksum = crc16(data.buffer.slice(0, headerLength + header.contentLength))
if (computedChecksum !== checksum) {
console.error('Checksum Failed', {
expected: computedChecksum.toString(16),
received: checksum.toString(16)
})
return

async setBasicSettings ({ state, vo_set, io_set, ovp_set, ocp_set }) {
if (this.settings === undefined) {
throw new Error('Settings not loaded')
}
console.info('setBasicSettings', { state, vo_set, io_set, ovp_set, ocp_set })
const basicSet = Object.assign({}, this.settings, Object.fromEntries(Object.entries({
state, vo_set, io_set, ovp_set, ocp_set
}).filter(([k, v]) => v !== undefined)))
const index = this.settingsQueue.length
this.settingsQueue[index] = basicSet
const out = new Uint8Array(10)
const outDv = new DataView(out.buffer, out.byteOffset, out.length)
outDv.setUint8(0, index | 0x20)
outDv.setUint8(1, basicSet.state)
outDv.setUint16(2, basicSet.vo_set * 1000, true)
outDv.setUint16(4, basicSet.io_set * 1000, true)
outDv.setUint16(6, basicSet.ovp_set * 1000, true)
outDv.setUint16(8, basicSet.ocp_set * 1000, true)
await this.sendReport(FUNCTIONS.BASIC_SET, out, 0)
}
console.debug('content', contentView)

switch (header.functionType) {
case FUNCTIONS.BASIC_INFO:
this.receiveBasicInfo({
vIn: contentView.getUint16(0, true) / 1000,
vOut: contentView.getUint16(2, true) / 1000,
iOut: contentView.getUint16(4, true) / 1000,
voMax: contentView.getUint16(6, true) / 1000,
temp1: contentView.getUint16(8, true) / 10,
temp2: contentView.getUint16(10, true) / 10,
dc5V: contentView.getUint16(12, true) / 1000,
outMode: contentView.getUint8(14),
workSt: contentView.getUint8(15)
/** Handle input reports from the DP100
* @param {HIDInputReportEvent} event
*/
inputReportHandler (event) {
console.debug('device.inputreport', event)
const data = event.data
const headerLength = 4
const header = {
deviceAddr: data.getUint8(0),
functionType: event.data.getUint8(1),
sequence: event.data.getUint8(2),
contentLength: event.data.getUint8(3),
}
const contentView = new DataView(data.buffer.slice(headerLength, headerLength + header.contentLength))
const checksum = data.getUint16(headerLength + header.contentLength, true)
const computedChecksum = crc16(data.buffer.slice(0, headerLength + header.contentLength))
if (computedChecksum !== checksum) {
console.error('Checksum Failed', {
expected: computedChecksum.toString(16), received: checksum.toString(16)
})
break
default:
console.warn('Unhandled function', header.functionType)
return
}
switch (header.functionType) {
case FUNCTIONS.BASIC_INFO:
this.receiveBasicInfo({
vIn: contentView.getUint16(0, true) / 1000,
vOut: contentView.getUint16(2, true) / 1000,
iOut: contentView.getUint16(4, true) / 1000,
voMax: contentView.getUint16(6, true) / 1000,
temp1: contentView.getUint16(8, true) / 10,
temp2: contentView.getUint16(10, true) / 10,
dc5V: contentView.getUint16(12, true) / 1000,
outMode: contentView.getUint8(14),
workSt: contentView.getUint8(15)
})
break
case FUNCTIONS.BASIC_SET:
if (contentView.byteLength === 1) {
this.settings = this.settingsQueue.pop(contentView.getUint8(0))
break
}
this.receiveBasicSettings({
index: contentView.getUint8(0),
state: contentView.getUint8(1),
vo_set: contentView.getUint16(2, true) / 1000,
io_set: contentView.getUint16(4, true) / 1000,
ovp_set: contentView.getUint16(6, true) / 1000,
ocp_set: contentView.getUint16(8, true) / 1000,
})
break
default:
console.warn('Unhandled function', header.functionType)
}
}

/** Handle basic info from the DP100
* @param {Object} basicInfo
* @param {Number} basicInfo.vIn - Input voltage in V.
* @param {Number} basicInfo.vOut - Output voltage in V.
* @param {Number} basicInfo.iOut - Output current in A.
* @param {Number} basicInfo.voMax - Max output voltage in V.
* @param {Number} basicInfo.temp1 - Temperature 1 in °C.
* @param {Number} basicInfo.temp2 - Temperature 2 in °C.
* @param {Number} basicInfo.dc5V - 5V rail in V.
* @param {Number} basicInfo.outMode - Output mode.
* @param {Number} basicInfo.workSt - Work state.
*/
receiveBasicInfo ({ vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt }) {
console.debug('receiveBasicInfo', { vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt })
this.info = { vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt }
}
}

/** Handle basic info from the DP100
* @param {Object} basicInfo
* @param {Number} basicInfo.vIn - Input voltage in mV.
* @param {Number} basicInfo.vOut - Output voltage in mV.
* @param {Number} basicInfo.iOut - Output current in mA.
* @param {Number} basicInfo.voMax - Max output voltage in mV.
* @param {Number} basicInfo.temp1 - Temperature 1 in 0.1°C.
* @param {Number} basicInfo.temp2 - Temperature 2 in 0.1°C.
* @param {Number} basicInfo.dc5V - 5V rail in mV.
* @param {Number} basicInfo.outMode - Output mode.
* @param {Number} basicInfo.workSt - Work state.
*/
receiveBasicInfo ({ vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt }) {
const BasicInfoEvent = new CustomEvent('basicInfo', {
detail: { vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt }
})
document.dispatchEvent(BasicInfoEvent)
/** Handle basic settings from the DP100
* @param {Object} basicSettings
* @param {Number} basicSettings.index - Setting index.
* @param {Number} basicSettings.state - Setting state.
* @param {Number} basicSettings.vo_set - Output voltage setting in V.
* @param {Number} basicSettings.io_set - Output current setting in A.
* @param {Number} basicSettings.ovp_set - Over-voltage protection setting in V.
* @param {Number} basicSettings.ocp_set - Over-current protection setting in A.
*/
receiveBasicSettings ({ index, state, vo_set, io_set, ovp_set, ocp_set }) {
console.debug('receiveBasicSettings', { index, state, vo_set, io_set, ovp_set, ocp_set })
this.settings = { state, vo_set, io_set, ovp_set, ocp_set }
}
}
}
Loading

0 comments on commit 2a82cca

Please sign in to comment.