state_spec.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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. it 'should affect state when bulb is off' do
  31. state = @client.patch_state({'command' => 'night_mode'}, @id_params)
  32. expect(state['bulb_mode']).to eq('night')
  33. expect(state['effect']).to eq('night_mode')
  34. end
  35. it 'should affect state when bulb is on' do
  36. @client.patch_state({'status' => 'ON'}, @id_params)
  37. state = @client.patch_state({'command' => 'night_mode'}, @id_params)
  38. expect(state['status']).to eq('ON')
  39. expect(state['bulb_mode']).to eq('night')
  40. expect(state['effect']).to eq('night_mode')
  41. end
  42. it 'should revert to previous mode when status is toggled' do
  43. @client.patch_state({'status' => 'ON', 'kelvin' => 100}, @id_params)
  44. state = @client.patch_state({'command' => 'night_mode'}, @id_params)
  45. expect(state['effect']).to eq('night_mode')
  46. state = @client.patch_state({'status' => 'OFF'}, @id_params)
  47. expect(state['bulb_mode']).to eq('white')
  48. expect(state['kelvin']).to eq(100)
  49. @client.patch_state({'status' => 'ON', 'hue' => 0}, @id_params)
  50. state = @client.patch_state({'command' => 'night_mode'}, @id_params)
  51. expect(state['effect']).to eq('night_mode')
  52. state = @client.patch_state({'status' => 'OFF'}, @id_params)
  53. expect(state['bulb_mode']).to eq('color')
  54. expect(state['hue']).to eq(0)
  55. end
  56. end
  57. context 'deleting' do
  58. it 'should support deleting state' do
  59. desired_state = {
  60. 'status' => 'ON',
  61. 'level' => 10,
  62. 'hue' => 49,
  63. 'saturation' => 20
  64. }
  65. @client.patch_state(desired_state, @id_params)
  66. resulting_state = @client.get_state(@id_params)
  67. expect(resulting_state).to_not be_empty
  68. @client.delete_state(@id_params)
  69. resulting_state = @client.get_state(@id_params)
  70. expect(resulting_state).to be_empty
  71. end
  72. end
  73. context 'persistence' do
  74. it 'should persist parameters' do
  75. desired_state = {
  76. 'status' => 'ON',
  77. 'level' => 100,
  78. 'hue' => 0,
  79. 'saturation' => 100
  80. }
  81. @client.patch_state(desired_state, @id_params)
  82. patched_state = @client.get_state(@id_params)
  83. states_are_equal(desired_state, patched_state)
  84. desired_state = {
  85. 'status' => 'ON',
  86. 'level' => 10,
  87. 'hue' => 49,
  88. 'saturation' => 20
  89. }
  90. @client.patch_state(desired_state, @id_params)
  91. patched_state = @client.get_state(@id_params)
  92. states_are_equal(desired_state, patched_state)
  93. end
  94. it 'should affect member groups when changing group 0' do
  95. group_0_params = @id_params.merge(group_id: 0)
  96. desired_state = {
  97. 'status' => 'ON',
  98. 'level' => 100,
  99. 'hue' => 0,
  100. 'saturation' => 100
  101. }
  102. @client.patch_state(desired_state, group_0_params)
  103. individual_state = desired_state.merge('level' => 10)
  104. patched_state = @client.patch_state(individual_state, @id_params)
  105. expect(patched_state).to_not eq(desired_state)
  106. states_are_equal(individual_state, patched_state)
  107. group_4_state = @client.get_state(group_0_params.merge(group_id: 4))
  108. states_are_equal(desired_state, group_4_state)
  109. @client.patch_state(desired_state, group_0_params)
  110. group_1_state = @client.get_state(group_0_params.merge(group_id: 1))
  111. states_are_equal(desired_state, group_1_state)
  112. end
  113. it 'should keep group 0 state' do
  114. group_0_params = @id_params.merge(group_id: 0)
  115. desired_state = {
  116. 'status' => 'ON',
  117. 'level' => 100,
  118. 'hue' => 0,
  119. 'saturation' => 100
  120. }
  121. patched_state = @client.patch_state(desired_state, group_0_params)
  122. states_are_equal(desired_state, patched_state)
  123. end
  124. it 'should clear group 0 state after member group state changes' do
  125. group_0_params = @id_params.merge(group_id: 0)
  126. desired_state = {
  127. 'status' => 'ON',
  128. 'level' => 100,
  129. 'kelvin' => 100
  130. }
  131. @client.patch_state(desired_state, group_0_params)
  132. @client.patch_state(desired_state.merge('kelvin' => 10), @id_params)
  133. resulting_state = @client.get_state(group_0_params)
  134. expect(resulting_state.keys).to_not include('kelvin')
  135. states_are_equal(desired_state.reject { |x| x == 'kelvin' }, resulting_state)
  136. end
  137. it 'should not clear group 0 state when updating member group state if value is the same' 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' => 100), @id_params)
  146. resulting_state = @client.get_state(group_0_params)
  147. expect(resulting_state).to include('kelvin')
  148. states_are_equal(desired_state, resulting_state)
  149. end
  150. it 'changing member state mode and then changing level should preserve group 0 brightness for original mode' do
  151. group_0_params = @id_params.merge(group_id: 0)
  152. desired_state = {
  153. 'status' => 'ON',
  154. 'level' => 100,
  155. 'hue' => 0,
  156. 'saturation' => 100
  157. }
  158. @client.delete_state(group_0_params)
  159. @client.patch_state(desired_state, group_0_params)
  160. # color -> white mode. should not have brightness because brightness will
  161. # have been previously unknown to group 0.
  162. @client.patch_state(desired_state.merge('color_temp' => 253, 'level' => 11), @id_params)
  163. resulting_state = @client.get_state(group_0_params)
  164. expect(resulting_state.keys).to_not include('level')
  165. # color -> effect mode. same as above
  166. @client.patch_state(desired_state, group_0_params)
  167. @client.patch_state(desired_state.merge('mode' => 0), @id_params)
  168. resulting_state = @client.get_state(group_0_params)
  169. expect(resulting_state).to_not include('level')
  170. # white mode -> color.
  171. white_mode_desired_state = {'status' => 'ON', 'color_temp' => 253, 'level' => 11}
  172. @client.patch_state(white_mode_desired_state, group_0_params)
  173. @client.patch_state({'hue' => 10}, @id_params)
  174. resulting_state = @client.get_state(group_0_params)
  175. expect(resulting_state).to_not include('level')
  176. @client.patch_state({'hue' => 10}, group_0_params)
  177. resulting_state = @client.get_state(group_0_params)
  178. expect(resulting_state['level']).to eq(100)
  179. # white mode -> effect mode. level never set for group 0, so level should
  180. # level should be present.
  181. @client.patch_state(white_mode_desired_state, group_0_params)
  182. @client.patch_state({'mode' => 0}, @id_params)
  183. resulting_state = @client.get_state(group_0_params)
  184. expect(resulting_state).to_not include('level')
  185. # effect mode -> color. same as white mode -> color
  186. effect_mode_desired_state = {'status' => 'ON', 'mode' => 0, 'level' => 100}
  187. @client.patch_state(effect_mode_desired_state, group_0_params)
  188. @client.patch_state({'hue' => 10}, @id_params)
  189. resulting_state = @client.get_state(group_0_params)
  190. expect(resulting_state).to_not include('level')
  191. # effect mode -> white
  192. @client.patch_state(effect_mode_desired_state, group_0_params)
  193. @client.patch_state({'color_temp' => 253}, @id_params)
  194. resulting_state = @client.get_state(group_0_params)
  195. expect(resulting_state).to_not include('level')
  196. end
  197. end
  198. context 'fields' do
  199. it 'should support the color field' do
  200. desired_state = {
  201. 'hue' => 0,
  202. 'saturation' => 100,
  203. 'status' => 'ON'
  204. }
  205. @client.patch_state(
  206. desired_state.merge(hue: 100),
  207. @id_params
  208. )
  209. @client.patch_state(
  210. { color: '255,0,0' },
  211. @id_params
  212. )
  213. state = @client.get_state(@id_params)
  214. expect(state.keys).to include(*desired_state.keys)
  215. expect(state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
  216. @client.patch_state(
  217. { color: {r: 0, g: 255, b: 0} },
  218. @id_params
  219. )
  220. state = @client.get_state(@id_params)
  221. desired_state.merge!('hue' => 120)
  222. expect(state.keys).to include(*desired_state.keys)
  223. expect(state.select { |x| desired_state.include?(x) } ).to eq(desired_state)
  224. end
  225. end
  226. context 'increment/decrement commands' do
  227. it 'should assume state after sufficiently many down commands' do
  228. id = @id_params.merge(type: 'cct')
  229. @client.delete_state(id)
  230. @client.patch_state({status: 'on'}, id)
  231. expect(@client.get_state(id)).to_not include('brightness', 'kelvin')
  232. 10.times do
  233. @client.patch_state(
  234. { commands: ['level_down', 'temperature_down'] },
  235. id
  236. )
  237. end
  238. state = @client.get_state(id)
  239. expect(state).to include('level', 'kelvin')
  240. expect(state['level']).to eq(0)
  241. expect(state['kelvin']).to eq(0)
  242. end
  243. it 'should assume state after sufficiently many up commands' do
  244. id = @id_params.merge(type: 'cct')
  245. @client.delete_state(id)
  246. @client.patch_state({status: 'on'}, id)
  247. expect(@client.get_state(id)).to_not include('level', 'kelvin')
  248. 10.times do
  249. @client.patch_state(
  250. { commands: ['level_up', 'temperature_up'] },
  251. id
  252. )
  253. end
  254. state = @client.get_state(id)
  255. expect(state).to include('level', 'kelvin')
  256. expect(state['level']).to eq(100)
  257. expect(state['kelvin']).to eq(100)
  258. end
  259. it 'should affect known state' do
  260. id = @id_params.merge(type: 'cct')
  261. @client.delete_state(id)
  262. @client.patch_state({status: 'on'}, id)
  263. expect(@client.get_state(id)).to_not include('level', 'kelvin')
  264. 10.times do
  265. @client.patch_state(
  266. { commands: ['level_up', 'temperature_up'] },
  267. id
  268. )
  269. end
  270. @client.patch_state(
  271. { commands: ['level_down', 'temperature_down'] },
  272. id
  273. )
  274. state = @client.get_state(id)
  275. expect(state).to include('level', 'kelvin')
  276. expect(state['level']).to eq(90)
  277. expect(state['kelvin']).to eq(90)
  278. end
  279. end
  280. end