Register device via Python

Hello.
I’m trying to register devices via the https://www.thethingsnetwork.org/docs/applications/manager/api/index.html API from my django webserver (python).

I’m struggling to get it to work and I was hoping someone here could help me out.

I would like to register my devices as OTAA and I think I have to use the SetDevice api call
At the moment this is my python code:

import requests

device_id = "testid"
app_eui = "xxxxxxxxxxxxxxxxxx"
app_id = 'apipythontester'
headers = {"Authorization": "Key ttn-account-v2.xxxxxx"}

request_data = {
    altitude": 0,
    "app_id": "apipythontester",
    "attributes": {
        "key": "",
        "value": ""
    },
    "description": "tester for the api call",
    "dev_id": "testid",,
    "latitude": 52.375,
    "longitude": 4.887,
    "lorawan_device": {
        "activation_constraints": "otaa",
        "app_eui": app_eui,
        "app_id": app_id,
        "app_key": "xxxxxx",
        # "app_s_key": "xxxxx", I commented this out because it is not needed in OTAA?
        "dev_eui": "xxxxxxxx",
        "dev_id": device_id,
        "disable_f_cnt_check": False,
        "f_cnt_down": 0,
        "f_cnt_up": 0,
        "last_seen": 0,
        # "nwk_s_key": "xxxxx", I commented this out because it is not needed in OTAA?
        "uses32_bit_f_cnt": True
    }
}
response = requests.post('http://eu.thethings.network:8084/applications/apipythontester/devices',
                         data=request_data, headers=headers)
print(response)

When I execute this code I get a 400 return
This is my first time ever conductign API calls and I’m a bit lost at what is going wrong.

I would be very grateful if someone could get me on the right tracks.

Register a device by hand in the console with a web debugger window open on the network traffic page - that will show you the exact call to where with what JSON.

1 Like

Thank you foryour quick reply!
I tried that to no avail.

To make sure things are not going wrong somewhere else I have just setup a simple application that I would like to delete with the API call. This with the intention so there can be nothing wrong with the formatting.

Here I unfortunately also get the 400 error: bad header name

import requests

url = 'http://eu.thethings.network:8094/applications/removable_application/'
headers = {"Authorization": "Key ttn-account-v2.xxx"}
data_delete = {"app_id": "removable_application"}

response = requests.delete(url, data=data_delete, headers=headers)

print(response)

Am I doing something crucially wrong?

Thanks once again for your time.

It seems you try to talk to the “v2” stack.

TTN is moving to their v3 stack, which is already active.
The v2 stack will change to read-only mode very soon. I think it is not much use right now to develop an any new application that talks to the v2 stack.

2 Likes

Thank you very much I didn’t realize I was doing this.

I’m going to put some more time and research in the V3 stack. Im still very new to TTN.

Any suggestions in where I should start looking to make my application communciate with the TTN 3?

Have a look here: API | The Things Stack for LoRaWAN

In particular, I think you’re looking for: End Device APIs | The Things Stack for LoRaWAN

The authorization header is now “Bearer XXXX”.

1 Like

Thanks for all your help.
In the end I did make my solution for the things network V2 because I cannot yet upgrade my TTIG gateway to V3 stack so for now I’m stuck with V2.

I’ll leave my solution here in case someone else will need it:
Python3 combined with django, add user to TTN after new device is created in backend:

    @receiver(post_save, sender=Device)
    def register_device_into_ttn(sender, instance, created, **kwargs):
        device_id = (instance.device_id + "_" + instance.user.first_name + "_" + instance.user.last_name).casefold()
        app_key = instance.app_key
        dev_eui = instance.device_serial
        description = instance.device_description
        app_eui = '123456789101112'
        app_id = 'api-test-app'
        add_device_data = {
            "app_id": app_id,
            "attributes": {
            "key": "",
            "value": ""
            },
            "description": description,
            "dev_id": device_id,
            "lorawan_device": {
                "activation_constraints": "otaa",
                "app_eui": app_eui,
                "app_id": app_id,
                "app_key": app_key,
                "dev_eui": dev_eui,
                "dev_id": device_id
                }
            }

        url = 'https://eu.thethings.network:8094/applications/api-test-app'
        headers = {"Authorization": "Key ttn-account-v2.xxxxxx"}

        response = requests.post('http://eu.thethings.network:8084/applications/api-test-app/devices',
                         data=json.dumps(add_device_data), headers=headers)

Just because you have to use V2 for GW for now does not mean you are stuck with V2 for the App/device registration for new deployments - V2 GW’s pass their traffic through to V3 back end via the PacketBroker so a V2 GW can still service V3 devices and apps wrt traffic - main issue is if the required integration is not available in V3 but only in V2 then that can hold you back…

2 Likes

Thanks a lot for your reply!
I’ve been trying to set up the API for V3 now. The devices do show up in my end-devices panel only the app-key is missing and when I try to connect them it’s being registered as an ABP device.

I used the method recommended by descartes before to have a sneak peak at how the web console registers the devices.

When I’m trying to do a sample post with made up figures (hence they are not hidden) im getting the following error responses:

Printing content of response1: b'{"ids":{"device_id":"test-for-app-key","application_ids":{"application_id":"cttwatermeter"},"dev_eui":"1133113311552216","join_eui":"8866554433221198"},"created_at":"2021-04-29T08:53:27.319Z","updated_at":"2021-04-29T08:53:27.319Z","name":"rikappkeytester","version_ids":{},"network_server_address":"eu1.cloud.thethings.network","application_server_address":"eu1.cloud.thethings.network","join_server_address":"eu1.cloud.thethings.network"}'
web_1  | Printing content of response2: b'{"code":3,"message":"error:pkg/networkserver:field_value (invalid value of field `supports_join`)","details":[{"@type":"type.googleapis.com/ttn.lorawan.v3.ErrorDetails","namespace":"pkg/networkserver","name":"field_value","message_format":"invalid value of field `{field}`","attributes":{"field":"supports_join"},"correlation_id":"036fe97ce040468c8f5bba63a65195b1","code":3}]}'
web_1  | Printing content of response3: b'{"code":3,"message":"error:pkg/applicationserver/redis:read_only_field (read-only field `ids.join_eui`)","details":[{"@type":"type.googleapis.com/ttn.lorawan.v3.ErrorDetails","namespace":"pkg/applicationserver/redis","name":"read_only_field","message_format":"read-only field `{field}`","attributes":{"field":"ids.join_eui"},"correlation_id":"fa23ff4216e44e49964e1e9a6300e05c","code":3}]}'
web_1  | Printing content of response4: b'{"code":3,"message":"error:pkg/joinserver/redis:read_only_field (read-only field `ids.join_eui`)","details":[{"@type":"type.googleapis.com/ttn.lorawan.v3.ErrorDetails","namespace":"pkg/joinserver/redis","name":"read_only_field","message_format":"read-only field `{field}`","attributes":{"field":"ids.join_eui"},"correlation_id":"f7ed038f98544cec817a7beec760002b","code":3}]}'

I’m using the following JSON data to send to the server (which I “stole” from inspecting the browser while creating an end-device):

data1 = {"end_device": {
    "ids": {"device_id": "test-for-app-key", "dev_eui": "1133113311552216", "join_eui": "8866554433221198"},
    "join_server_address": "eu1.cloud.thethings.network", "network_server_address": "eu1.cloud.thethings.network",
    "application_server_address": "eu1.cloud.thethings.network", "name": "rikappkeytester"}, "field_mask": {
    "paths": ["join_server_address", "network_server_address", "application_server_address", "ids.dev_eui",
              "ids.join_eui", "name"]}}

data2 = {"end_device": {"multicast": False, "supports_join": True, "lorawan_version": "MAC_V1_0_2",
                        "ids": {"device_id": "test-for-app-key", "dev_eui": "1133113311552216",
                                "join_eui": "8866554433221198"}, "mac_settings": {"supports_32_bit_f_cnt": True},
                        "supports_class_c": False, "supports_class_b": False,
                        "lorawan_phy_version": "PHY_V1_0_2_REV_B", "frequency_plan_id": "EU_863_870_TTN"},
         "field_mask": {"paths": ["multicast", "supports_join", "lorawan_version", "ids.device_id", "ids.dev_eui",
                                  "ids.join_eui", "mac_settings.supports_32_bit_f_cnt", "supports_class_c",
                                  "supports_class_b", "lorawan_phy_version", "frequency_plan_id"]}}

data3 = {"end_device": {
    "ids": {"device_id": "test-for-app-key", "dev_eui": "1133113311552216", "join_eui": "8866554433221198"}},
         "field_mask": {"paths": ["ids.device_id", "ids.dev_eui", "ids.join_eui"]}}

data4 = {"end_device": {
    "ids": {"device_id": "test-for-app-key", "dev_eui": "1133113311552216", "join_eui": "8866554433221198"},
    "network_server_address": "eu1.cloud.thethings.network",
    "application_server_address": "eu1.cloud.thethings.network", "network_server_kek_label": "",
    "application_server_kek_label": "", "application_server_id": "", "net_id": None,
    "root_keys": {"app_key": {"key": "7138710500000000713871051F500739"}}}, "field_mask": {
    "paths": ["network_server_address", "application_server_address", "ids.device_id", "ids.dev_eui",
              "ids.join_eui", "network_server_kek_label", "application_server_kek_label", "application_server_id",
              "net_id", "root_keys.app_key.key"]}}

url1 = 'https://eu1.cloud.thethings.network/api/v3/applications/cttwatermeter/devices'
url2 = 'https://eu1.cloud.thethings.network/api/v3/ns/applications/cttwatermeter/devices/tester2'
url3 = 'https://eu1.cloud.thethings.network/api/v3/as/applications/cttwatermeter/devices/tester2'
url4 = 'https://eu1.cloud.thethings.network/api/v3/js/applications/cttwatermeter/devices/tester2'

headers = {
    "Authorization": "Bearer NNSXS.xxxx",
    'Content-Type': 'application/json'}

response1 = requests.post(url1, data=json.dumps(data1), headers=headers)
print(f'Printing content of response1: {response1.content}')
response2 = requests.put(url2, data=json.dumps(data2), headers=headers)
print(f'Printing content of response2: {response2.content}')
response3 = requests.put(url3, data=json.dumps(data3), headers=headers)
print(f'Printing content of response3: {response3.content}')
response4 = requests.put(url4, data=json.dumps(data4), headers=headers)
print(f'Printing content of response4: {response4.content}')

what am I doing wrong? I have the feeling something in these field_masks is going wrong but I’m not sure where to correct it and how. If I remove the field masks I dont get an error but then my device is registered without the app key

I think the field mask should be passed as comma-separated string in the query part of the URL, so the part after the question mark (’?’), like:

https://eu1.cloud.thethings.network/api/v3/applications/cttwatermeter/devices?field_mask=network_server_address,application_server_address,…

Thanks once again for your quick reply but I’m still getting the same error.

When I try to post the join server request for example:

{"end_device": {
     "ids": {"device_id": "test-for-app-key", "dev_eui": "1133113311552216", "join_eui": "8866554433221198"},
    "network_server_address": "eu1.cloud.thethings.network",
    "application_server_address": "eu1.cloud.thethings.network", "network_server_kek_label": "",
    "application_server_kek_label": "", "application_server_id": "", "net_id": None,
    "root_keys": {"app_key": {"key": "7138710500000000713871051F500739"}}}, "field_mask": {
    "paths": ["network_server_address", "application_server_address", "ids.device_id", "ids.dev_eui",
              "ids.join_eui", "network_server_kek_label", "application_server_kek_label",
              "application_server_id",
              "net_id", "root_keys.app_key.key"]}}

It’s giving the following error:

 {
    "code": 3,
    "message": "error:pkg/joinserver/redis:read_only_field (read-only field `ids.join_eui`)",
"details": [{
    "@type": "type.googleapis.com/ttn.lorawan.v3.ErrorDetails",
    "namespace": "pkg/joinserver/redis",
    "name": "read_only_field",
    "message_format": "read-only field `{field}`",
    "attributes": {
        "field": "ids.join_eui"
    },
    "correlation_id": "10267a7f2c8943509c2a180ee428cb3d",
    "code": 3
}]
}

It’s complaining about that those field masks fields are read-only fields? Sorry for bothering once again

Very weird!
When I changed the request method from PUT to POST for the JS, NS, and ASEndRegsitryDevice everything seems to work straight away! I guess it’s solved now!

As far as I remember, in REST, you typically use a POST to create a new object, and you use a PUT to modify/update an existing object. The field mask indicates which fields exactly you are updating.

2 Likes

Turns out I was WRONG (regarding the field mast)! So please disregard my earlier message.

Just carefully read the documentation at API | The Things Stack for LoRaWAN and, as suggested earlier, it helps a lot to use the inspect view of your browser while you execute the actions in the console.