├── .gitignore ├── LICENSE ├── README.rst ├── foscam ├── __init__.py └── foscam.py ├── setup.py └── tests ├── __init__.py └── camtest.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Quatanium Co., Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | foscam-python-lib 3 | ================= 4 | 5 | .. image:: https://img.shields.io/pypi/v/pyfoscam.svg 6 | :target: https://pypi.python.org/pypi/pyfoscam 7 | 8 | Foscam Python2 Library for H.264 IP Cameras (FI9821W/P/HD816W/P) 9 | 10 | TODO 11 | ==== 12 | 13 | 1. Support more camera models. 14 | 15 | Getting Started 16 | =============== 17 | 18 | Install 19 | ------- 20 | 21 | 22 | .. code:: bash 23 | 24 | $ pip install pyfoscam 25 | 26 | Simple example 27 | -------------- 28 | Here is a simple example to move camera lens up and stop after 1s. 29 | 30 | .. code:: python 31 | 32 | from foscam import FoscamCamera 33 | from time import sleep 34 | 35 | mycam = FoscamCamera('192.168.0.110', 88, 'admin', 'pwd') 36 | mycam.ptz_move_up() 37 | sleep(1) 38 | mycam.ptz_stop_run() 39 | 40 | Asynchronous feature 41 | -------------------- 42 | This example uses the asynchronous feature provided by ``FoscamCamera``. 43 | 44 | Normally, a command is sent synchronously, waiting for results and blocking the main thread. 45 | 46 | By initializing ``FoscamCamera`` with `daemon=True` (defaults to False), commands are sent asynchronously. 47 | 48 | .. code:: python 49 | 50 | mycam = FoscamCamera('192.168.0.110', 88, 'admin', 'pwd', daemon=True) 51 | mycam.get_ip_info() 52 | mycam.get_port_info() 53 | mycam.refresh_wifi_list() 54 | 55 | 56 | Send command with callback 57 | -------------------------- 58 | This example illustrates the use of a callback function when the command completes. 59 | 60 | .. code:: python 61 | 62 | from foscam import FoscamCamera, FOSCAM_SUCCESS 63 | def print_ipinfo(returncode, params): 64 | if returncode != FOSCAM_SUCCESS: 65 | print 'Failed to get IPInfo!' 66 | return 67 | print 'IP: %s, Mask: %s' % (params['ip'], params['mask']) 68 | 69 | mycam = FoscamCamera('192.168.0.110', 88, 'admin', 'pwd', daemon=False) 70 | mycam.get_ip_info(print_ipinfo) 71 | -------------------------------------------------------------------------------- /foscam/__init__.py: -------------------------------------------------------------------------------- 1 | from .foscam import FoscamCamera 2 | 3 | -------------------------------------------------------------------------------- /foscam/foscam.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module to exploit Foscam Foscam FI9821W/P/HD816W/P camera. 3 | 4 | 2016-01-22 Python 3 update by https://github.com/markomanninen 5 | """ 6 | 7 | # Python 3 support. Also print -> print(). 8 | try: 9 | from urllib import urlopen 10 | except ImportError: 11 | from urllib.request import urlopen 12 | try: 13 | from urllib import urlencode 14 | except ImportError: 15 | from urllib.parse import urlencode 16 | try: 17 | from urllib import unquote 18 | except ImportError: 19 | from urllib.parse import unquote 20 | 21 | import xml.etree.ElementTree as ET 22 | from threading import Thread 23 | try: 24 | import ssl 25 | ssl_enabled=True 26 | except ImportError: 27 | ssl_enabled=False 28 | 29 | from collections import OrderedDict 30 | 31 | # Foscam error code. 32 | FOSCAM_SUCCESS = 0 33 | ERROR_FOSCAM_FORMAT = -1 34 | ERROR_FOSCAM_AUTH = -2 35 | ERROR_FOSCAM_CMD = -3 # Access deny. May the cmd is not supported. 36 | ERROR_FOSCAM_EXE = -4 # CGI execute fail. 37 | ERROR_FOSCAM_TIMEOUT = -5 38 | ERROR_FOSCAM_UNKNOWN = -7 # -6 and -8 are reserved. 39 | ERROR_FOSCAM_UNAVAILABLE = -8 # Disconnected or not a cam. 40 | 41 | FTP_MODE_PASV = 0 42 | FTP_MODE_PORT = 1 43 | 44 | FTP_ADDR_FORMAT_ERROR = -1 45 | FTP_CONNECT_ERROR = -2 46 | FTP_LOGIN_ERROR = -3 47 | FTP_DIR_ERROR = -4 48 | 49 | class FoscamError(Exception): 50 | def __init__(self, code ): 51 | super(FoscamError, self).__init__() 52 | self.code = int(code) 53 | 54 | def __str__(self): 55 | return 'ErrorCode: %s' % self.code 56 | 57 | class FoscamCamera(object): 58 | '''A python implementation of the foscam HD816W''' 59 | 60 | def __init__(self, host, port, usr, pwd, daemon=False, ssl=None, verbose=True): 61 | ''' 62 | If ``daemon`` is True, the command will be sent unblockedly. 63 | ''' 64 | self.host = host 65 | self.port = port 66 | self.usr = usr 67 | self.pwd = pwd 68 | self.daemon = daemon 69 | self.verbose = verbose 70 | self.ssl = ssl 71 | if ssl_enabled: 72 | if port==443 and ssl is None: 73 | self.ssl = True 74 | if self.ssl is None: 75 | self.ssl = False 76 | 77 | @property 78 | def url(self): 79 | _url = '%s:%s' % (self.host, self.port) 80 | return _url 81 | 82 | def send_command(self, cmd, params=None, raw=False): 83 | ''' 84 | Send command to foscam. 85 | ''' 86 | paramstr = '' 87 | if params: 88 | paramstr = urlencode(params) 89 | paramstr = '&' + paramstr if paramstr else '' 90 | cmdurl = 'http://%s/cgi-bin/CGIProxy.fcgi?usr=%s&pwd=%s&cmd=%s%s' % ( 91 | self.url, 92 | self.usr, 93 | self.pwd, 94 | cmd, 95 | paramstr, 96 | ) 97 | if self.ssl and ssl_enabled: 98 | cmdurl = cmdurl.replace('http:','https:') 99 | 100 | # Parse parameters from response string. 101 | if self.verbose: 102 | print ('Send Foscam command: %s' % cmdurl) 103 | try: 104 | raw_string = '' 105 | if self.ssl and ssl_enabled: 106 | gcontext = ssl.SSLContext(ssl.PROTOCOL_TLSv1) # disable cert 107 | raw_string = urlopen(cmdurl,context=gcontext, timeout=5).read() 108 | else: 109 | raw_string = urlopen(cmdurl,timeout=5).read() 110 | if raw: 111 | if self.verbose: 112 | print ('Returning raw Foscam response: len=%d' % len(raw_string)) 113 | return FOSCAM_SUCCESS, raw_string 114 | root = ET.fromstring(raw_string) 115 | except: 116 | if self.verbose: 117 | print ('Foscam exception: ' + raw_string) 118 | return ERROR_FOSCAM_UNAVAILABLE, None 119 | code = ERROR_FOSCAM_UNKNOWN 120 | params = OrderedDict() 121 | for child in root.iter(): 122 | if child.tag == 'result': 123 | code = int(child.text) 124 | 125 | elif child.tag != 'CGI_Result': 126 | if type(child.text) == str: 127 | child.text = unquote(child.text) 128 | params[child.tag] = child.text 129 | 130 | if self.verbose: 131 | print ('Received Foscam response: %s, %s' % (code, params)) 132 | return code, params 133 | 134 | def execute_command(self, cmd, params=None, callback=None, raw=False): 135 | ''' 136 | Execute a command and return a parsed response. 137 | ''' 138 | def execute_with_callbacks(cmd, params=None, callback=None, raw=False): 139 | code, params = self.send_command(cmd, params, raw) 140 | if callback: 141 | callback(code, params) 142 | return code, params 143 | 144 | if self.daemon: 145 | t = Thread(target=execute_with_callbacks, 146 | args=(cmd, ), kwargs={'params':params, 'callback':callback, 'raw':raw}) 147 | t.daemon = True 148 | t.start() 149 | else: 150 | return execute_with_callbacks(cmd, params, callback, raw) 151 | 152 | # *************** Network ****************** 153 | 154 | def get_ip_info(self, callback=None): 155 | ''' 156 | Get ip infomation 157 | ''' 158 | return self.execute_command('getIPInfo', callback=callback) 159 | 160 | def set_ip_info(self, is_dhcp, ip='', gate='', mask='', 161 | dns1='', dns2='', callback=None): 162 | ''' 163 | isDHCP: 0(False), 1(True) 164 | System will reboot automatically to take effect after call this CGI command. 165 | ''' 166 | params = {'isDHCP': is_dhcp, 167 | 'ip': ip, 168 | 'gate': gate, 169 | 'mask': mask, 170 | 'dns1': dns1, 171 | 'dns2': dns2, 172 | } 173 | 174 | return self.execute_command('setIpInfo', params, callback=callback) 175 | 176 | def get_port_info(self, callback=None): 177 | ''' 178 | Get http port and media port of camera. 179 | ''' 180 | return self.execute_command('getPortInfo', callback=callback) 181 | 182 | def set_port_info(self, webport, mediaport, httpsport, 183 | onvifport, callback=None): 184 | ''' 185 | Set http port and media port of camera. 186 | ''' 187 | params = {'webPort' : webport, 188 | 'mediaPort' : mediaport, 189 | 'httpsPort' : httpsport, 190 | 'onvifPort' : onvifport, 191 | } 192 | return self.execute_command('setPortInfo', params, callback=callback) 193 | 194 | def refresh_wifi_list(self, callback=None): 195 | ''' 196 | Start scan the aps around. 197 | This operation may takes a while, about 20s or above, 198 | the other operation on this device will be blocked during the period. 199 | ''' 200 | return self.execute_command('refreshWifiList', callback=callback) 201 | 202 | def get_wifi_list(self, startno, callback=None): 203 | ''' 204 | Get the aps around after refreshWifiList. 205 | Note: Only 10 aps will be returned one time. 206 | ''' 207 | params = {'startNo': startno} 208 | return self.execute_command('getWifiList', params, callback=callback) 209 | 210 | def set_wifi_setting(self, ssid, psk, isenable, isusewifi, nettype, 211 | encryptype, authmode, keyformat, defaultkey, 212 | key1='', key2='', key3='', key4='', 213 | key1len=64, key2len=64, key3len=64, key4len=64, 214 | callback=None): 215 | ''' 216 | Set wifi config. 217 | Camera will not connect to AP unless you enject your cable. 218 | ''' 219 | params = {'isEnable' : isenable, 220 | 'isUseWifi' : isusewifi, 221 | 'ssid' : ssid, 222 | 'netType' : nettype, 223 | 'encryptType': encryptype, 224 | 'psk' : psk, 225 | 'authMode' : authmode, 226 | 'keyFormat' : keyformat, 227 | 'defaultKey' : defaultkey, 228 | 'key1' : key1, 229 | 'key2' : key2, 230 | 'key3' : key3, 231 | 'key4' : key4, 232 | 'key1Len' : key1len, 233 | 'key2Len' : key2len, 234 | 'key3Len' : key3len, 235 | 'key4Len' : key4len, 236 | } 237 | return self.execute_command('setWifiSetting', params, callback=callback) 238 | 239 | def get_wifi_config(self, callback=None): 240 | ''' 241 | Get wifi config 242 | ''' 243 | return self.execute_command('getWifiConfig', callback=callback) 244 | 245 | def get_upnp_config(self, callback=None): 246 | ''' 247 | Get UpnP config. 248 | ''' 249 | return self.execute_command('getUPnPConfig', callback=callback) 250 | 251 | def set_upnp_config(self, isenable, callback=None): 252 | ''' 253 | Set UPnP config 254 | ''' 255 | params = {'isEnable': isenable} 256 | return self.execute_command('setUPnPConfig', params, callback=callback) 257 | 258 | def get_ddns_config(self, callback=None): 259 | ''' 260 | Get DDNS config. 261 | ''' 262 | return self.execute_command('getDDNSConfig', callback=callback) 263 | 264 | def set_ddns_config(self, isenable, hostname, ddnsserver, 265 | user, password, callback=None): 266 | ''' 267 | Set DDNS config. 268 | ''' 269 | params = {'isEnable': isenable, 270 | 'hostName': hostname, 271 | 'ddnsServer': ddnsserver, 272 | 'user': user, 273 | 'password': password, 274 | } 275 | return self.execute_command('setDDNSConfig', params, callback=callback) 276 | 277 | 278 | # *************** AV Settings ****************** 279 | 280 | def get_sub_video_stream_type(self, callback=None): 281 | ''' 282 | Get the stream type of sub stream. 283 | ''' 284 | return self.execute_command('getSubVideoStreamType', callback=callback) 285 | 286 | def set_sub_video_stream_type(self, format, callback=None): 287 | ''' 288 | Set the stream fromat of sub stream. 289 | Supported format: (1) H264 : 0 290 | (2) MotionJpeg 1 291 | ''' 292 | params = {'format': format} 293 | return self.execute_command('setSubVideoStreamType', 294 | params, callback=callback) 295 | 296 | def set_sub_stream_format(self, format, callback=None): 297 | ''' 298 | Set the stream fromat of sub stream???? 299 | ''' 300 | params = {'format': format} 301 | return self.execute_command('setSubStreamFormat', 302 | params, callback=callback) 303 | 304 | def get_main_video_stream_type(self, callback=None): 305 | ''' 306 | Get the stream type of main stream 307 | ''' 308 | return self.execute_command('getMainVideoStreamType', callback=callback) 309 | 310 | def set_main_video_stream_type(self, streamtype, callback=None): 311 | ''' 312 | Set the stream type of main stream 313 | ''' 314 | params = {'streamType': streamtype} 315 | return self.execute_command('setMainVideoStreamType', 316 | params, callback=callback) 317 | 318 | def get_video_stream_param(self, callback=None): 319 | ''' 320 | Get video stream param 321 | ''' 322 | return self.execute_command('getVideoStreamParam', callback=callback) 323 | 324 | def set_video_stream_param(self, streamtype, resolution, bitrate, 325 | framerate, gop, isvbr, callback=None): 326 | ''' 327 | Set the video stream param of stream N 328 | streamtype(0~3): Stream N. 329 | resolution(0~4): 0 720P, 330 | 1 VGA(640*480), 331 | 2 VGA(640*360), 332 | 3 QVGA(320*240), 333 | 4 QVGA(320*180). 334 | bitrate: Bit rate of stream type N(20480~2097152). 335 | framerate: Frame rate of stream type N. 336 | GOP: P frames between 1 frame of stream type N. 337 | The suggest value is: X * framerate. 338 | isvbr: 0(Not in use currently), 1(In use). 339 | ''' 340 | params = {'streamType': streamtype, 341 | 'resolution': resolution, 342 | 'bitRate' : bitrate, 343 | 'frameRate' : framerate, 344 | 'GOP' : gop, 345 | 'isVBR' : isvbr 346 | } 347 | return self.execute_command('setVideoStreamParam', 348 | params, callback=callback) 349 | 350 | def mirror_video(self, is_mirror, callback=None): 351 | ''' 352 | Mirror video 353 | ``is_mirror``: 0 not mirror, 1 mirror 354 | ''' 355 | params = {'isMirror': is_mirror} 356 | return self.execute_command('mirrorVideo', params, callback=callback) 357 | 358 | def flip_video(self, is_flip, callback=None): 359 | ''' 360 | Flip video 361 | ``is_flip``: 0 Not flip, 1 Flip 362 | ''' 363 | params = {'isFlip': is_flip } 364 | return self.execute_command('flipVideo', params, callback=callback) 365 | 366 | def get_mirror_and_flip_setting(self, callback=None): 367 | 368 | return self.execute_command('getMirrorAndFlipSetting', None, callback=callback) 369 | 370 | 371 | # *************** User account ****************** 372 | 373 | def change_user_name(self, usrname, newusrname, callback=None): 374 | ''' 375 | Change user name. 376 | ''' 377 | params = {'usrName': usrname, 378 | 'newUsrName': newusrname, 379 | } 380 | return self.execute_command('changeUserName', params, callback=callback) 381 | 382 | def change_password(self, usrname, oldpwd, newpwd, callback=None): 383 | ''' 384 | Change password. 385 | ''' 386 | params = {'usrName': usrname, 387 | 'oldPwd' : oldpwd, 388 | 'newPwd' : newpwd, 389 | } 390 | return self.execute_command('changePassword', 391 | params, callback=callback) 392 | 393 | # *************** Device manage ******************* 394 | 395 | def set_system_time(self, time_source, ntp_server, date_format, 396 | time_format, time_zone, is_dst, dst, year, 397 | mon, day, hour, minute, sec, callback=None): 398 | ''' 399 | Set systeim time 400 | ''' 401 | if ntp_server not in ['time.nist.gov', 402 | 'time.kriss.re.kr', 403 | 'time.windows.com', 404 | 'time.nuri.net', 405 | ]: 406 | raise ValueError('Unsupported ntpServer') 407 | 408 | params = {'timeSource': time_source, 409 | 'ntpServer' : ntp_server, 410 | 'dateFormat': date_format, 411 | 'timeFormat': time_format, 412 | 'timeZone' : time_zone, 413 | 'isDst' : is_dst, 414 | 'dst' : dst, 415 | 'year' : year, 416 | 'mon' : mon, 417 | 'day' : day, 418 | 'hour' : hour, 419 | 'minute' : minute, 420 | 'sec' : sec 421 | } 422 | 423 | return self.execute_command('setSystemTime', params, callback=callback) 424 | 425 | def get_system_time(self, callback=None): 426 | ''' 427 | Get system time. 428 | ''' 429 | return self.execute_command('getSystemTime', callback=callback) 430 | 431 | def get_dev_name(self, callback=None): 432 | ''' 433 | Get camera name. 434 | ''' 435 | return self.execute_command('getDevName', callback=callback) 436 | 437 | def set_dev_name(self, devname, callback=None): 438 | ''' 439 | Set camera name 440 | ''' 441 | params = {'devName': devname.encode('gbk')} 442 | return self.execute_command('setDevName', params, callback=callback) 443 | 444 | def get_dev_state(self, callback=None): 445 | ''' 446 | Get all device state 447 | cmd: getDevState 448 | return args: 449 | ...... 450 | record: 0 Not in recording; 1 Recording 451 | sdState: 0 No sd card; 1 Sd card OK; 2 SD card read only 452 | sdFreeSpace: Free space of sd card by unit of k 453 | sdTotalSpace: Total space of sd card by unit of k 454 | ...... 455 | ''' 456 | return self.execute_command('getDevState', callback=callback) 457 | 458 | def get_dev_info(self, callback=None): 459 | ''' 460 | Get camera information 461 | cmd: getDevInfo 462 | ''' 463 | return self.execute_command('getDevInfo', callback=callback) 464 | 465 | def open_infra_led(self, callback=None): 466 | ''' 467 | Force open infra led 468 | cmd: openInfraLed 469 | ''' 470 | return self.execute_command('openInfraLed', {}, callback=callback) 471 | 472 | def close_infra_led(self, callback=None): 473 | ''' 474 | Force close infra led 475 | cmd: closeInfraLed 476 | ''' 477 | return self.execute_command('closeInfraLed', callback=callback) 478 | 479 | def get_infra_led_config(self, callback=None): 480 | ''' 481 | Get Infrared LED configuration 482 | cmd: getInfraLedConfig 483 | ''' 484 | return self.execute_command('getInfraLedConfig', callback=callback) 485 | 486 | def set_infra_led_config(self, mode, callback=None): 487 | ''' 488 | Set Infrared LED configuration 489 | cmd: setInfraLedConfig 490 | mode(0,1): 0=Auto mode, 1=Manual mode 491 | ''' 492 | params = {'mode': mode} 493 | return self.execute_command('setInfraLedConfig', params, callback=callback) 494 | 495 | def get_product_all_info(self, callback=None): 496 | ''' 497 | Get camera information 498 | cmd: getProductAllInfo 499 | ''' 500 | return self.execute_command('getProductAllInfo', callback=callback) 501 | 502 | # *************** PTZ Control ******************* 503 | 504 | def ptz_move_up(self, callback=None): 505 | ''' 506 | Move up 507 | ''' 508 | return self.execute_command('ptzMoveUp', callback=callback) 509 | 510 | def ptz_move_down(self, callback=None): 511 | ''' 512 | Move down 513 | ''' 514 | return self.execute_command('ptzMoveDown', callback=callback) 515 | 516 | def ptz_move_left(self, callback=None): 517 | ''' 518 | Move left 519 | ''' 520 | return self.execute_command('ptzMoveLeft', callback=callback) 521 | 522 | def ptz_move_right(self, callback=None): 523 | ''' 524 | Move right. 525 | ''' 526 | return self.execute_command('ptzMoveRight', callback=callback) 527 | 528 | def ptz_move_top_left(self, callback=None): 529 | ''' 530 | Move to top left. 531 | ''' 532 | return self.execute_command('ptzMoveTopLeft', callback=callback) 533 | 534 | def ptz_move_top_right(self, callback=None): 535 | ''' 536 | Move to top right. 537 | ''' 538 | return self.execute_command('ptzMoveTopRight', callback=callback) 539 | 540 | def ptz_move_bottom_left(self, callback=None): 541 | ''' 542 | Move to bottom left. 543 | ''' 544 | return self.execute_command('ptzMoveBottomLeft', callback=callback) 545 | 546 | def ptz_move_bottom_right(self, callback=None): 547 | ''' 548 | Move to bottom right. 549 | ''' 550 | return self.execute_command('ptzMoveBottomRight', callback=callback) 551 | 552 | def ptz_stop_run(self, callback=None): 553 | ''' 554 | Stop run PT 555 | ''' 556 | return self.execute_command('ptzStopRun', callback=callback) 557 | 558 | def ptz_reset(self, callback=None): 559 | ''' 560 | Reset PT to default position. 561 | ''' 562 | return self.execute_command('ptzReset', callback=callback) 563 | 564 | def ptz_get_preset(self, callback=None): 565 | ''' 566 | Get presets. 567 | ''' 568 | return self.execute_command('getPTZPresetPointList', callback=callback) 569 | 570 | def ptz_goto_preset(self, name, callback=None): 571 | ''' 572 | Move to preset. 573 | ''' 574 | params = {'name': name} 575 | return self.execute_command('ptzGotoPresetPoint', params, callback=callback) 576 | 577 | def get_ptz_speed(self, callback=None): 578 | ''' 579 | Get the speed of PT 580 | ''' 581 | return self.execute_command('getPTZSpeed', callback=callback) 582 | 583 | def set_ptz_speed(self, speed, callback=None): 584 | ''' 585 | Set the speed of PT 586 | ''' 587 | return self.execute_command('setPTZSpeed', {'speed':speed}, 588 | callback=callback) 589 | 590 | def get_ptz_selftestmode(self, callback=None): 591 | ''' 592 | Get the selftest mode of PTZ 593 | ''' 594 | return self.execute_command('getPTZSelfTestMode', callback=callback) 595 | 596 | def set_ptz_selftestmode(self, mode=0, callback=None): 597 | ''' 598 | Set the selftest mode of PTZ 599 | mode = 0: No selftest 600 | mode = 1: Normal selftest 601 | mode = 1: After normal selftest, then goto presetpoint-appointed 602 | ''' 603 | return self.execute_command('setPTZSelfTestMode', 604 | {'mode':mode}, 605 | callback=callback 606 | ) 607 | 608 | def get_ptz_preset_point_list(self, callback=None): 609 | ''' 610 | Get the preset list. 611 | ''' 612 | return self.execute_command('getPTZPresetPointList', {}, callback=callback) 613 | 614 | 615 | # *************** AV Function ******************* 616 | def get_motion_detect_config(self, callback=None): 617 | ''' 618 | Get motion detect config 619 | ''' 620 | return self.execute_command('getMotionDetectConfig', callback=callback) 621 | 622 | def set_motion_detect_config(self, params, callback=None): 623 | ''' 624 | Get motion detect config 625 | ''' 626 | return self.execute_command('setMotionDetectConfig', params, callback=callback) 627 | 628 | def set_motion_detection(self, enabled=1): 629 | ''' 630 | Get the current config and set the motion detection on or off 631 | ''' 632 | result, current_config = self.get_motion_detect_config() 633 | if result != FOSCAM_SUCCESS: 634 | return result 635 | current_config['isEnable'] = enabled 636 | self.set_motion_detect_config(current_config) 637 | return FOSCAM_SUCCESS 638 | 639 | def enable_motion_detection(self): 640 | ''' 641 | Enable motion detection 642 | ''' 643 | result = self.set_motion_detection(1) 644 | return result 645 | 646 | def disable_motion_detection(self): 647 | ''' 648 | disable motion detection 649 | ''' 650 | result = self.set_motion_detection(0) 651 | return result 652 | 653 | # These API calls support FI9900P devices, which use a different CGI command 654 | def get_motion_detect_config1(self, callback=None): 655 | ''' 656 | Get motion detect config 657 | ''' 658 | return self.execute_command('getMotionDetectConfig1', callback=callback) 659 | 660 | def set_motion_detect_config1(self, params, callback=None): 661 | ''' 662 | Get motion detect config 663 | ''' 664 | return self.execute_command('setMotionDetectConfig1', params, callback=callback) 665 | 666 | def set_motion_detection1(self, enabled=1): 667 | ''' 668 | Get the current config and set the motion detection on or off 669 | ''' 670 | result, current_config = self.get_motion_detect_config1() 671 | if result != FOSCAM_SUCCESS: 672 | return result 673 | current_config['isEnable'] = enabled 674 | self.set_motion_detect_config1(current_config) 675 | 676 | def enable_motion_detection1(self): 677 | ''' 678 | Enable motion detection 679 | ''' 680 | self.set_motion_detection1(1) 681 | 682 | def disable_motion_detection1(self): 683 | ''' 684 | disable motion detection 685 | ''' 686 | self.set_motion_detection1(0) 687 | 688 | def get_alarm_record_config(self, callback=None): 689 | ''' 690 | Get alarm record config 691 | ''' 692 | return self.execute_command('getAlarmRecordConfig', callback=callback) 693 | 694 | def set_alarm_record_config(self, is_enable_prerecord=1, 695 | prerecord_secs=5, alarm_record_secs=300, callback=None): 696 | ''' 697 | Set alarm record config 698 | Return: set result(0-success, -1-error) 699 | ''' 700 | params = {'isEnablePreRecord': is_enable_prerecord, 701 | 'preRecordSecs' : prerecord_secs, 702 | 'alarmRecordSecs' : alarm_record_secs 703 | } 704 | return self.execute_command('setAlarmRecordConfig', params, callback=callback) 705 | 706 | def get_local_alarm_record_config(self, callback=None): 707 | ''' 708 | Get local alarm-record config 709 | ''' 710 | return self.execute_command('getLocalAlarmRecordConfig', callback=callback) 711 | 712 | def set_local_alarm_record_config(self, is_enable_local_alarm_record = 1, 713 | local_alarm_record_secs = 30, callback=None): 714 | ''' 715 | Set local alarm-record config 716 | `is_enable_local_alarm_record`: 0 disable, 1 enable 717 | ''' 718 | params = {'isEnableLocalAlarmRecord': is_enable_local_alarm_record, 719 | 'localAlarmRecordSecs' : local_alarm_record_secs} 720 | return self.execute_command('setLocalAlarmRecordConfig', params, callback=callback) 721 | 722 | def get_h264_frm_ref_mode(self, callback=None): 723 | ''' 724 | Get grame shipping reference mode of H264 encode stream. 725 | Return args: 726 | mode: 0 Normal reference mode 727 | 1 Two frames are seprated by four skipping frames 728 | ''' 729 | return self.execute_command('getH264FrmRefMode', callback=callback) 730 | 731 | def set_h264_frm_ref_mode(self, mode=1, callback=None): 732 | ''' 733 | Set frame shipping reference mode of H264 encode stream. 734 | params: 735 | `mode`: see docstr of meth::get_h264_frm_ref_mode 736 | ''' 737 | params = {'mode': mode} 738 | return self.execute_command('setH264FrmRefMode', params, callback) 739 | 740 | def get_schedule_record_config(self, callback=None): 741 | ''' 742 | Get schedule record config. 743 | cmd: getScheduleRecordConfig 744 | Return args: 745 | isEnable: 0/1 746 | recordLevel: 0 ~ ? 747 | spaceFullMode: 0 ~ ? 748 | isEnableAudio: 0/1 749 | schedule[N]: N <- (0 ~ 6) 750 | ''' 751 | return self.execute_command('getScheduleRecordConfig', callback=callback) 752 | 753 | def set_schedule_record_config(self, is_enable, record_level, 754 | space_full_mode, is_enable_audio, 755 | schedule0 = 0, schedule1 = 0, schedule2 = 0, 756 | schedule3 = 0, schedule4 = 0, schedule5 = 0, 757 | schedule6 = 0, callback=None): 758 | ''' 759 | Set schedule record config. 760 | cmd: setScheduleRecordConfig 761 | args: See docstring of meth::get_schedule_record_config 762 | ''' 763 | 764 | params = {'isEnable' : is_enable, 765 | 'isEnableAudio': is_enable_audio, 766 | 'recordLevel' : record_level, 767 | 'spaceFullMode': space_full_mode, 768 | 'schedule0' : schedule0, 769 | 'schedule1' : schedule1, 770 | 'schedule2' : schedule2, 771 | 'schedule3' : schedule3, 772 | 'schedule4' : schedule4, 773 | 'schedule5' : schedule5, 774 | 'schedule6' : schedule6, 775 | } 776 | return self.execute_command('setScheduleRecordConfig', params, callback=callback) 777 | 778 | def get_record_path(self, callback=None): 779 | ''' 780 | Get Record path: sd/ftp 781 | cmd: getRecordPath 782 | return args: 783 | path: (0,SD), (2, FTP) 784 | free: free size(K) 785 | total: total size(K) 786 | ''' 787 | return self.execute_command('getRecordPath', callback=callback) 788 | 789 | def set_record_path(self, path, callback=None): 790 | ''' 791 | Set Record path: sd/ftp 792 | cmd: setRecordPath 793 | param: 794 | path: (0,SD), (2, FTP) 795 | ''' 796 | params = {'Path': path} 797 | return self.execute_command('setRecordPath', params, callback=callback) 798 | 799 | # *************** SnapPicture Function ******************* 800 | 801 | def snap_picture_2(self, callback=None): 802 | ''' 803 | Manually request snapshot. Returns raw JPEG data. 804 | cmd: snapPicture2 805 | ''' 806 | return self.execute_command('snapPicture2', {}, callback=callback, raw=True) 807 | 808 | # ******************* SMTP Functions ********************* 809 | 810 | def set_smtp_config(self, params, callback=None): 811 | ''' 812 | Set smtp settings using the array of parameters 813 | ''' 814 | return self.execute_command('setSMTPConfig', params, callback=callback) 815 | 816 | def get_smtp_config(self, callback=None): 817 | ''' 818 | Get smtp settings using the array of parameters 819 | ''' 820 | return self.execute_command('getSMTPConfig', callback=callback) 821 | 822 | # ******************* FTP Functions ********************* 823 | 824 | def set_ftp_config_new(self, address, port, mode, username, password, callback=None): 825 | ''' 826 | Set ftp settings using the array of parameters (PASV: 0, PORT: 1) 827 | ''' 828 | encoded_password = ",".join([str(ord(x)) for x in list(password)]) 829 | params = {'ftpAddr': address, 830 | 'ftpPort': str(port), 831 | 'mode': str(port), 832 | 'userName': username, 833 | 'password': encoded_password 834 | } 835 | return self.execute_command('setFtpConfigNew', params, callback=callback) 836 | 837 | def get_ftp_config(self, callback=None): 838 | ''' 839 | Get ftp settings using the array of parameters 840 | ''' 841 | return self.execute_command('getFtpConfig', callback=callback) 842 | 843 | def test_ftp_server_new(self, address, port, mode, username, password, callback=None): 844 | ''' 845 | Get ftp settings using the array of parameters (PASV: 0, PORT: 1) 846 | ''' 847 | encoded_password = ",".join([str(ord(x)) for x in list(password)]) 848 | params = {'ftpAddr': address, 849 | 'ftpPort': str(port), 850 | 'mode': str(port), 851 | 'fptUserName': username, 852 | 'ftpPassword': encoded_password 853 | } 854 | return self.execute_command('testFtpServerNew', params, callback=callback) 855 | 856 | # ********************** Misc **************************** 857 | 858 | def get_log(self, offset, count=10, callback=None): 859 | ''' 860 | Retrieve log records from camera. 861 | cmd: getLog 862 | param: 863 | offset: log offset for first record 864 | count: number of records to return 865 | ''' 866 | params = {'offset': offset, 'count': count} 867 | return self.execute_command('getLog', params, callback=callback) 868 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from setuptools import setup, find_packages 4 | import os 5 | 6 | 7 | CLASSIFIERS = [ 8 | 'Development Status :: 3 - Alpha', 9 | 'Environment :: Console', 10 | 'Intended Audience :: Customer Service', 11 | 'Intended Audience :: Developers', 12 | 'Intended Audience :: Education', 13 | 'Intended Audience :: Science/Research', 14 | 'Intended Audience :: Telecommunications Industry', 15 | 'Natural Language :: English', 16 | 'Operating System :: POSIX', 17 | 'Topic :: Software Development :: Libraries :: Python Modules', 18 | 'Topic :: Multimedia :: Sound/Audio', 19 | 'Topic :: Utilities', 20 | "Programming Language :: Python", 21 | "Programming Language :: Python :: 2", 22 | "Programming Language :: Python :: 2.6", 23 | "Programming Language :: Python :: 2.7", 24 | ] 25 | 26 | setup( 27 | name='pyfoscam', 28 | version='1.2.1', 29 | description='Foscam Python Library for H.264 IP Cameras (FI9821W/P/HD816W/P)', 30 | long_description=open('README.rst', 'r').read(), 31 | author='Cherish Chen', 32 | author_email='sinchb128@gmail.com', 33 | url='https://github.com/quatanium/foscam-python-lib', 34 | include_package_data=True, 35 | license='MIT', 36 | packages=find_packages(exclude=['tests']), 37 | zip_safe=False, 38 | keywords=['foscam', 'Camera', 'IPC'], 39 | ) 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quatanium/foscam-python-lib/f0ac8cacb8126ff6d200042048f89969f790be01/tests/__init__.py -------------------------------------------------------------------------------- /tests/camtest.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import unittest 3 | from time import sleep 4 | 5 | try: # PY3 6 | from configparser import ConfigParser 7 | except ImportError: 8 | from ConfigParser import SafeConfigParser as ConfigParser 9 | 10 | from foscam.foscam import FoscamCamera, FOSCAM_SUCCESS 11 | 12 | config = ConfigParser() 13 | config_filepath = os.path.join(os.path.dirname(__file__), 'camtest.cfg') 14 | 15 | if os.path.exists(config_filepath): 16 | config.read([config_filepath]) 17 | 18 | config_defaults = config.defaults() 19 | 20 | CAM_HOST = config_defaults.get('host') or '' 21 | CAM_PORT = config_defaults.get('port') or 88 22 | CAM_USER = config_defaults.get('user') or 'admin' 23 | CAM_PASS = config_defaults.get('pass') or 'foscam' 24 | CAM_WIFI_SSID = config_defaults.get('wifi_ssid') or '' 25 | CAM_WIFI_PASS = config_defaults.get('wifi_pass') or '' 26 | 27 | 28 | class CallbackForTest(object): 29 | def __call__(self, *args, **kwargs): 30 | self.args = args 31 | self.kwargs = kwargs 32 | 33 | 34 | class TestFoscam(unittest.TestCase): 35 | def setUp(self): 36 | self.foscam = FoscamCamera(CAM_HOST, CAM_PORT, CAM_USER, CAM_PASS) 37 | 38 | # ***************** Test Account Functions *************************** 39 | def test_change_user_name(self): 40 | self.foscam.change_user_name('admin', 'cherish') 41 | self.foscam.usr = 'cherish' 42 | self.foscam.get_ip_info() 43 | self.foscam.change_user_name('cherish', 'admin') 44 | 45 | def test_change_password(self): 46 | self.foscam.change_password('admin', 'foscam', 'qhomecam') 47 | self.foscam.pwd = 'qhomecam' 48 | self.foscam.get_ip_info() 49 | self.foscam.change_password('admin', 'qhomecam', 'foscam') 50 | 51 | # ***************** Test AV Functions ********************* 52 | def test_sub_video_stream(self): 53 | self.foscam.get_sub_video_stream_type() 54 | self.foscam.set_sub_stream_format(1) 55 | self.foscam.get_sub_video_stream_type() 56 | 57 | def test_video_stream_param(self): 58 | self.foscam.get_main_video_stream_type() 59 | self.foscam.get_video_stream_param() 60 | self.foscam.set_video_stream_param(0, 0, 2 * 1024 * 1024, 30, 30, 1) 61 | self.foscam.get_video_stream_param() 62 | 63 | def test_mirror_video(self): 64 | # Turn on mirror 65 | rc, args = self.foscam.mirror_video(1) 66 | self.assertEqual(rc, 0) 67 | # Turn off mirror 68 | rc, args = self.foscam.mirror_video(0) 69 | self.assertEqual(rc, 0) 70 | 71 | def test_flip_video(self): 72 | # Filp 73 | rc, args = self.foscam.flip_video(1) 74 | self.assertEqual(rc, 0) 75 | # Not Filp 76 | rc, args = self.foscam.flip_video(0) 77 | self.assertEqual(rc, 0) 78 | 79 | def test_get_mirror_and_flip_setting(self): 80 | rc, args = self.foscam.get_mirror_and_flip_setting() 81 | self.assertEqual(rc, 0) 82 | 83 | # ***************** Test Network Functions ********************* 84 | def test_get_ip_info(self): 85 | rc, info = self.foscam.get_ip_info() 86 | self.assertTrue(rc == 0) 87 | 88 | # ***************** Test Network Functions ********************* 89 | def test_set_ip_info(self): 90 | old = self.foscam.get_ip_info() 91 | self.foscam.set_ip_info(is_dhcp=0, ip='192.168.0.110', 92 | gate='192.168.0.1', mask='255.255.255.0', 93 | dns1='192.168.0.1', dns2='8.8.8.8') 94 | # Wait to reboot. 95 | sleep(30) 96 | self.foscam.get_ip_info() 97 | 98 | def test_set_port(self): 99 | rc, args = self.foscam.get_port_info() 100 | rc, args = self.foscam.set_port_info(webport=88, mediaport=88, 101 | httpsport=443, onvifport=888) 102 | self.assertEqual(rc, 0) 103 | rc, args = self.foscam.get_port_info() 104 | 105 | def test_set_upnp(self): 106 | self.foscam.get_upnp_config() 107 | self.foscam.set_upnp_config(0) 108 | self.foscam.set_upnp_config(1) 109 | 110 | def test_wifi(self): 111 | self.foscam.refresh_wifi_list() 112 | self.foscam.get_wifi_list(0) 113 | self.foscam.get_wifi_config() 114 | 115 | self.foscam.set_wifi_setting( 116 | ssid=CAM_WIFI_SSID, 117 | psk=CAM_WIFI_PASS, 118 | isenable=0, 119 | isusewifi=0, 120 | nettype=0, 121 | encryptype=4, 122 | authmode=1, 123 | keyformat=0, 124 | defaultkey=1) 125 | 126 | # *************** PTZ Move ******************************** 127 | 128 | def test_move_up(self): 129 | # Test Move up 130 | self.foscam.ptz_move_up() 131 | self.foscam.ptz_stop_run() 132 | 133 | def test_move_down(self): 134 | # Test Move down 135 | self.foscam.ptz_move_down() 136 | self.foscam.ptz_stop_run() 137 | 138 | def test_move_left(self): 139 | # Test Move left 140 | self.foscam.ptz_move_left() 141 | self.foscam.ptz_stop_run() 142 | 143 | def test_move_right(self): 144 | self.foscam.ptz_move_right() 145 | self.foscam.ptz_stop_run() 146 | 147 | def test_move_top_left(self): 148 | self.foscam.ptz_move_top_left() 149 | self.foscam.ptz_stop_run() 150 | 151 | def test_move_top_right(self): 152 | self.foscam.ptz_move_top_right() 153 | self.foscam.ptz_stop_run() 154 | 155 | def test_move_bottom_left(self): 156 | self.foscam.ptz_move_bottom_left() 157 | self.foscam.ptz_stop_run() 158 | 159 | def test_move_bottom_right(self): 160 | self.foscam.ptz_move_bottom_right() 161 | self.foscam.ptz_stop_run() 162 | 163 | def test_reset(self): 164 | self.foscam.ptz_reset() 165 | 166 | def test_ptz_speed(self): 167 | self.foscam.get_ptz_speed() 168 | self.foscam.set_ptz_speed(4) 169 | self.foscam.ptz_move_up() 170 | self.foscam.ptz_stop_run() 171 | 172 | def test_ptz_selftest(self): 173 | self.foscam.set_ptz_selftestmode(mode=1) 174 | flag, kwargs = self.foscam.get_ptz_selftestmode() 175 | self.assertTrue('mode' in kwargs and int(kwargs['mode']) is 1) 176 | 177 | self.foscam.set_ptz_selftestmode(mode=0) 178 | flag, kwargs = self.foscam.get_ptz_selftestmode() 179 | self.assertTrue('mode' in kwargs and int(kwargs['mode']) is 0) 180 | 181 | # ***************** Test device manage ****************** 182 | 183 | def test_get_set_system_time(self): 184 | rc, args = self.foscam.get_system_time() 185 | self.assertTrue(rc == 0) 186 | self.foscam.set_system_time(time_source=args['timeSource'], 187 | ntp_server=args['ntpServer'], 188 | date_format=args['dateFormat'], 189 | time_format=args['timeFormat'], 190 | time_zone=args['timeZone'], 191 | is_dst=args['isDst'], 192 | dst=args['dst'], 193 | year=args['year'], 194 | mon=args['mon'], 195 | day=args['day'], 196 | hour=args['hour'], 197 | minute=args['minute'], 198 | sec=args['sec'], 199 | ) 200 | 201 | def test_devname(self): 202 | args = self.foscam.get_dev_name() 203 | self.foscam.set_dev_name('cherish`s cam') 204 | self.foscam.get_dev_name() 205 | self.foscam.set_dev_name(args['devName']) 206 | 207 | def test_dev_state(self): 208 | rc, args = self.foscam.get_dev_state() 209 | self.assertEqual(rc, 0) 210 | 211 | def test_dev_info(self): 212 | rc, args = self.foscam.get_dev_info() 213 | self.assertEqual(rc, 0) 214 | 215 | def test_open_infra_led(self): 216 | rc, args = self.foscam.open_infra_led() 217 | self.assertEqual(rc, 0) 218 | 219 | def test_close_infra_led(self): 220 | rc, args = self.foscam.close_infra_led() 221 | self.assertEqual(rc, 0) 222 | 223 | def test_get_infra_led_config(self): 224 | rc, args = self.foscam.get_infra_led_config() 225 | self.assertEqual(rc, 0) 226 | 227 | def test_set_infra_led_config(self): 228 | rc, args = self.foscam.set_infra_led_config(1) 229 | self.assertEqual(rc, 0) 230 | 231 | def test_get_product_all_info(self): 232 | rc, args = self.foscam.get_product_all_info() 233 | self.assertEqual(rc, 0) 234 | self.assertIn('modelName', args) 235 | 236 | # ************ Test AV Function ************************* 237 | 238 | def test_get_alarm_record_config(self): 239 | print(self.foscam.get_alarm_record_config()) 240 | 241 | def test_set_alarm_record_config(self): 242 | rc, old_args = self.foscam.get_alarm_record_config() 243 | new_args = {'isEnablePreRecord': 1, 244 | 'alarmRecordSecs': 240, 245 | 'preRecordSecs': 5 246 | } 247 | self.foscam.set_alarm_record_config( 248 | alarm_record_secs=new_args['alarmRecordSecs'], 249 | prerecord_secs=new_args['preRecordSecs']) 250 | 251 | rc, args = self.foscam.get_alarm_record_config() 252 | self.assertTrue(rc == 0) 253 | self.assertTrue(int(args['alarmRecordSecs']) == 254 | new_args['alarmRecordSecs']) 255 | self.assertTrue(int(args['preRecordSecs']) == 256 | new_args['preRecordSecs']) 257 | 258 | self.foscam.set_alarm_record_config( 259 | alarm_record_secs=old_args['alarmRecordSecs'], 260 | prerecord_secs=old_args['preRecordSecs']) 261 | 262 | def test_get_local_alarm_record_config(self): 263 | rc, args = self.foscam.get_local_alarm_record_config() 264 | self.assertTrue(set(args) == {'isEnableLocalAlarmRecord', 265 | 'localAlarmRecordSecs'}) 266 | 267 | def test_set_local_alarm_recor_config(self): 268 | rc, args = self.foscam.set_local_alarm_record_config( 269 | is_enable_local_alarm_record=1, 270 | local_alarm_record_secs=60) 271 | self.assertTrue(rc == 0) 272 | rc, args = self.foscam.get_local_alarm_record_config() 273 | self.assertTrue(all([rc == 0, 274 | args['isEnableLocalAlarmRecord'] == '1', 275 | args['localAlarmRecordSecs'] == '60'])) 276 | 277 | def test_get_h264_frm_ref_mode(self): 278 | rc, args = self.foscam.get_h264_frm_ref_mode() 279 | self.assertEqual(rc, 0) 280 | self.assertEqual(args.keys(), ['mode']) 281 | 282 | def test_set_h264_frm_ref_mode(self): 283 | mode = 3 284 | rc, args = self.foscam.set_h264_frm_ref_mode(mode=mode) 285 | self.assertEqual(rc, 0) 286 | rc, args = self.foscam.get_h264_frm_ref_mode() 287 | self.assertEqual(rc, 0) 288 | self.assertEqual(args['mode'], str(mode)) 289 | 290 | def test_get_schedule_record_config(self): 291 | all_args = {'isEnable', 'isEnableAudio', 'recordLevel', 292 | 'schedule0', 'schedule1', 'schedule2', 293 | 'schedule3', 'schedule4', 'schedule5', 294 | 'schedule6', 'spaceFullMode'} 295 | rc, args = self.foscam.get_schedule_record_config() 296 | self.assertTrue(rc == 0) 297 | self.assertTrue(set(args) == all_args) 298 | 299 | def test_set_schedule_record_config(self): 300 | rc, args = self.foscam.set_schedule_record_config( 301 | is_enable=1, record_level=4, 302 | space_full_mode=0, is_enable_audio=0) 303 | self.assertTrue(rc == 0) 304 | 305 | def test_get_record_path(self): 306 | rc, args = self.foscam.get_record_path() 307 | self.assertTrue(rc == 0) 308 | self.assertTrue(set(args) == {'path', 'free', 'total'}) 309 | 310 | def test_set_record_path(self): 311 | rc, args = self.foscam.get_record_path() 312 | self.assertTrue(rc == 0) 313 | rc, args = self.foscam.set_record_path(path=0) 314 | self.assertTrue(rc == 0) 315 | 316 | def test_get_ptz_preset_point_list(self): 317 | rc, args = self.foscam.get_ptz_preset_point_list() 318 | self.assertTrue('point0' in args) 319 | 320 | # ******************* Other ***************************** 321 | def test_unblocked_execute(self): 322 | self.foscam.daemon = True 323 | self.foscam.ptz_move_up() 324 | sleep(0.5) 325 | self.foscam.ptz_stop_run() 326 | 327 | def test_callback(self): 328 | def print_res(*args, **kwargs): 329 | with open('temp.txt', 'w') as f: 330 | f.write(str(args)) 331 | f.write(str(kwargs)) 332 | 333 | self.foscam.daemon = True 334 | self.foscam.get_ip_info(print_res) 335 | timeout = 10 336 | flag = False 337 | while timeout >= 0: 338 | try: 339 | with open('temp.txt', 'r') as new_f: 340 | self.assertTrue(new_f.read() != '') 341 | flag = True 342 | break 343 | except Exception as e: 344 | print(e) 345 | pass 346 | sleep(0.5) 347 | timeout -= 0.5 348 | self.assertTrue(flag) 349 | 350 | # *************** SnapPicture Function ******************* 351 | 352 | def test_snap_picture_2(self): 353 | # also test callback with raw data 354 | callback = CallbackForTest() 355 | rc, data = self.foscam.snap_picture_2(callback=callback) 356 | self.assertEqual(rc, FOSCAM_SUCCESS) 357 | with open('test.jpg', 'wb') as fp: 358 | fp.write(data) 359 | self.assertSequenceEqual(callback.args, (rc, data)) 360 | 361 | # ********************** Misc **************************** 362 | 363 | def test_get_log(self): 364 | # also test callback with non-raw data 365 | callback = CallbackForTest() 366 | rc, args = self.foscam.get_log(0, callback=callback) 367 | self.assertEqual(rc, FOSCAM_SUCCESS) 368 | self.assertTrue('log0' in args) 369 | self.assertSequenceEqual(callback.args, (rc, args)) 370 | 371 | 372 | if __name__ == '__main__': 373 | unittest.main() 374 | --------------------------------------------------------------------------------