Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
log.setLevel(level=logging.INFO)
log.setLevel(level=logging.DEBUG) # Debug hack!

import base64

import tinytuya
from tinytuya.Contrib.RFRemoteControlDevice import RFRemoteControlDevice

LOCAL_KEY = '0123456789abcdef'

Expand Down Expand Up @@ -255,5 +258,59 @@ def test_not_a_bulb(self):
self.assertDictEqual(result_payload, expected_payload)


def build_mock_rf():
d = RFRemoteControlDevice('DEVICE_ID_HERE', 'IP_ADDRESS_HERE', LOCAL_KEY, control_type=1)
d.set_version(3.3)
d.set_value = MagicMock()
return d


class TestRFRemoteControlDevice(unittest.TestCase):
def test_rf_decode_button_returns_dict(self):
# Bug 1: rf_decode_button was missing the () call on base64.b64decode,
# causing it to always return None instead of the decoded JSON dict.
sample = {"study_feq": "433", "ver": "2"}
encoded = base64.b64encode(json.dumps(sample).encode()).decode()

result = RFRemoteControlDevice.rf_decode_button(encoded)

self.assertIsNotNone(result, "rf_decode_button returned None — function was not called")
self.assertDictEqual(result, sample)

def test_rf_send_button_payload_structure(self):
# Bug 2: send_command('rfstudy_send', ...) was building the wrong payload:
# - used 'study_feq' (string) instead of 'feq' (int)
# - omitted 'mode' and 'rate' fields
# - omitted 'ver' inside each key dict
# Bug 3: rf_send_button was forwarding study_feq from the decoded button into
# feq, but feq must always be 0 so the device uses the frequency embedded in
# the code. Passing the actual frequency value selects a different chip path.
d = build_mock_rf()

# Use a button that has study_feq set to a non-zero value to confirm it is
# NOT forwarded into the payload's feq field.
button_data = {"study_feq": "433", "ver": "2"}
base64_code = base64.b64encode(json.dumps(button_data).encode()).decode()

d.rf_send_button(base64_code)

call_args = d.set_value.call_args
dp = call_args[0][0]
payload = json.loads(call_args[0][1])

self.assertEqual(dp, RFRemoteControlDevice.DP_SEND_IR)
self.assertEqual(payload['control'], 'rfstudy_send')

self.assertIn('feq', payload, "payload missing 'feq' (was 'study_feq')")
self.assertNotIn('study_feq', payload, "payload must not contain 'study_feq' for rfstudy_send")
self.assertIsInstance(payload['feq'], int, "'feq' must be int, not string")
self.assertEqual(payload['feq'], 0, "feq must be 0 so the device uses the frequency embedded in the code")
self.assertIn('mode', payload, "payload missing 'mode'")
self.assertIn('rate', payload, "payload missing 'rate'")

self.assertIn('key1', payload)
self.assertIn('ver', payload['key1'], "key1 missing 'ver'")


if __name__ == '__main__':
unittest.main()
23 changes: 19 additions & 4 deletions tinytuya/Contrib/RFRemoteControlDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,26 @@ def send_command( self, mode, data={} ):
data['freq'] = '0'
if 'ver' not in data or not data['ver']:
data['ver'] = '2'
command = { RFRemoteControlDevice.NSDP_CONTROL: mode, 'rf_type': data['rf_type'], 'study_feq': data['freq'], 'ver': data['ver'] }
if mode == 'rfstudy_send':
# rfstudy_send uses a different payload schema than the study/exit commands:
# - frequency field is 'feq' (int), not 'study_feq' (string)
# - requires 'mode' and 'rate' fields
# - each key dict must include 'ver'
if 'mode' not in data:
data['mode'] = 0
if 'rate' not in data:
data['rate'] = 0
ver = data['ver']
command = { RFRemoteControlDevice.NSDP_CONTROL: mode, 'rf_type': data['rf_type'], 'feq': int(data['freq']), 'mode': data['mode'], 'rate': data['rate'], 'ver': ver }
for i in range( 1, 10 ):
k = 'key%d' % i
if k in data:
command[k] = data[k]
key_data = dict(data[k])
if 'ver' not in key_data:
key_data['ver'] = ver
command[k] = key_data
else:
command = { RFRemoteControlDevice.NSDP_CONTROL: mode, 'rf_type': data['rf_type'], 'study_feq': data['freq'], 'ver': data['ver'] }
self.set_value( RFRemoteControlDevice.DP_SEND_IR, json.dumps(command), nowait=True )
elif mode == 'send_cmd':
data[RFRemoteControlDevice.NSDP_CONTROL] = mode
Expand Down Expand Up @@ -172,7 +186,8 @@ def rf_send_button( self, base64_code, times=6, delay=0, intervals=0 ):
key1 = { 'code': base64_code, 'times': times, 'delay': delay, 'intervals': intervals }
data = { 'key1': key1 }
if bdata:
if 'study_feq' in bdata: data['freq'] = bdata['study_feq']
# study_feq is intentionally NOT forwarded to feq.
# feq=0 tells the device to use the frequency embedded in the code itself.
if 'ver' in bdata: data['ver'] = bdata['ver']
return self.send_command( 'rfstudy_send', data )

Expand Down Expand Up @@ -255,7 +270,7 @@ def rf_print_button( base64_code, use_log=None ):
@staticmethod
def rf_decode_button( base64_code ):
try:
jstr = base64.b64decode
jstr = base64.b64decode( base64_code )
jdata = json.loads( jstr )
return jdata
except:
Expand Down
Loading