test_config.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. # python-cc1101 - Python Library to Transmit RF Signals via C1101 Transceivers
  2. #
  3. # Copyright (C) 2020 Fabian Peter Hammerle <fabian@hammerle.me>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. import unittest.mock
  18. import warnings
  19. import pytest
  20. import cc1101
  21. from cc1101.options import PacketLengthMode, SyncMode
  22. # pylint: disable=protected-access
  23. @pytest.mark.parametrize(
  24. ("xfer_return_value", "sync_word"),
  25. [([64, 211, 145], b"\xd3\x91"), ([64, 0, 0], b"\0\0")],
  26. )
  27. def test_get_sync_word(transceiver, xfer_return_value, sync_word):
  28. transceiver._spi.xfer.return_value = xfer_return_value
  29. assert transceiver.get_sync_word() == sync_word
  30. transceiver._spi.xfer.assert_called_once_with([0x04 | 0xC0, 0, 0])
  31. def test_set_sync_word(transceiver):
  32. transceiver._spi.xfer.return_value = [15, 15, 15]
  33. transceiver.set_sync_word(b"\x12\x34")
  34. transceiver._spi.xfer.assert_called_once_with([0x04 | 0x40, 0x12, 0x34])
  35. @pytest.mark.parametrize("sync_word", [b"", b"\0", "\x12\x34\x56"])
  36. def test_set_sync_word_invalid_length(transceiver, sync_word):
  37. with pytest.raises(ValueError, match=r"\bexpected two bytes\b"):
  38. transceiver.set_sync_word(sync_word)
  39. _FREQUENCY_CONTROL_WORD_HERTZ_PARAMS = [
  40. ([0x10, 0xA7, 0x62], 433000000),
  41. ([0x10, 0xAB, 0x85], 433420000),
  42. ([0x10, 0xB1, 0x3B], 434000000),
  43. ([0x21, 0x62, 0x76], 868000000),
  44. ]
  45. @pytest.mark.parametrize(
  46. ("control_word", "hertz"), _FREQUENCY_CONTROL_WORD_HERTZ_PARAMS
  47. )
  48. def test__frequency_control_word_to_hertz(control_word, hertz):
  49. assert cc1101.CC1101._frequency_control_word_to_hertz(
  50. control_word
  51. ) == pytest.approx(hertz, abs=200)
  52. @pytest.mark.parametrize(
  53. ("control_word", "hertz"), _FREQUENCY_CONTROL_WORD_HERTZ_PARAMS
  54. )
  55. def test__hertz_to_frequency_control_word(control_word, hertz):
  56. assert cc1101.CC1101._hertz_to_frequency_control_word(hertz) == control_word
  57. _FILTER_BANDWIDTH_MANTISSA_EXPONENT_REAL_PARAMS = [
  58. # > The default values give 203 kHz channel filter bandwidth,
  59. # > assuming a 26.0 MHz crystal.
  60. (0, 2, 203e3),
  61. # "Table 26: Channel Filter Bandwidths [kHz] (assuming a 26 MHz crystal)"
  62. (0, 0, 812e3),
  63. (0, 1, 406e3),
  64. (0, 2, 203e3),
  65. (1, 0, 650e3),
  66. (1, 1, 325e3),
  67. (3, 0, 464e3),
  68. (3, 1, 232e3),
  69. (3, 2, 116e3),
  70. (3, 3, 58e3),
  71. ]
  72. @pytest.mark.parametrize(
  73. ("mantissa", "exponent", "real"), _FILTER_BANDWIDTH_MANTISSA_EXPONENT_REAL_PARAMS
  74. )
  75. def test__filter_bandwidth_floating_point_to_real(mantissa, exponent, real):
  76. assert cc1101.CC1101._filter_bandwidth_floating_point_to_real(
  77. mantissa=mantissa, exponent=exponent
  78. ) == pytest.approx(real, rel=1e-3)
  79. @pytest.mark.parametrize(
  80. ("mdmcfg4", "real"),
  81. [
  82. (0b10001100, 203e3),
  83. (0b10001010, 203e3),
  84. (0b10001110, 203e3),
  85. (0b11111100, 58e3),
  86. (0b01011100, 325e3),
  87. ],
  88. )
  89. def test__get_filter_bandwidth_hertz(transceiver, mdmcfg4, real):
  90. transceiver._spi.xfer.return_value = [15, mdmcfg4]
  91. assert transceiver._get_filter_bandwidth_hertz() == pytest.approx(real, rel=1e-3)
  92. transceiver._spi.xfer.assert_called_once_with([0x10 | 0x80, 0])
  93. @pytest.mark.parametrize(
  94. ("mdmcfg4_before", "mdmcfg4_after", "exponent", "mantissa"),
  95. [
  96. (0b00001010, 0b10111010, 0b10, 0b11),
  97. (0b00001100, 0b01001100, 0b01, 0b00),
  98. (0b00001100, 0b10111100, 0b10, 0b11),
  99. (0b00001100, 0b11011100, 0b11, 0b01),
  100. (0b01011100, 0b11011100, 0b11, 0b01),
  101. (0b11111100, 0b11011100, 0b11, 0b01),
  102. ],
  103. )
  104. def test__set_filter_bandwidth(
  105. transceiver, mdmcfg4_before, mdmcfg4_after, exponent, mantissa
  106. ):
  107. transceiver._spi.xfer.return_value = [15, 15]
  108. with unittest.mock.patch.object(
  109. transceiver, "_read_single_byte", return_value=mdmcfg4_before
  110. ):
  111. transceiver._set_filter_bandwidth(mantissa=mantissa, exponent=exponent)
  112. transceiver._spi.xfer.assert_called_once_with([0x10 | 0x40, mdmcfg4_after])
  113. @pytest.mark.parametrize(
  114. ("mdmcfg4", "symbol_rate_exponent"), [(0b1001100, 12), (0b10011001, 9)]
  115. )
  116. def test__get_symbol_rate_exponent(transceiver, mdmcfg4, symbol_rate_exponent):
  117. transceiver._spi.xfer.return_value = [15, mdmcfg4]
  118. assert transceiver._get_symbol_rate_exponent() == symbol_rate_exponent
  119. transceiver._spi.xfer.assert_called_once_with([0x10 | 0x80, 0])
  120. @pytest.mark.parametrize(
  121. ("mdmcfg3", "symbol_rate_mantissa"), [(0b00100010, 34), (0b10101010, 170)]
  122. )
  123. def test__get_symbol_rate_mantissa(transceiver, mdmcfg3, symbol_rate_mantissa):
  124. transceiver._spi.xfer.return_value = [15, mdmcfg3]
  125. assert transceiver._get_symbol_rate_mantissa() == symbol_rate_mantissa
  126. transceiver._spi.xfer.assert_called_once_with([0x11 | 0x80, 0])
  127. _SYMBOL_RATE_MANTISSA_EXPONENT_REAL_PARAMS = [
  128. # > The default values give a data rate of 115.051 kBaud
  129. # > (closest setting to 115.2 kBaud), assuming a 26.0 MHz crystal.
  130. (34, 12, 115051),
  131. (34, 12 + 1, 115051 * 2),
  132. (34, 12 - 1, 115051 / 2),
  133. ]
  134. @pytest.mark.parametrize(
  135. ("mantissa", "exponent", "real"), _SYMBOL_RATE_MANTISSA_EXPONENT_REAL_PARAMS
  136. )
  137. def test__symbol_rate_floating_point_to_real(mantissa, exponent, real):
  138. assert cc1101.CC1101._symbol_rate_floating_point_to_real(
  139. mantissa=mantissa, exponent=exponent
  140. ) == pytest.approx(real, rel=1e-5)
  141. @pytest.mark.parametrize(
  142. ("mantissa", "exponent", "real"), _SYMBOL_RATE_MANTISSA_EXPONENT_REAL_PARAMS
  143. )
  144. def test__symbol_rate_real_to_floating_point(mantissa, exponent, real):
  145. assert cc1101.CC1101._symbol_rate_real_to_floating_point(real) == (
  146. mantissa,
  147. exponent,
  148. )
  149. @pytest.mark.parametrize(
  150. ("mdmcfg2", "sync_mode"),
  151. [
  152. (0b00000000, SyncMode.NO_PREAMBLE_AND_SYNC_WORD),
  153. (0b00000001, SyncMode.TRANSMIT_16_MATCH_15_BITS),
  154. (0b00000010, SyncMode.TRANSMIT_16_MATCH_16_BITS),
  155. (0b00000011, SyncMode.TRANSMIT_32_MATCH_30_BITS),
  156. (0b00000110, SyncMode.TRANSMIT_16_MATCH_16_BITS),
  157. (0b00000111, SyncMode.TRANSMIT_32_MATCH_30_BITS),
  158. (0b00001100, SyncMode.NO_PREAMBLE_AND_SYNC_WORD),
  159. (0b01101011, SyncMode.TRANSMIT_32_MATCH_30_BITS),
  160. (0b01101111, SyncMode.TRANSMIT_32_MATCH_30_BITS),
  161. ],
  162. )
  163. def test_get_sync_mode(transceiver, mdmcfg2, sync_mode):
  164. transceiver._spi.xfer.return_value = [15, mdmcfg2]
  165. assert transceiver.get_sync_mode() == sync_mode
  166. transceiver._spi.xfer.assert_called_once_with([0x12 | 0x80, 0])
  167. @pytest.mark.parametrize(
  168. ("mdmcfg2_before", "mdmcfg2_after", "sync_mode", "threshold_enabled"),
  169. [
  170. (0b00000010, 0b00000000, SyncMode.NO_PREAMBLE_AND_SYNC_WORD, None),
  171. (0b00000010, 0b00000001, SyncMode.TRANSMIT_16_MATCH_15_BITS, None),
  172. (0b00000010, 0b00000010, SyncMode.TRANSMIT_16_MATCH_16_BITS, None),
  173. (0b00000010, 0b00000011, SyncMode.TRANSMIT_32_MATCH_30_BITS, None),
  174. (0b01101110, 0b01101111, SyncMode.TRANSMIT_32_MATCH_30_BITS, None),
  175. (0b00000010, 0b00000110, SyncMode.TRANSMIT_16_MATCH_16_BITS, True),
  176. (0b00000010, 0b00000111, SyncMode.TRANSMIT_32_MATCH_30_BITS, True),
  177. (0b01101110, 0b01101111, SyncMode.TRANSMIT_32_MATCH_30_BITS, True),
  178. (0b00000010, 0b00000010, SyncMode.TRANSMIT_16_MATCH_16_BITS, False),
  179. (0b00000010, 0b00000011, SyncMode.TRANSMIT_32_MATCH_30_BITS, False),
  180. (0b01101110, 0b01101011, SyncMode.TRANSMIT_32_MATCH_30_BITS, False),
  181. ],
  182. )
  183. def test_set_sync_mode(
  184. transceiver, mdmcfg2_before, mdmcfg2_after, sync_mode, threshold_enabled
  185. ):
  186. transceiver._spi.xfer.return_value = [15, 15]
  187. with unittest.mock.patch.object(
  188. transceiver, "_read_single_byte", return_value=mdmcfg2_before
  189. ):
  190. transceiver.set_sync_mode(
  191. sync_mode, _carrier_sense_threshold_enabled=threshold_enabled
  192. )
  193. transceiver._spi.xfer.assert_called_once_with([0x12 | 0x40, mdmcfg2_after])
  194. @pytest.mark.parametrize(
  195. ("mdmcfg1", "length"),
  196. [
  197. (0b00000010, 2),
  198. (0b00010010, 3),
  199. (0b00100010, 4),
  200. (0b00110010, 6),
  201. (0b01000010, 8),
  202. (0b01010010, 12),
  203. (0b01100010, 16),
  204. (0b01110010, 24),
  205. ],
  206. )
  207. def test_get_preamble_length_bytes(transceiver, mdmcfg1, length):
  208. transceiver._spi.xfer.return_value = [0, mdmcfg1]
  209. assert transceiver.get_preamble_length_bytes() == length
  210. transceiver._spi.xfer.assert_called_once_with([0x13 | 0x80, 0])
  211. @pytest.mark.parametrize(
  212. ("mdmcfg1_before", "mdmcfg1_after", "length"),
  213. [
  214. (0b00000010, 0b00000010, 2),
  215. (0b00000010, 0b00010010, 3),
  216. (0b00000010, 0b00100010, 4),
  217. (0b00000010, 0b00110010, 6),
  218. (0b00000010, 0b01000010, 8),
  219. (0b00000010, 0b01010010, 12),
  220. (0b00000010, 0b01100010, 16),
  221. (0b00000010, 0b01110010, 24),
  222. (0b01010010, 0b01100010, 16),
  223. (0b01110010, 0b00000010, 2),
  224. (0b01110010, 0b01000010, 8),
  225. (0b11011010, 0b11101010, 16),
  226. (0b11110111, 0b11000111, 8),
  227. (0b11111110, 0b10001110, 2),
  228. ],
  229. )
  230. def test_set_preamble_length_bytes(transceiver, mdmcfg1_before, mdmcfg1_after, length):
  231. transceiver._spi.xfer.return_value = [15, 15]
  232. with unittest.mock.patch.object(
  233. transceiver, "_read_single_byte", return_value=mdmcfg1_before
  234. ):
  235. transceiver.set_preamble_length_bytes(length)
  236. transceiver._spi.xfer.assert_called_once_with([0x13 | 0x40, mdmcfg1_after])
  237. @pytest.mark.parametrize(
  238. "length", [-21, 0, 1, 5, 7, 9, 10, 11, 13, 14, 15, 17, 20, 23, 25, 32, 42]
  239. )
  240. def test_set_preamble_length_bytes_invalid(transceiver, length):
  241. with pytest.raises(ValueError, match=r"\bpreamble length\b"):
  242. transceiver.set_preamble_length_bytes(length)
  243. def test_get_packet_length_bytes(transceiver):
  244. xfer_mock = transceiver._spi.xfer
  245. xfer_mock.return_value = [0, 8]
  246. assert transceiver.get_packet_length_bytes() == 8
  247. xfer_mock.assert_called_once_with([0x06 | 0x80, 0])
  248. @pytest.mark.parametrize("packet_length", [21])
  249. def test_set_packet_length_bytes(transceiver, packet_length):
  250. xfer_mock = transceiver._spi.xfer
  251. xfer_mock.return_value = [15, 15]
  252. transceiver.set_packet_length_bytes(packet_length)
  253. xfer_mock.assert_called_once_with([0x06 | 0x40, packet_length])
  254. @pytest.mark.parametrize("packet_length", [-21, 0, 256, 1024])
  255. def test_set_packet_length_bytes_fail(transceiver, packet_length):
  256. with pytest.raises(Exception):
  257. transceiver.set_packet_length_bytes(packet_length)
  258. transceiver._spi.xfer.assert_not_called()
  259. @pytest.mark.parametrize(
  260. ("pktctrl0_before", "pktctrl0_after"),
  261. (
  262. # unchanged
  263. (0b00000000, 0b00000000),
  264. (0b00010000, 0b00010000),
  265. (0b00010001, 0b00010001),
  266. (0b01000000, 0b01000000),
  267. (0b01000010, 0b01000010),
  268. (0b01110000, 0b01110000),
  269. (0b01110010, 0b01110010),
  270. # disabled
  271. (0b00010100, 0b00010000),
  272. (0b01000100, 0b01000000),
  273. (0b01000110, 0b01000010),
  274. (0b01110110, 0b01110010),
  275. ),
  276. )
  277. def test_disable_checksum(transceiver, pktctrl0_before, pktctrl0_after):
  278. xfer_mock = transceiver._spi.xfer
  279. xfer_mock.return_value = [15, 15]
  280. with unittest.mock.patch.object(
  281. transceiver, "_read_single_byte", return_value=pktctrl0_before
  282. ):
  283. transceiver.disable_checksum()
  284. xfer_mock.assert_called_once_with([0x08 | 0x40, pktctrl0_after])
  285. @pytest.mark.parametrize(
  286. ("pktctrl0", "expected_mode"),
  287. (
  288. (0b00000000, PacketLengthMode.FIXED),
  289. (0b00000001, PacketLengthMode.VARIABLE),
  290. (0b01000100, PacketLengthMode.FIXED),
  291. (0b01000101, PacketLengthMode.VARIABLE),
  292. ),
  293. )
  294. def test_get_packet_length_mode(transceiver, pktctrl0, expected_mode):
  295. xfer_mock = transceiver._spi.xfer
  296. xfer_mock.return_value = [0, pktctrl0]
  297. assert transceiver.get_packet_length_mode() == expected_mode
  298. xfer_mock.assert_called_once_with([0x08 | 0x80, 0])
  299. @pytest.mark.parametrize(
  300. ("pktctrl0_before", "pktctrl0_after", "mode"),
  301. (
  302. (0b00000000, 0b00000000, PacketLengthMode.FIXED),
  303. (0b00000001, 0b00000000, PacketLengthMode.FIXED),
  304. (0b00000001, 0b00000001, PacketLengthMode.VARIABLE),
  305. (0b00000010, 0b00000000, PacketLengthMode.FIXED),
  306. (0b00000010, 0b00000001, PacketLengthMode.VARIABLE),
  307. (0b01000100, 0b01000100, PacketLengthMode.FIXED),
  308. (0b01000100, 0b01000101, PacketLengthMode.VARIABLE),
  309. (0b01000101, 0b01000100, PacketLengthMode.FIXED),
  310. (0b01000101, 0b01000101, PacketLengthMode.VARIABLE),
  311. ),
  312. )
  313. def test_set_packet_length_mode(transceiver, pktctrl0_before, pktctrl0_after, mode):
  314. xfer_mock = transceiver._spi.xfer
  315. xfer_mock.return_value = [15, 15]
  316. with unittest.mock.patch.object(
  317. transceiver, "_read_single_byte", return_value=pktctrl0_before
  318. ):
  319. transceiver.set_packet_length_mode(mode)
  320. xfer_mock.assert_called_once_with([0x08 | 0x40, pktctrl0_after])
  321. @pytest.mark.parametrize(
  322. ("freq_hz", "warn"),
  323. (
  324. (100e6, True),
  325. (281.6e6, True),
  326. (281.65e6, False), # within tolerance
  327. (281.7e6, False),
  328. (433.92e6, False),
  329. ),
  330. )
  331. def test_set_base_frequency_hertz_low_warning(transceiver, freq_hz, warn):
  332. with unittest.mock.patch.object(
  333. transceiver, "_set_base_frequency_control_word"
  334. ) as set_control_word_mock:
  335. with warnings.catch_warnings(record=True) as caught_warnings:
  336. transceiver.set_base_frequency_hertz(freq_hz)
  337. assert set_control_word_mock.call_count == 1
  338. if warn:
  339. assert len(caught_warnings) == 1
  340. assert (
  341. str(caught_warnings[0].message)
  342. == "CC1101 is unable to transmit at frequencies below 281.7 MHz"
  343. )
  344. else:
  345. assert not caught_warnings