def setup_minimal_master_platform(port): # type: (str) -> None config = ConfigParser() config.read(constants.get_config_file()) platform = Platform.get_platform() Injectable.value(controller_serial=Serial(port, 115200)) if platform == Platform.Type.DUMMY: Injectable.value(maintenance_communicator=None) Injectable.value(master_controller=MasterDummyController()) elif platform in Platform.CoreTypes: from master.core import ucan_communicator _ = ucan_communicator core_cli_serial_port = config.get('OpenMotics', 'cli_serial') Injectable.value(cli_serial=Serial(core_cli_serial_port, 115200)) Injectable.value(master_communicator=CoreCommunicator()) Injectable.value(maintenance_communicator=None) Injectable.value(memory_file=MemoryFile()) Injectable.value(master_controller=MasterCoreController()) elif platform in Platform.ClassicTypes: Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) from master.classic import eeprom_extension _ = eeprom_extension Injectable.value(master_communicator=MasterCommunicator()) Injectable.value(maintenance_communicator=None) Injectable.value(master_controller=MasterClassicController()) else: logger.warning('Unhandled master implementation for %s', platform)
def _register_background_consumers(self): if self._master_version and not self._background_consumers_registered: if Platform.get_platform() == Platform.Type.CLASSIC: # This import/code will eventually be migrated away to MasterControllers from master.master_communicator import BackgroundConsumer self._master_communicator.register_consumer( BackgroundConsumer( master_api.shutter_status(self._master_version), 0, self._on_shutter_update)) self._background_consumers_registered = True
def get_state(self): authorized_mode = self._authorized_mode if Platform.get_platform() == Platform.Type.CORE_PLUS: authorized_mode = True # TODO: Should be handled by actual button return { 'run_gpio': self._last_run_gpio, 'run_i2c': self._last_run_i2c, 'run_buttons': self._last_button_check, 'run_state_check': self._last_state_check, 'authorized_mode': authorized_mode }
def check_gateway_health(timeout=60): since = time.time() pending = ['unknown'] while since > time.time() - timeout: try: http_port = Platform.http_port() response = requests.get('http://127.0.0.1:{}/health_check'.format(http_port), timeout=2) data = response.json() if data['success']: pending = [k for k, v in data['health'].items() if not v['state']] if not pending: return except Exception as ex: pass time.sleep(10) message = 'health check failed {}'.format(pending) logger.error(message) raise SystemExit(EXIT_CODES['failed_health_check'])
def __init__( self, master_communicator=INJECTED): # type: (CoreCommunicator) -> None super(FrontpanelCoreController, self).__init__() self._master_communicator = master_communicator self._master_communicator.register_consumer( BackgroundConsumer(CoreAPI.event_information(), 0, self._handle_event)) self._led_states = {} # type: Dict[str, LedStateTracker] self._led_event_lock = Lock() self._carrier = True self._connectivity = True self._activity = False self._cloud = False self._vpn = False self._led_drive_states = {} # type: Dict[str, Tuple[bool, str]] self._check_buttons_thread = None self._authorized_mode_buttons = [False, False] self._authorized_mode_buttons_pressed_since = None # type: Optional[float] self._authorized_mode_buttons_released = False self._platform = Platform.get_platform()
if __name__ == '__main__': setup_logger() config = ConfigParser() config.read(constants.get_config_file()) Injectable.value(config_db_lock=Lock()) Injectable.value(config_db=constants.get_config_database_file()) Injectable.value(eeprom_db=constants.get_eeprom_extension_database_file()) controller_serial_port = config.get('OpenMotics', 'controller_serial') Injectable.value(controller_serial=Serial(controller_serial_port, 115200)) from gateway import config as config_controller _ = config_controller if Platform.get_platform() == Platform.Type.CORE_PLUS: from gateway.hal import master_controller_core # type: ignore from master_core import maintenance, core_communicator, ucan_communicator # type: ignore _ = master_controller_core, maintenance, core_communicator, ucan_communicator # type: ignore else: from gateway.hal import master_controller_classic # type: ignore from master import maintenance, master_communicator, eeprom_extension # type: ignore _ = master_controller_classic, maintenance, master_communicator, eeprom_extension # type: ignore lock_file = constants.get_init_lockfile() if os.path.isfile(lock_file): with open(lock_file) as fd: content = fd.read() if content == 'factory_reset': logger.info('Running factory reset...')
""" import sqlite3 import logging import time import pytz from datetime import datetime from croniter import croniter from random import randint from threading import Thread from ioc import Injectable, Inject, INJECTED, Singleton from platform_utils import Platform from gateway.webservice import params_parser import ujson as json if Platform.get_platform() == Platform.Type.CLASSIC: from master.master_communicator import CommunicationTimedOutException else: # TODO: Replace for the Core+ class CommunicationTimedOutException(Exception): # type: ignore pass logger = logging.getLogger('openmotics') class Schedule(object): timezone = None def __init__(self, id, name, start, repeat, duration, end, schedule_type,
def update(version, expected_md5): """ Execute the actual update: extract the archive and execute the bash update script. :param version: the new version (after the update). :param expected_md5: the md5 sum provided by the server. """ version_mapping = {} has_master_hardware = Platform.has_master_hardware() try: config = ConfigParser() config.read(constants.get_config_file()) from_version = config.get('OpenMotics', 'version') logger.info('==================================') logger.info('Starting update {} -> {}'.format(from_version, version)) update_file = constants.get_update_file() update_dir = os.path.dirname(update_file) # Change to update directory. os.chdir(update_dir) if os.path.exists(update_file): logger.info(' -> Extracting update.tgz') extract_legacy_update(update_file, expected_md5) else: logger.info(' -> Fetching metadata') meta = fetch_metadata(config, version, expected_md5) logger.info(' -> Downloading firmware for update {}'.format(meta['version'])) for data in meta['firmwares']: download_firmware(data['type'], data['url'], data['sha256']) version_mapping[data['type']] = data['version'] except Exception: logger.exception('failed to preprepare update') raise SystemExit(EXIT_CODES['failed_preprepare_update']) errors = [] services_running = True try: date = datetime.now().strftime('%Y%m%d%H%M%S') # TODO: should update and re-execute itself before proceeding? logger.info(' -> Checking services') check_services() logger.info(' -> Stopping services') stop_services() services_running = False if has_master_hardware: gateway_os = FIRMWARE_FILES['gateway_os'] if os.path.exists(gateway_os): os_version = version_mapping.get('gateway_os') logger.info(' -> Updating Gateway OS to {0}'.format(os_version if os_version else 'unknown version')) error = update_gateway_os(gateway_os, os_version) if error: errors.append(error) gateway_service = FIRMWARE_FILES['gateway_service'] if os.path.exists(gateway_service): service_version = version_mapping.get('gateway_service') logger.info(' -> Updating Gateway service to {0}'.format(service_version if service_version else 'unknown version')) error = update_gateway_backend(gateway_service, date, service_version) if error: errors.append(error) if has_master_hardware: master_type = get_master_type() master_firmware = FIRMWARE_FILES[master_type] if os.path.exists(master_firmware): master_version = version_mapping.get(master_type) logger.info(' -> Updating Master firmware to {0}'.format(master_version if master_version else 'unknown version')) error = update_master_firmware(master_type, master_firmware, master_version) if error: errors.append(error) for module, filename, arguments in [('energy', FIRMWARE_FILES['energy'], []), ('power', FIRMWARE_FILES['power'], ['--8'])]: if os.path.exists(filename): energy_version = version_mapping.get(module) logger.info(' -> Updating {0} firmware to {1}'.format(module, energy_version if energy_version else 'unknown version')) error = update_energy_firmware(module, filename, energy_version, arguments) if error: errors.append(error) for module in MODULE_TYPES: module_firmware = FIRMWARE_FILES[module] module_version = version_mapping.get(module) if os.path.exists(module_firmware): logger.info(' -> Updating {0} firmware to {1}'.format(module, module_version if module_version else 'unknown version')) error = update_module_firmware(module, module_firmware, module_version) if error: errors.append(error) logger.info('Checking master communication') check_master_communication() gateway_frontend = FIRMWARE_FILES['gateway_frontend'] if os.path.exists(gateway_frontend): frontend_version = version_mapping.get('gateway_frontend') logger.info(' -> Updating Gateway frontend to {0}'.format(frontend_version if frontend_version else 'unknown version')) error = update_gateway_frontend(gateway_frontend, date, frontend_version) if error: errors.append(error) if os.path.exists(gateway_frontend) or os.path.exists(gateway_service): clean_update_backups() logger.info(' -> Starting services') start_services() services_running = True logger.info(' -> Waiting for health check') check_gateway_health() except Exception as exc: logger.exception('Unexpected exception updating') errors.append(exc) # TODO: rollback finally: if not services_running: logger.info(' -> Starting services') start_services() logger.info(' -> Running cleanup') cmd('rm -v -rf {}/*'.format(update_dir), shell=True) if errors: logger.error('Exceptions:') for error in errors: logger.error('- {0}'.format(error)) raise errors[0] config.set('OpenMotics', 'version', version) temp_file = constants.get_config_file() + '.update' with open(temp_file, 'w') as configfile: config.write(configfile) shutil.move(temp_file, constants.get_config_file()) cmd(['sync']) if os.path.exists('/tmp/post_update_reboot'): logger.info('Scheduling reboot in 5 minutes') subprocess.Popen('sleep 300 && reboot', close_fds=True, shell=True) logger.info('DONE') logger.info('exit 0')
def get_master_type(): if Platform.get_platform() in Platform.CoreTypes: return 'master_coreplus' else: return 'master_classic'
def main(): # type: () -> None """ The main function. """ parser = argparse.ArgumentParser(description='Tool to control the master.') parser.add_argument('--port', dest='port', action='store_true', help='get the serial port device') parser.add_argument('--sync', dest='sync', action='store_true', help='sync the serial port') parser.add_argument('--reset', dest='reset', action='store_true', help='reset the master') parser.add_argument('--hard-reset', dest='hardreset', action='store_true', help='perform a hardware reset on the master') parser.add_argument('--version', dest='version', action='store_true', help='get the version of the master') parser.add_argument('--wipe', dest='wipe', action='store_true', help='wip the master eeprom') parser.add_argument('--update', dest='update', action='store_true', help='update the master firmware') parser.add_argument('--master-firmware-classic', help='path to the hexfile with the classic firmware') parser.add_argument('--master-firmware-core', help='path to the hexfile with the core+ firmware') args = parser.parse_args() setup_logger() config = ConfigParser() config.read(constants.get_config_file()) port = config.get('OpenMotics', 'controller_serial') if args.port: print(port) return if not any([args.sync, args.version, args.reset, args.hardreset, args.wipe, args.update]): parser.print_help() setup_minimal_master_platform(port) platform = Platform.get_platform() if args.hardreset: master_cold_reset() return elif args.update: if platform in Platform.CoreTypes: firmware = args.master_firmware_core if not firmware: print('error: --master-firmware-core is required to update') sys.exit(1) else: firmware = args.master_firmware_classic if not firmware: print('error: --master-firmware-classic is required to update') sys.exit(1) master_update(firmware) return communicator = get_communicator() communicator.start() try: if args.sync: master_sync() elif args.version: master_version() elif args.reset: master_reset() elif args.wipe: master_factory_reset() finally: communicator.stop()
def main(): supported_modules = ['O', 'R', 'D', 'I', 'T', 'C'] supported_modules_gen3 = ['O3', 'R3', 'D3', 'I3', 'T3', 'C3'] supported_can_modules = ['UC'] all_supported_modules = supported_modules + supported_modules_gen3 + supported_can_modules parser = argparse.ArgumentParser( description='Tool to bootload the slave modules.') parser.add_argument( '-t', '--type', dest='type', choices=all_supported_modules + [m.lower() for m in all_supported_modules], required=True, help='The type of module to bootload (choices: {0})'.format( ', '.join(all_supported_modules))) parser.add_argument('-f', '--file', dest='file', required=True, help='The filename of the hex file to bootload') parser.add_argument('-v', '--version', dest='version', required=False, help='The version of the firmware to flash') parser.add_argument('--verbose', dest='verbose', action='store_true', help='Show the serial output') args = parser.parse_args() module_type = args.type.upper() filename = args.file version = args.version gen3_firmware = module_type.endswith('3') if gen3_firmware: module_type = module_type[0] config = ConfigParser() config.read(constants.get_config_file()) port = config.get('OpenMotics', 'controller_serial') setup_minimal_master_platform(port) communicator = get_communicator() communicator.start() try: if Platform.get_platform() in Platform.CoreTypes: from master.core.slave_updater import SlaveUpdater update_success = SlaveUpdater.update_all( module_type=module_type, hex_filename=filename, gen3_firmware=gen3_firmware, version=version) else: from master.classic.slave_updater import bootload_modules try: if os.path.getsize(args.file) <= 0: print('Could not read hex or file is empty: {0}'.format( args.file)) return False except OSError as ex: print('Could not open hex: {0}'.format(ex)) return False if module_type == 'UC': print( 'Updating uCAN modules not supported on Classic platform') return True # Don't fail the update update_success = bootload_modules(module_type=module_type, filename=filename, gen3_firmware=gen3_firmware, version=version) finally: communicator.stop() time.sleep(3) return update_success
def setup_platform(message_client_name): # type: (Optional[str]) -> None setup_target_platform(Platform.get_platform(), message_client_name)
def start(self): """ Starts the monitoring thread """ self._ensure_gateway_api() if Platform.get_platform() == Platform.Type.CLASSIC: self._thread.start()
def build_graph(): config = ConfigParser() config.read(constants.get_config_file()) config_lock = Lock() scheduling_lock = Lock() metrics_lock = Lock() config_database_file = constants.get_config_database_file() # TODO: Clean up dependencies more to reduce complexity # IOC announcements # When below modules are imported, the classes are registerd in the IOC graph. This is required for # instances that are used in @Inject decorated functions below, and is also needed to specify # abstract implementations depending on e.g. the platform (classic vs core) or certain settings (classic # thermostats vs gateway thermostats) from power import power_communicator, power_controller from plugins import base from gateway import (metrics_controller, webservice, scheduling, observer, gateway_api, metrics_collector, maintenance_controller, comm_led_controller, users, pulses, config as config_controller, metrics_caching, watchdog) from cloud import events _ = (metrics_controller, webservice, scheduling, observer, gateway_api, metrics_collector, maintenance_controller, base, events, power_communicator, comm_led_controller, users, power_controller, pulses, config_controller, metrics_caching, watchdog) if Platform.get_platform() == Platform.Type.CORE_PLUS: from gateway.hal import master_controller_core from master_core import maintenance, core_communicator, ucan_communicator from master import eeprom_extension # TODO: Obsolete, need to be removed _ = master_controller_core, maintenance, core_communicator, ucan_communicator else: from gateway.hal import master_controller_classic from master import maintenance, master_communicator, eeprom_extension _ = master_controller_classic, maintenance, master_communicator, eeprom_extension thermostats_gateway_feature = Feature.get_or_none( name='thermostats_gateway') thermostats_gateway_enabled = thermostats_gateway_feature is not None and thermostats_gateway_feature.enabled if Platform.get_platform( ) == Platform.Type.CORE_PLUS or thermostats_gateway_enabled: from gateway.thermostat.gateway import thermostat_controller_gateway _ = thermostat_controller_gateway else: from gateway.thermostat.master import thermostat_controller_master _ = thermostat_controller_master # IPC Injectable.value(message_client=MessageClient('openmotics_service')) # Cloud API parsed_url = urlparse(config.get('OpenMotics', 'vpn_check_url')) Injectable.value(gateway_uuid=config.get('OpenMotics', 'uuid')) Injectable.value(cloud_endpoint=parsed_url.hostname) Injectable.value(cloud_port=parsed_url.port) Injectable.value(cloud_ssl=parsed_url.scheme == 'https') Injectable.value(cloud_api_version=0) # User Controller Injectable.value(user_db=config_database_file) Injectable.value(user_db_lock=config_lock) Injectable.value(token_timeout=3600) Injectable.value( config={ 'username': config.get('OpenMotics', 'cloud_user'), 'password': config.get('OpenMotics', 'cloud_pass') }) # Configuration Controller Injectable.value(config_db=config_database_file) Injectable.value(config_db_lock=config_lock) # Energy Controller power_serial_port = config.get('OpenMotics', 'power_serial') Injectable.value(power_db=constants.get_power_database_file()) if power_serial_port: Injectable.value(power_serial=RS485( Serial(power_serial_port, 115200, timeout=None))) else: Injectable.value(power_serial=None) Injectable.value(power_communicator=None) Injectable.value(power_controller=None) # Pulse Controller Injectable.value(pulse_db=constants.get_pulse_counter_database_file()) # Scheduling Controller Injectable.value( scheduling_db=constants.get_scheduling_database_file()) Injectable.value(scheduling_db_lock=scheduling_lock) # Master Controller controller_serial_port = config.get('OpenMotics', 'controller_serial') Injectable.value( controller_serial=Serial(controller_serial_port, 115200)) if Platform.get_platform() == Platform.Type.CORE_PLUS: from master_core.memory_file import MemoryFile, MemoryTypes core_cli_serial_port = config.get('OpenMotics', 'cli_serial') Injectable.value(cli_serial=Serial(core_cli_serial_port, 115200)) Injectable.value(passthrough_service=None) # Mark as "not needed" Injectable.value( memory_files={ MemoryTypes.EEPROM: MemoryFile(MemoryTypes.EEPROM), MemoryTypes.FRAM: MemoryFile(MemoryTypes.FRAM) }) # TODO: Remove; should not be needed for Core Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) else: passthrough_serial_port = config.get('OpenMotics', 'passthrough_serial') Injectable.value( eeprom_db=constants.get_eeprom_extension_database_file()) if passthrough_serial_port: Injectable.value( passthrough_serial=Serial(passthrough_serial_port, 115200)) from master.passthrough import PassthroughService _ = PassthroughService # IOC announcement else: Injectable.value(passthrough_service=None) # Metrics Controller Injectable.value(metrics_db=constants.get_metrics_database_file()) Injectable.value(metrics_db_lock=metrics_lock) # Webserver / Presentation layer Injectable.value(ssl_private_key=constants.get_ssl_private_key_file()) Injectable.value(ssl_certificate=constants.get_ssl_certificate_file())
def _migrate(cls, master_controller=INJECTED ): # type: (MasterClassicController) -> None # Core(+) platforms never had non-ORM rooms if Platform.get_platform() in Platform.CoreTypes: return # Import legacy code @Inject def _load_eeprom_extension(eeprom_extension=INJECTED): # type: (EepromExtension) -> EepromExtension return eeprom_extension eext_controller = _load_eeprom_extension() from master.classic.eeprom_models import (OutputConfiguration, InputConfiguration, SensorConfiguration, ShutterConfiguration, ShutterGroupConfiguration, PulseCounterConfiguration) rooms = {} # type: Dict[int, Room] floors = {} # type: Dict[int, Floor] # Rooms and floors logger.info('* Rooms & floors') for room_id in range(100): try: RoomsMigrator._get_or_create_room(eext_controller, room_id, rooms, floors, skip_empty=True) except Exception: logger.exception('Could not migrate single RoomConfiguration') # Main objects for eeprom_model, orm_model, filter_ in [ (OutputConfiguration, Output, lambda o: True), (InputConfiguration, Input, lambda i: i.module_type in ['i', 'I']), (SensorConfiguration, Sensor, lambda s: True), (ShutterConfiguration, Shutter, lambda s: True), (ShutterGroupConfiguration, ShutterGroup, lambda s: True) ]: logger.info('* {0}s'.format(eeprom_model.__name__)) try: for classic_orm in master_controller._eeprom_controller.read_all( eeprom_model): try: object_id = classic_orm.id if object_id is None: continue if not filter_(classic_orm): RoomsMigrator._delete_eext_fields( eext_controller, eeprom_model.__name__, object_id, ['room']) continue try: room_id = int( RoomsMigrator._read_eext_fields( eext_controller, eeprom_model.__name__, object_id, ['room']).get('room', 255)) except ValueError: room_id = 255 object_orm, _ = orm_model.get_or_create( number=object_id) # type: ignore if room_id == 255: object_orm.room = None else: object_orm.room = RoomsMigrator._get_or_create_room( eext_controller, room_id, rooms, floors) object_orm.save() RoomsMigrator._delete_eext_fields( eext_controller, eeprom_model.__name__, object_id, ['room']) except Exception: logger.exception('Could not migrate single {0}'.format( eeprom_model.__name__)) except Exception: logger.exception('Could not migrate {0}s'.format( eeprom_model.__name__)) # PulseCounters pulse_counter = None # type: Optional[PulseCounter] # - Master try: logger.info('* PulseCounters (master)') for pulse_counter_classic_orm in master_controller._eeprom_controller.read_all( PulseCounterConfiguration): try: pulse_counter_id = pulse_counter_classic_orm.id try: room_id = int( RoomsMigrator._read_eext_fields( eext_controller, 'PulseCounterConfiguration', pulse_counter_id, ['room']).get('room', 255)) except ValueError: room_id = 255 pulse_counter = PulseCounter.get_or_none( number=pulse_counter_id) if pulse_counter is None: pulse_counter = PulseCounter( number=pulse_counter_id, name=pulse_counter_classic_orm.name, persistent=False, source=u'master') else: pulse_counter.name = pulse_counter_classic_orm.name pulse_counter.persistent = False pulse_counter.source = u'master' if room_id == 255: pulse_counter.room = None else: pulse_counter.room = RoomsMigrator._get_or_create_room( eext_controller, room_id, rooms, floors) pulse_counter.save() RoomsMigrator._delete_eext_fields( eext_controller, 'PulseCounterConfiguration', pulse_counter_id, ['room']) except Exception: logger.exception( 'Could not migrate classic master PulseCounter') except Exception: logger.exception('Could not migrate classic master PulseCounters') # - Old SQLite3 old_sqlite_db = constants.get_pulse_counter_database_file() if os.path.exists(old_sqlite_db): try: logger.info('* PulseCounters (gateway)') import sqlite3 connection = sqlite3.connect( old_sqlite_db, detect_types=sqlite3.PARSE_DECLTYPES, check_same_thread=False, isolation_level=None) cursor = connection.cursor() for row in cursor.execute( 'SELECT id, name, room, persistent FROM pulse_counters ORDER BY id ASC;' ): try: pulse_counter_id = int(row[0]) room_id = int(row[2]) pulse_counter = PulseCounter.get_or_none( number=pulse_counter_id) if pulse_counter is None: pulse_counter = PulseCounter( number=pulse_counter_id, name=str(row[1]), persistent=row[3] >= 1, source=u'gateway') else: pulse_counter.name = str(row[1]) pulse_counter.persistent = row[3] >= 1 pulse_counter.source = u'gateway' if room_id == 255: pulse_counter.room = None else: pulse_counter.room = RoomsMigrator._get_or_create_room( eext_controller, room_id, rooms, floors) pulse_counter.save() except Exception: logger.exception( 'Could not migratie gateway PulseCounter') os.rename(old_sqlite_db, '{0}.bak'.format(old_sqlite_db)) except Exception: logger.exception('Could not migrate gateway PulseCounters')