|
35 | 35 | <button id="connect">connect</button> |
36 | 36 | <script type="module"> |
37 | 37 | import uplot from 'https://cdn.jsdelivr.net/npm/uplot@1.6.31/+esm' |
| 38 | + import { DP100 } from './assets/js/dp100.js' |
38 | 39 |
|
39 | 40 | const dark = window.matchMedia('(prefers-color-scheme: dark)').matches |
40 | | - const vendorId = 11836, productId = 44801 // DP100's HID IDs |
41 | | - const deviceAddr = 251 // DP100's device address |
42 | | - |
43 | | - /** |
44 | | - * Calculate the buffers CRC-16/MODBUS checksum. |
45 | | - * |
46 | | - * @param {ArrayBuffer} buffer - The buffer to calculate the CRC16 for. |
47 | | - * @return {number} - The CRC16 checksum. |
48 | | - */ |
49 | | - function crc16 (buffer) { |
50 | | - let crc = 0xFFFF |
51 | | - |
52 | | - for (const byte of new Uint8Array(buffer)) { |
53 | | - crc = crc ^ byte |
54 | | - |
55 | | - for (let j = 0; j < 8; j++) { |
56 | | - const odd = crc & 0x0001 |
57 | | - crc = crc >> 1 |
58 | | - if (odd) { |
59 | | - crc = crc ^ 0xA001 |
60 | | - } |
61 | | - } |
62 | | - } |
63 | | - |
64 | | - return crc |
65 | | - } |
66 | | - |
67 | | - /** DP100 Modbus Function IDs */ |
68 | | - const FUNCTIONS = Object.freeze({ |
69 | | - DEVICE_INFO: 0x10, |
70 | | - FIRM_INFO: 17, |
71 | | - START_TRANS: 18, |
72 | | - DATA_TRANS: 19, |
73 | | - END_TRANS: 20, |
74 | | - DEV_UPGRADE: 21, |
75 | | - BASIC_INFO: 48, |
76 | | - BASIC_SET: 53, |
77 | | - SYSTEM_INFO: 0x40, |
78 | | - SYSTEM_SET: 69, |
79 | | - SCAN_OUT: 80, |
80 | | - SERIAL_OUT: 85, |
81 | | - DISCONNECT: 0x80, |
82 | | - NONE: 0xFF, |
83 | | - }) |
84 | | - |
85 | | - const WORK_MODES = Object.freeze({ |
86 | | - CC: 0, |
87 | | - CV: 1, |
88 | | - OFF: 2, |
89 | | - }) |
90 | | - |
91 | | - const WORK_MODE_MAP = { |
92 | | - [WORK_MODES.CV]: 'Constant Voltage', |
93 | | - [WORK_MODES.CC]: 'Constant Current', |
94 | | - [WORK_MODES.OFF]: 'Off', |
95 | | - } |
96 | | - |
97 | | - /** DP100 device class. |
98 | | - * |
99 | | - * This class is used to interact with the DP100 power supply. |
100 | | - * @example |
101 | | - * |
102 | | - * class MyPSU extends DP100 { |
103 | | - * receiveBasicInfo ({vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt}) { |
104 | | - * console.info('Input Voltage:', vIn, 'V') |
105 | | - * console.info('Output Voltage:', vOut, 'V') |
106 | | - * console.info('Output Current:', iOut, 'A') |
107 | | - * console.info('Max Output Voltage:', voMax, 'V') |
108 | | - * console.info('Temperature 1:', temp1, '°C') |
109 | | - * console.info('Temperature 2:', temp2, '°C') |
110 | | - * console.info('DC 5V:', dc5V, 'V') |
111 | | - * console.info('Output Mode:', WORK_MODE_MAP[outMode]) |
112 | | - * console.info('Work State:', workSt) |
113 | | - * } |
114 | | - * } |
115 | | - * |
116 | | - * const psu = new MyPSU() |
117 | | - * await psu.connect() |
118 | | - */ |
119 | | - class DP100 { |
120 | | - |
121 | | - device = null |
122 | | - |
123 | | - /** Connect to the DP100 device. */ |
124 | | - async connect () { |
125 | | - [this.device] = await navigator.hid.requestDevice({ |
126 | | - filters: [{ vendorId, productId }] |
127 | | - }) |
128 | | - await this.device.open() |
129 | | - this.device.addEventListener('inputreport', this.inputReportHandler.bind(this)) |
130 | | - setInterval( |
131 | | - () => this.sendReport(FUNCTIONS.BASIC_INFO), 10 |
132 | | - ) |
133 | | - } |
134 | | - |
135 | | - /** Send a report to the DP100 |
136 | | - * @param {Number} functionId -- The function to call on the DP100. |
137 | | - * @param {Uint8Array} content -- The data to send to the DP100. |
138 | | - */ |
139 | | - async sendReport (functionId, content = null) { |
140 | | - content = content || new Uint8Array(0) |
141 | | - const report = new Uint8Array([ |
142 | | - deviceAddr, |
143 | | - functionId, |
144 | | - content.length, |
145 | | - content, |
146 | | - 0, // checksum |
147 | | - 0 // checksum |
148 | | - ]) |
149 | | - const reportView = new DataView(report.buffer, report.byteOffset, report.byteLength) |
150 | | - const checksum = crc16(report.buffer.slice(0, report.length - 2)) |
151 | | - reportView.setUint16(report.length - 2, checksum, true) |
152 | | - console.debug('device.sendReport', reportView) |
153 | | - return this.device.sendReport(0, report) |
154 | | - } |
155 | | - |
156 | | - /** Handle input reports from the DP100 |
157 | | - * @param {HIDInputReportEvent} event |
158 | | - */ |
159 | | - inputReportHandler (event) { |
160 | | - console.debug('device.inputreport', event) |
161 | | - const data = event.data |
162 | | - const headerLength = 4 |
163 | | - const header = { |
164 | | - deviceAddr: data.getUint8(0), |
165 | | - functionType: event.data.getUint8(1), |
166 | | - sequence: event.data.getUint8(2), |
167 | | - contentLength: event.data.getUint8(3), |
168 | | - } |
169 | | - const contentView = new DataView(data.buffer.slice(headerLength, headerLength + header.contentLength)) |
170 | | - const checksum = data.getUint16(headerLength + header.contentLength, true) |
171 | | - const computedChecksum = crc16(data.buffer.slice(0, headerLength + header.contentLength)) |
172 | | - if (computedChecksum !== checksum) { |
173 | | - console.error('Checksum Failed', { |
174 | | - expected: computedChecksum.toString(16), |
175 | | - received: checksum.toString(16) |
176 | | - }) |
177 | | - return |
178 | | - } |
179 | | - console.debug('content', contentView) |
180 | | - |
181 | | - switch (header.functionType) { |
182 | | - case FUNCTIONS.BASIC_INFO: |
183 | | - const basicInfo = { |
184 | | - vIn: contentView.getUint16(0, true) / 1000, |
185 | | - vOut: contentView.getUint16(2, true) / 1000, |
186 | | - iOut: contentView.getUint16(4, true) / 1000, |
187 | | - voMax: contentView.getUint16(6, true) / 1000, |
188 | | - temp1: contentView.getUint16(8, true) / 10, |
189 | | - temp2: contentView.getUint16(10, true) / 10, |
190 | | - dc5V: contentView.getUint16(12, true) / 1000, |
191 | | - outMode: contentView.getUint8(14), |
192 | | - workSt: contentView.getUint8(15) |
193 | | - } |
194 | | - this.receiveBasicInfo(basicInfo) |
195 | | - break |
196 | | - default: |
197 | | - console.warn('Unhandled function', header.functionType) |
198 | | - } |
199 | | - } |
200 | | - |
201 | | - /** Handle basic info from the DP100 |
202 | | - * @param {Object} basicInfo |
203 | | - * @param {Number} basicInfo.vIn - Input voltage in mV. |
204 | | - * @param {Number} basicInfo.vOut - Output voltage in mV. |
205 | | - * @param {Number} basicInfo.iOut - Output current in mA. |
206 | | - * @param {Number} basicInfo.voMax - Max output voltage in mV. |
207 | | - * @param {Number} basicInfo.temp1 - Temperature 1 in 0.1°C. |
208 | | - * @param {Number} basicInfo.temp2 - Temperature 2 in 0.1°C. |
209 | | - * @param {Number} basicInfo.dc5V - 5V rail in mV. |
210 | | - * @param {Number} basicInfo.outMode - Output mode. |
211 | | - * @param {Number} basicInfo.workSt - Work state. |
212 | | - */ |
213 | | - receiveBasicInfo ({ vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt }) { |
214 | | - const BasicInfoEvent = new CustomEvent('basicInfo', { |
215 | | - detail: { vIn, vOut, iOut, voMax, temp1, temp2, dc5V, outMode, workSt } |
216 | | - }) |
217 | | - document.dispatchEvent(BasicInfoEvent) |
218 | | - } |
219 | | - } |
220 | 41 |
|
221 | 42 | let opts = { |
222 | 43 | id: 'uv-graph', |
|
0 commit comments