Shapes touch events?
Hi, I'm writing software in Python to control my Nanoleaf Shapes over their HTTP API.
I would like to receive touch events in my software, so I can do things like turn the installation into a giant music keyboard/synthesiser/sequencer. Or ruggedise the shapes somehow, then dance on them and have them change colour when you step on them. There are lots of possibilities!
I'm receiving layout events (Server-Sent Events) over the HTTP API, but not touch events.
The API documentation says that only Canvas supports touch events “at this time”.
Is there a way to receive touch events from Shapes?
Some people on other web sites mention a UDP API that permits this. That would be great (even better than HTTP).
Alternatively, is there a way to load code of my choice onto the Shapes Controller, and handle the touch events there?
Thanks,
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:
#!python3import requestsimport socketimport threadingimport structfrom sys import exitHOSTNAME='[REDACTED]'PORT=16021TOKEN='[REDACTED]'TOUCH_EVENTS_PORT=12345INADDR_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 = TrueEVENT_SIZE=5def 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_tnum_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_SIZEevent_data = data[event_offset:event_offset + EVENT_SIZE]# Event packet begins with panel ID as big-endian uint16_tpanel_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 swipeswiped_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 = hostnameself.port = portself.token = tokenself.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 = Noneelse:json_response = response.json()return json_responsedef 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 = Noneelse:json_response = response.json()return json_responsedef 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 chunkdef 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:passglobal keep_listeningkeep_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 -
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 -
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 -
Heya Poppy , wondering if you have any response to the above?
0 -
Hi Matt,
Thanks for bringing this to our attention.
I fixed the table for Touch Type and Touch Strength Byte.Regards
Aliakbar Eski0
Please sign in to leave a comment.
Comments
5 comments