#iot #real-time #messaging #broker #json-rpc #rpc-api

bin+lib objtalk

a lightweight realtime database for IoT projects

9 releases

0.3.0 Nov 23, 2022
0.2.1 Nov 22, 2022
0.1.5 Jun 6, 2021

#786 in Database interfaces

43 downloads per month

MIT license

150KB
3.5K SLoC

Rust 2.5K SLoC // 0.0% comments JavaScript 1K SLoC // 0.0% comments

objtalk

Documentation Automated Builds

objtalk is a lightweight realtime database that can be used for IoT projects. It can be used to store and query the state of devices in realtime and also to communicate between devices using events and rpc. Think mqtt but on steroids.

objtalk stores objects. Each object has a name, a value (json) and a timestamp that indicates when the object was last modified. Objects can be created, modified, removed and queried over an API. Queries can also watch for changes and return them in real time to the client.

The API can be accessed using multiple transports. The protocol is JSON-RPC-like and can be used over TCP or over a WebSocket. HTTP/REST can also be used for simple commands.

All objects stored in objtalk can also be viewed in a browser using the builtin admin panel.

Installation

Install a precompiled binary or use cargo:

$ cargo install objtalk

Using the server

Create a config file, for example called objtalk.toml:

[storage]
backend = "sqlite"
sqlite.filename = "objtalk.db"

[[http]]
addr = "127.0.0.1:3000"
admin.enabled = true
#admin.asset-overrides = "admin"
#allow-origin = "*"

[[tcp]]
addr = "127.0.0.1:3001"

Start the server:

$ objtalk-server --config objtalk.toml
http transport listening on http://127.0.0.1:3000
tcp transport listening on 127.0.0.1:3001

Visit the admin panel at http://127.0.0.1:3000.

Using the client

$ objtalk-cli -u http://127.0.0.1:3000 set foo 42
$ objtalk-cli -u http://127.0.0.1:3000 get foo
[
    {
        "name": "foo",
        "value": 42,
        "lastModified": "2021-05-07T17:53:29.066420Z"
    }
]
$ objtalk-cli -u http://127.0.0.1:3000 remove foo

Using objtalk as a rust library

The objtalk crate provides the objtalk-server and objtalk-cli binaries, but you can also use it as a library to integrate objtalk into your rust project. Take a look at the documentation for a list of all available methods. You can use the server and client feature flags to trim down the library.

Libraries for other languages

Other integrations

API/Protocol

Basics

set name value

set creates or replaces an object.

using objtalk-cli:

$ objtalk-cli set sensor '{"temperature":20}'

over http:

$ curl -X POST 127.0.0.1:3000/objects/sensor -d '{"temperature":20}'

over tcp or websocket:

{
    "id": 1,
    "type": "set",
    "name": "sensor",
    "value": { "temperature": 20 }
}

{
    "requestId": 1,
    "result": {
        "success": true
    }
}

patch name value

patch creates or updates an object. If an object with the same name already exists the value is merged (non-deep).

using objtalk-cli:

$ objtalk-cli replace sensor '{"temperature":20}'

over http:

$ curl -X PATCH 127.0.0.1:3000/objects/sensor -d '{"temperature":20}'

over tcp or websocket:

{
    "id": 1,
    "type": "patch",
    "name": "sensor",
    "value": {
        "temperature": 20
    }
}

{
    "requestId": 1,
    "result": {
        "success": true
    }
}

get pattern

get returns all objects where the name matches pattern.

Object names look like a file path and consist of multiple parts seperated by forward slashes. Each part can be either a string, a + or *. + matches anything until the next slash, * matches anything until the end of the string. Multiple sub-patterns can be combined using a comma and are logically OR-ed togeter.

Some examples:

  • * matches all objects
  • device/lamp/+ matches device/lamp/livingroom and device/lamp/bedroom, but not device/sensor/livingroom
  • device/+/livingroom matches device/lamp/livingroom and device/sensor/livingroom
  • device/* matches device/lamp/livingroom, device/lamp/bedroom and device/sensor/livingroom
  • device/lamp/+,device/sensor/+ matches device/lamp/livingroom, device/lamp/bedroom and device/sensor/livingroom

using objtalk-cli:

$ objtalk-cli get '*'

over http:

$ curl '127.0.0.1:3000/query?pattern=*'

over tcp or websocket:

{
    "id": 1,
    "type": "get",
    "pattern": "*"
}

{
    "requestId": 1,
    "result": {
        "objects": [
            {
                "name": "sensor",
                "value": { "temperature": 20 },
                "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
            }
        ]
    }
}

query pattern

query returns all objects matching pattern and watches for changes. The initial response contains all objects that match pattern. When new objects with a matching name are created, changed or removed a queryAdd event, queryChange event or queryRemove event is emitted.

using objtalk-cli: unsupported

over http:

$ curl '127.0.0.1:3000/query?pattern=*' -H "Accept: text/event-stream"

over tcp or websocket:

{
    "id": 1,
    "type": "query",
    "pattern": "*"
}

{
    "requestId": 1,
    "result": {
        "queryId": "01234567-89ab-cdef-0123-456789abcdef",
        "objects": [
            {
                "name": "sensor",
                "value": { "temperature": 20 },
                "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
            }
        ]
    }
}

{
    "type": "queryChange",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef",
    "object": {
        "name": "sensor",
        "value": { "temperature": 21 },
        "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
    }
}

{
    "type": "queryAdd",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef",
    "object": {
        "name": "foo",
        "value": { "bar": true },
        "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
    }
}

{
    "type": "queryChange",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef",
    "object": {
        "name": "foo",
        "value": { "bar": false },
        "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
    }
}

{
    "type": "queryRemove",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef",
    "object": {
        "name": "foo",
        "value": { "bar": false },
        "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
    }
}

unsubscribe queryId

unsubscribe stops watching for changes and removes a query.

using objtalk-cli: unsupported

over http: implicit when the event-stream is closed

over tcp or websocket:

{
    "id": 1,
    "type": "unsubscribe",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef"
}
{
    "requestId": 1,
    "result": {
        "success": true
    }
}

remove name

remove removes an object. The return value indicates if an object with the name existed and was successfully removed.

using objtalk-cli:

$ objtalk-cli remove sensor

over http:

$ curl -X DELETE 127.0.0.1:3000/objects/sensor

over tcp or websocket:

{
    "id": 1,
    "type": "remove",
    "name": "sensor"
}

{
    "requestId": 1,
    "result": {
        "existed": true
    }
}

Events

Objects can also emit events. You can listen for events by creating a query.

emit object event data

emit publishes an event on an existing object.

using objtalk-cli:

$ objtalk-cli emit gamepad buttonPress '{"button":"a"}'

over http:

$ curl -X POST 127.0.0.1:3000/events/gamepad -d '{"event":"buttonPress","data":{"button":"a"}}'

over tcp or websocket:

{
    "id": 1,
    "type": "emit",
    "object": "gamepad",
    "event": "buttonPress",
    "data": { "button": "a" }
}

{
    "requestId": 1,
    "result": {
        "success": true
    }
}

Which produces the following event on all queries that match that object:

{
    "type": "queryEvent",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef",
    "object": "gamepad",
    "event": "buttonPress",
    "data": { "button": "a" }
}

RPC

Methods can be called on objects. One client is connected to objtalk and listens for method calls, performs them and pushes the result to objtalk. Other clients can call these methods.

Calling methods

invoke calls a method on an object.

using objtalk-cli:

$ objtalk-cli invoke device/lamp/livingroom setState '{"on":true}'

over http:

$ curl -X POST 127.0.0.1:3000/invoke/device/lamp/livingroom -d '{"method":"setState","args":{"on":true}}'

over tcp or websocket:

{
    "id": 1,
    "type": "invoke",
    "object": "device/lamp/livingroom",
    "method": "setState",
    "args": { "on": true }
}

{
    "requestId": 1,
    "result": {
        "success": true
    }
}

Providing method calls

To provide rpc calls for an object a client ("provider") has to connect to objtalk and has to create a query with provideRpc set to true. Once another client ("consumer") tries to call a method on the object a queryInvocation event is emitted on the query. The provider can process the request and return a result to the consumer using the invokeResult command.

The whole flow looks like this:

{
    "id": 1,
    "type": "query",
    "pattern": "device/lamp/livingroom",
    "provideRpc": true
}

{
    "requestId": 1,
    "result": {
        "queryId": "01234567-89ab-cdef-0123-456789abcdef",
        "objects": [
            {
                "name": "device/lamp/livingroom",
                "value": { "on": false },
                "lastModified": "YYYY-MM-DDTHH:MM:SS.SSSSSSSSSZ"
            }
        ]
    }
}

{
    "type": "queryInvocation",
    "queryId": "01234567-89ab-cdef-0123-456789abcdef",
    "invocationId": "fedcba98-7654-3210-fedc-ba9876543210",
    "object": "device/lamp/livingroom",
    "method": "setState",
    "args": { "on": true }
}

{
    "id": 2,
    "type": "invokeResult",
    "invocationId": "fedcba98-7654-3210-fedc-ba9876543210",
    "result": { "success": true }
}

{
    "requestId": 2,
    "result": {
        "success": true
    }
}

Disconnect commands

A client can register a list of commands that are executed once the client disconnects. This can be used, for example, to indicate that a device went offline by changing the value of an object. The supported commands are set, patch, remove and emit.

setDisconnectCommands commands

setDisconnectCommands sets the list of commands to execute when the client disconnects.

using objtalk-cli: unsupported

over http: unsupported

over tcp or websocket:

{
    "id": 1,
    "type": "setDisconnectCommands",
    "commands": [
        {
            "type": "set",
            "name": "device/foo",
            "value": { "offline": true },
        },
        {
            "type": "patch",
            "name": "device/foo",
            "value": { "offline": true },
        },
        {
            "type": "remove",
            "name": "client",
        },
        {
            "type": "emit",
            "name": "device/foo",
            "event": "offline",
            "data": {},
        }
    ]
}

{
    "requestId": 1,
    "result": {
        "success": true
    }
}

Dependencies

~8–24MB
~324K SLoC