Software
There is no Exponiert Library. It's not neccessary. The browser simply deals with the websocket provided by the Nodejs server. The server uses the standard I2C library. Read these examples and insert what you need.
Browser
If you want to show a nice web page on the screen, you just need to connect it using standard websocket:
/* global WebSocket */ function dataReceived (data) { console.log(data) websocket.sendData({ 'type': 'statusleds', 'register': 0x06, 'value': 0b00110110, }) } const websocket = { connection: false, sendData: function (data) { if (websocket.connection.readyState === 1) { // Connection is working websocket.connection.send(JSON.stringify(data)) console.log('Send Data: ', data) } }, tryConnection: function () { // open connection const networkAddress = 'ws://localhost:1337' websocket.connection = new WebSocket(networkAddress) websocket.connection.onopen = function () { // Connection created // The page can start requests now console.log('We have a Websocket connection') } websocket.connection.onerror = function () { // Could not establish connection to server // retry websocket.tryConnection() } // incoming messages websocket.connection.onmessage = function (message) { // The server has to send json without chunks or anything try { const data = JSON.parse(message.data) console.log('Received from Websocket:', message.data) dataReceived(data) } catch (e) { console.error('Invalid JSON: ', message.data) } } }, init: function () { // if user is running mozilla then use it's built-in WebSocket window.WebSocket = window.WebSocket || window.MozWebSocket websocket.tryConnection() // Check if connection is still open regularly setInterval(function () { if ((websocket.connection) && (websocket.connection.readyState > 1)) { // We had a connection but it does no longer work console.log('We have no Websocket connection') websocket.tryConnection() } }, 500) } } websocket.init()
Server
The Nodejs server controls the Exponiert Hardware and talks to the browser. It needs two NPM packages: i2c-bus and ws.
'use strict' // You will see this name in eg. 'ps -aux' or 'top' command process.title = 'kastanie-exponiert' // Port where we'll run the websocket server const webSocketsServerPort = 1337 const webSocket = require('ws') // Hardware connection to devices const i2c = require('i2c-bus') // Open connection on i2c bus 1 (Raspberry Pi GPIO) const i2c1 = i2c.openSync(1) // ##### Websocket ##### function wsSendGlobal (data) { // Send a message to all clients // console.log('Message', data.name, 'sent to', wss.clients.size, 'clients.') wss.clients.forEach(function (client) { if (client.readyState === webSocket.OPEN) { client.send( JSON.stringify(data) ) } }) } function handleRequest (message) { // On incoming websocket message, turn on the lights changeLEDs(message) } // Start WebSocket server const wss = new webSocket.Server({ port: webSocketsServerPort }) // This callback function is called every time someone // tries to connect to the WebSocket server wss.on('connection', function (ws) { // Everything here is local variables for clients console.log('Websocket Connection started.') // user sent some message ws.on('message', function (message) { message = JSON.parse(message) console.log('Received from Websocket', message) handleRequest(message) }) // user disconnected ws.on('close', function (ws) { console.log('Websocket Connection closed.') }) }) // ##### I2C ##### function readI2cByte (deviceI2cAddress) { let result try { // Always read Byte 0x01, // There are the inputs of Exponiert Digital result = i2c1.readByteSync(deviceI2cAddress, 0x01) } catch (e) { return false } return result } function writeI2cByte (deviceI2cAddress, outputdata) { const buffer = Buffer.from(outputdata) let result try { result = i2c1.i2cWriteSync(deviceI2cAddress, 2, buffer) } catch (e) { console.log('Could not send, I2C-Adress broken:', deviceI2cAddress) wsSendGlobal({ type: 'error', value: 'Adress broken:' + deviceI2cAddress }) return false } return result } (() => { // Rotary Encoder 8 Bit // Test: i2cget -y 1 0x64 0x01 const deviceI2cAddress = 0x64 let oldReading = readI2cByte(deviceI2cAddress) // Readings when rotating clockwise // Found in datasheet of encoder // or simply by rotating and logging const pincodes = [ 206, 78, 110, 238, 254, 190, 182, 180, 176, 240, 208, 80, 64, 96, 224, 228, 236, 172, 188, 252, 253, 221, 93, 89, 81, 113, 97, 33, 32, 48, 112, 120, 248, 216, 217, 249, 251, 235, 171, 163, 35, 51, 50, 18, 16, 17, 49, 177, 241, 225, 227, 243, 247, 246, 214, 86, 22, 23, 21, 5, 1, 3, 19, 83, 115, 114, 118, 119, 127, 125, 109, 45, 13, 15, 11, 10, 2, 6, 7, 39, 55, 53, 61, 63, 191, 187, 186, 154, 138, 142, 134, 132, 4, 12, 14, 30, 31, 27, 155, 159, 223, 215, 213, 197, 196, 204, 76, 72, 8, 136, 140, 141, 143, 135, 199, 207, 239, 111, 107, 106, 104, 232, 168, 160, 128, 192, 200, 202 ] // move since position was sent last time let delta = 0 setInterval(() => { const newReading = readI2cByte(deviceI2cAddress) if (newReading === false) { // Connection broken return } if (newReading === oldReading) { return } let newPosition = pincodes.indexOf(newReading) const oldPosition = pincodes.indexOf(oldReading) if (newPosition > oldPosition + 64) newPosition -= 128 if (newPosition < oldPosition - 64) newPosition += 128 delta += newPosition - oldPosition console.log('Rotated to:', delta) oldReading = newReading }, 20) setInterval(() => { if (delta !== 0) { wsSendGlobal({ type: 'rotation', value: delta }) delta = 0 } }, 50) })(); (() => { // Joystick and Buttons // Test: i2cget -y 1 0x60 0x01 let oldButtons = readI2cByte(0x60) setInterval(() => { // pressed keys have bit set to 1 const newButtons = readI2cByte(0x60) if ((newButtons & 0b00000001) && (!(oldButtons & 0b00000001))) { wsSendGlobal({ type: 'button', value: 'down', direction: 'down' }) } if ((!(newButtons & 0b00000001)) && (oldButtons & 0b00000001)) { wsSendGlobal({ type: 'button', value: 'down', direction: 'up' }) } if ((newButtons & 0b00000010) && (!(oldButtons & 0b00000010))) { wsSendGlobal({ type: 'button', value: 'right', direction: 'down' }) } if ((!(newButtons & 0b00000010)) && (oldButtons & 0b00000010)) { wsSendGlobal({ type: 'button', value: 'right', direction: 'up' }) } if ((newButtons & 0b00000100) && (!(oldButtons & 0b00000100))) { wsSendGlobal({ type: 'button', value: 'left', direction: 'down' }) } if ((!(newButtons & 0b00000100)) && (oldButtons & 0b00000100)) { wsSendGlobal({ type: 'button', value: 'left', direction: 'up' }) } if ((newButtons & 0b00001000) && (!(oldButtons & 0b00001000))) { wsSendGlobal({ type: 'button', value: 'up', direction: 'down' }) } if ((!(newButtons & 0b00001000)) && (oldButtons & 0b00001000)) { wsSendGlobal({ type: 'button', value: 'up', direction: 'up' }) } oldButtons = newButtons // Read buttons every 50ms (20Hz) }, 50) })() function changeLEDs (data) { // Set LEDs // Read page 6 for reference: // https://www.mouser.de/datasheet/2/302/PCA9532-1127723.pdf switch (data.type) { case 'buttons': writeI2cByte(0x60, [data.register, data.value]) break case 'statusleds': writeI2cByte(0x64, [data.register, data.value]) break } }
Text and Sources: The MIT License 2021 Kastanie Eins GmbH