4th Law internal typings / base classes

Usage no npm install needed!

<script type="module">
  import 4thLawTypingsPublic from 'https://cdn.skypack.dev/@4th-law/typings-public';


Codacy Grade: Codacy Badge Build Status: CircleCI

License Status:

FOSSA Status


4th Law's Typings module provides structure for integrating new modules with 4th Law's framework. The Typings module includes information about how to format Sensor profiles, Actuator profiles, User input profiles, and control Modalities.

A profile is a node module that creates an interface between some piece of hardware, such as a camera or servo, and allows your javascript or typescript to send or receive information from said profile. This level of abstraction and modularity makes it easier to swap new hardware in, since the logical interface doesn't change. We subdivide profiles into Sensors and Actuators, and Sensors have a specific subclass called a User Input. Modalities are node modules that instantiate a control system for a physical platform, such as a drone, self-driving car, or robot.


Profiles, as stated above, have two categories, identified by the private _category variable as either "SENSOR" or "ACTUATOR". They also have a _type which identifies the class of sensor or actuator. There are currently five kinds of actuators, ["MECHANICAL", "HYDRAULIC", "PNEUMATIC", "THERMAL", "ELECTRICAL"], and 14 kinds of sensors, ["THERMAL", "ELECTRICAL", "ACOUSTIC", "CHEMICAL", "ENVIRONMENTAL", "PROXIMITY", "FORCE", "PRESSURE", "OPTICAL", "POSITIONAL", "NAVIGATIONAL", "IONIC", "FLUIDIC", "AUTOMOTIVE"], note the overlap at "THERMAL" and "ELECTRICAL". All of these are specified in the NProfile.EType enum.

Profiles also have a _make, _model, _id, _version, and optional _specs variables. These are described below:

  • _make is the manufacturer id of your sensor or actuator, and is a string
  • _model is the model id of your sensor or actuator, and is a string
  • _id is the product id of your sensor or actuator, and is a string
  • _version is the version of your profile, formatted as "vX.X.X"
  • _sampleInterval is an optional parameter that holds, as number, the period of time in milliseconds between updates to the value of _lastDataPoint, a variable that will be explained in greater detail under the Sensor section.
  • _specs, another optional parameter, holds any information that is specific to your sensor or actuator. If there's information like framerate for a camera, you should include it in the _specs object as a key: value pair. For example, the _specs variable for a camera profile might look something like this:
_specs = {
  framerate: 25,
  width: 200,
  height: 100,

Note that _category is of type NProfile.ECategory, which can be found in the typings index.d.ts file and is either ACTUATOR or SENSOR, while the _type variable is of type NProfile.EType, which can also be found in the typings index.d.ts file.

_category, _type, _make, _model, _id, and _version are all automatically packaged into an object, and returned by the info() function. If you define the _specs and/or _sampleInterval variables in your profile, they will also be automatically included in the object returned by info()


Sensor provide a specific kind of functionality, which is to provide a system (drone, robot, etc.) with information, whether it be brightness, camera images, temperature, acidity, or what key was just typed. We establish a distinction between two broad types of sensors, regular Sensors and InputDevices. The distinction is more than a conceptual one, because they each handle data in different ways. While a regular sensor is in some sense passive (the user must make a call to receive data), an InputDevice is active and will release an event that you can capture.

Regular Sensor

A regular sensor has a private variable called _lastDataPoint, which is returned by the data() getter function, called as profile.data. You will have to program the sensor profile so that it updates the value of _lastDataPoint at an acceptable frequency. This means that if you call data() too frequently, you could potentially get duplicate values back, since you could have called data() before _lastDataPoint had a chance to be updated. Alternatively, if you call data() at too LOW a frequency, you could fail to catch values of _lastDataPoint, that is the value of _lastDataPoint could be updated more than once in between two calls of to data(), meaning you would have lost those values. Unless you build in some logic on your own into the profile, this data will not be available to you.


Input Devices operate on a different logic from Regular Sensors. Instead of having a variable that must be accessed, they use an EventEmitter which is passed in at construction. Internally, the event emitter is fired whenever new data comes in (or however you wish to program your profile), and if we have a reference to the event emitter externally (which in principle we should have since we passed it in) which we can use to listen for these events, acting on them as they happen. In this way, unless an event is somehow interrupted, we cannot miss data, and there's no real way to oversample our sensor, since IT sends data to US.


Actuator profiles are profiles that provide an interface with an actuator, such as a servo or DC motor. The basic characteristic of these profiles is to serve as output devices for your system, and as such provide built-in functionality for setting target output values through the target() setter function, called as profile.target = targetValue. There is another function called current(), called as currentValue = profile.current, which returns the current output of the profile. It's possible to have current() return the same value as target(), but this extra flexibility has been added to allow you to work with cases where it is ill-advised or impossible for your actuator to discontinuously change it's output. Accordingly, you will have to program yourself the logic into your profile that dictates the relationship between the _target and _current variables.


The Data Namespace defines the structure of data that you will be sending to and receiving from your profiles.

The EUnit enum specifies different units you can attach to your data, and contains [METER, METER_per_SECOND, METER_per_SECOND_2, DEGREE, DEGREE_per_SECOND, DEGREE_per_SECOND_2], with METER_per_SECOND_2 is meters per second squared, and DEGREE_per_SECOND_2 being degrees per second squared.

NData.IValue specifies a number value and an EUnit unit, to help avoid confusion if you have sensors generating information with different units.

NData.ITelemetry specifies linear and angular positions, velocities, and accelerations as

linear_position: {
  x: NData.IValue
  y: NData.IValue
  z: NData.IValue
angular_position: {
  yaw: NData.IValue
  pitch: NData.IValue
  roll: NData.IValue
linear_velocity: {
  x: NData.IValue
  y: NData.IValue
  z: NData.IValue
angular_velocity: {
  yaw:  NData.IValue
  pitch: NData.IValue
  roll: NData.IValue
linear_acceleration: {
  x: NData.IValue
  y: NData.IValue
  z: NData.IValue
angular_acceleration: {
  yaw: NData.IValue
  pitch: NData.IValue
  roll: NData.IValue

NData.ITelemetry also includes information about the name of the sensor, the specific id of the sensor (to pick it out from other sensors on your platform), the coordinate system for the position, velocity, and acceleration data, as well as a timestamp. This information is encoded as

name: string
id: string
system: NData.ECoordinateSystem
ts: number

NData.ECoordinateSystem helps you specify whether your position data is in geospherical coordinates (latitude and longitude) or in bodycentric cartesian coordinates, values are [GEOSPHERICAL, BODYCARTESIAN].

There is also an enum EDimensionType is used by the soon-to-be mentioned ActuatorInput interface that specifies whether a value is meant as a continuous change over time (velocity), a discrete change over time (number of steps), or an absolute desired position. The EDimensionType enum is shown below for convenience:

enum EDimensionType {
  VELOCITY = "VELOCITY", // could be rectilinear, curvilinear, or angular
  POSITION = "POSITION", // same as the velocity comment
  STEPS = "STEPS", // passes the number of steps a stepper motor needs to move

To help organize information coming from sensor and being sent to actuators, we provide the ISensor and IActuatorInput interfaces.

The ISensor interface has name variable [ a string ], an id variable [ a string], a ts variable [ a number ] and a data variable [ of any type ]. The name holds the name of the sensor, the id holds the unique identifier of the particular sensor on your platform this data came from, the ts (timestamp) indicates when this data was generated, and the data variable holds the actual contents of the sensor data.

The IActuatorInput interface has value [ a number ], type [ NProfile.EType ], dimensionType [EDimensionType], and optional range [ two number in an array ] variables. The value is the information the actuator needs to actually actuate, the rest of the information is primarily for ease of enforcing correctness in your code. The range variable specifies the boundaries of what value should be, so that in case you go outside this value you can throw an error or handle the event accordingly.


NData.ITelemetry NData.ISensor NData.IValue NData.EUnit NData.ECoordinateSystem

NProfile.Category NProfile.EType NProfile.EDimensionType NProfile.IActuatorInput