""" plugin.video.surveillanceroom A Kodi add-on by Maikito26 This module is to exploit Foscam HD Cameras, ie FI9821W/P/HD816W/P. """ import urllib, sys import xml.etree.ElementTree as ET from threading import Thread import utils # Foscam error code. FOSCAM_SUCCESS = 0 ERROR_FOSCAM_FORMAT = -1 ERROR_FOSCAM_AUTH = -2 ERROR_FOSCAM_CMD = -3 # Access deny. May the cmd is not supported. ERROR_FOSCAM_EXE = -4 # CGI execute fail. ERROR_FOSCAM_TIMEOUT = -5 ERROR_FOSCAM_UNKNOWN = -7 # -6 and -8 are reserved. ERROR_FOSCAM_UNAVAILABLE = -8 # Disconnected or not a cam. class FoscamError(Exception): def __init__(self, code ): super(FoscamError, self).__init__() self.code = int(code) def __str__(self): return 'ErrorCode: %s' % self.code class FoscamCamera(object): '''A python implementation of the foscam HD816W''' def __init__(self, camera_settings, daemon=False, verbose=True): ''' If ``daemon`` is True, the command will be sent unblockedly. ''' self.host = camera_settings[1] self.port = camera_settings[2] self.usr = camera_settings[3] self.pwd = camera_settings[4] self.daemon = daemon self.verbose = verbose @property def url(self): _url = '%s:%s' % (self.host, self.port) return _url @property def video_url(self): _videoUrl = "rtsp://{0}:{1}@{2}/videoMain".format(self.usr, self.pwd, self.url) return _videoUrl @property def video_sub_url(self): _videoSubUrl = "rtsp://{0}:{1}@{2}/videoSub".format(self.usr, self.pwd, self.url) return _videoSubUrl @property def mjpeg_url(self): _mjpegUrl = "http://{0}/cgi-bin/CGIStream.cgi?cmd={{cmd}}&usr={1}&pwd={2}&".format(self.url, self.usr, self.pwd) return _mjpegUrl.format(cmd = 'GetMJStream') #MJPEG stream is VGA resolution @ 15fps @property def snapshot_url(self): _snapshotUrl = "http://{0}/cgi-bin/CGIProxy.fcgi?cmd={{cmd}}&usr={1}&pwd={2}&".format(self.url, self.usr, self.pwd) return _snapshotUrl.format(cmd = 'snapPicture2') def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): return None def send_command(self, cmd, params=None): ''' Send command to foscam. ''' paramstr = '' if params: paramstr = urllib.urlencode(params) paramstr = '&' + paramstr if paramstr else '' cmdurl = 'http://%s/cgi-bin/CGIProxy.fcgi?usr=%s&pwd=%s&cmd=%s%s' % ( self.url, self.usr, self.pwd, cmd, paramstr, ) # Parse parameters from response string. if self.verbose: utils.log(4, 'Send Foscam command: %s' % cmdurl) try: raw_string = '' raw_string = urllib.urlopen(cmdurl).read() root = ET.fromstring(raw_string) except: if self.verbose: utils.log(3, 'Foscam exception: ' + raw_string) return ERROR_FOSCAM_UNAVAILABLE, None code = ERROR_FOSCAM_UNKNOWN params = dict() if utils._atleast_python27: #Check added for compatibility with Mac/python2.6 kodi builds for child in root.iter(): if child.tag == 'result': code = int(child.text) elif child.tag != 'CGI_Result': params[child.tag] = child.text else: for child in root.getiterator(): if child.tag == 'result': code = int(child.text) elif child.tag != 'CGI_Result': params[child.tag] = child.text if self.verbose: utils.log(4, 'Received Foscam response: %s, %s' % (code, params)) return code, params def execute_command(self, cmd, params=None, callback=None): ''' Execute a command and return a parsed response. ''' def execute_with_callbacks(cmd, params=None, callback=None): code, params = self.send_command(cmd, params) if callback: callback(code, params) return code, params if self.daemon: t = Thread(target=execute_with_callbacks, args=(cmd, ), kwargs={'params':params, 'callback':callback}) t.daemon = True t.start() else: return execute_with_callbacks(cmd, params, callback) def set_ir_on(self, callback=None): ''' Turn on the IR LED ''' return self.execute_command('openInfraLed', callback=callback) def set_ir_off(self, callback=None): ''' Turn off the IR LED ''' return self.execute_command('closeInfraLed', callback=callback) def get_ir_config(self, callback=None): ''' Get IR Config mode: 0 Auto 1 Manual ''' return self.execute_command('getInfraLedConfig', callback=callback) def set_ir_config(self, mode, callback=None): ''' Set IR Config mode: 0 Auto 1 Manual ''' params = {'mode': mode} return self.execute_command('setInfraLedConfig', params, callback=callback) # *************** AV Settings ****************** def set_snapshot_config(self, quality, callback=None): ''' Set snapshot config snapPicQuality: 0 Low quality 1 Normal quality 2 High quality saveLocation: 0 Save to sd card 1 Not in use now 2 Upload to FTP ''' response_ok, response = self.get_snapshot_config() save_location = response.get('saveLocation') params = {'snapPicQuality': quality, 'saveLocation': save_location} self.execute_command('setSnapConfig', params, callback=callback) def get_snapshot_config(self, callback=None): ''' Get snapshot config: snapPicQuality: 0 Low quality 1 Normal quality 2 High quality saveLocation: 0 Save to sd card 1 Not in use now 2 Upload to FTP ''' self.execute_command('getSnapConfig', callback=callback) def enable_mjpeg(self): ''' Set the sub stream type to Motion jpeg. ''' self.set_sub_video_stream_type(1) def disable_mjpeg(self): ''' Set the sub stream type to Motion jpeg. ''' self.set_sub_video_stream_type(0) def get_sub_video_stream_type(self, callback=None): ''' Get the stream type of sub stream. ''' return self.execute_command('getSubVideoStreamType', callback=callback) def set_sub_video_stream_type(self, format, callback=None): ''' Set the stream fromat of sub stream. Supported format: (1) H264 : 0 (2) MotionJpeg 1 ''' params = {'format': format} return self.execute_command('setSubVideoStreamType', params, callback=callback) def set_sub_stream_format(self, format, callback=None): ''' Set the stream fromat of sub stream 0-3 ''' params = {'format': format} return self.execute_command('setSubStreamFormat', params, callback=callback) def get_main_video_stream_type(self, callback=None): ''' Get the stream type of main stream ''' return self.execute_command('getMainVideoStreamType', callback=callback) def set_main_video_stream_type(self, streamtype, callback=None): ''' Set the stream type of main stream 0-3 ''' params = {'streamType': streamtype} return self.execute_command('setMainVideoStreamType', params, callback=callback) def get_video_stream_param(self, callback=None): ''' Get video stream param ''' return self.execute_command('getVideoStreamParam', callback=callback) def set_video_stream_param(self, streamtype, resolution, bitrate, framerate, gop, isvbr, callback=None): ''' Set the video stream param of stream N streamtype(0~3): Stream N. resolution(0~4): 0 720P, 1 VGA(640*480), 2 VGA(640*360), 3 QVGA(320*240), 4 QVGA(320*180). bitrate: Bit rate of stream type N(20480~2097152). framerate: Frame rate of stream type N. GOP: P frames between 1 frame of stream type N. The suggest value is: X * framerate. isvbr: 0(Not in use currently), 1(In use). ''' params = {'streamType': streamtype, 'resolution': resolution, 'bitRate' : bitrate, 'frameRate' : framerate, 'GOP' : gop, 'isVBR' : isvbr } return self.execute_command('setVideoStreamParam', params, callback=callback) def mirror_video(self, is_mirror=None, callback=None): ''' Mirror video ``is_mirror``: 0 not mirror, 1 mirror ''' if is_mirror == None: response_ok, response = self.get_mirror_and_flip_setting() is_mirror = response.get('isMirror') if is_mirror == '0': is_mirror = '1' else: is_mirror = '0' params = {'isMirror': is_mirror} return self.execute_command('mirrorVideo', params, callback=callback) def flip_video(self, is_flip=None, callback=None): ''' Flip video ``is_flip``: 0 Not flip, 1 Flip ''' if is_flip == None: response_ok, response = self.get_mirror_and_flip_setting() is_flip = response.get('isFlip') if is_flip == '0': is_flip = '1' else: is_flip = '0' params = {'isFlip': is_flip } return self.execute_command('flipVideo', params, callback=callback) def get_mirror_and_flip_setting(self, callback=None): return self.execute_command('getMirrorAndFlipSetting', None, callback=callback) # *************** Device manage ******************* def reboot(self, callback=None): return self.execute_command('rebootSystem', callback=callback) def set_system_time(self, time_source, ntp_server, date_format, time_format, time_zone, is_dst, dst, year, mon, day, hour, minute, sec, callback=None): ''' Set systeim time ''' if ntp_server not in ['time.nist.gov', 'time.kriss.re.kr', 'time.windows.com', 'time.nuri.net', ]: raise ValueError('Unsupported ntpServer') params = {'timeSource': time_source, 'ntpServer' : ntp_server, 'dateFormat': date_format, 'timeFormat': time_format, 'timeZone' : time_zone, 'isDst' : is_dst, 'dst' : dst, 'year' : year, 'mon' : mon, 'day' : day, 'hour' : hour, 'minute' : minute, 'sec' : sec } return self.execute_command('setSystemTime', params, callback=callback) def get_system_time(self, callback=None): ''' Get system time. ''' return self.execute_command('getSystemTime', callback=callback) def get_dev_name(self, callback=None): ''' Get camera name. ''' return self.execute_command('getDevName', callback=callback) def set_dev_name(self, devname, callback=None): ''' Set camera name ''' params = {'devName': devname.encode('gbk')} return self.execute_command('setDevName', params, callback=callback) def get_dev_state(self, callback=None): ''' Get all device state cmd: getDevState return args: ...... IOAlarm 0 Disabled; 1 No Alarm; 2 Detect Alarm motionDetectAlarm 0 Disabled; 1 No Alarm; 2 Detect Alarm soundAlarm 0 Disabled; 1 No Alarm; 2 Detect Alarm record: 0 Not in recording; 1 Recording sdState: 0 No sd card; 1 Sd card OK; 2 SD card read only sdFreeSpace: Free space of sd card by unit of k sdTotalSpace: Total space of sd card by unit of k infraLedState 0 OFF; 1 ON ...... ''' return self.execute_command('getDevState', callback=callback) def set_pwr_freq(self, mode, callback=None): ''' Set power frequency of sensor mode: 0 60HZ 1 50Hz ''' params = {'freq': mode} return self.execute_command('setPwrFreq', params, callback=callback) def get_osd_setting(self, callback=None): ''' Gets the OSD setting ...... isEnableTimeStamp: Time stamp will display on screen or not isEnableDevName: Camera name will display on screen or not dispPos: OSD display position, currently can only be 0 isEnableOSDMask: Is OSD mask effective ...... ''' return self.execute_command('getOSDSetting ', callback=callback) def set_osd_setting(self, params, callback=None): ''' Sets the OSD setting -- CURRENTLY NOT FUNCTIONAL AS A PYTHON FUNCTION UNTIL PARAMETERS ARE SET ...... isEnableTimeStamp: Time stamp will display on screen or not isEnableDevName: Camera name will display on screen or not dispPos: OSD display position, currently can only be 0 isEnableOSDMask: Is OSD mask effective ...... ''' params = {} return self.execute_command('setOSDSetting', params, callback=callback) # *************** PTZ Control ******************* def ptz_move_up(self, callback=None): ''' Move up ''' return self.execute_command('ptzMoveUp', callback=callback) def ptz_move_down(self, callback=None): ''' Move down ''' return self.execute_command('ptzMoveDown', callback=callback) def ptz_move_left(self, callback=None): ''' Move left ''' return self.execute_command('ptzMoveLeft', callback=callback) def ptz_move_right(self, callback=None): ''' Move right. ''' return self.execute_command('ptzMoveRight', callback=callback) def ptz_move_top_left(self, callback=None): ''' Move to top left. ''' return self.execute_command('ptzMoveTopLeft', callback=callback) def ptz_move_top_right(self, callback=None): ''' Move to top right. ''' return self.execute_command('ptzMoveTopRight', callback=callback) def ptz_move_bottom_left(self, callback=None): ''' Move to bottom left. ''' return self.execute_command('ptzMoveBottomLeft', callback=callback) def ptz_move_bottom_right(self, callback=None): ''' Move to bottom right. ''' return self.execute_command('ptzMoveBottomRight', callback=callback) def ptz_stop_run(self, callback=None): ''' Stop run PT ''' return self.execute_command('ptzStopRun', callback=callback) def ptz_reset(self, callback=None): ''' Reset PT to default position. ''' return self.execute_command('ptzReset', callback=callback) def ptz_home_location(self, mode, callback=None): ''' Reset PT to home position. mode: 0 Return if add-on default preset point exists or not 1 Go to add-on default preset if it exists and no action if it doesn't 2 Return if add-on default preset point exists or not, and go to point if it does or camera default if it doesn't 3 Reset to camera default location ''' if mode < 3: response_code, response = self.ptz_get_preset() try: qty = int(response.get('cnt')) for x in range(4, qty): point = response.get('point%d' %x) if 'surveillanceroom_default' in point: if mode == 0: return True else: return True, self.ptz_goto_preset('surveillanceroom_default') except: pass if mode == 2: self.ptz_reset() return False return self.ptz_reset() def ptz_get_preset(self, callback=None): ''' Get presets. cnt: Current preset point count pointN: The name of point N ''' return self.execute_command('getPTZPresetPointList', callback=callback) def ptz_goto_preset(self, name, callback=None): ''' Move to preset. 4 points are default: LeftMost\RightMost\TopMost\BottomMost ''' params = {'name': name} return self.execute_command('ptzGotoPresetPoint', params, callback=callback) def ptz_add_preset(self, name = None, callback=None): ''' Sets current position as preset point. - Only 16 presets in list, with 4 default. ''' if name == None: name = 'surveillanceroom_default' params = {'name': name} return self.execute_command('ptzAddPresetPoint', params, callback=callback) def ptz_delete_preset(self, name = None, callback=None): ''' Delete a preset point from the preset point list. ''' if name == None: name = 'surveillanceroom_default' params = {'name': name} return self.execute_command('ptzDeletePresetPoint', params, callback=callback) def get_ptz_speed(self, callback=None): ''' Get the speed of PT ''' return self.execute_command('getPTZSpeed', callback=callback) def set_ptz_speed(self, speed, callback=None): ''' Set the speed of PT speed: 0 Very slow 1 Slow 2 Normal speed 3 Fast 4 Very fast ''' return self.execute_command('setPTZSpeed', {'speed':speed}, callback=callback) def get_ptz_selftestmode(self, callback=None): ''' Get the selftest mode of PTZ ''' return self.execute_command('getPTZSelfTestMode', callback=callback) def set_ptz_selftestmode(self, mode=0, callback=None): ''' Set the selftest mode of PTZ mode = 0: No selftest mode = 1: Normal selftest mode = 1: After normal selftest, then goto presetpoint-appointed ''' return self.execute_command('setPTZSelfTestMode', {'mode':mode}, callback=callback ) def ptz_zoom_in(self, callback=None): ''' Zoom In ''' return self.execute_command('zoomIn', callback=callback) def ptz_zoom_out(self, callback=None): ''' Zoom Out ''' return self.execute_command('zoomOut', callback=callback) def ptz_zoom_stop(self, callback=None): ''' Zoom Stop ''' return self.execute_command('zoomStop', callback=callback) def get_ptz_zoom_speed(self, callback=None): ''' Get Zoom Speed speed: 0 slow 1 normal 2 fast ''' return self.execute_command('getZoomSpeed', callback=callback) def set_ptz_zoom_speed(self, speed, callback=None): ''' Set Zoom Speed speed: 0 slow 1 normal 2 fast ''' params = {'speed': speed} return self.execute_command('setZoomSpeed', params, callback=callback) # *************** Alarm Function ******************* def is_alarm_active(self, motion_enabled, sound_enabled): ''' Returns the state of the alarm on the camera. ''' alarmActive = False success_code, response = self.get_dev_state() if success_code == 0: for alarm, enabled in (('motionDetect', motion_enabled), ('sound', sound_enabled)): if enabled: param = "{0}Alarm".format(alarm) if response[param] == '2': alarmActive = True break return success_code, alarmActive, alarm return success_code, False, None def get_sound_detect_config(self, callback=None): ''' Get sound detect config ''' return self.execute_command('getAudioAlarmConfig', callback=callback) def set_sound_detect_config(self, params, callback=None): ''' Set sound detect config ''' return self.execute_command('setAudioAlarmConfig', params, callback=callback) def set_sound_detection(self, enabled=1): ''' Get the current config and set the sound detection on or off ''' result, current_config = self.get_sound_detect_config() current_config['isEnable'] = enabled self.set_sound_detect_config(current_config) def enable_sound_detection(self): ''' Enable sound detection ''' self.set_sound_detection(1) def disable_sound_detection(self): ''' disable sound detection ''' self.set_sound_detection(0) def get_sound_sensitivity(self): ''' Get the current config and set the sound detection on or off ''' result, current_config = self.get_sound_detect_config() return current_config['sensitivity'] def set_sound_sensitivity(self, sensitivity): ''' Get the current config and set the sound detection on or off ''' result, current_config = self.get_sound_detect_config() current_config['sensitivity'] = sensitivity self.set_sound_detect_config(current_config) def get_sound_triggerinterval(self): ''' Get the current config and set the sound detection on or off ''' result, current_config = self.get_sound_detect_config() return current_config['triggerInterval'] def set_sound_triggerinterval(self, triggerInterval): ''' Get the current config and set the sound detection on or off ''' result, current_config = self.get_sound_detect_config() current_config['triggerInterval'] = triggerInterval self.set_sound_detect_config(current_config) def get_motion_detect_config(self, callback=None): ''' Get motion detect config ''' return self.execute_command('getMotionDetectConfig', callback=callback) def set_motion_detect_config(self, params, callback=None): ''' Set motion detect config ''' return self.execute_command('setMotionDetectConfig', params, callback=callback) def set_motion_detection(self, enabled=1): ''' Get the current config and set the motion detection on or off ''' result, current_config = self.get_motion_detect_config() current_config['isEnable'] = enabled self.set_motion_detect_config(current_config) def enable_motion_detection(self): ''' Enable motion detection ''' self.set_motion_detection(1) def disable_motion_detection(self): ''' disable motion detection ''' self.set_motion_detection(0) def get_motion_sensitivity(self): ''' Get the current config and set the motion detection on or off ''' result, current_config = self.get_motion_detect_config() return current_config['sensitivity'] def set_motion_sensitivity(self, sensitivity): ''' Get the current config and set the motion detection on or off ''' result, current_config = self.get_motion_detect_config() current_config['sensitivity'] = sensitivity self.set_motion_detect_config(current_config) def get_motion_triggerinterval(self): ''' Get the current config and set the motion detection on or off ''' result, current_config = self.get_motion_detect_config() return current_config['triggerInterval'] def set_motion_triggerinterval(self, triggerInterval): ''' Get the current config and set the motion detection on or off ''' result, current_config = self.get_motion_detect_config() current_config['triggerInterval'] = triggerInterval self.set_motion_detect_config(current_config)