Diff of /app/resources/virb.py [000000] .. [4de1c7]

Switch to unified view

a b/app/resources/virb.py
1
#!/usr/bin/python3
2
# -*- coding: utf8 -*-
3
"""Python 3 Module to interact with Garmin Virb cameras over wifi / mass storage
4
This module also allows you to interact with the Garmin website and
5
fetch firmware updates when you don't feel like using the Garmin supplied
6
windows or Mac software.
7
8
I can't give you any warranty that any of these things will work,
9
especially the firmware upgrades could be dangerous.
10
"""
11
__author__ = 'Jan KLopper (jan@underdark.nl)'
12
__version__ = 0.2
13
14
import os
15
import simplejson
16
import requests
17
18
class Virb(object):
19
  """Class to interact with Garmin Virb cameras over wifi / http"""
20
  def __init__(self, host=('192.168.0.1', 80)):
21
    """Sets up the connection with the Virb device
22
23
    Accepts an ip and port which should be routable from the device running
24
    the code.
25
26
    host = ('192.168.0.1', 80)
27
    """
28
    self.host = host
29
    self.requestcount = 0
30
31
  def status(self):
32
    """Returns the current camera status"""
33
    command = 'status'
34
    data = {'command': command}
35
    return self._do_post(data=data)
36
37
  def features(self):
38
    """Returns the features"""
39
    command = 'features'
40
    data = {'command': command}
41
    return self._do_post(data=data)[command]
42
43
  def get_features(self):
44
    """Returns the features list as a dictionary"""
45
    features = self.features()
46
    results = {'enabled': {},
47
               'disabled': {}}
48
    for feature in features:
49
      name = str(feature['feature'])
50
      value = feature['value']
51
      try:
52
        value = int(value)
53
      except ValueError:
54
        value = str(value)
55
      if feature['enabled']:
56
        results['enabled'][name] = value
57
      else:
58
        results['disabled'][name] = value
59
    return results
60
61
  def set_features(self, feature, value):
62
    """Update a features"""
63
    command = 'updateFeature'
64
    data = {'command': command,
65
            'feature': feature,
66
            'value': value}
67
    return self._do_post(data=data)['features']
68
69
  def sensors(self):
70
    """Returns the current camera sensor readings"""
71
    command = 'sensors'
72
    data = {'command': command}
73
    sensors = self._do_post(data=data)
74
    if not sensors:
75
      raise VirbNoSensors('no Sensors are currently available')
76
77
  def device_info(self):
78
    """Returns the cameras device info"""
79
    command = 'deviceInfo'
80
    data = {'command': command}
81
    return self._do_post(data=data)[command]
82
83
  def locate(self):
84
    """Starts the camera emmiting its lost sound/flash"""
85
    command = 'locate'
86
    data = {'command':command}
87
    return bool(int(self._do_post(data=data)['result']))
88
89
  def found(self):
90
    """Stops the camera emmiting its lost sound/flash"""
91
    command = 'found'
92
    data = {'command':command}
93
    return bool(int(self._do_post(data=data)['result']))
94
95
  def media_dir_list(self):
96
    """Returns the list of media directories on the device"""
97
    command = 'mediaDirList'
98
    data = {'command':command}
99
    return self._do_post(data=data)#['mediaDirs']
100
101
  def media_list(self, path=None):
102
    """Returns the list of media directories on the device"""
103
    command = "mediaList"
104
    data = {'command':command}
105
    if path:
106
      data['path'] = path
107
    return self._do_post(data=data)['media']
108
109
110
  def live_preview(self, streamtype="rtp"):
111
    """Returns the cameras live preview url"""
112
    command = 'livePreview'
113
    data = {'command':command,
114
            'streamType':streamtype}
115
    return self._do_post(data=data)['url']
116
117
  def snap_picture(self, timer=0):
118
    """Take a picture"""
119
    command = 'snapPicture'
120
    data = {'command':command,
121
            'selfTimer':timer}
122
    return self._do_post(data=data)
123
124
  def start_recording(self):
125
    """Start recording"""
126
    command = "startRecording"
127
    data = {'command':command}
128
    return bool(int(self._do_post(data=data)['result']))
129
130
  def stop_recording(self):
131
    """Stop recording"""
132
    command = 'stopRecording'
133
    data = {'command':command}
134
    return bool(int(self._do_post(data=data)['result']))
135
136
  def stop_stil_tecording(self):
137
    """Stop recording still images"""
138
    command = 'stopStillRecording'
139
    data = {'command':command}
140
    return bool(int(self._do_post(data=data)['result']))
141
142
  def _do_post(self, url='virb', data=None):
143
    url = 'http://%s:%d/%s' % (self.host[0], self.host[1], url)
144
    request = requests.post(url, data=simplejson.dumps(data))
145
    self.requestcount += 1
146
    try:
147
      return request.json()
148
    except simplejson.scanner.JSONDecodeError:
149
      return request.text
150
151
152
class VirbUsb(object):
153
  """Class to interact with Garmin Virb devices over USB"""
154
  def __init__(self, device):
155
    """Sets the USB device path as seen on the filesystem
156
157
    For example:
158
    device = /media/garmin-virb"""
159
    self.device = device
160
161
  def get_log(self):
162
    """Yields a list of log entries on the device"""
163
    logfile = open('%s/Garmin/elog.txt' % self.device, 'r')
164
    logentry = []
165
    for line in logfile.readline():
166
      if line:
167
        logentry.append(line)
168
      if line == '-----------------------------------------':
169
        yield logentry
170
        logentry = []
171
172
  def clear_log(self):
173
    """Clears the log entries on the device"""
174
    logfile = open('%s/Garmin/elog.txt' % self.device, 'w')
175
    logfile.close()
176
    return True
177
178
  def get_tracks(self):
179
    """Lists all the gpx tracks on the devices"""
180
    trackpath = '%s/Garmin/GPX' % self.device
181
    fileslist = os.listdir(trackpath)
182
    return [filename for filename in fileslist
183
            if os.path.isfile(os.path.join(trackpath, filename))]
184
185
  def get_activity(self):
186
    """Lists all FIT activity files on the device
187
188
    Use https://github.com/dtcooper/python-fitparse to parse these files into
189
    something meaningfull. Or install fitparse using pip: `pip install fitparse`
190
    """
191
    activitypath = '%s/Garmin/Activity' % self.device
192
    fileslist = os.listdir(activitypath)
193
    return [filename for filename in fileslist
194
            if os.path.isfile(os.path.join(activitypath, filename))]
195
196
  def get_media(self, extensions=('jpg', 'mp4')):
197
    """Lists all pictures/videos on the devices
198
199
    Possibly filter on extensions (lowercase) using the extensions argument as
200
      a tuple
201
    """
202
    mediapath = '%s/DCIM/100_VIRB' % self.device
203
    fileslist = os.listdir(mediapath)
204
    return [filename for filename in fileslist
205
            if (os.path.isfile(os.path.join(mediapath, filename)) and
206
                filename[-3:].lower() in extensions)]
207
208
  def update_firmware(self, version=None):
209
    """This Upgrades the firmware on the Virb
210
211
    It follows the known update procedure but handles everything on its own.
212
213
    GCD Update Procedure
214
215
    Use the links in Firmware History to download the zipped file for the
216
    version you need.
217
    (https://www8.garmin.com/support/download_details.jsp?id=6565)
218
    Unzip the downloaded archive and extract the gcd file
219
    Rename the gcd file to gupdate.gcd
220
    Connect the VIRB to your computer via usb
221
    Copy the gupdate.gcd file to [µSD]/Garmin/gupdate.gcd
222
    Disconnect and reboot the VIRB
223
    Once the update is completed, the VIRB will delete the gupdate.gcd file"""
224
    if version:
225
      garmin = Garmin()
226
      firmware = garmin.get_firmware(version=version)
227
      targetpath = '%s/Garmin/gupdate-.gcd' % self.device
228
      print('Writing out data to device, do not reboot / power down')
229
      targetfile = open(targetpath, 'w')
230
      targetfile.write(firmware)
231
      print('Finishing writing out data to device, do not reboot / power down')
232
      os.fsync(targetfile)
233
      targetfile.close()
234
      print('All Done. Please reboot the Virb')
235
      return True
236
    return False
237
238
239
class Garmin(object):
240
  """Class to namespace any Garmin specific funtions and methods that have no
241
  direct use for any specific device information"""
242
  @staticmethod
243
  def get_firmware(device="VIRB", version=4.00):
244
    """Version as float"""
245
    return requests.get("https://download.garmin.com/software/%s_%d.gcd" % (
246
        device, int(version*100)))
247
248
class VirbError(Exception):
249
  """General exception class for Virb camera class"""
250
251
252
class VirbNoSensors(VirbError):
253
  """No sensors where located / enabled / connected to the Virb"""
254
255
256
if __name__ == '__main__':
257
  camera = Virb()
258
  print(repr(camera.status()))
259
  print(repr(camera.device_info()))
260
  print(repr(camera.features()))
261
  print(repr(camera.get_features()))
262
  try:
263
    print(repr(camera.sensors()))
264
  except VirbNoSensors:
265
    print('no sensors connected')
266
  print(repr(camera.media_dir_list()))
267
  #print(repr(camera.SnapPicture()))