mirror of
https://github.com/Asraelite/littlebigcomputer.git
synced 2025-07-17 08:16:50 +00:00
191 lines
4.6 KiB
JavaScript
191 lines
4.6 KiB
JavaScript
import tmi from 'tmi.js';
|
|
import { WebSocketServer } from 'ws';
|
|
|
|
import * as secrets from './secret.js';
|
|
|
|
const CHECKSUM_SEND_INTERVAL = 8;
|
|
|
|
// Define configuration options
|
|
const opts = {
|
|
options: { debug: false },
|
|
identity: {
|
|
username: secrets.USERNAME,
|
|
password: secrets.ACCESS_TOKEN,
|
|
},
|
|
channels: [secrets.CHANNEL_NAME]
|
|
};
|
|
|
|
// Create a client with our options
|
|
const client = new tmi.Client(opts);
|
|
|
|
client.connect().catch(console.error);
|
|
|
|
client.on('connected', onConnectedHandler);
|
|
|
|
function onConnectedHandler(addr, port) {
|
|
console.log(`* Connected to ${addr}:${port}`);
|
|
|
|
listen();
|
|
}
|
|
|
|
function listen() {
|
|
const wss = new WebSocketServer({ port: 8090 });
|
|
|
|
wss.on('connection', (ws) => {
|
|
let cancelRequested = false;
|
|
|
|
ws.on('message', (message) => {
|
|
const payload = JSON.parse(message);
|
|
if (payload.type === 'data') {
|
|
const data = JSON.parse(payload.data);
|
|
console.log(`sending ${data.length} values with inveral ${payload.speed}ms...`);
|
|
write(data, (update) => {
|
|
ws.send(update);
|
|
}, () => cancelRequested, payload.speed);
|
|
} else if (payload.type === 'cancel') {
|
|
cancelRequested = true;
|
|
console.log('cancel requested');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Commands:
|
|
// 0: reset checksum
|
|
// 1: address low
|
|
// 2: address high
|
|
// 3: value low; write; increment address
|
|
// 4: value high
|
|
// 5: checksum low; compare
|
|
// 6: checksum high
|
|
// 7: unlock address
|
|
async function write(data, progressCallback, isCancelRequested, waitIntervalMs) {
|
|
let previousAddress = null;
|
|
let previousVhValue = null;
|
|
let checksum = 0;
|
|
|
|
// Reset checksum
|
|
send(0, 0);
|
|
await wait(waitIntervalMs);
|
|
|
|
for (const i in data) {
|
|
const remaining = (data.length - i) + (data.length / CHECKSUM_SEND_INTERVAL);
|
|
const eta = remaining * (waitIntervalMs * 2) / 1000;
|
|
progressCallback(JSON.stringify({
|
|
status: 'sending',
|
|
message: `${i}/${data.length} (${(i / data.length * 100).toFixed(1)}%) ETA: ${formatDuration(eta)}`,
|
|
checksum,
|
|
}));
|
|
|
|
if (i % CHECKSUM_SEND_INTERVAL === 0 && i != 0) {
|
|
await sendChecksum(checksum, waitIntervalMs);
|
|
}
|
|
|
|
const [address, value] = data[i];
|
|
checksum += address + value;
|
|
checksum &= 0xffffff;
|
|
const [al, ah] = [address & 0xfff, address >> 12];
|
|
const [vl, vh] = [value & 0xfff, value >> 12];
|
|
|
|
if (address !== previousAddress + 1) {
|
|
send(0, 7); // unlock address
|
|
await wait(waitIntervalMs);
|
|
send(al, 1);
|
|
await wait(waitIntervalMs);
|
|
send(ah, 2);
|
|
await wait(waitIntervalMs);
|
|
}
|
|
previousAddress = address;
|
|
|
|
if (vh !== previousVhValue) {
|
|
send(vh, 4);
|
|
await wait(waitIntervalMs);
|
|
}
|
|
previousVhValue = vh;
|
|
send(vl, 3);
|
|
await wait(waitIntervalMs);
|
|
|
|
if (isCancelRequested()) {
|
|
console.log('cancelled');
|
|
progressCallback(JSON.stringify({
|
|
status: 'cancelled',
|
|
message: `Cancelled`,
|
|
checksum,
|
|
}));
|
|
return;
|
|
}
|
|
}
|
|
|
|
await sendChecksum(checksum, waitIntervalMs);
|
|
|
|
progressCallback(JSON.stringify({
|
|
status: 'complete',
|
|
message: `Sent ${data.length} values`,
|
|
checksum,
|
|
}));
|
|
}
|
|
|
|
async function wait(timeMs = 1500) {
|
|
await new Promise(resolve => setTimeout(resolve, timeMs));
|
|
}
|
|
|
|
function send(value, command) {
|
|
// console.log(`sending ${command}: ${value} `);
|
|
say(encode(value, command));
|
|
}
|
|
|
|
async function sendChecksum(checksum, waitIntervalMs) {
|
|
const [cl, ch] = [checksum & 0xfff, checksum >> 12];
|
|
send(ch, 6);
|
|
await wait(waitIntervalMs);
|
|
send(cl, 5);
|
|
await wait(waitIntervalMs);
|
|
}
|
|
|
|
function say(message) {
|
|
client.say(secrets.CHANNEL_NAME, message);
|
|
}
|
|
|
|
function formatDuration(seconds) {
|
|
seconds = Math.ceil(seconds);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const remainingSeconds = seconds % 60;
|
|
if (minutes === 0) {
|
|
return `${remainingSeconds}s`;
|
|
} else {
|
|
return `${minutes}m${remainingSeconds}s`;
|
|
}
|
|
}
|
|
|
|
|
|
// 1000: al
|
|
// 1001: ah
|
|
// 1010: vl
|
|
// 1011: vh
|
|
|
|
function encode(value, command) {
|
|
const aBitCount = 4;
|
|
const bBitCount = 4;
|
|
const cBitCount = 4;
|
|
const dBitCount = 4;
|
|
|
|
const valueBitString = value.toString(2).padStart(12, '0');
|
|
const commandBitString = command.toString(2).padStart(4, '0');
|
|
const bitString = commandBitString + valueBitString;
|
|
|
|
let index = 0;
|
|
const a = parseInt(bitString.slice(0, aBitCount), 2);
|
|
index += aBitCount;
|
|
const b = parseInt(bitString.slice(index, index + bBitCount), 2);
|
|
index += bBitCount;
|
|
const c = parseInt(bitString.slice(index, index + cBitCount), 2);
|
|
index += cBitCount;
|
|
const d = parseInt(bitString.slice(index, index + dBitCount), 2);
|
|
index += dBitCount;
|
|
|
|
const maxTotal = 2 ** aBitCount + 2 ** bBitCount + 2 ** cBitCount + 2 ** dBitCount;
|
|
const e = maxTotal - (a + b + c + d);
|
|
|
|
const values = [...Array(a).fill('a'), ...Array(b).fill('b'), ...Array(c).fill('c'), ...Array(d).fill('d'), ...Array(e).fill('e')];
|
|
return values.join(' ');
|
|
}
|