Skip to content

Commit ca15fb3

Browse files
author
Kevin J Walters
committed
Changing the advertisements used by Advanced RPS Game to have manufacturer data declared and set in the desired serialization order.
Changing the id numbers on manufacturer data in the advertisements to use a broadly sequential set. Removing unused RPS_VERSION. adafruit#1185
1 parent 60b97cc commit ca15fb3

File tree

2 files changed

+161
-31
lines changed

2 files changed

+161
-31
lines changed

CLUE_Rock_Paper_Scissors/advanced/rps_advertisements.py

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
# the protocol and a descriptor for the encryption type
3030

3131
# From adafruit_ble.advertising
32+
# 0xFF is "Manufacturer Specific Data" as per list of types in
33+
# https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile/
3234
MANUFACTURING_DATA_ADT = 0xFF
3335
ADAFRUIT_COMPANY_ID = 0x0822
3436

@@ -38,16 +40,14 @@
3840

3941
# According to https://github.com/adafruit/Adafruit_CircuitPython_BLE/blob/master/adafruit_ble/advertising/adafruit.py
4042
# 0xf000 (to 0xffff) is for range for Adafruit customers
41-
GM_JOIN_ID = 0xfe30
42-
RPS_VERSION = 0xff30
43-
RPS_ROUND_ID = 0xff74
44-
RPS_ENC_DATA_ID = 0xff34
45-
RPS_KEY_DATA_ID = 0xff54
46-
RPS_ACK_ID = 0xff52
47-
# These ID numbers have all been carefully selected to obtain a particular
48-
# ordering of the fields within the ManufacturerData based on the current
49-
# dict key ordering - this is bad practice and FRAGILE as the prefix
50-
# matching falls apart if this changes
43+
44+
# These four are used as part of prefix matching
45+
RPS_ENC_DATA_ID = 0xfe41
46+
RPS_KEY_DATA_ID = 0xfe42
47+
RPS_ROUND_ID = 0xfe43
48+
GM_JOIN_ID = 0xfe44
49+
50+
RPS_ACK_ID = 0xfe51
5151

5252
# Data formats for shared fields
5353
_DATA_FMT_ROUND = "B"
@@ -85,25 +85,24 @@ class RpsEncDataAdvertisement(Advertisement):
8585
key_encoding="<H"
8686
)
8787

88-
sequence_number = ManufacturerDataField(ADAFRUIT_SEQ_ID, "<" + _SEQ_FMT)
89-
"""Sequence number of the data. Used in acknowledgements."""
90-
9188
enc_data = ManufacturerDataField(RPS_ENC_DATA_ID, "<" + _DATA_FMT_ENC_DATA)
9289
round_no = ManufacturerDataField(RPS_ROUND_ID, "<" + _DATA_FMT_ROUND)
90+
sequence_number = ManufacturerDataField(ADAFRUIT_SEQ_ID, "<" + _SEQ_FMT)
91+
"""Sequence number of the data. Used in acknowledgements."""
9392
ack = ManufacturerDataField(RPS_ACK_ID, "<" + _DATA_FMT_ACK)
9493
"""Round number starting at 1."""
9594

96-
def __init__(self, *, enc_data=None, round_no=None, ack=None, sequence_number=None):
97-
"""ack must be set to () to send this optional, data-less field."""
95+
def __init__(self, *, enc_data=None, round_no=None, sequence_number=None, ack=None):
96+
"""enc_data must be either set here in the constructor or set first."""
9897
super().__init__()
9998
if enc_data is not None:
10099
self.enc_data = enc_data
101100
if round_no is not None:
102101
self.round_no = round_no
103-
if ack is not None:
104-
self.ack = ack
105102
if sequence_number is not None:
106103
self.sequence_number = sequence_number
104+
if ack is not None:
105+
self.ack = ack
107106

108107

109108
class RpsKeyDataAdvertisement(Advertisement):
@@ -136,25 +135,24 @@ class RpsKeyDataAdvertisement(Advertisement):
136135
key_encoding="<H"
137136
)
138137

139-
sequence_number = ManufacturerDataField(ADAFRUIT_SEQ_ID, "<" + _SEQ_FMT)
140-
"""Sequence number of the data. Used in acknowledgements."""
141-
142138
key_data = ManufacturerDataField(RPS_KEY_DATA_ID, "<" + _DATA_FMT_KEY_DATA)
143139
round_no = ManufacturerDataField(RPS_ROUND_ID, "<" + _DATA_FMT_ROUND)
140+
sequence_number = ManufacturerDataField(ADAFRUIT_SEQ_ID, "<" + _SEQ_FMT)
141+
"""Sequence number of the data. Used in acknowledgements."""
144142
ack = ManufacturerDataField(RPS_ACK_ID, "<" + _DATA_FMT_ACK)
145143
"""Round number starting at 1."""
146144

147-
def __init__(self, *, key_data=None, round_no=None, ack=None, sequence_number=None):
148-
"""ack must be set to () to send this optional, data-less field."""
145+
def __init__(self, *, key_data=None, round_no=None, sequence_number=None, ack=None):
146+
"""key_data must be either set here in the constructor or set first."""
149147
super().__init__()
150148
if key_data is not None:
151149
self.key_data = key_data
152150
if round_no is not None:
153151
self.round_no = round_no
154-
if ack is not None:
155-
self.ack = ack
156152
if sequence_number is not None:
157153
self.sequence_number = sequence_number
154+
if ack is not None:
155+
self.ack = ack
158156

159157

160158
class RpsRoundEndAdvertisement(Advertisement):
@@ -187,22 +185,21 @@ class RpsRoundEndAdvertisement(Advertisement):
187185
key_encoding="<H"
188186
)
189187

188+
round_no = ManufacturerDataField(RPS_ROUND_ID, "<" + _DATA_FMT_ROUND)
190189
sequence_number = ManufacturerDataField(ADAFRUIT_SEQ_ID, "<" + _SEQ_FMT)
191190
"""Sequence number of the data. Used in acknowledgements."""
192-
193-
round_no = ManufacturerDataField(RPS_ROUND_ID, "<" + _DATA_FMT_ROUND)
194191
ack = ManufacturerDataField(RPS_ACK_ID, "<" + _DATA_FMT_ACK)
195192
"""Round number starting at 1."""
196193

197-
def __init__(self, *, round_no=None, ack=None, sequence_number=None):
198-
"""ack must be set to () to send this optional, data-less field."""
194+
def __init__(self, *, round_no=None, sequence_number=None, ack=None):
195+
"""round_no must be either set here in the constructor or set first."""
199196
super().__init__()
200197
if round_no is not None:
201198
self.round_no = round_no
202-
if ack is not None:
203-
self.ack = ack
204199
if sequence_number is not None:
205200
self.sequence_number = sequence_number
201+
if ack is not None:
202+
self.ack = ack
206203

207204

208205
class JoinGameAdvertisement(Advertisement):
@@ -235,7 +232,7 @@ class JoinGameAdvertisement(Advertisement):
235232
)
236233

237234
game = ManufacturerDataField(GM_JOIN_ID, "<" + _DATA_FMT)
238-
"""RPS choice."""
235+
"""The name of the game, limited to eight characters."""
239236

240237
def __init__(self, *, game=None):
241238
super().__init__()
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2020 Kevin J. Walters
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
23+
import sys
24+
import os
25+
26+
import unittest
27+
from unittest.mock import MagicMock
28+
29+
verbose = int(os.getenv('TESTVERBOSE', '2'))
30+
31+
# PYTHONPATH needs to be set to find adafruit_ble
32+
33+
# Mocking library used by adafruit_ble
34+
sys.modules['_bleio'] = MagicMock()
35+
36+
# Borrowing the dhalbert/tannewt technique from adafruit/Adafruit_CircuitPython_Motor
37+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
38+
39+
# import what we are testing or will test in future
40+
# pylint: disable=unused-import,wrong-import-position
41+
from rps_advertisements import JoinGameAdvertisement, \
42+
RpsEncDataAdvertisement, \
43+
RpsKeyDataAdvertisement, \
44+
RpsRoundEndAdvertisement
45+
46+
# pylint: disable=line-too-long
47+
48+
class Test_RpsEncDataAdvertisement(unittest.TestCase):
49+
50+
def test_bytes_order(self):
51+
"""Testing the order of data inside the manufacturer's field to ensure it follows the
52+
fields are set in. This is new behaviour to benefit prefix matching."""
53+
54+
rpsedad1 = RpsEncDataAdvertisement(enc_data=b"FIRST", round_no=33, sequence_number=17)
55+
56+
# This checks value is not the old incorrect order
57+
self.assertNotEqual(bytes(rpsedad1),
58+
b"\x16\xff\x22\x08\x03\x03\x00\x11\nA\xfeFIRST\x00\x00\x00\x03C\xfe\x21",
59+
msg="Checking order of serialised data for"
60+
" ackless RpsEncDataAdvertisement does"
61+
" not follow previous incorrect order")
62+
63+
# This check for correct order
64+
self.assertEqual(bytes(rpsedad1),
65+
b"\x16\xff\x22\x08\x0a\x41\xfeFIRST\x00\x00\x00\x03C\xfe\x21\x03\x03\x00\x11",
66+
msg="Checking order of serialised data for"
67+
" ackless RpsEncDataAdvertisement")
68+
69+
rpsedad1.ack = 29
70+
self.assertEqual(bytes(rpsedad1),
71+
b"\x1a\xff\x22\x08\nA\xfeFIRST\x00\x00\x00\x03C\xfe!\x03\x03\x00\x11\x03Q\xfe\x1d",
72+
msg="Checking order of serialised data for"
73+
" RpsEncDataAdvertisement with ack set post construction")
74+
75+
76+
class Test_RpsKeyDataAdvertisement(unittest.TestCase):
77+
78+
def test_bytes_order(self):
79+
"""Testing the order of data inside the manufacturer's field to ensure it follows the
80+
fields are set in. This is new behaviour to benefit prefix matching."""
81+
82+
rpskdad1 = RpsKeyDataAdvertisement(key_data=b"FIRST", round_no=33, sequence_number=17)
83+
84+
# This checks value is not the old incorrect order
85+
self.assertNotEqual(bytes(rpskdad1),
86+
b"\x16\xff\x22\x08\x03\x03\x00\x11\nB\xfeFIRST\x00\x00\x00\x03C\xfe\x21",
87+
msg="Checking order of serialised data for"
88+
" ackless RpsKeyDataAdvertisement does"
89+
" not follow previous incorrect order")
90+
91+
# This check for correct order
92+
self.assertEqual(bytes(rpskdad1),
93+
b"\x16\xff\x22\x08\x0a\x42\xfeFIRST\x00\x00\x00\x03C\xfe\x21\x03\x03\x00\x11",
94+
msg="Checking order of serialised data for"
95+
" ackless RpsKeyDataAdvertisement")
96+
97+
rpskdad1.ack = 29
98+
self.assertEqual(bytes(rpskdad1),
99+
b"\x1a\xff\x22\x08\nB\xfeFIRST\x00\x00\x00\x03C\xfe!\x03\x03\x00\x11\x03Q\xfe\x1d",
100+
msg="Checking order of serialised data for"
101+
" RpsKeyDataAdvertisement with ack set post construction")
102+
103+
104+
class Test_RpsRoundEndAdvertisement(unittest.TestCase):
105+
106+
def test_bytes_order(self):
107+
"""Testing the order of data inside the manufacturer's field to ensure it follows the
108+
fields are set in. This is new behaviour to benefit prefix matching."""
109+
110+
rpsread1 = RpsRoundEndAdvertisement(round_no=133, sequence_number=201)
111+
112+
# This checks value is not the old incorrect order
113+
self.assertNotEqual(bytes(rpsread1),
114+
b"\x0b\xff\x22\x08\x03\x03\x00\xc9\x03C\xfe\x85",
115+
msg="Checking order of serialised data for"
116+
" ackless RpsRoundEndAdvertisement does"
117+
" not follow previous incorrect order")
118+
119+
# This check for correct order
120+
self.assertEqual(bytes(rpsread1),
121+
b"\x0b\xff\x22\x08\x03C\xfe\x85\x03\x03\x00\xc9",
122+
msg="Checking order of serialised data for"
123+
" ackless RpsRoundEndAdvertisement")
124+
125+
rpsread1.ack = 200
126+
self.assertEqual(bytes(rpsread1),
127+
b"\x0f" b"\xff\x22\x08\x03C\xfe\x85\x03\x03\x00\xc9" b"\x03Q\xfe\xc8",
128+
msg="Checking order of serialised data for"
129+
" RpsRoundEndAdvertisement with ack set post construction")
130+
131+
132+
if __name__ == '__main__':
133+
unittest.main(verbosity=verbose)

0 commit comments

Comments
 (0)