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