accessories.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #
  2. # Copyright (c) 2021 Project CHIP Authors
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import filecmp
  16. import logging
  17. import os
  18. import subprocess
  19. import sys
  20. import threading
  21. from xmlrpc.server import SimpleXMLRPCServer
  22. _DEFAULT_CHIP_ROOT = os.path.abspath(
  23. os.path.join(os.path.dirname(__file__), '..', '..', '..'))
  24. IP = '127.0.0.1'
  25. PORT = 9000
  26. if sys.platform == 'linux':
  27. IP = '10.10.10.5'
  28. class AppsRegister:
  29. _instance = None
  30. __accessories = {}
  31. def init(self):
  32. self.__startXMLRPCServer()
  33. def uninit(self):
  34. self.__stopXMLRPCServer()
  35. @property
  36. def accessories(self):
  37. """List of registered accessory applications."""
  38. return self.__accessories.values()
  39. def add(self, name, accessory):
  40. self.__accessories[name] = accessory
  41. def remove(self, name):
  42. self.__accessories.pop(name)
  43. def removeAll(self):
  44. self.__accessories = {}
  45. def get(self, name):
  46. return self.__accessories[name]
  47. def kill(self, name):
  48. accessory = self.__accessories[name]
  49. if accessory:
  50. accessory.kill()
  51. def killAll(self):
  52. for accessory in self.__accessories.values():
  53. accessory.kill()
  54. def start(self, name, args):
  55. accessory = self.__accessories[name]
  56. if accessory:
  57. # The args param comes directly from the sys.argv[2:] of Start.py and should contain a list of strings in
  58. # key-value pair, e.g. [option1, value1, option2, value2, ...]
  59. options = self.__createCommandLineOptions(args)
  60. return accessory.start(options)
  61. return False
  62. def stop(self, name):
  63. accessory = self.__accessories[name]
  64. if accessory:
  65. return accessory.stop()
  66. return False
  67. def reboot(self, name):
  68. accessory = self.__accessories[name]
  69. if accessory:
  70. return accessory.stop() and accessory.start()
  71. return False
  72. def factoryResetAll(self):
  73. for accessory in self.__accessories.values():
  74. accessory.factoryReset()
  75. def factoryReset(self, name):
  76. accessory = self.__accessories[name]
  77. if accessory:
  78. return accessory.factoryReset()
  79. return False
  80. def waitForMessage(self, name, message):
  81. accessory = self.__accessories[name]
  82. if accessory:
  83. # The message param comes directly from the sys.argv[2:] of WaitForMessage.py and should contain a list of strings that
  84. # comprise the entire message to wait for
  85. return accessory.waitForMessage(' '.join(message))
  86. return False
  87. def createOtaImage(self, otaImageFilePath, rawImageFilePath, rawImageContent, vid='0xDEAD', pid='0xBEEF'):
  88. # Write the raw image content
  89. with open(rawImageFilePath, 'w') as rawFile:
  90. rawFile.write(rawImageContent)
  91. # Add an OTA header to the raw file
  92. otaImageTool = _DEFAULT_CHIP_ROOT + '/src/app/ota_image_tool.py'
  93. cmd = [otaImageTool, 'create', '-v', vid, '-p', pid, '-vn', '2',
  94. '-vs', "2.0", '-da', 'sha256', rawImageFilePath, otaImageFilePath]
  95. s = subprocess.Popen(cmd)
  96. s.wait()
  97. if s.returncode != 0:
  98. raise Exception('Cannot create OTA image file')
  99. return True
  100. def compareFiles(self, file1, file2):
  101. if filecmp.cmp(file1, file2, shallow=False) is False:
  102. raise Exception('Files %s and %s do not match' % (file1, file2))
  103. return True
  104. def __startXMLRPCServer(self):
  105. self.server = SimpleXMLRPCServer((IP, PORT))
  106. self.server.register_function(self.start, 'start')
  107. self.server.register_function(self.stop, 'stop')
  108. self.server.register_function(self.reboot, 'reboot')
  109. self.server.register_function(self.factoryReset, 'factoryReset')
  110. self.server.register_function(self.waitForMessage, 'waitForMessage')
  111. self.server.register_function(self.compareFiles, 'compareFiles')
  112. self.server.register_function(self.createOtaImage, 'createOtaImage')
  113. self.server_thread = threading.Thread(target=self.server.serve_forever)
  114. self.server_thread.start()
  115. def __stopXMLRPCServer(self):
  116. self.server.shutdown()
  117. def __createCommandLineOptions(self, args):
  118. if not args:
  119. return {}
  120. # args should contain a list of strings in key-value pair, e.g. [option1, value1, option2, value2, ...]
  121. if (len(args) % 2) != 0:
  122. logging.warning("Unexpected command line options %r - not key/value pairs (odd length)" % (args,))
  123. return {}
  124. # Create a dictionary from the key-value pair list
  125. options = {args[i]: args[i+1] for i in range(0, len(args), 2)}
  126. return options