Giter Club home page Giter Club logo

thingsboard-python-client-sdk's Introduction

ThingsBoard MQTT and HTTP client Python SDK

Join the chat at https://gitter.im/thingsboard/chat

ThingsBoard is an open-source IoT platform for data collection, processing, visualization, and device management. This project is a Python library that provides convenient client SDK for both Device and Gateway APIs.

SDK supports:

  • Unencrypted and encrypted (TLS v1.2) connection
  • QoS 0 and 1 (MQTT only)
  • Automatic reconnect
  • All Device MQTT APIs provided by ThingsBoard
  • All Gateway MQTT APIs provided by ThingsBoard
  • Most Device HTTP APIs provided by ThingsBoard
  • Device Claiming
  • Firmware updates

The Device MQTT API and the Gateway MQTT API are base on the Paho MQTT library. The Device HTTP API is based on the Requests library.

Installation

To install using pip:

pip3 install tb-mqtt-client

Getting Started

Client initialization and telemetry publishing

MQTT

from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo


telemetry = {"temperature": 41.9, "enabled": False, "currentFirmwareVersion": "v1.2.2"}

# Initialize ThingsBoard client
client = TBDeviceMqttClient("127.0.0.1", username="A1_TEST_TOKEN")
# Connect to ThingsBoard
client.connect()
# Sending telemetry without checking the delivery status
client.send_telemetry(telemetry) 
# Sending telemetry and checking the delivery status (QoS = 1 by default)
result = client.send_telemetry(telemetry)
# get is a blocking call that awaits delivery status  
success = result.get() == TBPublishInfo.TB_ERR_SUCCESS
# Disconnect from ThingsBoard
client.disconnect()

MQTT using TLS

TLS connection to localhost. See https://thingsboard.io/docs/user-guide/mqtt-over-ssl/ for more information about client and ThingsBoard configuration.

from tb_device_mqtt import TBDeviceMqttClient
import socket


client = TBDeviceMqttClient(socket.gethostname())
client.connect(tls=True,
               ca_certs="mqttserver.pub.pem",
               cert_file="mqttclient.nopass.pem")
client.disconnect()

HTTP

from tb_device_http import TBHTTPDevice


client = TBHTTPDevice('https://thingsboard.example.com', 'secret-token')
client.connect()
client.send_telemetry({'temperature': 41.9})

Using Device APIs

TBDeviceMqttClient provides access to Device MQTT APIs of ThingsBoard platform. It allows to publish telemetry and attribute updates, subscribe to attribute changes, send and receive RPC commands, etc. Use TBHTTPClient for the Device HTTP API.

Subscription to attributes

You can subscribe to attribute updates from the server. The following example demonstrates how to subscribe to attribute updates from the server.

MQTT
import time
from tb_device_mqtt import TBDeviceMqttClient


def on_attributes_change(client, result, exception):
    if exception is not None:
        print("Exception: " + str(exception))
    else:
        print(result)

        
client = TBDeviceMqttClient("127.0.0.1", username="A1_TEST_TOKEN")
client.connect()
client.subscribe_to_attribute("uploadFrequency", on_attributes_change)
client.subscribe_to_all_attributes(on_attributes_change)
while True:
    time.sleep(1)
HTTP

Note: The HTTP API only allows a subscription to updates for all attribute.

from tb_device_http import TBHTTPClient


client = TBHTTPClient('https://thingsboard.example.com', 'secret-token')

def callback(data):
    print(data)
    # ...

# Subscribe
client.subscribe('attributes', callback)
# Unsubscribe
client.unsubscribe('attributes')

Telemetry pack sending

You can send multiple telemetry messages at once. The following example demonstrates how to send multiple telemetry messages at once.

MQTT
from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo
import time


telemetry_with_ts = {"ts": int(round(time.time() * 1000)), "values": {"temperature": 42.1, "humidity": 70}}
client = TBDeviceMqttClient("127.0.0.1", username="A1_TEST_TOKEN")
# we set maximum amount of messages sent to send them at the same time. it may stress memory but increases performance
client.max_inflight_messages_set(100)
client.connect()
results = []
result = True
for i in range(0, 100):
    results.append(client.send_telemetry(telemetry_with_ts))
for tmp_result in results:
    result &= tmp_result.get() == TBPublishInfo.TB_ERR_SUCCESS
print("Result " + str(result))
client.disconnect()
HTTP

Unsupported, the HTTP API does not allow the packing of values.

Request attributes from server

You can request attributes from the server. The following example demonstrates how to request attributes from the server.

MQTT
import time
from tb_device_mqtt import TBDeviceMqttClient


def on_attributes_change(client,result, exception):
    if exception is not None:
        print("Exception: " + str(exception))
    else:
        print(result)

        
client = TBDeviceMqttClient("127.0.0.1", username="A1_TEST_TOKEN")
client.connect()
client.request_attributes(["configuration","targetFirmwareVersion"], callback=on_attributes_change)
while True:
    time.sleep(1)
HTTP
from tb_device_http import TBHTTPClient


client = TBHTTPClient('https://thingsboard.example.com', 'secret-token')

client_keys = ['attr1', 'attr2']
shared_keys = ['shared1', 'shared2']
data = client.request_attributes(client_keys=client_keys, shared_keys=shared_keys)

Respond to server RPC call

You can respond to RPC calls from the server. The following example demonstrates how to respond to RPC calls from the server. Please install psutil using 'pip install psutil' command before running the example.

MQTT
try:
    import psutil
except ImportError:
    print("Please install psutil using 'pip install psutil' command")
    exit(1)
import time
import logging
from tb_device_mqtt import TBDeviceMqttClient

# dependently of request method we send different data back
def on_server_side_rpc_request(client, request_id, request_body):
    print(request_id, request_body)
    if request_body["method"] == "getCPULoad":
        client.send_rpc_reply(request_id, {"CPU percent": psutil.cpu_percent()})
    elif request_body["method"] == "getMemoryUsage":
        client.send_rpc_reply(request_id, {"Memory": psutil.virtual_memory().percent})

client = TBDeviceMqttClient("127.0.0.1", username="A1_TEST_TOKEN")
client.set_server_side_rpc_request_handler(on_server_side_rpc_request)
client.connect()
while True:
    time.sleep(1)
HTTP

You can use HTTP API client in case you want to use HTTP API instead of MQTT API.

from tb_device_http import TBHTTPClient


client = TBHTTPClient('https://thingsboard.example.com', 'secret-token')

def callback(data):
    rpc_id = data['id']
    # ... do something with data['params'] and data['method']...
    response_params = {'result': 1}
    client.send_rpc(name='rpc_response', rpc_id=rpc_id, params=response_params)

# Subscribe
client.subscribe('rpc', callback)
# Unsubscribe
client.unsubscribe('rpc')

Using Gateway APIs

TBGatewayMqttClient extends TBDeviceMqttClient, thus has access to all it's APIs as a regular device. Besides, gateway is able to represent multiple devices connected to it. For example, sending telemetry or attributes on behalf of other, constrained, device. See more info about the gateway here:

Telemetry and attributes sending

import time
from tb_gateway_mqtt import TBGatewayMqttClient


gateway = TBGatewayMqttClient("127.0.0.1", username="TEST_GATEWAY_TOKEN")
gateway.connect()
gateway.gw_connect_device("Test Device A1")

gateway.gw_send_telemetry("Test Device A1", {"ts": int(round(time.time() * 1000)), "values": {"temperature": 42.2}})
gateway.gw_send_attributes("Test Device A1", {"firmwareVersion": "2.3.1"})

gateway.gw_disconnect_device("Test Device A1")
gateway.disconnect()

Request attributes

You can request attributes from the server. The following example demonstrates how to request attributes from the server.

import time
from tb_gateway_mqtt import TBGatewayMqttClient


def callback(result, exception):
    if exception is not None:
        print("Exception: " + str(exception))
    else:
        print(result)

        
gateway = TBGatewayMqttClient("127.0.0.1", username="TEST_GATEWAY_TOKEN")
gateway.connect()
gateway.gw_request_shared_attributes("Test Device A1", ["temperature"], callback)

while True:
    time.sleep(1)

Respond to RPC

You can respond to RPC calls from the server. The following example demonstrates how to respond to RPC calls from the server. Please install psutil using 'pip install psutil' command before running the example.

import time

from tb_gateway_mqtt import TBGatewayMqttClient
try:
    import psutil
except ImportError:
    print("Please install psutil using 'pip install psutil' command")
    exit(1)

    
def rpc_request_response(client, request_id, request_body):
    # request body contains id, method and other parameters
    print(request_body)
    method = request_body["data"]["method"]
    device = request_body["device"]
    req_id = request_body["data"]["id"]
    # dependently of request method we send different data back
    if method == 'getCPULoad':
        gateway.gw_send_rpc_reply(device, req_id, {"CPU load": psutil.cpu_percent()})
    elif method == 'getMemoryLoad':
        gateway.gw_send_rpc_reply(device, req_id, {"Memory": psutil.virtual_memory().percent})
    else:
        print('Unknown method: ' + method)

        
gateway = TBGatewayMqttClient("127.0.0.1", username="TEST_GATEWAY_TOKEN")
gateway.connect()
# now rpc_request_response will process rpc requests from servers
gateway.gw_set_server_side_rpc_request_handler(rpc_request_response)
# without device connection it is impossible to get any messages
gateway.gw_connect_device("Test Device A1")
while True:
    time.sleep(1)

Other Examples

There are more examples for both device and gateway in corresponding folders.

Support

Licenses

This project is released under Apache 2.0 License.

thingsboard-python-client-sdk's People

Contributors

ashvayka avatar greatyang avatar imbeacon avatar kumar-dheeraj avatar loelkes avatar samson0v avatar serhiilikh avatar want-eat-meat avatar wilterdinkrobert avatar xalt7x avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

thingsboard-python-client-sdk's Issues

TLS self signed certificate python Error

i have used these commands to generate server.pem ( self signed certificate ) and server_key.pem (privarte key)
openssl ecparam -out server_key.pem -name secp256r1 -genkey
openssl req -new -key server_key.pem -x509 -nodes -days 365 -out server.pem

and the server running fine
In client side used below cmds to get client certificate and private key

openssl ecparam -out key.pem -name secp256r1 -genkey
openssl req -new -key key.pem -x509 -nodes -days 365 -out cert.pem

In python

client.tls_set(ca_certs="server.pem", certfile="cert.pem ", keyfile="key.pem", \ cert_reqs=ssl.CERT_NONE,tls_version=ssl.PROTOCOL_TLSv1_2, ciphers=None);

client.tls_insecure_set(False)
client.connect("XXXXXXXX", 8883, 60)

Error:

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] self signed certificate (_ssl.c:1131)

Configuration

In thingsboard.conf

export MQTT_SSL_ENABLED=true
export MQTT_SSL_BIND_ADDRESS=0.0.0.0
export MQTT_SSL_BIND_PORT=8883
export MQTT_SSL_CREDENTIALS_TYPE=PEM
export MQTT_SSL_PEM_CERT=/etc/thingsboard/conf/server.pem
export MQTT_SSL_PEM_KEY=/etc/thingsboard/conf/server_key.pem
export MQTT_SSL_PEM_KEY_PASSWORD=password **

Versions (please complete the following information):

  • OS: [e.g. Ubuntu 18.04]
  • ThingsBoard v.3.3.2PE
  • Python 3.8.10

Python MQTT client hangs without error after a few hundred writes

I've tested Thingsboard API (HTTP using JMeter) with very good results. Now I've been asked to do the same using MQTT. Iโ€™m using the docker image described in https://thingsboard.io/docs/user-guide/install/docker/

For starters, the examples in https://thingsboard.io/docs/reference/python-client-sdk/ do not work with tb-mqtt-client latest versions. By trial and error, I managed to make it work with version 1.1, the only one that worked for sending attributes and telemetry (python -m pip install tb-mqtt-client==1.1).
Using the MQTT python client to create devices never succeeds in provision more than 10.000. It hangs before that without any error.
Since what I was asked to test was sending data (attributes and telemetry) I resorted to pycurl to provision the devices and get their tokens so I could send data to each device.
Following the example in https://github.com/thingsboard/thingsboard-python-client-sdk/blob/master/examples/device/send_telemetry_and_attr.py I created these two functions to send data on custom MQTT class of my own where properties like server address and device token are populated when the device is provisioned using http.

    def write_device_attribute(self) -> bool:
        attribute = {"withoutHistory": random.randint(-1000000, 1000000)}
        client = TBDeviceMqttClient(host=self.server_address, token=self.device_token)
        client.connect()
        result = client.send_attributes(attribute)
        success = result.get() == TBPublishInfo.TB_ERR_SUCCESS
        client.disconnect()
        del client
        return success
    
    def write_device_telemetry(self) -> bool:
        telemetry = {"withHistory": random.randint(-1000000, 1000000)}
        client = TBDeviceMqttClient(host=self.server_address, token=self.device_token)
        client.connect()
        result = client.send_telemetry(telemetry,0)
        success = result.get() == TBPublishInfo.TB_ERR_SUCCESS
        client.disconnect()
        del client
        return success

These functions are called by several threads but each thread only writes to itโ€™s own devices:
#################################################################
def write_attributes(user_n):
    """Write to the user devices attribute

    Args:
        user_n (int): integer identifying the user 1..N_USERS
    """
    try:
        
        for _ in range(N_DATA_POINTS):
            for n in range(N_DEVICES):
                success = MQTT_CLIENTS[user_n][n].write_device_attribute()
                Q_Request_log.put_nowait(f"{now_string()};attr;{success}")
                time.sleep(SLEEP_TIME)
    
    except TB_EXCEPTION as err:
        print(err)
#################################################################
def write_telemetry(user_n):
    """Write telemetry to the user devices

    Args:
        user_n (int): integer identifying the user 1..N_USERS
    """
    try:
        
        for _ in range(N_DATA_POINTS):
            for n in range(N_DEVICES):
                success = MQTT_CLIENTS[user_n][n].write_device_telemetry()
                Q_Request_log.put_nowait(f"{now_string()};ts;{success}")
                time.sleep(SLEEP_TIME)
    
    except TB_EXCEPTION as err:
        print(err)
#################################################################

And the main function:
โ€ฆ

    # Send telemetry (attribute, no history). Launch a thread for each user.
    start_time = datetime.now()
    for i in range(N_USERS):
        thread_a = threading.Thread(target = write_attributes, args=(i,))
        thread_a.start()
        threads_At.append(thread_a)

    # wait for all threads to finish
    for thread in threads_At:
        thread.join()
        
    end_time = datetime.now()
    print(f"Write attribute;{elapsed_time(start_time, end_time)}")
    
    # Send telemetry (attribute, time series). Launch a thread for each user.
    start_time = datetime.now()
    for i in range(N_USERS):
        thread_a = threading.Thread(target = write_telemetry, args=(i,))
        thread_a.start()
        threads_TS.append(thread_a)

    # wait for all threads to finish
    for thread in threads_TS:
        thread.join()
        
    end_time = datetime.now()
    print(f"Send telemetry;{elapsed_time(start_time, end_time)}")

โ€ฆ

This code starts by running as expected but after a few hundred attribute writes it gets stuck. Checking the process with ps -u I see that it is waiting in a interruptible sleep (waiting for an event to complete), specifically state โ€œSl+โ€.

Any clue as why this works fine for low numbers but gets stuck on a long run? The server is almost idle at 2% CPU usage, lots of free memory and disk.

[Question] Error when performing a firmware update

Hello,

I am using the TBDeviceMqttClient class from the "tb_device_mqtt.py".

I am getting this error when launching a firmware update:
File "/home/ubuntu/.local/lib/python3.8/site-packages/tb_device_mqtt.py", line 324, in _on_decoded_message callback = self._attr_request_dict.pop(req_id) KeyError: 1

My question concerns the messages received by the client:

  • The client receives all the update details (fw_title, fw_version, fw_tag, fw_size, fw_checksum_algorithm, fw_checksum) on the "v1/devices/me/attributes" topic
  • Then if the versions are different the client sends a request and it receives the update on "v2/fw/request/${requestId}/chunk/${chunk}" topic
  • The client receives also a message on the "v1/devices/me/attributes/response/+" topic which includes all the changed shared attributes related to the update

When the client receives a message on the "v1/devices/me/attributes/response/+" topic, it has to get the callback function: callback = self._attr_request_dict.pop(req_id)
However, the self._attr_request_dict is updated (and thus the callback is added to the dict) when the user calls the request_attributes function which calls the _add_attr_request_callback.
In the case of a firmware update, no callback function was added or defined to be called when a message is received on the "v1/devices/me/attributes/response/+" topic, and as a consequence, the error is raised.

Is there any problem in the client script or am I missing something?

Importing python module should not trigger package installation

After the last commit (7b1ffcc), import of sdk_utils or anything which depends on it (tb_device_mqtt & tb_gateway_mqtt) will result in attempted installation of mmh3/pymmh3 via pip. This is counter-intuitive and surprising - importing a Python module should not have side effect which modify the system state.

Furthermore, this makes the tb-mqtt-client package unusable on embedded systems which either lack pip (to save space), have a read-only rootfs or lack internet access. Automated installation of packages at runtime also breaks installation reproducibility (as there's no way to control which version of the dependency is installed) and defeats attempts to achieve license compliance.

Dependencies should be expessed in setup.py so that they can be installed in the usual way.

Payload must be a string, byte array, int, float or None

import psutil
import time
import logging
from tb_device_mqtt import TBDeviceMqttClient

dependently of request method we send different data back

def on_server_side_rpc_request(request_id, request_body):
print(request_id, request_body)
try:
if request_body["method"] == "getCPULoad":
client.send_rpc_reply(request_id, {"CPU percent": psutil.cpu_percent()})
elif request_body["method"] == "getMemoryUsage":
client.send_rpc_reply(request_id, {"Memory": psutil.virtual_memory().percent})
except Exception as e:
print(e)
client = TBDeviceMqttClient("127.0.0.1", "A1_TEST_TOKEN")
client.set_server_side_rpc_request_handler(on_server_side_rpc_request)
client.connect()
while True:
time.sleep(1)

-> Throw exception Payload must be a string, byte array, int, float or None -> I have to convert like client.send_rpc_reply(request_id, str({"Memory": psutil.virtual_memory().percent})) but no reply thingsboard receives ?
Any suggestion ?

Token connect not possible 1.4

with python 3.7 and client 1.6 not possible connect to server using Token (examples) .
TBDeviceMqttClient init does not have this option, require PORT and username + password
if you want connect with TOKEN connection string
client = TBDeviceMqttClient("xxx.xxx.xxx.xxx",1883,None, "TOKEN")

Module naming may cause issues & clashes with other python packages

Installing tb-mqtt-client results in the following files in python's site-packages directory:

__init__.py
sdk_utils.py
tb_device_http.py
tb_device_mqtt.py
tb_gateway_mqtt.py
utils.py

This gives me a few concerns:

  • The __init__.py file at the top-level of site-packages may be imported automatically when anything else from that directory is imported. This could lead to issues if any content is added to this file.
  • The names __init__.py, sdk_utils.py and utils.py are not very unique and may easily clash with modules from other Python packages.

I recommend moving these files into a single top-level module or prefixing the file names with something unique to prevent issues.

Can't publish data with timestamp (ts) through gw_send_telemetry

Everything works fine if the timestamp is not included, but when I try to use the function from https://thingsboard.io/docs/reference/python-client-sdk/#using-gateway-apis it gives me an error in ThingsBoard platform logs (using sudo journalctl -u thingsboard.service)

Example that works:
from time import time from tb_gateway_mqtt import TBGatewayMqttClient gateway = TBGatewayMqttClient("THINGSBOARD_PLATFORM_IP", 1883, "GATEWAY_ACCESS_TOKEN") gateway.connect() gateway.gw_send_telemetry("DEVICE_ID", {"telemetryKey": "telemetryValue"})

Example that doesn't work:
from time import time from tb_gateway_mqtt import TBGatewayMqttClient gateway = TBGatewayMqttClient("THINGSBOARD_PLATFORM_IP", 1883, "GATEWAY_ACCESS_TOKEN")<br /> gateway.connect() gateway.gw_send_telemetry("DEVICE_ID", {"ts": int(round(time()*1000)), "values": {"telemetryKey": "telemetryValue"}})

When gw_send_telemetry is called in the second example, it gives me this in logs:
Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: Exception in thread "MqttTransportContext-50-2" com.google.gson.JsonSyntaxException: Can't parse value: {"040100":{"ts":1677701139953,"testTelemetry":3}} Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler$3.onSuccess(GatewaySessionHandler.java:353) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler$3.onSuccess(GatewaySessionHandler.java:349) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at com.google.common.util.concurrent.Futures$CallbackListener.run(Futures.java:1080) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1426) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594) Mar 01 20:06:01 ip-172-31-34-70 thingsboard.jar[1750750]: at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

In my opinion, the problem is the format of the message, because it doesn't add the "telemetry": needed for this kind of input value, but maybe it's another issue that I'm not seeing. Any help would be appreciated

Incompatibility with asyncua library

I try to use the tb_device_mqtt library together with the asyncronous opc-ua library asyncua and asyncio.Queue().

With the standard paho-mqtt library the sending of the data works fine. With tb_device_mqtt library it is not working.

The relevant part of the code looks like this:
p_plc is the asyncua client
q_queue is an async.Queue

    async with g_plc:
	    await subscribe()
	    while g_running:
		    if(not g_queue.empty()):
			    g_logger.info("Queue size start: %d" % g_queue.qsize())	
			    while(not g_queue.empty()):
				    send_data(await g_queue.get())
		    await g_plc.check_connection()
		    await asyncio.sleep(0.001)
	    await asyncio.sleep(2)

The send_data function sends the data to the thingsboard server.

With paho-mqtt I send the data with:

	ret = g_rcc_mqtt.publish('v1/devices/me/telemetry', data, 1)
	g_logger.info(ret)

With tb_device_mqtt I send the data with:

	result = g_rcc_mqtt.send_telemetry(data)
	success = result.get() == TBPublishInfo.TB_ERR_SUCCESS
	g_logger.info(success)

With tb_device_mqtt I get after some seconds the exception: Message publish failed: The client is not currently connected.
Is there an example that shows how to use tb_device_mqtt library inside asyncio code.

This and the Gateway

Indecisive between this one and the Thingsboard gateway.
I am new two both of this.
Currently, I have a control software(Raspberry PI) which read data from all sensors and stored in InfluxDB and also manage all the output relays. I want to push all these info to my Thingsboard cloud. I am not sure which is a good options.

Thanks;

Request multiple attributes gw_request_shared_attributes return no values

I try to request an array of shared keys with gw_request_shared_attributes.

import config
import time

from tb_gateway_mqtt import TBGatewayMqttClient

def callback(client, result, exception):
    print('on change:::')
    print(result)

gateway = TBGatewayMqttClient(config.TB_HOST, config.TB_ACCESS_KEY)

keys = ['keyA', 'keyB']

gateway.connect()
gateway.gw_connect_device('myDevice')
gateway.gw_request_shared_attributes('myDevice', keys, callback)

while True:
    time.sleep(1)

response is without any value: {'id': 1, 'device': 'myDevice'}

It works if I do it key by key. But I have around 10keys to request so I must find an efficient solution.

gateway.gw_request_shared_attributes('myDevice', ["keyA"] , callback)
#  {'id': 1, 'device': 'myDevice'. value: True}

gateway.gw_request_shared_attributes('myDevice',["keyB"], callback)
#  {'id': 2, 'device': 'myDevice'. value: True}

Issue with new 1.8.7 version

With the new version 1.8.7 we encounter the following error:

024-03-19 16:42:42.709 ERROR [Sending thread ] [tb_device_mqtt ] __sending_thread_main - Error during data sending:
Traceback (most recent call last):
File "/root/.local/lib/python3.10/site-packages/tb_device_mqtt.py", line 634, in __sending_thread_main
self.__responses[item['id']] = {"info": info, "timeout_ts": int(time.time()) + DEFAULT_TIMEOUT}

Probably related to d7fd7b8

image

Support for installs without GCC using PYMMH3?

Hello,
I was trying to install this library on an Inhand Networks IG502 IOT gateway. The device provides capability for users to run and execute python and python libraries, and no more. It does not seem possible to run packages with C dependencies. Is it possible the code could be updated to switch to PYMMH3 if MMH3 install fails? Obviously this would impact performance if GCC is not available. I am using this gateway more like a sensor node, and expect it to send a telemetry update at most once a minute. High performance is not applicable in my use case.

Thank you for your consideration.

Remote shell - Response payload docs

I'm using this library in order to reply from the remote shell RPC but I'm not sure what is the payload the shell is expecting since I just can see undefined in the terminal.

As an example, the gateway replies to the server the following payload:

result = {
    "data":
    [
        {
            "stdout": "Command not found",
            "stderr": ""
        }
    ],
    "done": True,
    "qos": 0
}
gateway.gw_send_rpc_reply(device, req_id, result)

Is this a valid reply? Could you add some examples in the documentation, please?

Thank you

Modbus connection |ERROR| - [tb_gateway_service.py] - tb_gateway_service - _connect_with_connectors - 547 - 'str' object has no attribute 'get'"

""2022-07-09 11:08:23" - |INFO| - [tb_gateway_service.py] - tb_gateway_service - init - 112 - Gateway starting..."

""2022-07-09 11:08:23" - |INFO| - [tb_gateway_service.py] - tb_gateway_service - init - 117 - ThingsBoard IoT gateway version: 3.1"

""2022-07-09 11:08:23" - |INFO| - [tb_gateway_mqtt.py] - tb_gateway_mqtt - gw_subscribe_to_attribute - 175 - Subscribed to | with id 1 for device *"

""2022-07-09 11:08:23" - |INFO| - [tb_loader.py] - tb_loader - import_module - 68 - Import ModbusConnector from /usr/local/lib/python3.9/site-packages/thingsboard_gateway-3.1-py3.9.egg/thingsboard_gateway/connectors/modbus."

""2022-07-09 11:08:23" - |WARNING| - [tb_gateway_service.py] - tb_gateway_service - _load_connectors - 502 - Cannot parse connector configuration as a JSON, it will be passed as a string."

""2022-07-09 11:08:23" - |ERROR| - [tb_gateway_service.py] - tb_gateway_service - _connect_with_connectors - 547 - 'str' object has no attribute 'get'"

Traceback (most recent call last):

File "/usr/local/lib/python3.9/site-packages/thingsboard_gateway-3.1-py3.9.egg/thingsboard_gateway/gateway/tb_gateway_service.py", line 535, in _connect_with_connectors

connector = self._implemented_connectors[connector_type](self,

File "/usr/local/lib/python3.9/site-packages/thingsboard_gateway-3.1-py3.9.egg/thingsboard_gateway/connectors/modbus/modbus_connector.py", line 98, in init

self.__config = self.__backward_compatibility_adapter.convert()

File "/usr/local/lib/python3.9/site-packages/thingsboard_gateway-3.1-py3.9.egg/thingsboard_gateway/connectors/modbus/backward_compability_adapter.py", line 40, in convert

if not self.__config.get('server'):

AttributeError: 'str' object has no attribute 'get'

""2022-07-09 11:08:23" - |INFO| - [tb_gateway_service.py] - tb_gateway_service - init - 214 - Gateway started."

""2022-07-09 11:08:23" - |INFO| - [tb_device_mqtt.py] - tb_device_mqtt - _on_connect - 139 - connection SUCCESS"

""2022-07-09 11:08:23" - |INFO| - [tb_gateway_mqtt.py] - tb_gateway_mqtt - gw_subscribe_to_attribute - 175 - Subscribed to | with id 2 for device *"

Port required on Client connection

I've just installed today this library but when trying to run I was getting an error:

File "/home/alr/.local/lib/python3.8/site-packages/paho/mqtt/client.py", line 979, in connect_async
    if port <= 0:
TypeError: '<=' not supported between instances of 'str' and 'int'

I solved adding the port information to the connection:

client = TBDeviceMqttClient("127.0.0.1", 1883, "token")

Cannot import TBDeviceMqttClient

Hi,

I have installed the tb_device_mqtt package on my ubuntu 22.04 running in VMware. It seem to have installed properly. However, when running the following code on jupyter notebook:

'from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo'

I get the following error:

'ImportError Traceback (most recent call last)
Cell In[9], line 1
----> 1 from tb_device_mqtt import TBPublishInfo

File ~/programs/anaconda3/lib/python3.11/site-packages/tb_device_mqtt.py:27
23 from threading import Thread
25 from simplejson import JSONDecodeError
---> 27 from sdk_utils import verify_checksum
30 FW_TITLE_ATTR = "fw_title"
31 FW_VERSION_ATTR = "fw_version"

File ~/programs/anaconda3/lib/python3.11/site-packages/sdk_utils.py:21
18 import logging
19 from subprocess import CalledProcessError
---> 21 from utils import install_package
23 try:
24 install_package('mmh3')

ImportError: cannot import name 'install_package' from 'utils' (/home/bard/programs/anaconda3/lib/python3.11/site-packages/utils/init.py)'

Am I doing anything wrong here? Any help is much appreciated. Thanks.

No response from basic device subscription attributes example

Hello,

I'm trying to run the below example listed, which it should subscribe to all device shared attributes & updates. However, when I run the python file and make some change in some of the device shared attributes in my Thingsboard instance, there's no message received.

from time import sleep
from tb_device_mqtt import TBDeviceMqttClient


def callback(result):
    print(result)

client = TBDeviceMqttClient("127.0.0.1", "A1_TEST_TOKEN")
client.connect()
client.subscribe_to_attribute("uploadFrequency", callback)
client.subscribe_to_all_attributes(callback)
while True:
    sleep(1)

Diggin into tb_device_mqtt.py, I could check that the necessary device subscription for this (topic 'v1/devices/me/attributes') is made on _on_connect method.

I understand that when I make a change on any shared attribute, I should receive the new value into console, right? As I receive with a simple mosquitto_sub example.

imagen

However, with the example listed, I don't receive any response ๐Ÿคท

imagen

Examples do not end after disconnect function is called

Hi again!

The provided examples do not exit even after calling the disconnect function of the client/gateway. Two reasons:

  • The mqtt client loop is not stopped in the disconnect function, _client.stop_loop() should be called (although I'm not sure if this is needed, but it should be a good practice)
  • The __timeout_thread is still running, because it loops forever (while True). This thread must be killed/stopped, otherwise the programs do not end.

Best regards,

Aitor

Roadmap

Is there a roadmap for the SDK releases?

when i use offical Respond to server RPC call example parameter

the official example as below:

from psutil import cpu_percent, virtual_memory
from time import sleep
from tb_device_mqtt import TBDeviceMqttClient


# dependently of request method we send different data back
def on_server_side_rpc_request(client, request_id, request_body):
    print(request_id, request_body)
    if request_body["method"] == "getCPULoad":
        client.send_rpc_reply(request_id, {"CPU percent": cpu_percent()})
    elif request_body["method"] == "getMemoryUsage":
        client.send_rpc_reply(request_id, {"Memory": virtual_memory().percent})

client = TBDeviceMqttClient("127.0.0.1", "A1_TEST_TOKEN")
client.set_server_side_rpc_request_handler(on_server_side_rpc_request)
client.connect()


while True:
    sleep(1)

but the SDK does not include client parameters. so I modified the local SDK to fit your official example and use client seems likes to be better for using global variables.
can you add this parameter back?

share device name in gateway_suscribe_attributes or all/device attributes

Hi there !

I'm currently using the python client sdk and i found a missing item. So maybe i'm not using correctly the SDK but when my gateway device TBGatewayMqttClient suscribe for attribute with gw_subscribe_to_attribute, the device name is not shared to the callback.
This is a problem for me because when i modify an attribute on TB dashboard i get the new attribute name : value but i do not get the device corresponding.

In tb_gateway_mqtt.py i had to make a modification in method called "_on_decoded_message" to make this happened :

`elif message.topic == GATEWAY_ATTRIBUTES_TOPIC:

        with self._lock:
            # callbacks for everything
            if self.__sub_dict.get("*|*"):
                for callback in self.__sub_dict["*|*"]:
                    self.__sub_dict["*|*"][callback](self, content["data"])
            # callbacks for device. in this case callback executes for all attributes in message
            target = content["device"] + "|*"
            if self.__sub_dict.get(target):
                for callback in self.__sub_dict[target]:
                    self.__sub_dict[target][callback](self, content["data"])
            # callback for atr. in this case callback executes for all attributes in message
            targets = [content["device"] + "|" + callback for callback in content["data"]]
            for target in targets:
                if self.__sub_dict.get(target):
                    for sub_id in self.__sub_dict[target]:
                        self.__sub_dict[target][sub_id](self, content["data"])`

to

`elif message.topic == GATEWAY_ATTRIBUTES_TOPIC:

        with self._lock:
            # callbacks for everything
            if self.__sub_dict.get("*|*"):
                for callback in self.__sub_dict["*|*"]:
                    self.__sub_dict["*|*"][callback](self, content["device"], content["data"])
            # callbacks for device. in this case callback executes for all attributes in message
            target = content["device"] + "|*"
            if self.__sub_dict.get(target):
                for callback in self.__sub_dict[target]:
                    self.__sub_dict[target][callback](self, content["device"], content["data"])
            # callback for atr. in this case callback executes for all attributes in message
            targets = [content["device"] + "|" + callback for callback in content["data"]]
            for target in targets:
                if self.__sub_dict.get(target):
                    for sub_id in self.__sub_dict[target]:
                        self.__sub_dict[target][sub_id](self, content["device"], content["data"])`

adding content["device"] allows me to get the device name in the callback and execute correspond process for this particular device. Is there another way to get the same result or do you think this modification has to be implemented ?

Connection must be established before sending any message

Hi guys!

I have been playing with the library (many thanks for your work!) and I have discovered that the provided examples do not work well because the library send messages too fast.

This is the code I'm using:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time
from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo

client = TBDeviceMqttClient("10.15.181.58", "xxxxxxxxxxxxxxxxxxxxxxxx")

client.connect()
# while not client._TBDeviceMqttClient__is_connected:
#     time.sleep(0.1)

result = client.send_telemetry({"temperature": 42.2})

client.disconnect()

And here you have what the Thingboard server reports:

2019-07-05 10:37:07,321 [nioEventLoopGroup-13-12] INFO  o.t.s.t.mqtt.MqttTransportHandler - [74b542dd-1890-4ab7-acae-99503d5b3847] Processing connect msg for client: !
2019-07-05 10:37:07,322 [nioEventLoopGroup-13-12] INFO  o.t.s.t.mqtt.MqttTransportHandler - [74b542dd-1890-4ab7-acae-99503d5b3847] Processing connect msg for client with user name: xxxxxxxxxxxxxxxxxxxxxxx!
2019-07-05 10:37:07,323 [nioEventLoopGroup-13-12] INFO  o.t.s.t.mqtt.MqttTransportHandler - [74b542dd-1890-4ab7-acae-99503d5b3847] Closing current session due to invalid msg order: MqttPublishMessage[fixedHeader=MqttFixedHeader[messageType=PUBLISH, isDup=false, qosLevel=AT_LEAST_ONCE, isRetain=false, remainingLength=50], variableHeader=MqttPublishVariableHeader[topicName=v1/devices/me/telemetry, packetId=1], payload=PooledSlicedByteBuf(ridx: 0, widx: 23, cap: 23/23, unwrapped: PooledUnsafeDirectByteBuf(ridx: 88, widx: 90, cap: 1024))]
2019-07-05 10:37:07,324 [nioEventLoopGroup-13-12] INFO  o.t.s.t.mqtt.MqttTransportHandler - [74b542dd-1890-4ab7-acae-99503d5b3847] Closing current session due to invalid msg order: MqttMessage[fixedHeader=MqttFixedHeader[messageType=DISCONNECT, isDup=false, qosLevel=AT_MOST_ONCE, isRetain=false, remainingLength=0], variableHeader=, payload=]
2019-07-05 10:37:07,327 [ForkJoinPool-9-worker-4] INFO  o.t.s.t.mqtt.MqttTransportHandler - [74b542dd-1890-4ab7-acae-99503d5b3847] Client connected!

As you can see, the session is closed before the client is connected, so the telemetry messages are not processed by the server.

Everything works fine after adding the while not client._TBDeviceMqttClient__is_connected: time.sleep(0.1), but that is just a hack and not a permanent solution. The first option is to expose the connection status with a getter function (client.is_connected()), but, otherwise, I am not sure if making the "connect()" function a blocking function is the right approach. How about buffering the messages until connection is established?

Thank you for your attention and for developing this project and best regards,

Aitor

Server attribute subscribe example error

The example in the readme says to use the callback as:

def on_attributes_change(client, result, exception):

But the callback doesn't bring the client information.
I solved this by using:

def on_attributes_change(result, exception):

Stuck in loop while connecting

For a while my code to post to thingsboard will work normally and then suddenly, the code will enter a loop where it repeatedly says connection SUCCESS every few ms until I terminate the script:

2020-05-15 23:51:31,012 -  INFO - connection SUCCESS
2020-05-15 23:51:31,016 -  INFO - connection SUCCESS
2020-05-15 23:51:31,877 -  INFO - connection SUCCESS
2020-05-15 23:51:31,897 -  INFO - connection SUCCESS
2020-05-15 23:51:31,939 -  INFO - connection SUCCESS
2020-05-15 23:51:31,945 -  INFO - connection SUCCESS
2020-05-15 23:51:31,953 -  INFO - connection SUCCESS
2020-05-15 23:51:31,973 -  INFO - connection SUCCESS
2020-05-15 23:51:32,010 -  INFO - connection SUCCESS
2020-05-15 23:51:32,016 -  INFO - connection SUCCESS
2020-05-15 23:51:32,022 -  INFO - connection SUCCESS
^CException ignored in: <module 'threading' from '/usr/lib/python3.7/threading.py'>
Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 1281, in _shutdown
    t.join()
  File "/usr/lib/python3.7/threading.py", line 1032, in join
    self._wait_for_tstate_lock()
  File "/usr/lib/python3.7/threading.py", line 1048, in _wait_for_tstate_lock
    elif lock.acquire(block, timeout):
KeyboardInterrupt

Here is how I'm using it:

from pyvesync_v2 import VeSync
import time
from time import localtime, strftime
#import os, sys, platform, datetime
import requests
from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo

TB_Token = "xxx"

manager = VeSync("email", "password", time_zone="America/New York")
manager.login()

# Get/Update Devices from server - populate device lists
manager.update()

my_switch = manager.outlets[0]

# Get energy usage data
manager.energy_update_interval = 3 * 60
manager.update()

# Display outlet device information
for device in manager.outlets:
    device.display()

while True:
    manager.update()
    print(strftime("%a, %d %b %Y %H:%M:%S",localtime()))
    print("%5.2fW - active for %d" %(manager.outlets[1].power,manager.outlets[1].active_time))
    if manager.outlets[1].power > 0.0:
        telemetry = {"power": manager.outlets[1].power}
        client = TBDeviceMqttClient("192.168.1.100", TB_Token)
        # Connect to ThingsBoard
        client.connect()
        # Sending telemetry without checking the delivery status
        client.send_telemetry(telemetry)
        # Sending telemetry and checking the delivery status (QoS = 1 by default)
        result = client.send_telemetry(telemetry)
        # get is a blocking call that awaits delivery status
        success = result.get() == TBPublishInfo.TB_ERR_SUCCESS
        # Disconnect from ThingsBoard
        client.disconnect()

    else:
        print("0.0W returned, possible API error.")
    time.sleep(manager.energy_update_interval)

Any thoughts on what I'm doing wrong?

ValidationError for number in array

from tb_device_mqtt import TBDeviceMqttClient, TBPublishInfo

import config

if __name__ == '__main__':
    telemetry = {
        "someArray": [1, 2, 3],
    }

    client = TBDeviceMqttClient(host=config.server_host,
                                token=config.device_test_token)
    client.connect()

    result = client.send_telemetry(telemetry)

    success = (result.get() == TBPublishInfo.TB_ERR_SUCCESS)

    if success:
        print("Success")
    else:
        print("Failure")

    client.disconnect()

the outputs of above code are

{'someArray': [1, 2, 3]} is not valid under any of the given schemas

Failed validating 'anyOf' in schema['items']:
    {'anyOf': [{'additionalProperties': False,
                'properties': {'ts': {'type': 'integer'},
                               'values': {'minProperties': 1,
                                          'patternProperties': {'.': {'type': ['integer',
                                                                               'string',
                                                                               'boolean',
                                                                               'number']}},
                                          'type': 'object'}},
                'type': 'object'},
               {'minProperties': 1,
                'patternProperties': {'.': {'type': ['integer',
                                                     'string',
                                                     'boolean',
                                                     'number']}},
                'type': 'object'}]}

On instance[0]:
    {'someArray': [1, 2, 3]}
Traceback (most recent call last):
  File "/Users/zj/PycharmProjects/ThingsBoard/telemetry_upload.py", line 14, in <module>
    result = client.send_telemetry(telemetry)
  File "/usr/local/anaconda3/lib/python3.9/site-packages/tb_device_mqtt.py", line 394, in send_telemetry
    self.validate(DEVICE_TS_OR_KV_VALIDATOR, telemetry)
  File "/usr/local/anaconda3/lib/python3.9/site-packages/tb_device_mqtt.py", line 302, in validate
    raise e
  File "/usr/local/anaconda3/lib/python3.9/site-packages/tb_device_mqtt.py", line 299, in validate
    validator.validate(data)
  File "/usr/local/anaconda3/lib/python3.9/site-packages/jsonschema/validators.py", line 254, in validate
    raise error
jsonschema.exceptions.ValidationError: {'someArray': [1, 2, 3]} is not valid under any of the given schemas

Failed validating 'anyOf' in schema['items']:
    {'anyOf': [{'additionalProperties': False,
                'properties': {'ts': {'type': 'integer'},
                               'values': {'minProperties': 1,
                                          'patternProperties': {'.': {'type': ['integer',
                                                                               'string',
                                                                               'boolean',
                                                                               'number']}},
                                          'type': 'object'}},
                'type': 'object'},
               {'minProperties': 1,
                'patternProperties': {'.': {'type': ['integer',
                                                     'string',
                                                     'boolean',
                                                     'number']}},
                'type': 'object'}]}

On instance[0]:
    {'someArray': [1, 2, 3]}

how should I do

mmh installation is run every time at startup

Hi,
I am using your ThingsBoard library on a Raspberry Pi Zero.
It is a valuable piece of software that allows me to track my current location on a map and display my planned driving route on the dashboard.
https://github.com/hishizuka/pizero_bikecomputer

Now, I report the problem.
This program runs the mmh install every time it starts up, according to the code after line 23 of sdk_utils.py.
With the Raspberry Pi Zero's processing power, it takes almost a minute to run the pip command, which is a pain.
Is it possible to use requirements.txt during installation and make it a one-time install check?

how to publish a json payload (complexy telemetry) using thingsboard-python-client-sdk?

In the MQTT API documentation, it is mentioned that: "Key is always a string, while value can be either string, boolean, double, long or JSON."

ref: https://thingsboard.io/docs/reference/mqtt-api/

Regarding this point, I don't see that the Python library supports a JSON "type" value.

reference:

KV_SCHEMA = {
"type": "object",
"patternProperties":
{
".": {"type": ["integer",
"string",
"boolean",
"number"]}
},
"minProperties": 1,
}

Additionally, I did some tests to validate this, and when I try to send a payload directly:

payload = {'someNumber':42,'someArray':[1,2,3],'someNestedObject':{'key':'value'}}
telemetry = {'newall': payload}
client.send_telemetry(telemetry,quality_of_service=1)

I get the following error message:


Failed validating 'anyOf' in schema['items']:
    {'anyOf': [{'additionalProperties': False,
                'properties': {'ts': {'type': 'integer'},
                               'values': {'minProperties': 1,
                                          'patternProperties': {'.': {'type': ['integer',
                                                                               'string',
                                                                               'boolean',
                                                                               'number']}},
                                          'type': 'object'}},
                'type': 'object'},
               {'minProperties': 1,
                'patternProperties': {'.': {'type': ['integer',
                                                     'string',
                                                     'boolean',
                                                     'number']}},
                'type': 'object'}]}

On instance[0]:
    {'newall': {'someArray': [1, 2, 3],
                'someNestedObject': {'key': 'value'},
                'someNumber': 42}}

Also, when I send my JSON payload as a string:

payload = {'someNumber':42,'someArray':[1,2,3],'someNestedObject':{'key':'value'}}
MQTT_MSG=json.dumps(payload)
telemetry = {'newall': payload}
client.send_telemetry(telemetry,quality_of_service=1)

The message is saved in Thingsboard as a string.

Additionally, validate the posting of a JSON payload as follows:

mosquitto_pub -d -h "ip" -t "v1/devices/me/telemetry" -u "token" -f "telemetry-data-as-object2.json"

My goal is to be able to publish my payload as JSON to be able to visualize it in the same way when I check the key through the REST AP (see images to visualize the difference)
02
01
I

Communication over proxy not supported

Compared to the Client object from paho.mqtt.client , the TBDeviceMqttClient object does not support the function proxy_set to set a proxy server.

Workaround is:
tb_client = TBDeviceMqttClient(...)
tb_client._client.proxy_set(proxy_type=socks.HTTP, proxy_addr="...", proxy_port=...)

Could you please provide this feature in the right way.

Thanks

Mqtt Connection Stopped

I am registering MQTT in two devices with same Topic and device Token. When trying to do so Mqtt server has been closed in first device.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.