MakeHaus JS Setup and Overview

From MakeProAudio MediaWiki
Revision as of 13:12, 20 June 2020 by Admin (talk | contribs) (Protected "MakeHaus JS Setup and Overview" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite)))
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Overview

makehaus-js is a typescript/javascript based node package to speak a proprietary JSON based protocol with the Tiles Hub to control MPA hardware widgets. It is also responsible for augmenting these hardware widgets in software via the MakeHaus Web UI based on a layout that the user supplies to it. The communication between makehaus-js and the MakeHaus Web UI is via a separate proprietary JSON based protocol.

Branching

The develop branch is the default development branch.

Cloning

Clone the repository and from the working directory execute

https://github.com/makeproaudio/makehaus-js.git
npm i

This will pull all necessary dependencies as defined in package.json into the node_modules/ folder.

Repository Structure

Only relevant files used in development are mentioned below

.vscode/
dist/
node_modules/
examples/
webapp/
src/
      control/
            api-*.ts
            client.ts
            Hub.ts
      parser/
      row/
      stack/
      widget/
      tcwidget/
      ui/
package.json
tsconfig.json
rollup.config.js
prettier.config.js

Initial Setup

Nothing special apart from instructions mentioned in the Initial Setup For Typescript Projects.

Typical Development Workflow

The development workflow described below is for those developers who want to modify the core makehaus-js repository and not for those who wish to have a dependency on the makehaus-js npm package. To use makehaus-js as a dependency, follow the instructions here.

In addition to the Typical Development Workflow For Typescript Projects:

  • The launch.json file inside the .vscode folder has specific debug configurations for makehaus-js. The most basic configuration here is labelled diagnostics. This is the go to launch configuration to establish sanity of your system.
  • In order to test with the Makehaus Web UI, make sure the Web UI is running from a separate VS Code Editor instance. Instructions for the same can be found in the MakeHaus Web UI section here.

Deployment

Nothing special to note in addition to Deployment For Typescript Projects.

Making A Standalone Build

Nothing special to note in addition to Deployment For Typescript Projects.

Publishing A New Version

In addition to Deployment For Typescript Projects:

  • If you’re deploying a new version of the MakeHaus Web UI, copy the build/ output of the Web UI to the webapp/ folder.

Technology

  • rxjs - an event streaming and processing library based on the reactive programming paradigm
  • node js events module - the native ‘events’ module of the node js runtime
  • socket.io - a barebones socket communication framework for websocket communication between makehaus-js and MakeHaus Web UI
  • express - a web server framework used to serve the MakeHaus Web UI and potential future web related use cases.
  • @makeproaudio/parameters-js

Architecture

makehaus-js is a highly event-driven, asynchronous system. The core components are:

  • Tiles Hub Raw Socket Client
  • Hub Proxy
  • Tiles API + Widgets (TCWidgets)
  • Rows, Stacks and UI Widgets (UIWidgets)
  • Widget Layout Parser
  • UI Manager

Tiles Hub Raw Socket Client

To start communicating with the Tiles Hub, makehaus becomes a client and must open a socket connection to a known IP and Port. The entry point here is src/control/client.ts. This file contains all the necessary logic packed to encapsulate the packetize-serialize-transport-deserialize-depacketize logic between the Tiles Hub and Client. The client is a singleton. In case of any server events, such as connection, disconnection, errors, etc., the client singleton forwards information to the upper layers of the application via node js events.

The client.ts file here is the only module responsible for packet transport between the client and the hub. In case of any network related glitches, this should be the place to look at.

Hub Proxy

The Hub Proxy, located at src/control/hub.ts consumes socket events coming from the Raw Socket Client to present API level functionality to the Developer. It has the following responsibility:

  • start a client session to the Tiles Hub via the Client and listen for events
  • bridge events between the Client and the upper application layers bidirectionally
  • mirror the actual state of the hub in terms of which tiles have been connected
  • spawn different object types per board type discovered on the Hub (refer to src/control/api-base.ts and src/control/api-butled.ts) for more details.

The Hub Proxy acts as an abstraction layer between the Tiles API Widgets layer and the Client. One of its primary responsibilities while performing this role is to ensure that widget event messages get delivered to the right destinations. Examples of widget event messages are:

  • touch events for every widget
  • left/right for encoders, update for faders
  • set color/brightness to rgb leds

The Hub Proxy is part of the makehaus-js architecture and not a standalone application.

Reactive Paradigm

To manage such async event messages in real-time at such a large scale, we can visualize these messages as a continuous stream. Depending on who is interested in what sort of message, different actors set up filters on this stream and process their respective messages. In this way, we can route messages to different locations in a step-filtered manner. Such a paradigm, where we react to specific event types in an asynchronous manner is known as “reactive programming”.

In MakeHaus, the Object we will be streaming is called ControlEvent located inside src/control/model.ts. The key to remember here is that any Control Event messages, either from or to the server will be channeled via the same Stream - bypassing it for communication will result in undesirable side effects.

Tiles API + Widgets (TCWidgets)

One of the responsibilities of the TileBase classes and its derivatives is to spawn Widgets. The idea of Widgets here is abstract - they stand for both software (UIWidget) and hardware widgets (TCWidget = TileChain Widget).
The derivatives of the TileBase classes create TCWidgets.

Refer to src/widget/widget.ts for more details on the Widget interface.

For more information on Widgets, check the public README file here.

TCWidgets and Tiles API

Rows, Stacks and UI Widgets (UIWidgets)

To augment hardware widgets in software, the concept of Rows and Stacks was born. UIWidget is just another derivative of abstract Widgets which have some special properties.

A UI has Rows.
Rows have Stacks.
Stacks have Widgets.

A Stack can have:

  • Exactly 1 UIWidget
  • Any number of TCWidget.

Stacks operate as a multiplexer for Widget operations. For example, let’s say you want to change the colour of a UI Button as well as a Hardware Button. While you can call the corresponding function on each Widget separately, you will also have to update the value of each parameter separately. Not just that, for any action you have to perform when either button is pressed, you’ll have to set up different handlers for each Widget, which pretty much will do the same operation. Then, the state has to be synchronized across both Widgets. Throw another Widget into the mix, and the application’s complexity starts growing exponentially.

The easier way to achieve the same would be to put all the Widgets into a Stack and call the corresponding wrapper function on the Stack. The Stack is responsible for the state of all Widgets which it is a parent of. At any point of time, a Widget can belong to exactly one Stack.

A Stack uses a Parameter to maintain its internal value state. To the outside world, it also behaves exactly like a Parameter - it can bind from and bind to another Parameter and therefore effectively, another Stack.

Rows are used to resemble a horizontal arrangement of UIWidgets. Apart from having a hierarchical value (UI cannot contain Stacks directly) and a weight property, which establishes how “fat” the Row should look on the UI, they have no further application logic.

Widget Layout Parser

The layout the user wishes to show is captured in a JSON object which resembles the hierarchy specified above:

UI has Rows. (1:n)
Row has Stacks. (1:n)
Stack has Widgets. (1:n)

Additionally, the UI can have a title, and for all TCWidgets, the layout JSON must specify the host and port of the TileChain. The name of the TileChain defined can then be used to reference and uniquely identify a TCWidget. The conventions for the same are mentioned here.

The LayoutParser module (src/parser/parser.ts) is responsible for the following:

  • accepting a layout JSON string and creating a Row/Stacks/Widgets model hierarchy
  • accepting a layout JSON string and spawning TCWidgets wherever mentioned. These Widgets are then added to their respective Stacks.
  • listening to Control Events of Hardware Widgets and mapping them to abstract Widget events.

To support the creation of these different objects, certain Registries have been created which perform basic housekeeping:

  • RowRegistry (src/row/rows.ts)
  • StackRegistry (src/stack/stacks.ts)
  • Widget Registry (src/widget/widgets.ts)

Use the methods in these Registry objects only to create your object hierarchy.

Widget Layout Parser

UI Manager

The UI Manager (src/ui/ui.ts) is the software equivalent of the Tiles Hub Raw Socket Client, i.e., it is responsible for raw socket based transport with the software UI. It is responsible for handling new client requests and sending data to the MakeHaus Web UI. In one special case, documented in the code, it also listens to events from the MakeHaus Web UI.

Helper Initialization Methods

To make initialization of the makehaus system much easier, a helper utility called MakeHaus (src/launch.ts) exposes an init function. More details about the same can be found here.

Supporting A New Board Type

Assuming that a new board type has been supported on the Tiles Hub, the following steps need to be performed to create a new board type:

  • Implement a new TCWidget class derivative (use src/tcwidget/ledbutton.ts as a reference)
  • Update the WidgetType constant (src/widget/widget.ts)
  • In the LayoutParser (src/parser/parser.ts) add a clause for the Widget Bindings
  • Implement a new TileBase class derivative (use src/control/api-butled.ts as a reference)
  • Update the Tile and the BoardType constant in src/control/api-base.ts
  • Export the new new TileBase class derivative for outside use in src/index.ts
  • Add another case for the data callback in src/control/hub.ts

Supporting A New UI Widget

There are no separate subclasses for different UI Widget Types. To support a new UI Widget type:

  • Update the WidgetType and the UIWidgetTypes constant in src/widget/widget.ts
  • The new name in WidgetType must be supported by the MakeHaus Web UI. Perform steps here to complete the loop.
  • Use the exact name of the new WidgetType in a layout.json file that the UI Manager sends to the Web UI Client.

Outlook

  • Make Hub Proxy more robust - The Hub Proxy is not perfectly mature and needs to handle the lifecycle events of the server more robustly
  • Multiple Tile Chains On Hub - Currently, only one TileChain on the Hub is supported. The Hub Proxy must support multiple Tile Chains on the same Hub.
  • Multiple Hubs - Currently, only one Hub is supported by MakeHaus throughout all the application layers. Supporting multiple hubs would require makehaus to do discovery.
  • Match UI Widgets Event Implementation To Be Stream Based - Currently, TC Widgets have a very sleek Events Streaming model which has several merits. Match the UI widgets to also be Stream driven.
  • Extract Tiles API To Separate npm Package - The Tiles Hub Raw Socket Client and Hub Proxy (everything inside src/control) are all that are required to work with the hardware widgets. Extracting these to a different npm package and letting the makehaus-js package depend on this new package would go a long way in adopting proper separation of concerns.
  • Support Dynamic Weight Change - The Weight property of both Rows and Widgets is not dynamic yet. Once you define it in layout, it cannot be changed until a restart.
  • Support Stacks With Multiple UI Widgets - Currently, only one UI Widget is allowed inside a Stack. If desired, extend this behaviour to become more flexible.
  • Support TextLCDDisplay - Currently, the TextLCDDisplay is only supported partially.