state_spec.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. require 'api_client'
  2. RSpec.describe 'State' do
  3. before(:all) do
  4. @client = ApiClient.new(ENV.fetch('ESPMH_HOSTNAME'), ENV.fetch('ESPMH_TEST_DEVICE_ID_BASE'))
  5. @client.upload_json('/settings', 'settings.json')
  6. end
  7. before(:each) do
  8. @id_params = {
  9. id: @client.generate_id,
  10. type: 'rgb_cct',
  11. group_id: 1
  12. }
  13. @client.delete_state(@id_params)
  14. end
  15. context 'toggle command' do
  16. it 'should toggle ON to OFF' do
  17. init_state = @client.patch_state({'status' => 'ON'}, @id_params)
  18. expect(init_state['status']).to eq('ON')
  19. next_state = @client.patch_state({'command' => 'toggle'}, @id_params)
  20. expect(next_state['status']).to eq('OFF')
  21. end
  22. it 'should toggle OFF to ON' do
  23. init_state = @client.patch_state({'status' => 'OFF'}, @id_params)
  24. expect(init_state['status']).to eq('OFF')
  25. next_state = @client.patch_state({'command' => 'toggle'}, @id_params)
  26. expect(next_state['status']).to eq('ON')
  27. end
  28. end
  29. context 'night mode command' do
  30. StateHelpers::ALL_REMOTE_TYPES
  31. .reject { |x| %w(rgb).include?(x) } # Night mode not supported for these types
  32. .each do |type|
  33. it "should affect state when bulb is OFF for #{type}" do
  34. params = @id_params.merge(type: type)
  35. @client.delete_state(params)
  36. state = @client.patch_state({'command' => 'night_mode'}, params)
  37. expect(state['bulb_mode']).to eq('night')
  38. expect(state['effect']).to eq('night_mode')
  39. end
  40. end
  41. StateHelpers::ALL_REMOTE_TYPES
  42. .reject { |x| %w(rgb).include?(x) } # Night mode not supported for these types
  43. .each do |type|
  44. it "should affect state when bulb is ON for #{type}" do
  45. params = @id_params.merge(type: type)
  46. @client.delete_state(params)
  47. @client.patch_state({'status' => 'ON'}, params)
  48. state = @client.patch_state({'command' => 'night_mode'}, params)
  49. # RGBW bulbs have to be OFF in order for night mode to take affect
  50. expect(state['status']).to eq('ON') if type != 'rgbw'
  51. expect(state['bulb_mode']).to eq('night')
  52. expect(state['effect']).to eq('night_mode')
  53. end
  54. end
  55. it 'should revert to previous mode when status is toggled' do
  56. @client.patch_state({'status' => 'ON', 'kelvin' => 100}, @id_params)
  57. state = @client.patch_state({'command' => 'night_mode'}, @id_params)
  58. expect(state['effect']).to eq('night_mode')
  59. state = @client.patch_state({'status' => 'OFF'}, @id_params)
  60. expect(state['bulb_mode']).to eq('white')
  61. expect(state['kelvin']).to eq(100)
  62. @client.patch_state({'status' => 'ON', 'hue' => 0}, @id_params)
  63. state = @client.patch_state({'command' => 'night_mode'}, @id_params)
  64. expect(state['effect']).to eq('night_mode')
  65. state = @client.patch_state({'status' => 'OFF'}, @id_params)
  66. expect(state['bulb_mode']).to eq('color')
  67. expect(state['hue']).to eq(0)
  68. end
  69. end
  70. context 'deleting' do
  71. it 'should support deleting state' do
  72. desired_state = {
  73. 'status' => 'ON',
  74. 'level' => 10,
  75. 'hue' => 49,
  76. 'saturation' => 20
  77. }
  78. @client.patch_state(desired_state, @id_params)
  79. resulting_state = @client.get_state(@id_params)
  80. expect(resulting_state).to_not be_empty
  81. @client.delete_state(@id_params)
  82. resulting_state = @client.get_state(@id_params)
  83. expect(resulting_state).to be_empty
  84. end
  85. end
  86. context 'persistence' do
  87. it 'should persist parameters' do
  88. desired_state = {
  89. 'status' => 'ON',
  90. 'level' => 100,
  91. 'hue' => 0,
  92. 'saturation' => 100
  93. }
  94. @client.patch_state(desired_state, @id_params)
  95. patched_state = @client.get_state(@id_params)
  96. states_are_equal(desired_state, patched_state)
  97. desired_state = {
  98. 'status' => 'ON',
  99. 'level' => 10,
  100. 'hue' => 49,
  101. 'saturation' => 20
  102. }
  103. @client.patch_state(desired_state, @id_params)
  104. patched_state = @client.get_state(@id_params)
  105. states_are_equal(desired_state, patched_state)
  106. end
  107. it 'should affect member groups when changing group 0' do
  108. group_0_params = @id_params.merge(group_id: 0)
  109. desired_state = {
  110. 'status' => 'ON',
  111. 'level' => 100,
  112. 'hue' => 0,
  113. 'saturation' => 100
  114. }
  115. @client.patch_state(desired_state, group_0_params)
  116. individual_state = desired_state.merge('level' => 10)
  117. patched_state = @client.patch_state(individual_state, @id_params)
  118. expect(patched_state).to_not eq(desired_state)
  119. states_are_equal(individual_state, patched_state)
  120. group_4_state = @client.get_state(group_0_params.merge(group_id: 4))
  121. states_are_equal(desired_state, group_4_state)
  122. @client.patch_state(desired_state, group_0_params)
  123. group_1_state = @client.get_state(group_0_params.merge(group_id: 1))
  124. states_are_equal(desired_state, group_1_state)
  125. end
  126. it 'should keep group 0 state' do
  127. group_0_params = @id_params.merge(group_id: 0)
  128. desired_state = {
  129. 'status' => 'ON',
  130. 'level' => 100,
  131. 'hue' => 0,
  132. 'saturation' => 100
  133. }
  134. patched_state = @client.patch_state(desired_state, group_0_params)
  135. states_are_equal(desired_state, patched_state)
  136. end
  137. it 'should clear group 0 state after member group state changes' do
  138. group_0_params = @id_params.merge(group_id: 0)
  139. desired_state = {
  140. 'status' => 'ON',
  141. 'level' => 100,
  142. 'kelvin' => 100
  143. }
  144. @client.patch_state(desired_state, group_0_params)
  145. @client.patch_state(desired_state.merge('kelvin' => 10), @id_params)
  146. resulting_state = @client.get_state(group_0_params)
  147. expect(resulting_state.keys).to_not include('kelvin')
  148. states_are_equal(desired_state.reject { |x| x == 'kelvin' }, resulting_state)
  149. end
  150. it 'should not clear group 0 state when updating member group state if value is the same' do
  151. group_0_params = @id_params.merge(group_id: 0)
  152. desired_state = {
  153. 'status' => 'ON',
  154. 'level' => 100,
  155. 'kelvin' => 100
  156. }
  157. @client.patch_state(desired_state, group_0_params)
  158. @client.patch_state(desired_state.merge('kelvin' => 100), @id_params)
  159. resulting_state = @client.get_state(group_0_params)
  160. expect(resulting_state).to include('kelvin')
  161. states_are_equal(desired_state, resulting_state)
  162. end
  163. it 'changing member state mode and then changing level should preserve group 0 brightness for original mode' do
  164. group_0_params = @id_params.merge(group_id: 0)
  165. desired_state = {
  166. 'status' => 'ON',
  167. 'level' => 100,
  168. 'hue' => 0,
  169. 'saturation' => 100
  170. }
  171. @client.delete_state(group_0_params)
  172. @client.patch_state(desired_state, group_0_params)
  173. # color -> white mode. should not have brightness because brightness will
  174. # have been previously unknown to group 0.
  175. @client.patch_state(desired_state.merge('color_temp' => 253, 'level' => 11), @id_params)
  176. resulting_state = @client.get_state(group_0_params)
  177. expect(resulting_state.keys).to_not include('level')
  178. # color -> effect mode. same as above
  179. @client.patch_state(desired_state, group_0_params)
  180. @client.patch_state(desired_state.merge('mode' => 0), @id_params)
  181. resulting_state = @client.get_state(group_0_params)
  182. expect(resulting_state).to_not include('level')
  183. # white mode -> color.
  184. white_mode_desired_state = {'status' => 'ON', 'color_temp' => 253, 'level' => 11}
  185. @client.patch_state(white_mode_desired_state, group_0_params)
  186. @client.patch_state({'hue' => 10}, @id_params)
  187. resulting_state = @client.get_state(group_0_params)
  188. expect(resulting_state).to_not include('level')
  189. @client.patch_state({'hue' => 10}, group_0_params)
  190. resulting_state = @client.get_state(group_0_params)
  191. expect(resulting_state['level']).to eq(100)
  192. # white mode -> effect mode. level never set for group 0, so level should
  193. # level should be present.
  194. @client.patch_state(white_mode_desired_state, group_0_params)
  195. @client.patch_state({'mode' => 0}, @id_params)
  196. resulting_state = @client.get_state(group_0_params)
  197. expect(resulting_state).to_not include('level')
  198. # effect mode -> color. same as white mode -> color
  199. effect_mode_desired_state = {'status' => 'ON', 'mode' => 0, 'level' => 100}
  200. @client.patch_state(effect_mode_desired_state, group_0_params)
  201. @client.patch_state({'hue' => 10}, @id_params)
  202. resulting_state = @client.get_state(group_0_params)
  203. expect(resulting_state).to_not include('level')
  204. # effect mode -> white
  205. @client.patch_state(effect_mode_desired_state, group_0_params)
  206. @client.patch_state({'color_temp' => 253}, @id_params)
  207. resulting_state = @client.get_state(group_0_params)
  208. expect(resulting_state).to_not include('level')
  209. end
  210. end
  211. context 'fields' do
  212. it 'should support the color field' do
  213. desired_state = {
  214. 'hue' => 0,
  215. 'saturation' => 100,
  216. 'status' => 'ON'
  217. }
  218. @client.patch_state(
  219. desired_state.merge(hue: 100),
  220. @id_params
  221. )
  222. @client.patch_state(
  223. { color: '255,0,0' },
  224. @id_params
  225. )
  226. state = @client.get_state(@id_params)
  227. expect(state.keys).to include(*desired_state.keys)
  228. expect(state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
  229. @client.patch_state(
  230. { color: {r: 0, g: 255, b: 0} },
  231. @id_params
  232. )
  233. state = @client.get_state(@id_params)
  234. desired_state.merge!('hue' => 120)
  235. expect(state.keys).to include(*desired_state.keys)
  236. expect(state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
  237. end
  238. it 'should support separate brightness fields for different modes' do
  239. desired_state = {
  240. 'hue' => 0,
  241. 'level' => 50
  242. }
  243. @client.patch_state(desired_state, @id_params)
  244. result = @client.get_state(@id_params)
  245. expect(result['bulb_mode']).to eq('color')
  246. expect(result['level']).to eq(50)
  247. @client.patch_state({'kelvin' => 100}, @id_params)
  248. @client.patch_state({'level' => 70}, @id_params)
  249. result = @client.get_state(@id_params)
  250. expect(result['bulb_mode']).to eq('white')
  251. expect(result['level']).to eq(70)
  252. @client.patch_state({'hue' => 0}, @id_params)
  253. result = @client.get_state(@id_params)
  254. expect(result['bulb_mode']).to eq('color')
  255. # Should retain previous brightness
  256. expect(result['level']).to eq(50)
  257. end
  258. end
  259. context 'increment/decrement commands' do
  260. it 'should assume state after sufficiently many down commands' do
  261. id = @id_params.merge(type: 'cct')
  262. @client.delete_state(id)
  263. @client.patch_state({status: 'on'}, id)
  264. expect(@client.get_state(id)).to_not include('brightness', 'kelvin')
  265. 10.times do
  266. @client.patch_state(
  267. { commands: ['level_down', 'temperature_down'] },
  268. id
  269. )
  270. end
  271. state = @client.get_state(id)
  272. expect(state).to include('level', 'kelvin')
  273. expect(state['level']).to eq(0)
  274. expect(state['kelvin']).to eq(0)
  275. end
  276. it 'should assume state after sufficiently many up commands' do
  277. id = @id_params.merge(type: 'cct')
  278. @client.delete_state(id)
  279. @client.patch_state({status: 'on'}, id)
  280. expect(@client.get_state(id)).to_not include('level', 'kelvin')
  281. 10.times do
  282. @client.patch_state(
  283. { commands: ['level_up', 'temperature_up'] },
  284. id
  285. )
  286. end
  287. state = @client.get_state(id)
  288. expect(state).to include('level', 'kelvin')
  289. expect(state['level']).to eq(100)
  290. expect(state['kelvin']).to eq(100)
  291. end
  292. it 'should affect known state' do
  293. id = @id_params.merge(type: 'cct')
  294. @client.delete_state(id)
  295. @client.patch_state({status: 'on'}, id)
  296. expect(@client.get_state(id)).to_not include('level', 'kelvin')
  297. 10.times do
  298. @client.patch_state(
  299. { commands: ['level_up', 'temperature_up'] },
  300. id
  301. )
  302. end
  303. @client.patch_state(
  304. { commands: ['level_down', 'temperature_down'] },
  305. id
  306. )
  307. state = @client.get_state(id)
  308. expect(state).to include('level', 'kelvin')
  309. expect(state['level']).to eq(90)
  310. expect(state['kelvin']).to eq(90)
  311. end
  312. end
  313. context 'state updates while off' do
  314. it 'should not affect persisted state' do
  315. @client.patch_state({'status' => 'OFF'}, @id_params)
  316. state = @client.patch_state({'hue' => 100}, @id_params)
  317. expect(state.count).to eq(1)
  318. expect(state).to include('status')
  319. end
  320. it 'should not affect persisted state using increment/decrement' do
  321. @client.patch_state({'status' => 'OFF'}, @id_params)
  322. 10.times do
  323. @client.patch_state(
  324. { commands: ['level_down', 'temperature_down'] },
  325. @id_params
  326. )
  327. end
  328. state = @client.get_state(@id_params)
  329. expect(state.count).to eq(1)
  330. expect(state).to include('status')
  331. end
  332. end
  333. end