Exemplo n.º 1
0
    def Start(self):
        """Connect to server and start background threads"""
        try:
            self.installDate = None
            try:
                self.installDate = self.config.get('Agent',
                                                   'InstallDate',
                                                   fallback=None)
            except:
                pass
            if not self.installDate:
                self.installDate = int(time())
                self.config.set('Agent', 'InstallDate', self.installDate)
            if not self.username and not self.password and not self.clientId:
                self.CheckSubscription()
            if not self.Connect():
                error('Error starting agent')
                return
            self.schedulerEngine = SchedulerEngine(self, 'client_scheduler')

            self.readQueue = Queue()
            self.writeQueue = Queue()
            self.hardware = Hardware()
            self.oSInfo = OSInfo()
            self.count = 10000
            self.buff = bytearray(self.count)

            self.writerThread = WriterThread('writer', self)
            self.writerThread.start()
            self.processorThread = ProcessorThread('processor', self)
            self.processorThread.start()
            self.systemInfo = []
            TimerThread(self.SendSystemInfo, 300)

            self.sensorsClient = sensors.SensorsClient(self)
            self.sensorsClient.SetDataChanged(self.OnDataChanged)
            # TimerThread(self.SendSystemState, 30, 5)
            self.updater = Updater(self.config)
            self.updater.start()
            events = self.schedulerEngine.get_scheduled_events()
            self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC)
            # self.sentHistoryData = {}
            # self.historySendFails = 0
            # self.historyThread = Thread(target=self.SendHistoryData)
            # self.historyThread.setDaemon(True)
            # self.historyThread.start()
        except Exception as e:
            exception('Initialize error: ' + str(e))
Exemplo n.º 2
0
 def __init__(self, host, port, cayenneApiHost):
     self.HOST = host
     self.PORT = port
     self.CayenneApiHost = cayenneApiHost
     self.onMessageReceived = None
     self.onMessageSent = None
     self.initialized = False
     self.machineName = gethostname()
     self.config = Config(APP_SETTINGS)
     inviteCode = self.config.get('Agent', 'InviteCode', fallback=None)
     if not inviteCode:
         error('No invite code found in {}'.format(APP_SETTINGS))
         print(
             'Please input an invite code. This can be retrieved from the Cayenne dashboard by adding a new Raspberry Pi device.\n'
             'The invite code will be part of the script name shown there: rpi_[invitecode].sh.'
         )
         inviteCode = input('Invite code: ')
         if inviteCode:
             self.config.set('Agent', 'InviteCode', inviteCode)
         else:
             print('No invite code set, exiting.')
             quit()
     self.installDate = None
     try:
         self.installDate = self.config.get('Agent',
                                            'InstallDate',
                                            fallback=None)
     except:
         pass
     if not self.installDate:
         self.installDate = int(time())
         self.config.set('Agent', 'InstallDate', self.installDate)
     self.networkConfig = Config(NETWORK_SETTINGS)
     #self.defaultRDServer = self.networkConfig.get('CONFIG','RemoteDesktopServerAddress')
     self.schedulerEngine = SchedulerEngine(self, 'client_scheduler')
     self.Initialize()
     self.CheckSubscription()
     self.FirstRun()
     self.updater = Updater(self.config, self.OnUpdateConfig)
     self.updater.start()
     self.initialized = True
Exemplo n.º 3
0
 def test_reload(self):
     start_date = datetime.datetime.strftime(
         datetime.datetime.utcnow() + datetime.timedelta(seconds=60),
         '%Y-%m-%dT%H:%M:%S.%fZ')
     schedule_events = [{
         'id': 'reload_1',
         'title': 'date_job',
         'actions': ['date_job_action'],
         'config': {
             'type': 'date',
             'start_date': start_date
         }
     }, {
         'id': 'reload_2',
         'title': 'daily_job',
         'actions': ['daily_job_action'],
         'config': {
             'type': 'interval',
             'unit': 'day',
             'interval': 1,
             'start_date': start_date
         }
     }]
     self.add_schedules(schedule_events)
     self.check_schedules_added(schedule_events)
     self.check_schedules_run(schedule_events)
     self.test_engine.stop()
     del self.test_engine
     del self.test_client
     self.test_client = TestClient()
     self.test_engine = SchedulerEngine(self.test_client, 'test')
     for event in schedule_events:
         if 'last_run' in event:
             del event['last_run']
     self.check_schedules_added(schedule_events)
     self.check_schedules_run(schedule_events, ('date_job', 'daily_job'))
Exemplo n.º 4
0
 def setUp(self):
     self.maxDiff = None
     self.test_client = TestClient()
     self.test_engine = SchedulerEngine(self.test_client, 'test')
     self.schedule_events = []
Exemplo n.º 5
0
class SchedulerTest(unittest.TestCase):
    def setUp(self):
        self.maxDiff = None
        self.test_client = TestClient()
        self.test_engine = SchedulerEngine(self.test_client, 'test')
        self.schedule_events = []

    def tearDown(self):
        self.remove_schedules()
        self.test_engine.stop()

    def add_schedules(self, schedule_events):
        for event in schedule_events:
            self.test_engine.add_scheduled_event(event, True)
        self.schedule_events = self.schedule_events + schedule_events

    def remove_schedules(self, engine=None):
        scheduled_events = {
            event['id']: event
            for event in self.schedule_events if 'id' in event
        }
        for event in scheduled_events.values():
            self.assertTrue(self.test_engine.remove_scheduled_event(event))

    def check_schedules_added(self, expected):
        actual = self.test_engine.get_scheduled_events()
        self.assertCountEqual(expected, actual)

    def check_schedules_run(self, expected, skip_jobs=()):
        print('Pause to allow scheduled events to execute')
        expected_to_run = [
            action for event in expected if event['title'] not in skip_jobs
            for action in event['actions']
        ]
        for i in range(70):
            time.sleep(1)
            if len(expected_to_run) > 0 and len(expected_to_run) == len(
                    self.test_client.actions_ran):
                break
        self.assertCountEqual(expected_to_run, self.test_client.actions_ran)

    def test_missing_id(self):
        start_date = datetime.datetime.strftime(
            datetime.datetime.utcnow() + datetime.timedelta(seconds=60),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        missing_id_event = {
            'title': 'no_id_job',
            'actions': ['no_id_job_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }
        self.assertFalse(
            self.test_engine.add_scheduled_event(missing_id_event, True))
        self.assertFalse(self.test_engine.get_scheduled_events())

    def test_overwrite_job(self):
        start_date = datetime.datetime.strftime(
            datetime.datetime.utcnow() + datetime.timedelta(seconds=60),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'overwrite_1',
            'title': 'overwritten_job',
            'actions': ['overwritten_job_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }, {
            'id': 'overwrite_1',
            'title': 'date_job_readd_same_id',
            'actions': ['date_job_readd_same_id_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }]
        self.add_schedules(schedule_events)
        expected = [
            event for event in schedule_events
            if 'id' in event and event['title'] != 'overwritten_job'
        ]
        self.check_schedules_added(expected)

    def test_current_schedules(self):
        start_date = datetime.datetime.strftime(
            datetime.datetime.utcnow() + datetime.timedelta(seconds=60),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        now = datetime.datetime.strftime(datetime.datetime.utcnow(),
                                         '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'current_1',
            'title': 'date_job',
            'actions': ['date_job_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }, {
            'id': 'current_2',
            'title': 'daily_job',
            'actions': ['daily_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': start_date
            }
        }, {
            'id': 'current_3',
            'title': 'every_3_days_job',
            'actions': ['every_3_days_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 3,
                'start_date': start_date
            }
        }, {
            'id': 'current_4',
            'title': 'now_date_job',
            'actions': ['now_date_job_action'],
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'current_5',
            'title': 'weekly_job',
            'actions': ['weekly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'week',
                'interval': 1,
                'start_date': start_date
            }
        }, {
            'id': 'current_6',
            'title': 'bi-weekly_job',
            'actions': ['weekly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'week',
                'interval': 2,
                'start_date': start_date
            }
        }, {
            'id': 'current_7',
            'title': 'every_4_months_job',
            'actions': ['every_4_months_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 4,
                'start_date': start_date
            }
        }, {
            'id': 'current_8',
            'title': 'every_3_months_job',
            'actions': ['every_3_months_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 3,
                'start_date': now
            }
        }, {
            'id': 'current_9',
            'title': 'hourly_job',
            'actions': ['hourly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'hour',
                'interval': 1,
                'start_date': start_date
            }
        }]
        self.add_schedules(schedule_events)
        self.check_schedules_added(schedule_events)
        self.check_schedules_run(schedule_events)

    def test_past_schedules(self):
        next_minute = datetime.datetime.utcnow() + datetime.timedelta(
            seconds=60)
        passed_date = datetime.datetime.strftime(
            datetime.datetime.utcnow() - datetime.timedelta(seconds=120),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        one_day_ago = datetime.datetime.strftime(
            next_minute - datetime.timedelta(days=1), '%Y-%m-%dT%H:%M:%S.%fZ')
        one_week_ago = datetime.datetime.strftime(
            next_minute - datetime.timedelta(days=7), '%Y-%m-%dT%H:%M:%S.%fZ')
        one_month_ago = datetime.datetime.strftime(
            schedule.month_delta(next_minute, -1), '%Y-%m-%dT%H:%M:%S.%fZ')
        one_year_ago = next_minute.replace(year=next_minute.year - 1)
        one_year_ago = datetime.datetime.strftime(one_year_ago,
                                                  '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'past_1',
            'title': 'expired_date_job',
            'actions': ['expired_date_job_action'],
            'config': {
                'type': 'date',
                'start_date': passed_date
            }
        }, {
            'id': 'past_2',
            'title': 'daily_job_started_one_day_ago',
            'actions': ['daily_job_started_one_day_ago_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': one_day_ago
            }
        }, {
            'id':
            'past_3',
            'title':
            'monthly_job_started_one_month_ago',
            'actions': ['monthly_job_started_one_month_ago_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': one_month_ago
            }
        }, {
            'id':
            'past_4',
            'title':
            'yearly_job_started_one_year_ago',
            'actions': ['yearly_job_started_one_year_ago_action'],
            'config': {
                'type': 'interval',
                'unit': 'year',
                'interval': 1,
                'start_date': one_year_ago
            }
        }, {
            'id':
            'past_5',
            'title':
            'every_2_years_job_started_one_year_ago',
            'actions': ['every_2_years_job_started_one_year_ago_action'],
            'config': {
                'type': 'interval',
                'unit': 'year',
                'interval': 2,
                'start_date': one_year_ago
            }
        }, {
            'id':
            'past_6',
            'title':
            'weekly_job_started_one_week_ago',
            'actions': ['weekly_job_started_one_week_ago_action'],
            'config': {
                'type': 'interval',
                'unit': 'week',
                'interval': 1,
                'start_date': one_week_ago
            }
        }]
        self.add_schedules(schedule_events)
        self.check_schedules_added(schedule_events)
        self.check_schedules_run(
            schedule_events,
            ('expired_date_job', 'every_2_years_job_started_one_year_ago'))

    def test_future_schedules(self):
        one_day_from_now = datetime.datetime.strftime(
            datetime.datetime.utcnow() + datetime.timedelta(days=1),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        end_of_month = datetime.datetime.strftime(
            datetime.datetime(2015, 1, 31), '%Y-%m-%dT%H:%M:%S.%fZ')
        future_month = datetime.datetime.strftime(
            datetime.datetime(2017, 12, 31), '%Y-%m-%dT%H:%M:%S.%fZ')
        future_year = datetime.datetime.strftime(datetime.datetime(2017, 1, 1),
                                                 '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id':
            'future_1',
            'title':
            'daily_job_starts_one_day_from_now',
            'actions': ['daily_job_starts_one_day_from_now_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': one_day_from_now
            }
        }, {
            'id': 'future_2',
            'title': 'end_of_month_job',
            'actions': ['end_of_month_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': end_of_month
            }
        }, {
            'id': 'future_3',
            'title': 'future_month_job',
            'actions': ['future_month_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': future_month
            }
        }, {
            'id': 'future_4',
            'title': 'future_year_job',
            'actions': ['future_year_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': future_year
            }
        }]
        self.add_schedules(schedule_events)
        self.check_schedules_added(schedule_events)
        skip_jobs = [event['title'] for event in schedule_events]
        self.check_schedules_run(schedule_events, skip_jobs)

    def test_reload(self):
        start_date = datetime.datetime.strftime(
            datetime.datetime.utcnow() + datetime.timedelta(seconds=60),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'reload_1',
            'title': 'date_job',
            'actions': ['date_job_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }, {
            'id': 'reload_2',
            'title': 'daily_job',
            'actions': ['daily_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': start_date
            }
        }]
        self.add_schedules(schedule_events)
        self.check_schedules_added(schedule_events)
        self.check_schedules_run(schedule_events)
        self.test_engine.stop()
        del self.test_engine
        del self.test_client
        self.test_client = TestClient()
        self.test_engine = SchedulerEngine(self.test_client, 'test')
        for event in schedule_events:
            if 'last_run' in event:
                del event['last_run']
        self.check_schedules_added(schedule_events)
        self.check_schedules_run(schedule_events, ('date_job', 'daily_job'))

    def test_delayed_load(self):
        self.test_engine.stop()
        del self.test_engine
        del self.test_client
        now = datetime.datetime.utcnow()
        if (now.second > 35):
            print('Sleep until the minute rolls over')
            time.sleep(60 - now.second)
        now = datetime.datetime.strftime(datetime.datetime.utcnow(),
                                         '%Y-%m-%dT%H:%M:%S.%fZ')
        self.schedule_events = [{
            'id': 'delay_1',
            'title': 'date_job',
            'actions': ['date_job_action'],
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'delay_2',
            'title': 'daily_job',
            'actions': ['daily_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'delay_3',
            'title': 'weekly_job',
            'actions': ['weekly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'week',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'delay_4',
            'title': 'monthly_job',
            'actions': ['monthly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'delay_5',
            'title': 'yearly_job',
            'actions': ['yearly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'year',
                'interval': 1,
                'start_date': now
            }
        }]
        for event in self.schedule_events:
            event_json = json.dumps(event)
            try:
                DbManager.Insert('scheduled_events', event['id'], event_json)
            except sqlite3.IntegrityError as e:
                DbManager.Update('scheduled_events', 'event = ?', event_json,
                                 'id = ?', event['id'])
        print('Pause before loading scheduler')
        time.sleep(20)
        print('Starting scheduler, time is {}'.format(
            datetime.datetime.utcnow()))
        self.test_client = TestClient()
        self.test_engine = SchedulerEngine(self.test_client, 'test')
        self.check_schedules_run(self.schedule_events)

    def test_concurrent_updates(self):
        now = datetime.datetime.strftime(datetime.datetime.utcnow(),
                                         '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'concurrent_1',
            'title': 'date_job',
            'actions': ['date_job_action'],
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'concurrent_1',
            'title': 'date_job_updated',
            'actions': ['date_job_action'],
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'concurrent_2',
            'title': 'daily_job',
            'actions': ['daily_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_2',
            'title': 'daily_job_updated',
            'actions': ['daily_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_3',
            'title': 'weekly_job',
            'actions': ['weekly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'week',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_3',
            'title': 'weekly_job_updated',
            'actions': ['weekly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'week',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_4',
            'title': 'monthly_job',
            'actions': ['monthly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_4',
            'title': 'monthly_job_updated',
            'actions': ['monthly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'month',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_5',
            'title': 'yearly_job',
            'actions': ['yearly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'year',
                'interval': 1,
                'start_date': now
            }
        }, {
            'id': 'concurrent_5',
            'title': 'yearly_job_updated',
            'actions': ['yearly_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'year',
                'interval': 1,
                'start_date': now
            }
        }]
        for event in schedule_events:
            threading.Thread(target=self.add_schedules,
                             daemon=True,
                             args=([event], )).start()
        #Only half the schedule_events should run since ones with the same id will overwrite previously added ones. Since we don't know what order that will take
        #we just make sure we only check that one of each action has run.
        run_events = {
            event['id']: event
            for event in schedule_events if 'id' in event
        }
        skip_jobs = [event['title'] for event in run_events.values()]
        self.check_schedules_run(schedule_events, skip_jobs)

    def test_update_schedules(self):
        start_date = datetime.datetime.strftime(
            datetime.datetime.utcnow() + datetime.timedelta(seconds=60),
            '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'update_1',
            'title': 'date_job',
            'actions': ['date_job_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }, {
            'id': 'update_2',
            'title': 'daily_job',
            'actions': ['daily_job_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': start_date
            }
        }]
        self.add_schedules(schedule_events)
        update_schedule_events = [{
            'id': 'update_3',
            'title': 'date_job_full_update',
            'actions': ['date_job_full_update_action'],
            'config': {
                'type': 'date',
                'start_date': start_date
            }
        }, {
            'id': 'update_4',
            'title': 'daily_job_full_update',
            'actions': ['daily_job_full_update_action'],
            'config': {
                'type': 'interval',
                'unit': 'day',
                'interval': 1,
                'start_date': start_date
            }
        }]
        self.assertTrue(
            self.test_engine.update_scheduled_events(update_schedule_events))
        self.schedule_events = update_schedule_events
        self.check_schedules_run(update_schedule_events)

    def start_http_server(self):
        self.server = HTTPServer(('localhost', 8000), TestHandler)
        self.server.received = []
        self.server.serve_forever()

    def test_http_notification(self):
        threading.Thread(target=self.start_http_server, daemon=True).start()
        now = datetime.datetime.strftime(datetime.datetime.utcnow(),
                                         '%Y-%m-%dT%H:%M:%S.%fZ')
        schedule_events = [{
            'id': 'http_1',
            'title': 'date_get_job',
            'actions': ['date_job_action'],
            'http_push': {
                'url': 'http://localhost:8000',
                'method': 'GET',
                'headers': {
                    'Content-Type': 'application/json'
                },
                'payload': {
                    'test': 'GET request'
                }
            },
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'http_2',
            'title': 'date_post_job',
            'actions': ['date_job_action'],
            'http_push': {
                'url': 'http://localhost:8000',
                'method': 'POST',
                'headers': {
                    'Content-Type': 'application/json'
                },
                'payload': {
                    'test': 'POST request'
                }
            },
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'http_3',
            'title': 'date_put_job',
            'actions': ['date_job_action'],
            'http_push': {
                'url': 'http://localhost:8000',
                'method': 'PUT',
                'headers': {
                    'Content-Type': 'application/json'
                },
                'payload': {
                    'test': 'PUT request'
                }
            },
            'config': {
                'type': 'date',
                'start_date': now
            }
        }, {
            'id': 'http_4',
            'title': 'date_delete_job',
            'actions': ['date_job_action'],
            'http_push': {
                'url': 'http://localhost:8000',
                'method': 'DELETE',
                'headers': {
                    'Content-Type': 'application/json'
                },
                'payload': {
                    'test': 'DELETE request'
                }
            },
            'config': {
                'type': 'date',
                'start_date': now
            }
        }]
        self.add_schedules(schedule_events)
        self.check_schedules_added(schedule_events)
        self.check_schedules_run(schedule_events)
        self.assertEqual(4, len(self.server.received))
        expected = [event['http_push']['payload'] for event in schedule_events]
        self.assertCountEqual(expected, self.server.received)
Exemplo n.º 6
0
 def test_delayed_load(self):
     self.test_engine.stop()
     del self.test_engine
     del self.test_client
     now = datetime.datetime.utcnow()
     if (now.second > 35):
         print('Sleep until the minute rolls over')
         time.sleep(60 - now.second)
     now = datetime.datetime.strftime(datetime.datetime.utcnow(),
                                      '%Y-%m-%dT%H:%M:%S.%fZ')
     self.schedule_events = [{
         'id': 'delay_1',
         'title': 'date_job',
         'actions': ['date_job_action'],
         'config': {
             'type': 'date',
             'start_date': now
         }
     }, {
         'id': 'delay_2',
         'title': 'daily_job',
         'actions': ['daily_job_action'],
         'config': {
             'type': 'interval',
             'unit': 'day',
             'interval': 1,
             'start_date': now
         }
     }, {
         'id': 'delay_3',
         'title': 'weekly_job',
         'actions': ['weekly_job_action'],
         'config': {
             'type': 'interval',
             'unit': 'week',
             'interval': 1,
             'start_date': now
         }
     }, {
         'id': 'delay_4',
         'title': 'monthly_job',
         'actions': ['monthly_job_action'],
         'config': {
             'type': 'interval',
             'unit': 'month',
             'interval': 1,
             'start_date': now
         }
     }, {
         'id': 'delay_5',
         'title': 'yearly_job',
         'actions': ['yearly_job_action'],
         'config': {
             'type': 'interval',
             'unit': 'year',
             'interval': 1,
             'start_date': now
         }
     }]
     for event in self.schedule_events:
         event_json = json.dumps(event)
         try:
             DbManager.Insert('scheduled_events', event['id'], event_json)
         except sqlite3.IntegrityError as e:
             DbManager.Update('scheduled_events', 'event = ?', event_json,
                              'id = ?', event['id'])
     print('Pause before loading scheduler')
     time.sleep(20)
     print('Starting scheduler, time is {}'.format(
         datetime.datetime.utcnow()))
     self.test_client = TestClient()
     self.test_engine = SchedulerEngine(self.test_client, 'test')
     self.check_schedules_run(self.schedule_events)
Exemplo n.º 7
0
class CloudServerClient:
    def __init__(self, host, port, cayenneApiHost):
        self.HOST = host
        self.PORT = port
        self.CayenneApiHost = cayenneApiHost
        self.onMessageReceived = None
        self.onMessageSent = None
        self.initialized = False
        self.machineName = gethostname()
        self.config = Config(APP_SETTINGS)
        inviteCode = self.config.get('Agent', 'InviteCode', fallback=None)
        if not inviteCode:
            error('No invite code found in {}'.format(APP_SETTINGS))
            print(
                'Please input an invite code. This can be retrieved from the Cayenne dashboard by adding a new Raspberry Pi device.\n'
                'The invite code will be part of the script name shown there: rpi_[invitecode].sh.'
            )
            inviteCode = input('Invite code: ')
            if inviteCode:
                self.config.set('Agent', 'InviteCode', inviteCode)
            else:
                print('No invite code set, exiting.')
                quit()
        self.installDate = None
        try:
            self.installDate = self.config.get('Agent',
                                               'InstallDate',
                                               fallback=None)
        except:
            pass
        if not self.installDate:
            self.installDate = int(time())
            self.config.set('Agent', 'InstallDate', self.installDate)
        self.networkConfig = Config(NETWORK_SETTINGS)
        #self.defaultRDServer = self.networkConfig.get('CONFIG','RemoteDesktopServerAddress')
        self.schedulerEngine = SchedulerEngine(self, 'client_scheduler')
        self.Initialize()
        self.CheckSubscription()
        self.FirstRun()
        self.updater = Updater(self.config, self.OnUpdateConfig)
        self.updater.start()
        self.initialized = True

    def __del__(self):
        self.Destroy()

    def OnUpdateConfig(self):
        pass
        # info('Requesting PT_AGENT_CONFIGURATION ')
        # data = {}
        # data['MachineName'] = self.MachineId
        # data['Timestamp'] = int(time())
        # data['Platform'] = 1  # raspberrypi platform id is 1
        # data['PacketType'] = PacketTypes.PT_AGENT_CONFIGURATION.value
        # self.EnqueuePacket(data)
    def Initialize(self):
        #debug('CloudServerClient init')
        try:
            self.mutex = RLock()
            self.readQueue = Queue()
            self.writeQueue = Queue()
            self.pingRate = 10
            self.pingTimeout = 35
            self.waitPing = 0
            self.lastPing = time() - self.pingRate - 1
            self.PublicIP = myDevices.ipgetter.myip()
            self.hardware = Hardware()
            self.oSInfo = OSInfo()
            self.downloadSpeed = DownloadSpeed(self.config)
            self.MachineId = None
            self.connected = False
            self.exiting = False
            self.Start
            self.count = 10000
            self.buff = bytearray(self.count)
            #start thread only after init of other fields
            self.sensorsClient = sensors.SensorsClient()
            self.sensorsClient.SetDataChanged(self.OnDataChanged,
                                              self.BuildPT_SYSTEM_INFO)
            self.processManager = services.ProcessManager()
            self.serviceManager = services.ServiceManager()
            self.wifiManager = WifiManager.WifiManager()
            self.writerThread = WriterThread('writer', self)
            self.writerThread.start()
            self.readerThread = ReaderThread('reader', self)
            self.readerThread.start()
            self.processorThread = ProcessorThread('processor', self)
            self.processorThread.start()
            #TimerThread(self.RequestSchedules, 600, 10)
            TimerThread(self.CheckConnectionAndPing, self.pingRate)
            self.sentHistoryData = {}
            self.historySendFails = 0
            self.historyThread = Thread(target=self.SendHistoryData)
            self.historyThread.setDaemon(True)
            self.historyThread.start()
        except Exception as e:
            exception('Initialize error: ' + str(e))

    def Destroy(self):
        info('Shutting down client')
        self.exiting = True
        self.sensorsClient.StopMonitoring()
        if hasattr(self, 'schedulerEngine'):
            self.schedulerEngine.stop()
        if hasattr(self, 'updater'):
            self.updater.stop()
        if hasattr(self, 'writerThread'):
            self.writerThread.stop()
        if hasattr(self, 'readerThread'):
            self.readerThread.stop()
        if hasattr(self, 'processorThread'):
            self.processorThread.stop()
        ThreadPool.Shutdown()
        self.Stop()
        info('Client shut down')

    # def Test(self):
    #     message = {}
    #     message['PacketType'] = PacketTypes.PT_DEVICE_COMMAND.value
    #     message['Type'] = ''
    #     message['Service'] = 'config'
    #     message['Id']=1021
    #     parameters = {}
    #     parameters['id'] = 16
    #     parameters['arguments'] = 'Asia/Tokyo'
    #     message['Parameters'] = parameters
    #     self.ExecuteMessage(message)
    #     message = {}
    #     message['PacketType'] = PacketTypes.PT_DEVICE_COMMAND.value
    #     message['Type'] = ''
    #     message['Service'] = 'config'
    #     message['Id']=1021
    #     parameters = {}
    #     parameters['id'] = 15
    #     parameters['arguments'] = ''
    #     message['Parameters'] = parameters
    #     self.ExecuteMessage(message)
    #     message = {}
    #     message['PacketType'] = PacketTypes.PT_DEVICE_COMMAND.value
    #     message['Type'] = ''
    #     message['Service'] = 'config'
    #     message['Id']=1021
    #     parameters = {}
    #     parameters['id'] = 0
    #     parameters['arguments'] = 'test'
    #     message['Parameters'] = parameters
    #     self.ExecuteMessage(message)
    def FirstRun(self):
        # self.BuildPT_LOCATION()
        self.BuildPT_SYSTEM_INFO()
        # data = {}
        # data['MachineName'] = self.MachineId
        # data['Timestamp'] = int(time())
        # data['PacketType'] = PacketTypes.PT_UTILIZATION.value
        # self.processManager.RefreshProcessManager()
        # data['VisibleMemory'] = 1000000
        # data['AvailableMemory'] = 100000
        # data['AverageProcessorUsage'] = 20
        # data['PeakProcessorUsage'] = 98
        # data['AverageMemoryUsage'] = 30
        # data['PeakMemoryUsage'] = 99
        # data['PercentProcessorTime'] = 80
        # self.EnqueuePacket(data)
        # data['PacketType'] = PacketTypes.PT_PROCESS_LIST.value
        # self.EnqueuePacket(data)
        # data['PacketType'] = PacketTypes.PT_DRIVE_LIST.value
        # self.EnqueuePacket(data)
        # data['PacketType'] = PacketTypes.PT_PRINTER_INFO.value
        # self.EnqueuePacket(data)
        self.RequestSchedules()
        # self.BuildPT_LOCATION()
        self.OnUpdateConfig()

    def BuildPT_LOCATION(self):
        data = {}
        data['position'] = {}
        data['position']['latitude'] = '30.022112'
        data['position']['longitude'] = '45.022112'
        data['position']['accuracy'] = '20'
        data['position']['method'] = 'Windows location provider'
        data['provider'] = 'other'
        data['time'] = int(time())
        data['PacketType'] = PacketTypes.PT_LOCATION.value
        data['MachineName'] = self.MachineId
        self.EnqueuePacket(data)

    def BuildPT_UTILIZATION(self):
        #debug('BuildPT_UTILIZATION')
        data = {}
        data['MachineName'] = self.MachineId
        data['Timestamp'] = int(time())
        data['PacketType'] = PacketTypes.PT_UTILIZATION.value
        self.processManager.RefreshProcessManager()
        data['VisibleMemory'] = self.processManager.VisibleMemory
        data['AvailableMemory'] = self.processManager.AvailableMemory
        data[
            'AverageProcessorUsage'] = self.processManager.AverageProcessorUsage
        data['PeakProcessorUsage'] = self.processManager.PeakProcessorUsage
        data['AverageMemoryUsage'] = self.processManager.AverageMemoryUsage
        data['PeakMemoryUsage'] = self.processManager.AverageMemoryUsage
        data['PercentProcessorTime'] = self.processManager.PercentProcessorTime
        self.EnqueuePacket(data)

    def OnDataChanged(self, raspberryValue):
        data = {}
        data['MachineName'] = self.MachineId
        data['PacketType'] = PacketTypes.PT_DATA_CHANGED.value
        data['Timestamp'] = int(time())
        data['RaspberryInfo'] = raspberryValue
        self.EnqueuePacket(data)
        del data
        del raspberryValue

    def BuildPT_SYSTEM_INFO(self):
        try:
            data = {}
            data['MachineName'] = self.MachineId
            data['PacketType'] = PacketTypes.PT_SYSTEM_INFO.value
            data['Timestamp'] = int(time())
            data['IpAddress'] = self.PublicIP
            data['GatewayMACAddress'] = self.hardware.getMac()
            raspberryValue = {}
            raspberryValue['NetworkSpeed'] = str(
                self.downloadSpeed.getDownloadSpeed())
            raspberryValue['AntiVirus'] = 'None'
            raspberryValue['Firewall'] = 'iptables'
            raspberryValue['FirewallEnabled'] = 'true'
            raspberryValue['ComputerMake'] = self.hardware.getManufacturer()
            raspberryValue['ComputerModel'] = self.hardware.getModel()
            raspberryValue['OsName'] = self.oSInfo.ID
            raspberryValue['OsBuild'] = self.oSInfo.ID_LIKE if hasattr(
                self.oSInfo, 'ID_LIKE') else self.oSInfo.ID
            raspberryValue['OsArchitecture'] = self.hardware.Revision
            raspberryValue['OsVersion'] = self.oSInfo.VERSION_ID
            raspberryValue['ComputerName'] = self.machineName
            raspberryValue['AgentVersion'] = self.config.get(
                'Agent', 'Version', fallback='1.0.1.0')
            raspberryValue['InstallDate'] = self.installDate
            raspberryValue['GatewayMACAddress'] = self.hardware.getMac()
            with self.sensorsClient.sensorMutex:
                raspberryValue[
                    'SystemInfo'] = self.sensorsClient.currentSystemInfo
                raspberryValue[
                    'SensorsInfo'] = self.sensorsClient.currentSensorsInfo
                raspberryValue['BusInfo'] = self.sensorsClient.currentBusInfo
            raspberryValue['OsSettings'] = RaspiConfig.getConfig()
            raspberryValue['NetworkId'] = WifiManager.Network.GetNetworkId()
            raspberryValue['WifiStatus'] = self.wifiManager.GetStatus()
            try:
                history = History()
                history.SaveAverages(raspberryValue)
            except:
                exception('History error')
            data['RaspberryInfo'] = raspberryValue
            self.EnqueuePacket(data)
            logJson('PT_SYSTEM_INFO: ' + dumps(data), 'PT_SYSTEM_INFO')
            del raspberryValue
            del data
            data = None
        except Exception as e:
            exception('ThreadSystemInfo unexpected error: ' + str(e))
        Debug()

    def BuildPT_STARTUP_APPLICATIONS(self):
        ThreadPool.Submit(self.ThreadServiceManager)

    def ThreadServiceManager(self):
        self.serviceManager.Run()
        sleep(GENERAL_SLEEP_THREAD)
        data = {}
        data['MachineName'] = self.MachineId
        data['PacketType'] = PacketTypes.PT_STARTUP_APPLICATIONS.value
        data['ProcessList'] = self.serviceManager.GetServiceList()
        self.EnqueuePacket(data)

    def BuildPT_PROCESS_LIST(self):
        ThreadPool.Submit(self.ThreadProcessManager)

    def ThreadProcessManager(self):
        self.processManager.Run()
        sleep(GENERAL_SLEEP_THREAD)
        data = {}
        data['MachineName'] = self.MachineId
        data['PacketType'] = PacketTypes.PT_PROCESS_LIST.value
        data['ProcessList'] = self.processManager.GetProcessList()
        self.EnqueuePacket(data)

    def ProcessPT_KILL_PROCESS(self, message):
        #debug('ProcessPT_KILL_PROCESS')
        pid = message['Pid']
        retVal = self.processManager.KillProcess(int(pid))
        data = {}
        data['MachineName'] = self.MachineId
        data['PacketType'] = PacketTypes.PT_AGENT_MESSAGE.value
        data['Type'] = 'Info'
        if retVal:
            data['Message'] = 'Process Killed!'
        else:
            data['Message'] = 'Process not Killed!'
        self.EnqueuePacket(data)

    def CheckSubscription(self):
        inviteCode = self.config.get('Agent', 'InviteCode')
        cayenneApiClient = CayenneApiClient(self.CayenneApiHost)
        authId = cayenneApiClient.loginDevice(inviteCode)
        if authId == None:
            error(
                'Registration failed for invite code {}, closing the process'.
                format(inviteCode))
            Daemon.Exit()
        else:
            info('Registration succeeded for invite code {}, auth id = {}'.
                 format(inviteCode, authId))
            self.config.set('Agent', 'Initialized', 'true')
            self.MachineId = authId

    @property
    def Start(self):
        #debug('Start')
        if self.connected:
            ret = False
            error('Start already connected')
        else:
            info('Connecting to: {}:{}'.format(self.HOST, self.PORT))
            count = 0
            with self.mutex:
                count += 1
                while self.connected == False and count < 30:
                    try:
                        self.sock = None
                        self.wrappedSocket = None
                        ##debug('Start wrap_socket')
                        self.sock = socket(AF_INET, SOCK_STREAM)
                        #self.wrappedSocket = wrap_socket(self.sock, ca_certs="/etc/myDevices/ca.crt", cert_reqs=CERT_REQUIRED)
                        self.wrappedSocket = wrap_socket(self.sock)
                        self.wrappedSocket.connect((self.HOST, self.PORT))
                        info('myDevices cloud connected')
                        self.connected = True
                    except socket_error as serr:
                        Daemon.OnFailure('cloud', serr.errno)
                        error('Start failed: ' + str(self.HOST) + ':' +
                              str(self.PORT) + ' Error:' + str(serr))
                        self.connected = False
                        sleep(30 - count)
        return self.connected

    def Stop(self):
        #debug('Stop started')
        Daemon.Reset('cloud')
        ret = True
        if self.connected == False:
            ret = False
            error('Stop not connected')
        else:
            with self.mutex:
                try:
                    self.wrappedSocket.shutdown(SHUT_RDWR)
                    self.wrappedSocket.close()
                    info('myDevices cloud disconnected')
                except socket_error as serr:
                    debug(str(serr))
                    error('myDevices cloud disconnected error:' + str(serr))
                    ret = False
                self.connected = False
        #debug('Stop finished')
        return ret

    def Restart(self):
        if not self.exiting:
            debug('Restarting cycle...')
            sleep(1)
            self.Stop()
            self.Start

    def SendMessage(self, message):
        logJson(message, 'SendMessage')
        ret = True
        if self.connected == False:
            error('SendMessage fail')
            ret = False
        else:
            try:
                data = bytes(message, 'UTF-8')
                max_size = 16383
                if len(data) > max_size:
                    start = 0
                    current = max_size
                    end = len(data)
                    self.wrappedSocket.send(data[start:current])
                    while current < end:
                        start = current
                        current = start + max_size if start + max_size < end else end
                        self.wrappedSocket.send(data[start:current])
                else:
                    self.wrappedSocket.send(data)
                if self.onMessageSent:
                    self.onMessageSent(message)
                message = None
            except socket_error as serr:
                error('SendMessage:' + str(serr))
                ret = False
                Daemon.OnFailure('cloud', serr.errno)
                sleep(1)
            except IOError as ioerr:
                debug('IOError: ' + str(ioerr))
                self.Restart()
                #Daemon.OnFailure('cloud', ioerr.errno)
            except socket_error as serr:
                Daemon.OnFailure('cloud', serr.errno)
            except:
                exception('SendMessage error')
        return ret

    def CheckJson(self, message):
        try:
            test = loads(message)
        except ValueError:
            return False
        return True

    def ReadMessage(self):
        ret = True
        if self.connected == False:
            ret = False
        else:
            try:
                self.count = 4096
                timeout_in_seconds = 10
                ready = select([self.wrappedSocket], [], [],
                               timeout_in_seconds)
                if ready[0]:
                    message = self.wrappedSocket.recv(self.count).decode()
                    buffering = len(message) == 4096
                    while buffering and message:
                        if self.CheckJson(message):
                            buffering = False
                        else:
                            more = self.wrappedSocket.recv(self.count).decode()
                            if not more:
                                buffering = False
                            else:
                                message += more
                    try:
                        if message:
                            messageObject = loads(message)
                            del message
                            self.readQueue.put(messageObject)
                        else:
                            error('ReadMessage received empty message string')
                    except:
                        exception('ReadMessage error: ' + str(message))
                        return False
                    Daemon.Reset('cloud')
            except IOError as ioerr:
                debug('IOError: ' + str(ioerr))
                self.Restart()
                #Daemon.OnFailure('cloud', ioerr.errno)
            except socket_error as serr:
                Daemon.OnFailure('cloud', serr.errno)
            except:
                exception('ReadMessage error')
                ret = False
                sleep(1)
                Daemon.OnFailure('cloud')
        return ret

    def RunAction(self, action):
        #execute action in machine
        debug('RunAction')
        if 'MachineName' in action and self.MachineId != action['MachineName']:
            debug('Scheduler action is not assigned for this machine: ' +
                  str(action))
            return
        self.ExecuteMessage(action)

    def SendNotification(self, notify, subject, body):
        info('SendNotification: ' + str(notify) + ' ' + str(subject) + ' ' +
             str(body))
        try:
            data = {}
            data['PacketType'] = PacketTypes.PT_NOTIFICATION.value
            data['MachineName'] = self.MachineId
            data['Subject'] = subject
            data['Body'] = body
            data['Notify'] = notify
            self.EnqueuePacket(data)
        except:
            debug('')
            return False
        return True

    def ProcessMessage(self):
        try:
            messageObject = self.readQueue.get(False)
            if not messageObject:
                return False
        except Empty:
            return False
        with self.mutex:
            retVal = self.CheckPT_ACK(messageObject)
        if retVal:
            return
        self.ExecuteMessage(messageObject)

    def CheckPT_ACK(self, messageObject):
        try:
            packetType = int(messageObject['PacketType'])
            if packetType == PacketTypes.PT_ACK.value:
                self.lastPing = time()
                return True
        except:
            debug('')
            error('CheckPT_ACK failure: ' + str(messageObject))
        return False

    def ExecuteMessage(self, messageObject):
        if not messageObject:
            return
        info("ExecuteMessage: " + str(messageObject['PacketType']))
        packetType = int(messageObject['PacketType'])
        if packetType == PacketTypes.PT_UTILIZATION.value:
            self.BuildPT_UTILIZATION()
            info(PacketTypes.PT_UTILIZATION)
            return
        if packetType == PacketTypes.PT_SYSTEM_INFO.value:
            self.BuildPT_SYSTEM_INFO()
            info(PacketTypes.PT_SYSTEM_INFO)
            return
        if packetType == PacketTypes.PT_UNINSTALL_AGENT.value:
            command = "sudo /etc/myDevices/uninstall/uninstall.sh"
            services.ServiceManager.ExecuteCommand(command)
            return
        if packetType == PacketTypes.PT_STARTUP_APPLICATIONS.value:
            self.BuildPT_STARTUP_APPLICATIONS()
            info(PacketTypes.PT_STARTUP_APPLICATIONS)
            return
        if packetType == PacketTypes.PT_PROCESS_LIST.value:
            self.BuildPT_PROCESS_LIST()
            info(PacketTypes.PT_PROCESS_LIST)
            return
        if packetType == PacketTypes.PT_KILL_PROCESS.value:
            self.ProcessPT_KILL_PROCESS(messageObject)
            info(PacketTypes.PT_KILL_PROCESS)
            return
        if packetType == PacketTypes.PT_INITIALIZED.value:
            #self.libMYOPX.SetSubscription(messageObject)
            info(PacketTypes.PT_INITIALIZED)
            return
        if packetType == PacketTypes.PT_PRODUCT_INFO.value:
            self.config.set('Subscription', 'ProductCode',
                            messageObject['ProductCode'])
            info(PacketTypes.PT_PRODUCT_INFO)
            return
        if packetType == PacketTypes.PT_START_RDS_LOCAL_INIT.value:
            error('PT_START_RDS_LOCAL_INIT not implemented')
            info(PacketTypes.PT_START_RDS_LOCAL_INIT)
            return
        if packetType == PacketTypes.PT_RESTART_COMPUTER.value:
            info(PacketTypes.PT_RESTART_COMPUTER)
            data = {}
            data['PacketType'] = PacketTypes.PT_AGENT_MESSAGE.value
            data['MachineName'] = self.MachineId
            data['Message'] = 'Computer Restarted!'
            self.EnqueuePacket(data)
            command = "sudo shutdown -r now"
            services.ServiceManager.ExecuteCommand(command)
            return
        if packetType == PacketTypes.PT_SHUTDOWN_COMPUTER.value:
            info(PacketTypes.PT_SHUTDOWN_COMPUTER)
            data = {}
            data['PacketType'] = PacketTypes.PT_AGENT_MESSAGE.value
            data['MachineName'] = self.MachineId
            data['Message'] = 'Computer Powered Off!'
            self.EnqueuePacket(data)
            command = "sudo shutdown -h now"
            services.ServiceManager.ExecuteCommand(command)
            return
        if packetType == PacketTypes.PT_SUPPORTED_SENSORS.value:
            self.sensorsClient.SupportedSensorsUpdate(messageObject)
            info(PacketTypes.PT_SUPPORTED_SENSORS)
            return
        if packetType == PacketTypes.PT_MACHINE_SENSORS.value:
            self.sensorsClient.OnDbSensors(messageObject)
            info(PacketTypes.PT_MACHINE_SENSORS)
            return
        if packetType == PacketTypes.PT_AGENT_CONFIGURATION.value:
            info('PT_AGENT_CONFIGURATION: ' + str(messageObject.Data))
            self.config.setCloudConfig(messageObject.Data)
            return
        if packetType == PacketTypes.PT_ADD_SENSOR.value:
            try:
                info(PacketTypes.PT_ADD_SENSOR)
                parameters = None
                deviceName = None
                deviceClass = None
                description = None
                #for backward compatibility check the DisplayName and overwrite it over the other variables
                displayName = None
                if 'DisplayName' in messageObject:
                    displayName = messageObject['DisplayName']

                if 'Parameters' in messageObject:
                    parameters = messageObject['Parameters']

                if 'DeviceName' in messageObject:
                    deviceName = messageObject['DeviceName']
                else:
                    deviceName = displayName

                if 'Description' in messageObject:
                    description = messageObject['Description']
                else:
                    description = deviceName

                if 'Class' in messageObject:
                    deviceClass = messageObject['Class']

                retValue = True
                retValue = self.sensorsClient.AddSensor(
                    deviceName, description, deviceClass, parameters)
            except Exception as ex:
                exception("PT_ADD_SENSOR Unexpected error" + str(ex))
                retValue = False
            data = {}
            if 'Id' in messageObject:
                data['Id'] = messageObject['Id']
            #0 - None, 1 - Pending, 2-Success, 3 - Not responding, 4 - Failure
            if retValue:
                data['State'] = 2
            else:
                data['State'] = 4
            data['PacketType'] = PacketTypes.PT_UPDATE_SENSOR.value
            data['MachineName'] = self.MachineId
            self.EnqueuePacket(data)
            return
        if packetType == PacketTypes.PT_REMOVE_SENSOR.value:
            try:
                info(PacketTypes.PT_REMOVE_SENSOR)
                retValue = False
                if 'Name' in messageObject:
                    Name = messageObject['Name']
                    retValue = self.sensorsClient.RemoveSensor(Name)
                data = {}
                data['Name'] = Name
                data['PacketType'] = PacketTypes.PT_REMOVE_SENSOR.value
                data['MachineName'] = self.MachineId
                data['Response'] = retValue
                self.EnqueuePacket(data)
            except Exception as ex:
                exception("PT_REMOVE_SENSOR Unexpected error" + str(ex))
                retValue = False
            return
        if packetType == PacketTypes.PT_DEVICE_COMMAND.value:
            info(PacketTypes.PT_DEVICE_COMMAND)
            self.ProcessDeviceCommand(messageObject)
            return
        if packetType == PacketTypes.PT_ADD_SCHEDULE.value:
            info(PacketTypes.PT_ADD_SCHEDULE.value)
            retVal = self.schedulerEngine.AddScheduledItem(messageObject, True)
            if 'Update' in messageObject:
                messageObject['Update'] = messageObject['Update']
            messageObject['PacketType'] = PacketTypes.PT_ADD_SCHEDULE.value
            messageObject['MachineName'] = self.MachineId
            messageObject['Status'] = str(retVal)
            self.EnqueuePacket(messageObject)
            return
        if packetType == PacketTypes.PT_REMOVE_SCHEDULE.value:
            info(PacketTypes.PT_REMOVE_SCHEDULE)
            retVal = self.schedulerEngine.RemoveScheduledItem(messageObject)
            messageObject['PacketType'] = PacketTypes.PT_REMOVE_SCHEDULE.value
            messageObject['MachineName'] = self.MachineId
            messageObject['Status'] = str(retVal)
            self.EnqueuePacket(messageObject)
            return
        if packetType == PacketTypes.PT_GET_SCHEDULES.value:
            info(PacketTypes.PT_GET_SCHEDULES)
            schedulesJson = self.schedulerEngine.GetSchedules()
            data['Schedules'] = schedulesJson
            data['PacketType'] = PacketTypes.PT_GET_SCHEDULES.value
            data['MachineName'] = self.MachineId
            self.EnqueuePacket(data)
            return
        if packetType == PacketTypes.PT_UPDATE_SCHEDULES.value:
            info(PacketTypes.PT_UPDATE_SCHEDULES)
            retVal = self.schedulerEngine.UpdateSchedules(messageObject)
            return
        if packetType == PacketTypes.PT_HISTORY_DATA_RESPONSE.value:
            info(PacketTypes.PT_HISTORY_DATA_RESPONSE)
            try:
                id = messageObject['Id']
                history = History()
                if messageObject['Status']:
                    history.Sent(True, self.sentHistoryData[id]['HistoryData'])
                    self.historySendFails = 0
                else:
                    history.Sent(False,
                                 self.sentHistoryData[id]['HistoryData'])
                    self.historySendFails += 1
                del self.sentHistoryData[id]
            except:
                exception('Processing history response packet failed')
            return
        info("Skipping not required packet: " + str(packetType))

    def ProcessDeviceCommand(self, messageObject):
        #     t1 = Thread(target=self.ThreadDeviceCommand)
        #     t1.start()
        # def ThreadDeviceCommand(self):
        commandType = messageObject['Type']
        commandService = messageObject['Service']
        parameters = messageObject['Parameters']
        info('PT_DEVICE_COMMAND: ' + dumps(messageObject))
        debug('ProcessDeviceCommand: ' + commandType + ' ' + commandService +
              ' ' + str(parameters))
        id = messageObject['Id']
        sensorId = None
        if 'SensorId' in messageObject:
            sensorId = messageObject['SensorId']
        data = {}
        retValue = ''
        if commandService == 'wifi':
            if commandType == 'status':
                retValue = self.wifiManager.GetStatus()
            if commandType == 'scan':
                retValue = self.wifiManager.GetWirelessNetworks()
            if commandType == 'setup':
                try:
                    ssid = parameters["ssid"]
                    password = parameters["password"]
                    interface = parameters["interface"]
                    retValue = self.wifiManager.Setup(ssid, password,
                                                      interface)
                except:
                    retValue = False
        if commandService == 'services':
            serviceName = parameters['ServiceName']
            if commandType == 'status':
                retValue = self.serviceManager.Status(serviceName)
            if commandType == 'start':
                retValue = self.serviceManager.Start(serviceName)
            if commandType == 'stop':
                retValue = self.serviceManager.Stop(serviceName)
        if commandService == 'sensor':
            debug('SENSOR_COMMAND processing: ' + str(parameters))
            method = None
            channel = None
            value = None
            driverClass = None
            sensorType = None
            sensorName = None
            if 'SensorName' in parameters:
                sensorName = parameters["SensorName"]
            if 'DriverClass' in parameters:
                driverClass = parameters["DriverClass"]
            if commandType == 'enable':
                sensor = None
                enable = None
                if 'Sensor' in parameters:
                    sensor = parameters["Sensor"]
                if 'Enable' in parameters:
                    enable = parameters["Enable"]
                retValue = self.sensorsClient.EnableSensor(sensor, enable)
            else:
                if commandType == 'edit':
                    description = sensorName
                    device = None
                    if "Description" in parameters:
                        description = parameters["Description"]
                    if "Args" in parameters:
                        args = parameters["Args"]
                    retValue = self.sensorsClient.EditSensor(
                        sensorName, description, driverClass, args)
                else:
                    if 'Channel' in parameters:
                        channel = parameters["Channel"]
                    if 'Method' in parameters:
                        method = parameters["Method"]
                    if 'Value' in parameters:
                        value = parameters["Value"]
                    if 'SensorType' in parameters:
                        sensorType = parameters["SensorType"]
                        #(self, commandType, sensorName, sensorType, driverClass, method, channel, value):
                    retValue = self.sensorsClient.SensorCommand(
                        commandType, sensorName, sensorType, driverClass,
                        method, channel, value)
        if commandService == 'gpio':
            method = parameters["Method"]
            channel = parameters["Channel"]
            value = parameters["Value"]
            debug('ProcessDeviceCommand: ' + commandService + ' ' + method +
                  ' ' + str(channel) + ' ' + str(value))
            retValue = str(
                self.sensorsClient.GpioCommand(commandType, method, channel,
                                               value))
            debug('ProcessDeviceCommand gpio returned value: ' + retValue)
        if commandService == 'config':
            try:
                config_id = parameters["id"]
                arguments = parameters["arguments"]
                (retValue, output) = RaspiConfig.Config(config_id, arguments)
                data["Output"] = output
                retValue = str(retValue)
            except:
                exception("Exception on config")
        data['Response'] = retValue
        data['Id'] = id
        data['PacketType'] = PacketTypes.PT_DEVICE_COMMAND_RESPONSE.value
        data['MachineName'] = self.MachineId
        info('PT_DEVICE_COMMAND_RESPONSE: ' + dumps(data))
        if sensorId:
            data['SensorId'] = sensorId
        self.EnqueuePacket(data)
        #if commandService == 'processes': #Kill command is handled with PT_KILL_PROCESS
    def EnqueuePacket(self, message):
        message['PacketTime'] = GetTime()
        #datetime.now().strftime("%Y-%m-%dT%H:%M:%S%z")
        json_data = dumps(message) + '\n'
        message = None
        #debug(json_data)
        self.writeQueue.put(json_data)

    def DequeuePacket(self):
        packet = None
        try:
            packet = self.writeQueue.get()
        except Empty:
            packet = None
        return packet

    def CheckConnectionAndPing(self):
        ticksStart = time()
        with self.mutex:
            try:
                if (ticksStart - self.lastPing > self.pingTimeout):
                    #debug('CheckConnectionAndPing EXPIRED - trying to reconnect')
                    self.Stop()
                    self.Start
                    self.lastPing = time() - self.pingRate - 1
                    warn(
                        'Restarting cloud connection -> CheckConnectionAndPing EXPIRED: '
                        + str(self.lastPing))
                if (ticksStart - self.waitPing >= self.pingRate):
                    #debug("CheckConnectionAndPing sending ACK packet")
                    self.SendAckPacket()
            except:
                debug('')
                error('CheckConnectionAndPing error')

    def SendAckPacket(self):
        data = {}
        debug('Last ping: ' + str(self.lastPing) + ' Wait ping: ' +
              str(self.waitPing))
        data['MachineName'] = self.MachineId
        data['IPAddress'] = self.PublicIP
        data['PacketType'] = PacketTypes.PT_ACK.value
        self.EnqueuePacket(data)
        self.waitPing = time()

    def RequestSchedules(self):
        data = {}
        data['MachineName'] = self.MachineId
        data['Stored'] = "dynamodb"
        data['PacketType'] = PacketTypes.PT_REQUEST_SCHEDULES.value
        self.EnqueuePacket(data)

    def SendHistoryData(self):
        try:
            info('SendHistoryData start')
            history = History()
            history.Reset()
            while True:
                try:
                    #If there is no acknowledgment after a minute we assume failure
                    sendFailed = [
                        key for key, item in self.sentHistoryData.items()
                        if (item['Timestamp'] + 60) < time()
                    ]
                    info('SendHistoryData previously SendFailed items: ' +
                         str(sendFailed))
                    for id in sendFailed:
                        self.historySendFails += len(sendFailed)
                        history.Sent(False,
                                     self.sentHistoryData[id]['HistoryData'])
                        del self.sentHistoryData[id]
                    historyData = history.GetHistoricalData()
                    if historyData:
                        data = {}
                        info('SendHistoryData historyData: ' +
                             str(historyData))
                        data['MachineName'] = self.MachineId
                        data['Timestamp'] = int(time())
                        data['PacketType'] = PacketTypes.PT_HISTORY_DATA.value
                        id = sha256(
                            dumps(historyData).encode('utf8')).hexdigest()
                        data['Id'] = id
                        data['HistoryData'] = historyData
                        info('Sending history data, id = {}'.format(id))
                        debug('SendHistoryData historyData: ' + str(data))
                        self.EnqueuePacket(data)
                        #this will keep acumulating
                        self.sentHistoryData[id] = data
                except Exception as ex:
                    exception('SendHistoryData error' + str(ex))
                delay = 60
                if self.historySendFails > 2:
                    delay = 120
                if self.historySendFails > 4:
                    #Wait an hour if we keep getting send failures.
                    delay = 3600
                    self.historySendFails = 0
                sleep(delay)
        except Exception as ex:
            exception('SendHistoryData general exception: ' + str(ex))
Exemplo n.º 8
0
class CloudServerClient:
    """Class to connect to the server and send and receive data"""
    def __init__(self, host, port, cayenneApiHost):
        """Initialize the client configuration"""
        self.HOST = host
        self.PORT = port
        self.CayenneApiHost = cayenneApiHost
        self.config = Config(APP_SETTINGS)
        self.networkConfig = Config(NETWORK_SETTINGS)
        self.username = self.config.get('Agent', 'Username', None)
        self.password = self.config.get('Agent', 'Password', None)
        self.clientId = self.config.get('Agent', 'ClientID', None)
        self.location = self.config.get('Agent', 'Location', "house0@room0@")
        self.mqtt_dis_prefix = self.config.get('Agent', 'MQTT_DIS_PREFIX',
                                               "homeassistant")
        self.connected = False
        self.exiting = Event()

        self.sensorsClient = None

    def __del__(self):
        """Delete the client"""
        self.Destroy()

    def Start(self):
        """Connect to server and start background threads"""
        try:
            self.installDate = None
            try:
                self.installDate = self.config.get('Agent',
                                                   'InstallDate',
                                                   fallback=None)
            except:
                pass
            if not self.installDate:
                self.installDate = int(time())
                self.config.set('Agent', 'InstallDate', self.installDate)
            if not self.username and not self.password and not self.clientId:
                self.CheckSubscription()
            if not self.Connect():
                error('Error starting agent')
                return
            self.schedulerEngine = SchedulerEngine(self, 'client_scheduler')

            self.readQueue = Queue()
            self.writeQueue = Queue()
            self.hardware = Hardware()
            self.oSInfo = OSInfo()
            self.count = 10000
            self.buff = bytearray(self.count)

            self.writerThread = WriterThread('writer', self)
            self.writerThread.start()
            self.processorThread = ProcessorThread('processor', self)
            self.processorThread.start()
            self.systemInfo = []
            TimerThread(self.SendSystemInfo, 300)

            self.sensorsClient = sensors.SensorsClient(self)
            self.sensorsClient.SetDataChanged(self.OnDataChanged)
            # TimerThread(self.SendSystemState, 30, 5)
            self.updater = Updater(self.config)
            self.updater.start()
            events = self.schedulerEngine.get_scheduled_events()
            self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC)
            # self.sentHistoryData = {}
            # self.historySendFails = 0
            # self.historyThread = Thread(target=self.SendHistoryData)
            # self.historyThread.setDaemon(True)
            # self.historyThread.start()
        except Exception as e:
            exception('Initialize error: ' + str(e))

    def Destroy(self):
        """Destroy client and stop client threads"""
        info('Shutting down client')
        self.exiting.set()
        if hasattr(self, 'sensorsClient'):
            self.sensorsClient.StopMonitoring()
        if hasattr(self, 'schedulerEngine'):
            self.schedulerEngine.stop()
        if hasattr(self, 'updater'):
            self.updater.stop()
        if hasattr(self, 'writerThread'):
            self.writerThread.stop()
        if hasattr(self, 'processorThread'):
            self.processorThread.stop()
        ThreadPool.Shutdown()
        self.Disconnect()
        info('Client shut down')

    def OnDataChanged(self, data):
        """Enqueue a packet containing changed system data to send to the server"""
        try:
            if len(data) > 15:
                items = [
                    {
                        item['channel']: item['value']
                    } for item in data
                    if not item['channel'].startswith(cayennemqtt.SYS_GPIO)
                ]
                info('Send changed data: {} + {}'.format(
                    items, cayennemqtt.SYS_GPIO))

            else:
                info('Send changed data: {}'.format([{
                    item['channel']:
                    item['value']
                } for item in data]))
            # items = {}
            # gpio_items = {}
            # for item in data:
            #     if not item['channel'].startswith(cayennemqtt.SYS_GPIO):
            #         items[item['channel']] = item['value']
            #     else:
            #         channel = item['channel'].replace(cayennemqtt.SYS_GPIO + ':', '').split(';')
            #         if not channel[0] in gpio_items:
            #             gpio_items[channel[0]] = str(item['value'])
            #         else:
            #             gpio_items[channel[0]] += ',' + str(item['value'])
            # info('Send changed data: {}, {}: {}'.format(items, cayennemqtt.SYS_GPIO, gpio_items))
        except:
            info('Send changed data')
            pass

        self.EnqueuePacket(data)

    def SendSystemInfo(self):
        """Enqueue a packet containing system info to send to the server"""
        try:
            currentSystemInfo = []
            cayennemqtt.DataChannel.add(currentSystemInfo,
                                        cayennemqtt.SYS_OS_NAME,
                                        value=self.oSInfo.ID)
            cayennemqtt.DataChannel.add(currentSystemInfo,
                                        cayennemqtt.SYS_OS_VERSION,
                                        value=self.oSInfo.VERSION_ID)
            cayennemqtt.DataChannel.add(currentSystemInfo,
                                        cayennemqtt.AGENT_VERSION,
                                        value=self.config.get(
                                            'Agent', 'Version', __version__))
            cayennemqtt.DataChannel.add(currentSystemInfo,
                                        cayennemqtt.SYS_POWER_RESET,
                                        value=0)
            cayennemqtt.DataChannel.add(currentSystemInfo,
                                        cayennemqtt.SYS_POWER_HALT,
                                        value=0)
            config = SystemConfig.getConfig()
            if config:
                channel_map = {
                    'I2C': cayennemqtt.SYS_I2C,
                    'SPI': cayennemqtt.SYS_SPI,
                    'Serial': cayennemqtt.SYS_UART,
                    'OneWire': cayennemqtt.SYS_ONEWIRE,
                    'DeviceTree': cayennemqtt.SYS_DEVICETREE
                }
                for key, channel in channel_map.items():
                    try:
                        cayennemqtt.DataChannel.add(currentSystemInfo,
                                                    channel,
                                                    value=config[key])
                    except:
                        pass
            if currentSystemInfo != self.systemInfo:
                data = currentSystemInfo
                if self.systemInfo:
                    data = [x for x in data if x not in self.systemInfo]
                if data:
                    self.systemInfo = currentSystemInfo
                    info('Send system info: {}'.format([{
                        item['channel']:
                        item['value']
                    } for item in data]))
                    self.EnqueuePacket(data)
        except Exception:
            exception('SendSystemInfo unexpected error')

    def CheckSubscription(self):
        """Check that an invite code is valid"""
        inviteCode = self.config.get('Agent', 'InviteCode', fallback=None)
        if not inviteCode:
            error('No invite code found in {}'.format(self.config.path))
            print(
                'Please input an invite code. This can be retrieved from the Cayenne dashboard by adding a new Raspberry Pi device.\n'
                'The invite code will be part of the script name shown there: rpi_[invitecode].sh.'
            )
            inviteCode = input('Invite code: ')
            if inviteCode:
                self.config.set('Agent', 'InviteCode', inviteCode)
            else:
                print('No invite code set, exiting.')
                raise SystemExit
        inviteCode = self.config.get('Agent', 'InviteCode')
        cayenneApiClient = CayenneApiClient(self.CayenneApiHost)
        credentials = cayenneApiClient.loginDevice(inviteCode)
        if credentials == None:
            error(
                'Registration failed for invite code {}, closing the process'.
                format(inviteCode))
            raise SystemExit
        else:
            info('Registration succeeded for invite code {}, credentials = {}'.
                 format(inviteCode, credentials))
            self.config.set('Agent', 'Initialized', 'true')
            try:
                self.username = credentials['mqtt']['username']
                self.password = credentials['mqtt']['password']
                self.clientId = credentials['mqtt']['clientId']
                self.config.set('Agent', 'Username', self.username)
                self.config.set('Agent', 'Password', self.password)
                self.config.set('Agent', 'ClientID', self.clientId)
            except:
                exception('Invalid credentials, closing the process')
                raise SystemExit

    def Connect(self):
        """Connect to the server"""
        self.connected = False
        count = 0
        while self.connected == False and count < 30 and not self.exiting.is_set(
        ):
            try:
                self.mqttClient = cayennemqtt.CayenneMQTTClient()
                self.mqttClient.on_message = self.OnMessage
                self.mqttClient.begin(self.username, self.password,
                                      self.clientId, self.HOST, self.PORT)
                self.mqttClient.loop_start()
                self.connected = True
            except OSError as oserror:
                Daemon.OnFailure('cloud', oserror.errno)
                error('Connect failed: ' + str(self.HOST) + ':' +
                      str(self.PORT) + ' Error:' + str(oserror))
                if self.exiting.wait(30):
                    # If we are exiting return immediately
                    return self.connected
                count += 1
        return self.connected

    def Disconnect(self):
        """Disconnect from the server"""
        Daemon.Reset('cloud')
        try:
            if hasattr(self, 'mqttClient'):
                self.mqttClient.loop_stop()
                info('myDevices cloud disconnected')
        except:
            exception('Error stopping client')

    def Restart(self):
        """Restart the server connection"""
        if not self.exiting.is_set():
            debug('Restarting cycle...')
            sleep(1)
            self.Disconnect()
            self.Connect()

    def CheckJson(self, message):
        """Check if a JSON message is valid"""
        try:
            test = loads(message)
        except ValueError:
            return False
        return True

    def OnMessage(self, message):
        """Add message from the server to the queue"""
        info('OnMessage: {}'.format(message))
        self.readQueue.put(message)

    def RunAction(self, action):
        """Run a specified action"""
        debug('RunAction: {}'.format(action))
        result = True
        command = action.copy()
        self.mqttClient.transform_command(command)
        result = self.ExecuteMessage(command)
        return result

    def ProcessMessage(self):
        """Process a message from the server"""
        try:
            messageObject = self.readQueue.get(False)
            if not messageObject:
                return False
        except Empty:
            return False
        self.ExecuteMessage(messageObject)

    def ExecuteMessage(self, message):
        """Execute an action described in a message object
        
        Returns: True if action was executed, False otherwise."""
        result = False
        if not message:
            return result
        channel = message['channel']
        info('ExecuteMessage: {}'.format(message))
        if channel in (cayennemqtt.SYS_POWER_RESET,
                       cayennemqtt.SYS_POWER_HALT):
            result = self.ProcessPowerCommand(message)
        elif channel.startswith(cayennemqtt.DEV_SENSOR):
            result = self.ProcessSensorCommand(message)
        elif channel.startswith(cayennemqtt.SYS_GPIO):
            result = self.ProcessGpioCommand(message)
        elif channel == cayennemqtt.AGENT_DEVICES:
            result = self.ProcessDeviceCommand(message)
        elif channel in (cayennemqtt.SYS_I2C, cayennemqtt.SYS_SPI,
                         cayennemqtt.SYS_UART, cayennemqtt.SYS_ONEWIRE):
            result = self.ProcessConfigCommand(message)
        elif channel == cayennemqtt.AGENT_MANAGE:
            result = self.ProcessAgentCommand(message)
        elif channel == cayennemqtt.AGENT_SCHEDULER:
            result = self.ProcessSchedulerCommand(message)
        else:
            info('Unknown message')
        return result

    def ProcessPowerCommand(self, message):
        """Process command to reboot/shutdown the system
        
        Returns: True if command was processed, False otherwise."""
        error_message = None
        try:
            self.EnqueueCommandResponse(message, error_message)
            commands = {
                cayennemqtt.SYS_POWER_RESET: 'sudo shutdown -r now',
                cayennemqtt.SYS_POWER_HALT: 'sudo shutdown -h now'
            }
            if int(message['payload']) == 1:
                debug('Processing power command')
                data = []
                cayennemqtt.DataChannel.add(data, message['channel'], value=1)
                self.EnqueuePacket(data)
                self.writeQueue.join()
                info('Calling execute: {}'.format(
                    commands[message['channel']]))
                output, result = executeCommand(commands[message['channel']])
                debug('ProcessPowerCommand: {}, result: {}, output: {}'.format(
                    message, result, output))
                if result != 0:
                    error_message = 'Error executing shutdown command'
        except Exception as ex:
            error_message = '{}: {}'.format(type(ex).__name__, ex)
        if error_message:
            error(error_message)
            data = []
            cayennemqtt.DataChannel.add(data, message['channel'], value=0)
            self.EnqueuePacket(data)
            raise ExecuteMessageError(error_message)
        return error_message == None

    def ProcessAgentCommand(self, message):
        """Process command to manage the agent state
        
        Returns: True if command was processed, False otherwise."""
        error = None
        try:
            if message['suffix'] == 'uninstall':
                output, result = executeCommand(
                    'sudo -n /etc/myDevices/uninstall/uninstall.sh',
                    disablePipe=True)
                debug('ProcessAgentCommand: {}, result: {}, output: {}'.format(
                    message, result, output))
                if result != 0:
                    error = 'Error uninstalling agent'
            # elif message['suffix'] == 'config':
            #     for key, value in message['payload'].items():
            #         if value is None:
            #             info('Remove config item: {}'.format(key))
            #             self.config.remove('Agent', key)
            #         else:
            #             info('Set config item: {} {}'.format(key, value))
            #             self.config.set('Agent', key, value)
            else:
                error = 'Unknown agent command: {}'.format(message['suffix'])
        except Exception as ex:
            error = '{}: {}'.format(type(ex).__name__, ex)
        self.EnqueueCommandResponse(message, error)
        if error:
            raise ExecuteMessageError(error)
        return error == None

    def ProcessConfigCommand(self, message):
        """Process system configuration command
        
        Returns: True if command was processed, False otherwise."""
        error = None
        try:
            value = 1 - int(
                message['payload']
            )  #Invert the value since the config script uses 0 for enable and 1 for disable
            command_id = {
                cayennemqtt.SYS_I2C: 11,
                cayennemqtt.SYS_SPI: 12,
                cayennemqtt.SYS_UART: 13,
                cayennemqtt.SYS_ONEWIRE: 19
            }
            result, output = SystemConfig.ExecuteConfigCommand(
                command_id[message['channel']], value)
            debug('ProcessConfigCommand: {}, result: {}, output: {}'.format(
                message, result, output))
            if result != 0:
                error = 'Error executing config command'
        except Exception as ex:
            error = '{}: {}'.format(type(ex).__name__, ex)
        self.EnqueueCommandResponse(message, error)
        return error == None

    def ProcessGpioCommand(self, message):
        """Process GPIO command
        
        Returns: True if command was processed, False otherwise."""
        error = None
        try:
            channel = int(message['channel'].replace(
                cayennemqtt.SYS_GPIO + ':', ''))
            result = self.sensorsClient.GpioCommand(
                message.get('suffix', 'value'), channel, message['payload'])
            debug('ProcessGpioCommand result: {}'.format(result))
            if result == 'failure':
                error = 'GPIO command failed'
        except Exception as ex:
            error = '{}: {}'.format(type(ex).__name__, ex)
        self.EnqueueCommandResponse(message, error)
        return error == None

    def ProcessSensorCommand(self, message):
        """Process sensor command
        
        Returns: True if command was processed, False otherwise."""
        error = None
        try:
            sensor_info = message['channel'].replace(
                cayennemqtt.DEV_SENSOR + ':', '').split(':')
            sensor = sensor_info[0]
            channel = None
            if len(sensor_info) > 1:
                channel = sensor_info[1]
            result = self.sensorsClient.SensorCommand(
                message.get('suffix', 'value'), sensor, channel,
                message['payload'])
            debug('ProcessSensorCommand result: {}'.format(result))
            if result is False:
                error = 'Sensor command failed'
        except Exception as ex:
            error = '{}: {}'.format(type(ex).__name__, ex)
        self.EnqueueCommandResponse(message, error)
        return error == None

    def ProcessDeviceCommand(self, message):
        """Process a device command to add/edit/remove a sensor
        
        Returns: True if command was processed, False otherwise."""
        error = None
        try:
            payload = message['payload']
            info('ProcessDeviceCommand payload: {}'.format(payload))
            if message['suffix'] == 'add':
                result = self.sensorsClient.AddSensor(payload['sensorId'],
                                                      payload['description'],
                                                      payload['class'],
                                                      payload['args'])
            elif message['suffix'] == 'edit':
                result = self.sensorsClient.EditSensor(payload['sensorId'],
                                                       payload['description'],
                                                       payload['class'],
                                                       payload['args'])
            elif message['suffix'] == 'delete':
                result = self.sensorsClient.RemoveSensor(payload['sensorId'])
            else:
                error = 'Unknown device command: {}'.format(message['suffix'])
            debug('ProcessDeviceCommand result: {}'.format(result))
            if result is False:
                error = 'Device command failed'
        except Exception as ex:
            error = '{}: {}'.format(type(ex).__name__, ex)
        self.EnqueueCommandResponse(message, error)
        return error == None

    def ProcessSchedulerCommand(self, message):
        """Process command to add/edit/remove a scheduled action
        
        Returns: True if command was processed, False otherwise."""
        error = None
        try:
            if message['suffix'] == 'add':
                result = self.schedulerEngine.add_scheduled_event(
                    message['payload'], True)
            elif message['suffix'] == 'edit':
                result = self.schedulerEngine.update_scheduled_event(
                    message['payload'])
            elif message['suffix'] == 'delete':
                result = self.schedulerEngine.remove_scheduled_event(
                    message['payload'])
            elif message['suffix'] == 'get':
                events = self.schedulerEngine.get_scheduled_events()
                self.EnqueuePacket(events, cayennemqtt.JOBS_TOPIC)
            else:
                error = 'Unknown schedule command: {}'.format(
                    message['suffix'])
            debug('ProcessSchedulerCommand result: {}'.format(result))
            if result is False:
                error = 'Schedule command failed'
        except Exception as ex:
            error = '{}: {}'.format(type(ex).__name__, ex)
        self.EnqueueCommandResponse(message, error)
        return error == None

    def EnqueueCommandResponse(self, message, error):
        """Send response after processing a command message"""
        if not 'cmdId' in message:
            # If there is no command id we assume this is a scheduled command and don't send a response message.
            return
        debug('EnqueueCommandResponse error: {}'.format(error))
        if error:
            response = 'error,{}={}'.format(message['cmdId'], error)
        else:
            response = 'ok,{}'.format(message['cmdId'])
        info(response)
        self.EnqueuePacket(response, cayennemqtt.COMMAND_RESPONSE_TOPIC)

    def EnqueuePacket(self, message, topic=cayennemqtt.DATA_TOPIC):
        """Enqueue a message packet to send to the server"""
        packet = (topic, message)
        self.writeQueue.put(packet)

    def DequeuePacket(self):
        """Dequeue a message packet to send to the server"""
        packet = (None, None)
        try:
            packet = self.writeQueue.get(False)
        except Empty:
            pass
        return packet