const express = require('express') const http = require('http') const WebSocket = require('ws') const osc = require('osc') const readline = require('readline') const fs = require('fs') const SerialPort = require('serialport') const port = 6676 // Vzemi iz argumenta const tty = process.argv[2] let eulerRotation = [0, 0, 0] let baudrate = parseInt(process.argv[3]) if (!baudrate) { baudrate = 115200 } const include_files = [ '/anim.js', '/control.js', '/osctl.js', '/test.js', '/node_modules/three/build/three.min.js', '/node_modules/nouislider/distribute/nouislider.min.js', '/node_modules/nouislider/distribute/nouislider.min.css', '/node_modules/osc/dist/osc-browser.js' ]; const app = express(); const server = http.Server(app); const DEBUG = true // Odprti serijski OSC link let scon = null let buttonHeld = [false, false, false, false, false, false, false, false] let buttonValue = [false, false, false, false, false, false, false, false] let switchHeld = [false, false, false, false] let switchValue = [false, false, false, false] function openSerial() { console.log('opening ', tty, baudrate) scon = new osc.SerialPort({ devicePath: tty, bitrate: baudrate, autoOpen: true, useSLIP: true }) scon.open() scon.on('open', e => { console.log('serial connection opened') //console.log(scon) }) scon.on('error', e => { console.error('tty error', e) //scon.close() }) scon.on('close', e => { console.warn('serial connection closed, restarting in 1 second') }) // Arduino OSC gre v web scon.on('message', msg => { // Debug incoming osc if (DEBUG) { //console.log('osc msg', msg) } sendAll(msg, null, null, osclients) }) if (scon._closeCode) { scon = null console.log('restarting serial connection') setTimeout(openSerial, 1000) } } if (tty) { openSerial(baudrate) } app.get('/', (req, res) => { res.sendFile(__dirname + '/index.html'); }); app.get('/ctl', (req, res) => { res.sendFile(__dirname + '/control.html'); }); app.get('/test', (req, res) => { res.sendFile(__dirname + '/test.html'); }); let settings = {}; app.get('/settings', function(req, res) { res.send(settings); }); include_files.map(function(file) { app.get(file, function(req, res){ res.sendFile(__dirname + file); }); }); server.listen(port, () => console.log('listening on *:' + port)) // Websocket init const wss = new WebSocket.Server({ server }) // Relay multicast to websockets // @TODO still sends to supercollider? Do we need two sockets? //var dgram = require('dgram'); //var sss = dgram.createSocket('udp4'); //sss.on('listening', () => { // sss.addMembership('224.0.1.9'); //}) //sss.bind(57120); const scudp = new osc.UDPPort({ //socket: sss remotePort: 57121, localPort: 57100 }) scudp.on('open', () => { console.log("UDP to OSC open") }); // turn on and off osc messages sent to UDP / Supercollider scudp.on('message', (msg) => { if (msg.address == "/xyzc"){ if ( msg.args[0] == '1' ) { console.log("XYZ ON ---- ", msg.args[0]); osconoff = 1; } else { console.log("XYZ OFF ---- "); console.log("ddddd", msg.args[0]); osconoff = 0 } } }); scudp.on('message', (msg) => { console.log('got UDP msg', msg); osclients.forEach( client => { if (client) { //console.log("sending", msg, info) client.send(msg) } }) }) scudp.on('error', (e) => { console.log('UDP OSC error!', e) }) scudp.open() function eulerFromQuaternion(quaternion) { // Quaternion to matrix. const w = quaternion[0], x = quaternion[1], y = quaternion[2], z = quaternion[3]; const x2 = x + x, y2 = y + y, z2 = z + z; const xx = x * x2, xy = x * y2, xz = x * z2; const yy = y * y2, yz = y * z2, zz = z * z2; const wx = w * x2, wy = w * y2, wz = w * z2; const matrix = [ 1 - ( yy + zz ), xy + wz, xz - wy, 0, xy - wz, 1 - ( xx + zz ), yz + wx, 0, xz + wy, yz - wx, 1 - ( xx + yy ), 0, 0, 0, 0, 1 ]; // Matrix to euler function clamp( value, min, max ) { return Math.max( min, Math.min( max, value ) ); } const m11 = matrix[ 0 ], m12 = matrix[ 4 ], m13 = matrix[ 8 ]; const m21 = matrix[ 1 ], m22 = matrix[ 5 ], m23 = matrix[ 9 ]; const m31 = matrix[ 2 ], m32 = matrix[ 6 ], m33 = matrix[ 10 ]; var euler = [ 0, 0, 0 ]; euler[1] = Math.asin( clamp( m13, - 1, 1 ) ); if ( Math.abs( m13 ) < 0.9999999 ) { euler[0] = Math.atan2( - m23, m33 ); euler[2] = Math.atan2( - m12, m11 ); } else { euler[0] = Math.atan2( m32, m22 ); euler[2] = 0; } return euler; } var threshold = 15; var thresholds = [15,15,15,15,15,15,15,15]; var floatthresholds = [15,15,15,15,15,15,15,15]; var threshdiff = 4; var touchvalue = [40,40,40,40,40,40,40,40]; var touchcount = [0,0,0,0,0,0,0,0]; var osconoff = 0; const sendAll = (msg, info, oscWS, osclients) => { //if (msg.address == '/XYZcontrols') console.log("XYZ ON ------------------------------------ ", msg[1]); if (msg.address == '/keys') { //console.log("TOUCH MSG",msg.args); for (let i = 1; i < 8; i++) { touchvalue[i]=msg.args[i]; } touchvalue[0]=msg.args[0]; // LOW PASS FILTER BUTTON 0 // touchvalue[0]=touchvalue[0]*0.95+msg.args[0]*0.05; //console.log("TOUCH VAL",touchvalue); // SELF-CALIBRATING TOUCH THRESHOLDS for (let i = 0; i < 8; i++) { if (floatthresholds[i]==15) thresholds[i]=floatthresholds[i]=msg.args[i]-threshdiff; if (msg.args[i]>(thresholds[i]+(threshdiff/2))) { floatthresholds[i]=0.99 * floatthresholds[i] + 0.01*(msg.args[i]-threshdiff); thresholds[i]=Math.round(floatthresholds[i]); } } //console.log("TOUCH FLTHR ",floatthresholds); //console.log("TOUCH THR ",thresholds); /* // LOGIC FOR SHIFT BUTTON 0 for (let i = 0; i < 1; i++) { // momentary state between 0 and 1 if (msg.args[i] <= thresholds[i] ) { buttonValue[i] = 1; } else { buttonValue[i] = 0; } msg.args.push(buttonValue[i]) } */ // LOGIC FOR MOMENTARY BUTTONS (1 - 3) for (let i = 0; i < 8; i++) { // momentary state between 0 and 1 buttonValue[i] = 0; if (touchvalue[i] <= thresholds[i] ) { if (buttonHeld[i] == false) { if (i!=0 || touchcount[i]) { buttonHeld[i] = true buttonValue[i] = 1; console.log("TOUCH PRESS ",i,touchvalue[i],msg.args[i],thresholds[i],floatthresholds[i]); } } touchcount[i]++; } else if (touchvalue[i] > thresholds[i]+(threshdiff/2)) { if (buttonHeld[i]) console.log("TOUCH PRESS RELEASE ",i,touchvalue[i],msg.args[i],thresholds[i],floatthresholds[i],touchcount[i]); buttonHeld[i] = false; touchcount[i]=0; } //0 IS SHIFT KEY, SENDS 1 ALL TIME WHEN HELD if (i==0 && buttonHeld[0]) buttonValue[0]=1; msg.args.push(buttonValue[i]) } // keys 0 + 3 = euler reset if (buttonHeld[0] && buttonHeld[3]) { eulerRotation = [0, 0, 0]; console.log("EULER ROTATION RESET"); } // LOGIC FOR TOGGLE SWITCHES (4-7) // for (let i = 4; i < 8; i++) { // // toggle button 2 state between 0 and 1 // if (msg.address == '/keys') { // if (msg.args[i] <= threshold ) { // if (switchHeld[i-4] == false) { // switchValue[i-4] = !switchValue[i-4] // switchHeld[i-4] = true // } // } else { // if (switchHeld[i-4]) { // switchHeld[i-4] = false // } // } // msg.args.push(switchValue[i-4] ? 1 : 0) // } // } //------------------------------------------------ } // Convert quaternion diff to euler angle diff if (msg.address == '/quaternionDiff') { const euler = eulerFromQuaternion(msg.args, 'XYZ'); sendAll({ address: '/eulerDiff', args: euler }, info, oscWS, osclients) eulerRotation[0] += euler[0] eulerRotation[1] += euler[1] eulerRotation[2] += euler[2] sendAll({ address: '/euler', args: eulerRotation }, info, oscWS, osclients) } osclients.forEach( client => { if (client && oscWS != client) { // console.log("sending", msg, info) client.send(msg) } }) if (scudp) { if (osconoff == 1) console.log("SENDING UDP", msg); scudp.send(msg) //msg.slice(7,15); //scudp.send(msg.slice(7,15)) } } const osclients = [] wss.on('connection', function (ws) { console.log('new client connection') const oscWS = new osc.WebSocketPort({ socket: ws, metadata: false }); // Vsi OSC sem grejo naprej na kliente OSC oscWS.on('packet', (packet, info) => { // Broadcast adjust msg const [address, args] = packet sendAll({ address, args}, info, oscWS, osclients) }) oscWS.on('error', error => { console.warn('Ignoring invalid OSC') console.warn(error) }) osclients.push(oscWS) })