Skip to main content
👋 Hello Nanoleaf fan, lovely to see you! Need to submit a ticket? Click here to contact us! 💚

Shapes touch events?

Comments

5 comments

  • Matt

    Ok, I've made some progress. There is a UDP API, and I do receive data via it when I tap my Hexagon. Here's my code:

    #!python3


     

    import requests

    import socket

    import threading

    import struct

    from sys import exit


     

    HOSTNAME='[REDACTED]'

    PORT=16021

    TOKEN='[REDACTED]'

    TOUCH_EVENTS_PORT=12345

    INADDR_ANY=''


     

    def dump_request(request):

        print('%s %s' % (request.method, request.url))

        for header_name, header_value in request.headers.items():

            print('%s: %s' % (header_name, header_value))

        if request.body:

            print(request.body)


     

    keep_listening = True


     

    EVENT_SIZE=5


     

    def listen_udp_touch_events(port):

        with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:

            sock.bind((INADDR_ANY, port))

            print(f"Listening for UDP packets on %d..." % port)

            while keep_listening:

                data, addr = sock.recvfrom(4096)

                # print("Received %d bytes from %s:" % (len(data), addr))

                print(data)

                # num_events as big-endian uint16_t

                num_events = struct.unpack_from('>h', data, 0)[0]

                # print("%d packet%s" % (num_events, '' if num_events == 1 else 's'))

                # Then num_events 5-byte event "packets"

                for event_index in range(num_events):

                    event_offset = 2 + event_index * EVENT_SIZE

                    event_data = data[event_offset:event_offset + EVENT_SIZE]

                    # Event packet begins with panel ID as big-endian uint16_t

                    panel_id = struct.unpack_from('>H', event_data, 0)[0]

                    # Then there is a 8-bit bitset whose documentation is not correct.

                    # It claims that 4 different possibilities can be encoded in a single bit,

                    # which is a mathematical impossibility. It probably got corrupted somehow.

                    touch_data = struct.unpack_from('=B', event_data, 2)[0]

                    # Finally, a big-endian uint16_t that is the panel ID that was swiped from,

                    # if the event is a swipe, or 0xFFFF if the event is not a swipe

                    swiped_from_panel_id = struct.unpack_from('>H', event_data, 3)[0]

                    if swiped_from_panel_id != 65535:

                        raise ValueError("Swiped from panel ID is %d not 16 as expected" % swiped_from_panel_id)

                    print("Panel ID %d, touch_data %d" % (panel_id, touch_data))


     

    class Shapes:


     

        def __init__(self, hostname, port, token):

            self.hostname = hostname

            self.port = port

            self.token = token

            self.base_url = 'http://%s:%d/api/v1/%s' % (self.hostname, self.port, self.token)


     

        def do_put(self, url_extra, payload):

            response = requests.put(url=self.base_url + url_extra, headers={'Content-Type': 'application/json'}, json=payload)

            # dump_request(response.request)

            response.raise_for_status()

            if response.status_code == 204:

                json_response = None

            else:

                json_response = response.json()

            return json_response


     

        def do_get(self, url_extra, parameters=None):

            response = requests.get(url=self.base_url + url_extra, headers={'Content-Type': 'application/json'}, params=parameters)

            dump_request(response.request)

            response.raise_for_status()

            if response.status_code == 204:

                json_response = None

            else:

                json_response = response.json()

            return json_response


     

        def do_streaming_get(self, url_extra, parameters=None):

            session = requests.Session()

            with session.get(

                url=self.base_url + url_extra,

                headers={

                    'Content-Type': 'application/json',

                    'TouchEventsPort': str(TOUCH_EVENTS_PORT)

                },

                params=parameters, stream=True

            ) as response:

                dump_request(response.request)

                response.raise_for_status()

                for chunk in response.iter_content():

                    if chunk:

                        yield chunk


     

        def set_power(self, new_power_state):

            self.do_put('/state', {"on": {"value": new_power_state}})


     

        def get_power(self):

            response = self.do_get('/state/on')

            return response['value']


     

        def get_touch_events(self):

            udp_thread = threading.Thread(target=listen_udp_touch_events, args=(TOUCH_EVENTS_PORT,), daemon=True)

            udp_thread.start()

            try:

                # Cannot use requests; "parameters" value here, as that will result in the commands being URL-encoded,

                # and the NanoLeaf Shapes don't understand that.

                for chunk in self.do_streaming_get('/events?id=1,2,3,4'):

                    print('Got chunk:')

                    print(chunk)

            except KeyboardInterrupt:

                pass

            global keep_listening

            keep_listening = False

            # udp_thread.join()


     

    def control_shapes(hostname, port, token):

        shapes = Shapes(hostname, port, token)

        shapes.set_power(True)

        shapes.get_touch_events()


     

    if __name__ == "__main__":

        control_shapes(HOSTNAME, PORT, TOKEN)

        exit(0)


     

    0
  • Matt

    My last remaining problem is the meaning of the “Touch Type and Touch Strength Byte”.

    The documentation says nothing about bits 3 to 7.

    Then it says that bit 2 is reserved.

    Then it says that bit 1 is the touch type, and bit 0 is the touch strength.

    But immediately below, it says that touch type values range from 0 to 4 (inclusive).

    This is mathematically impossible. There is no way to fit 5 possible values for “touch type” into a single bit.

    The most you can store in a single bit is 2 possibilities: 0 and 1.

    Nanoleaf R&D - can you please explain the meaning of this bitfield?

    Best if you correct the documentation.

    I think the problem may be that the table has got corrupted somehow. So maybe it would be better to explain in words, for example “the foo is stored in the high-order 3 bits of the byte”.

    0
  • Matt

    Oh also, the part where the documentation says that only Canvas supports touch events should be removed. They work on Shapes too, you just need to use the UDP API as well as the pseudo-RESTful API.

    0
  • Matt

    Heya Poppy , wondering if you have any response to the above?

    0
  • Engineering team @ Nanoleaf

    Hi Matt, 

    Thanks for bringing this to our attention. 
    I fixed the table for Touch Type and Touch Strength Byte.

    Regards
    Aliakbar Eski

    0

Please sign in to leave a comment.