docs: function TJBot(hardware, configuration, credentials) {
      if (!(this instanceof TJBot)) {
          throw new Error('"new" keyword required to create TJBot service instances');
      }
  
      // import configuration params
      this.configuration = Object.assign({}, TJBot.prototype.defaultConfiguration, configuration);
  
      // set logging level
      winston.level = this.configuration.log.level;
  
      // set up the hardware
      if (hardware == undefined) {
          throw new Error('must define a hardware configuration for TJBot');
      }
  
      hardware.forEach(function(device) {
          switch (device) {
          case 'camera':
              this._setupCamera();
              break;
  
          case 'led':
              this._setupLED();
              break;
  
          case 'microphone':
              this._setupMicrophone();
              break;
  
          case 'servo':
              this._setupServo(this.configuration.wave.servoPin);
              break;
  
          case 'speaker':
              this._setupSpeaker();
              break;
          }
      }, this);
  
      // set up additional services when their credentials are specified
      if (credentials != undefined) {
          // > conversation (renamed to assistant)
          // do this first so 'assistant' credentials will override 'conversation'
          // credentials, since 'assistant' is preferred
          if (credentials.hasOwnProperty('conversation')) {
              this._createServiceAPI('assistant', credentials['conversation']);
          }
  
          // > assistant
          if (credentials.hasOwnProperty('assistant')) {
              this._createServiceAPI('assistant', credentials['assistant']);
          }
  
          // > language translator
          if (credentials.hasOwnProperty('language_translator')) {
              this._createServiceAPI('language_translator', credentials['language_translator']);
          }
  
          // > speech to text
          if (credentials.hasOwnProperty('speech_to_text')) {
              this._createServiceAPI('speech_to_text', credentials['speech_to_text']);
          }
  
          // > text to speech
          if (credentials.hasOwnProperty('text_to_speech')) {
              this._createServiceAPI('text_to_speech', credentials['text_to_speech']);
          }
  
          // > tone analyzer
          if (credentials.hasOwnProperty('tone_analyzer')) {
              this._createServiceAPI('tone_analyzer', credentials['tone_analyzer']);
          }
  
          // > visual recognition
          if (credentials.hasOwnProperty('visual_recognition')) {
              this._createServiceAPI('visual_recognition', credentials['visual_recognition']);
          }
      }
  
      winston.info('Hello from TJBot! My name is ' + this.configuration.robot.name + '.');
      winston.verbose('TJBot library version ' + TJBot.prototype.version);
      
      winston.silly('TJBot configuration:');
      winston.silly(this.configuration);
  }