diff --git a/configs/bananapi_defconfig b/configs/bananapi_defconfig
index 1f72d3bdf6..ea0a6c3697 100644
--- a/configs/bananapi_defconfig
+++ b/configs/bananapi_defconfig
@@ -70,6 +70,7 @@ BR2_PACKAGE_PYTHON_SSL=y
BR2_PACKAGE_PYTHON_HASHLIB=y
BR2_PACKAGE_PYTHON_JINJA2=y
BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
BR2_PACKAGE_PYTHON_PYTZ=y
BR2_PACKAGE_PYTHON_TORNADO=y
BR2_PACKAGE_PYTHON_VERSIONTOOLS=y
diff --git a/configs/odroidc1_defconfig b/configs/odroidc1_defconfig
index 82bcae8a82..b26e40cb86 100644
--- a/configs/odroidc1_defconfig
+++ b/configs/odroidc1_defconfig
@@ -75,6 +75,7 @@ BR2_PACKAGE_PYTHON_SSL=y
BR2_PACKAGE_PYTHON_HASHLIB=y
BR2_PACKAGE_PYTHON_JINJA2=y
BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
BR2_PACKAGE_PYTHON_PYTZ=y
BR2_PACKAGE_PYTHON_TORNADO=y
BR2_PACKAGE_PYTHON_VERSIONTOOLS=y
diff --git a/configs/odroidc2_defconfig b/configs/odroidc2_defconfig
index 345f0292df..7e35a09c8d 100644
--- a/configs/odroidc2_defconfig
+++ b/configs/odroidc2_defconfig
@@ -24,7 +24,9 @@ BR2_PACKAGE_FFMPEG=y
BR2_PACKAGE_FFMPEG_GPL=y
BR2_PACKAGE_FFMPEG_NONFREE=y
BR2_PACKAGE_FFMPEG_SWSCALE=y
+BR2_PACKAGE_LIBWEBCAM=y
BR2_PACKAGE_MOTION=y
+BR2_PACKAGE_GZIP=y
BR2_PACKAGE_JQ=y
BR2_PACKAGE_CIFS_UTILS=y
# BR2_PACKAGE_E2FSPROGS_BADBLOCKS is not set
@@ -57,11 +59,21 @@ BR2_PACKAGE_LINUX_FIRMWARE_RTL_81XX=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX=y
BR2_PACKAGE_LINUX_FIRMWARE_RTL_88XX=y
BR2_PACKAGE_USB_MODESWITCH_DATA=y
+BR2_PACKAGE_PYTHON_SSL=y
+BR2_PACKAGE_PYTHON_HASHLIB=y
+BR2_PACKAGE_PYTHON_JINJA2=y
+BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
+BR2_PACKAGE_PYTHON_PYTZ=y
+BR2_PACKAGE_PYTHON_RPI_GPIO=y
+BR2_PACKAGE_PYTHON_TORNADO=y
+BR2_PACKAGE_PYTHON_VERSIONTOOLS=y
BR2_PACKAGE_CA_CERTIFICATES=y
BR2_PACKAGE_NETTLE=y
BR2_PACKAGE_LIBFUSE=y
BR2_PACKAGE_JPEG=y
BR2_PACKAGE_LIBV4L=y
+BR2_PACKAGE_LIBV4L_UTILS=y
BR2_PACKAGE_LIBXML2=y
BR2_PACKAGE_LIBTHEORA=y
BR2_PACKAGE_X264=y
diff --git a/configs/odroidxu4_defconfig b/configs/odroidxu4_defconfig
index d99c7ca9d3..c84538e2b0 100644
--- a/configs/odroidxu4_defconfig
+++ b/configs/odroidxu4_defconfig
@@ -65,6 +65,7 @@ BR2_PACKAGE_PYTHON_SSL=y
BR2_PACKAGE_PYTHON_HASHLIB=y
BR2_PACKAGE_PYTHON_JINJA2=y
BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
BR2_PACKAGE_PYTHON_PYTZ=y
BR2_PACKAGE_PYTHON_TORNADO=y
BR2_PACKAGE_PYTHON_VERSIONTOOLS=y
diff --git a/configs/raspberrypi2_defconfig b/configs/raspberrypi2_defconfig
index 4c876eb972..6f251e042e 100644
--- a/configs/raspberrypi2_defconfig
+++ b/configs/raspberrypi2_defconfig
@@ -70,6 +70,7 @@ BR2_PACKAGE_PYTHON_SSL=y
BR2_PACKAGE_PYTHON_HASHLIB=y
BR2_PACKAGE_PYTHON_JINJA2=y
BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
BR2_PACKAGE_PYTHON_PYTZ=y
BR2_PACKAGE_PYTHON_RPI_GPIO=y
BR2_PACKAGE_PYTHON_TORNADO=y
diff --git a/configs/raspberrypi3_defconfig b/configs/raspberrypi3_defconfig
index 91f3f40fb4..204286c3dc 100644
--- a/configs/raspberrypi3_defconfig
+++ b/configs/raspberrypi3_defconfig
@@ -70,6 +70,7 @@ BR2_PACKAGE_PYTHON_SSL=y
BR2_PACKAGE_PYTHON_HASHLIB=y
BR2_PACKAGE_PYTHON_JINJA2=y
BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
BR2_PACKAGE_PYTHON_PYTZ=y
BR2_PACKAGE_PYTHON_RPI_GPIO=y
BR2_PACKAGE_PYTHON_TORNADO=y
diff --git a/configs/raspberrypi_defconfig b/configs/raspberrypi_defconfig
index 3f89ed2c7a..5d8c8e4f27 100644
--- a/configs/raspberrypi_defconfig
+++ b/configs/raspberrypi_defconfig
@@ -72,6 +72,7 @@ BR2_PACKAGE_PYTHON_SSL=y
BR2_PACKAGE_PYTHON_HASHLIB=y
BR2_PACKAGE_PYTHON_JINJA2=y
BR2_PACKAGE_PYTHON_PILLOW=y
+BR2_PACKAGE_PYTHON_PYCURL=y
BR2_PACKAGE_PYTHON_PYTZ=y
BR2_PACKAGE_PYTHON_RPI_GPIO=y
BR2_PACKAGE_PYTHON_TORNADO=y
diff --git a/package/motioneye/platformupdate.py b/package/motioneye/platformupdate.py
new file mode 100644
index 0000000000..5f8caff60d
--- /dev/null
+++ b/package/motioneye/platformupdate.py
@@ -0,0 +1,53 @@
+
+# Copyright (c) 2017 Calin Crisan
+# This file is part of motionEyeOS.
+#
+# motionEyeOS is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import logging
+import os
+import subprocess
+
+
+def get_all_versions():
+ try:
+ return subprocess.check_output('fwupdate versions', shell=True).strip().split('\n')
+
+ except Exception as e:
+ logging.error('failed to list versions: %s' % e)
+
+
+def perform_update(version):
+ logging.info('stopping motioneye watch script')
+ os.system('kill $(pidof S85motioneye)')
+
+ logging.info('stopping netwatch script')
+ os.system('/etc/init.d/S41netwatch stop')
+
+ logging.error('downloading firmware version %s' % version)
+ if os.system('fwupdate download %s > /dev/null' % version):
+ logging.error('firmware download failed')
+
+ logging.error('extracting firmware')
+ if os.system('fwupdate extract > /dev/null'):
+ logging.error('firmware extracting failed')
+
+ logging.error('flashing boot partition')
+ if os.system('fwupdate flashboot > /dev/null'):
+ logging.error('firmware flash boot failed')
+
+ logging.error('rebooting')
+ if os.system('fwupdate flashreboot > /dev/null'):
+ logging.error('firmware flash reboot failed')
+
diff --git a/package/motioneye/update.py b/package/motioneye/update.py
deleted file mode 100644
index e9fd184b4b..0000000000
--- a/package/motioneye/update.py
+++ /dev/null
@@ -1,263 +0,0 @@
-
-# Copyright (c) 2015 Calin Crisan
-# This file is part of motionEyeOS.
-#
-# motionEyeOS is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-import json
-import logging
-import os.path
-import re
-import shutil
-import ssl
-import subprocess
-import time
-import urllib2
-
-import settings
-
-
-_BOARD = open('/etc/board').read().strip()
-_REPO = ('ccrisan', 'motioneyeos')
-_DOWNLOAD_URL = 'https://github.com/{owner}/{repo}/releases/download/%(version)s/motioneyeos-%(board)s-%(version)s.img.gz'.format(
- owner=_REPO[0], repo=_REPO[1])
-_LIST_VERSIONS_URL = 'https://api.github.com/repos/{owner}/{repo}/releases'.format(
- owner=_REPO[0], repo=_REPO[1])
-_DOWNLOAD_DIR = '/data/.firmware_update'
-_DOWNLOAD_FILE_NAME = os.path.join(_DOWNLOAD_DIR, 'firmware.gz')
-
-
-# versions
-
-def get_version():
- import motioneye
-
- return motioneye.VERSION
-
-
-def get_all_versions():
- url = _LIST_VERSIONS_URL
- url += '?_=' + str(int(time.time())) # prevents caching
-
- want_prereleases = subprocess.check_output('source /data/etc/os.conf && echo $os_prereleases', shell=True, stderr=subprocess.STDOUT).strip() == 'true'
-
- try:
- logging.debug('board is %s' % _BOARD)
- logging.debug('fetching %s...' % url)
-
- context = ssl._create_unverified_context()
-
- response = urllib2.urlopen(url, timeout=settings.REMOTE_REQUEST_TIMEOUT, context=context)
- releases = json.load(response)
-
- versions = []
- for release in releases:
- if release.get('prerelease') and not want_prereleases:
- continue
-
- for asset in release.get('assets', []):
- if not re.match('^motioneyeos-%s-\d{8}\.img.gz$' % _BOARD, asset['name']):
- continue
-
- versions.append(release['name'])
-
- logging.debug('available versions: %(versions)s' % {'versions': ', '.join(versions)})
-
- return sorted(versions)
-
- except Exception as e:
- logging.error('could not get versions: %s' % e, exc_info=True)
-
- return []
-
-
-def compare_versions(version1, version2):
- version1 = re.sub('[^0-9.]', '', version1)
- version2 = re.sub('[^0-9.]', '', version2)
-
- def int_or_0(n):
- try:
- return int(n)
-
- except:
- return 0
-
- version1 = [int_or_0(n) for n in version1.split('.')]
- version2 = [int_or_0(n) for n in version2.split('.')]
-
- len1 = len(version1)
- len2 = len(version2)
- length = min(len1, len2)
- for i in xrange(length):
- p1 = version1[i]
- p2 = version2[i]
-
- if p1 < p2:
- return -1
-
- elif p1 > p2:
- return 1
-
- if len1 < len2:
- return -1
-
- elif len1 > len2:
- return 1
-
- else:
- return 0
-
-
-# updating
-
-def download(version):
- url = _DOWNLOAD_URL % {'version': version, 'board': _BOARD}
-
- try:
- logging.info('downloading %s...' % url)
-
- shutil.rmtree(_DOWNLOAD_DIR, ignore_errors=True)
- os.makedirs(_DOWNLOAD_DIR)
- subprocess.check_call(['/usr/bin/wget', url, '--no-check-certificate', '-O', _DOWNLOAD_FILE_NAME])
-
- except Exception as e:
- logging.error('could not download update: %s' % e)
-
- raise
-
- try:
- logging.info('decompressing %s...' % _DOWNLOAD_FILE_NAME)
-
- subprocess.check_call(['/bin/gunzip', _DOWNLOAD_FILE_NAME])
-
- except Exception as e:
- logging.error('could not decompress archive: %s' % e)
-
- raise
-
- extracted_file_name = _DOWNLOAD_FILE_NAME.replace('.gz', '')
-
- try:
- logging.info('reading partiton table...')
-
- output = subprocess.check_output(['/sbin/fdisk', '-l', extracted_file_name])
- lines = [l.strip().replace('*', ' ') for l in output.split('\n') if l.startswith(extracted_file_name)]
- boot_info = lines[0].split()
- root_info = lines[1].split()
-
- boot_start, boot_end = int(boot_info[1]), int(boot_info[2])
- root_start, root_end = int(root_info[1]), int(root_info[2])
-
- except Exception as e:
- logging.error('could not read partition table: %s' % e)
-
- raise
-
- try:
- logging.info('extracting boot.img...')
-
- subprocess.check_call(['/bin/dd', 'if=' + extracted_file_name, 'of=' + os.path.join(_DOWNLOAD_DIR, 'boot.img'),
- 'bs=2048', 'skip=' + str(boot_start / 4), 'count=' + str((boot_end - boot_start + 1) / 4)])
-
- except Exception as e:
- logging.error('could not extract boot.img: %s' % e)
-
- raise
-
- try:
- logging.info('extracting root.img...')
-
- subprocess.check_call(['/bin/dd', 'if=' + extracted_file_name, 'of=' + os.path.join(_DOWNLOAD_DIR, 'root.img'),
- 'bs=2048', 'skip=' + str(root_start / 4), 'count=' + str((root_end - root_start + 1) / 4)])
-
- except Exception as e:
- logging.error('could not extract root.img: %s' % e)
-
- raise
-
-
-def perform_update(version):
- logging.info('updating to version %(version)s...' % {'version': version})
-
- logging.info('killing motioneye init script...')
- os.system('kill $(pidof S85motioneye)')
-
- logging.info('stopping netwatch init script...')
- os.system('/etc/init.d/S41netwatch stop')
-
- download(version)
-
- logging.info('backing up /boot/config.txt')
- if os.system('/bin/cp /boot/config.txt /tmp/config.txt'):
- logging.error('failed to backup /boot/config.txt')
-
- raise Exception('failed to backup /boot/config.txt')
-
- logging.info('unmounting boot partition...')
- if os.system('/bin/umount /boot'):
- logging.error('failed to unmount boot partition')
-
- raise Exception('failed to unmount boot partition')
-
- try:
- logging.info('installing boot image...')
- boot_img = os.path.join(_DOWNLOAD_DIR, 'boot.img')
-
- subprocess.check_call(['/bin/dd', 'if=' + boot_img, 'of=/dev/mmcblk0p1', 'bs=1M'])
-
- except Exception as e:
- logging.error('could not install boot image: %s' % e)
-
- raise
-
- logging.info('mounting boot partition read-write...')
- if os.system('/bin/mount -o rw /dev/mmcblk0p1 /boot'):
- logging.error('failed to mount boot partition')
-
- raise Exception('failed to mount boot partition')
-
- logging.info('restoring up /boot/config.txt')
- if os.system('/bin/cp /tmp/config.txt /boot/config.txt'):
- logging.error('failed to restore /boot/config.txt')
-
- raise Exception('failed to restore /boot/config.txt')
-
- logging.info('preparing to boot in fwupdate mode...')
- try:
- config_lines = [c.strip() for c in open('/boot/config.txt', 'r').readlines() if c.strip()]
-
- except Exception as e:
- logging.error('failed to read /boot/config.txt: %s' % e, exc_info=True)
-
- raise
-
- config_lines.append('initramfs fwupdater.gz')
-
- try:
- with open('/boot/config.txt', 'w') as f:
- for line in config_lines:
- f.write(line + '\n')
-
- except Exception as e:
- logging.error('failed to write /boot/config.txt: %s' % e, exc_info=True)
-
- raise
-
- logging.info('rebooting...')
-
- if os.system('/sbin/reboot'):
- logging.error('failed to reboot')
- logging.info('hard rebooting...')
- open('/proc/sysrq-trigger', 'w').write('b') # reboot
-