/ adafruit_vc0706.py
adafruit_vc0706.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2017 Tony DiCola for Adafruit Industries 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all 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, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `adafruit_vc0706` 24 ==================================================== 25 26 VC0706 serial TTL camera module. Allows basic image capture and download of 27 image data from the camera over a serial connection. See examples for demo 28 of saving image to a SD card (must be wired up separately). 29 30 * Author(s): Tony DiCola 31 32 Implementation Notes 33 -------------------- 34 35 **Hardware:** 36 37 * Adafruit `TTL Serial JPEG Camera with NTSC Video 38 <https://www.adafruit.com/product/397>`_ (Product ID: 397) 39 40 **Software and Dependencies:** 41 42 * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: 43 https://github.com/adafruit/circuitpython/releases 44 """ 45 from micropython import const 46 47 __version__ = "0.0.0-auto.0" 48 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_VC0706.git" 49 50 # pylint: disable=bad-whitespace 51 _SERIAL = const(0x00) 52 _RESET = const(0x26) 53 _GEN_VERSION = const(0x11) 54 _SET_PORT = const(0x24) 55 _READ_FBUF = const(0x32) 56 _GET_FBUF_LEN = const(0x34) 57 _FBUF_CTRL = const(0x36) 58 _DOWNSIZE_CTRL = const(0x54) 59 _DOWNSIZE_STATUS = const(0x55) 60 _READ_DATA = const(0x30) 61 _WRITE_DATA = const(0x31) 62 _COMM_MOTION_CTRL = const(0x37) 63 _COMM_MOTION_STATUS = const(0x38) 64 _COMM_MOTION_DETECTED = const(0x39) 65 _MOTION_CTRL = const(0x42) 66 _MOTION_STATUS = const(0x43) 67 _TVOUT_CTRL = const(0x44) 68 _OSD_ADD_CHAR = const(0x45) 69 70 _STOPCURRENTFRAME = const(0x0) 71 _STOPNEXTFRAME = const(0x1) 72 _RESUMEFRAME = const(0x3) 73 _STEPFRAME = const(0x2) 74 75 # pylint doesn't like the lowercase x but it makes it more readable. 76 # pylint: disable=invalid-name 77 IMAGE_SIZE_640x480 = const(0x00) 78 IMAGE_SIZE_320x240 = const(0x11) 79 IMAGE_SIZE_160x120 = const(0x22) 80 # pylint: enable=invalid-name 81 _BAUDRATE_9600 = const(0xAEC8) 82 _BAUDRATE_19200 = const(0x56E4) 83 _BAUDRATE_38400 = const(0x2AF2) 84 _BAUDRATE_57600 = const(0x1C1C) 85 _BAUDRATE_115200 = const(0x0DA6) 86 87 _MOTIONCONTROL = const(0x0) 88 _UARTMOTION = const(0x01) 89 _ACTIVATEMOTION = const(0x01) 90 91 __SET_ZOOM = const(0x52) 92 __GET_ZOOM = const(0x53) 93 94 _CAMERA_DELAY = const(10) 95 # pylint: enable=bad-whitespace 96 97 98 class VC0706: 99 """Driver for VC0706 serial TTL camera module. 100 :param ~busio.UART uart: uart serial or compatible interface 101 :param int buffer_size: Receive buffer size 102 """ 103 104 def __init__(self, uart, *, buffer_size=100): 105 self._uart = uart 106 self._buffer = bytearray(buffer_size) 107 self._frame_ptr = 0 108 self._command_header = bytearray(3) 109 for _ in range(2): # 2 retries to reset then check resetted baudrate 110 for baud in (9600, 19200, 38400, 57600, 115200): 111 self._uart.baudrate = baud 112 if self._run_command(_RESET, b"\x00", 5): 113 break 114 else: # for:else rocks! http://book.pythontips.com/en/latest/for_-_else.html 115 raise RuntimeError("Failed to get response from VC0706, check wiring!") 116 117 @property 118 def version(self): 119 """Return camera version byte string.""" 120 # Clear buffer to ensure the end of a string can be found. 121 self._send_command(_GEN_VERSION, b"\x01") 122 readlen = self._read_response(self._buffer, len(self._buffer)) 123 return str(self._buffer[:readlen], "ascii") 124 125 @property 126 def baudrate(self): 127 """Return the currently configured baud rate.""" 128 return self._uart.baudrate 129 130 @baudrate.setter 131 def baudrate(self, baud): 132 """Set the baudrate to 9600, 19200, 38400, 57600, or 115200. """ 133 divider = None 134 if baud == 9600: 135 divider = _BAUDRATE_9600 136 elif baud == 19200: 137 divider = _BAUDRATE_19200 138 elif baud == 38400: 139 divider = _BAUDRATE_38400 140 elif baud == 57600: 141 divider = _BAUDRATE_57600 142 elif baud == 115200: 143 divider = _BAUDRATE_115200 144 else: 145 raise ValueError("Unsupported baud rate") 146 args = [0x03, 0x01, (divider >> 8) & 0xFF, divider & 0xFF] 147 self._run_command(_SET_PORT, bytes(args), 7) 148 self._uart.baudrate = baud 149 150 @property 151 def image_size(self): 152 """Get the current image size, will return a value of IMAGE_SIZE_640x480, 153 IMAGE_SIZE_320x240, or IMAGE_SIZE_160x120. 154 """ 155 if not self._run_command(_READ_DATA, b"\0x04\x04\x01\x00\x19", 6): 156 raise RuntimeError("Failed to read image size!") 157 return self._buffer[5] 158 159 @image_size.setter 160 def image_size(self, size): 161 """Set the image size to a value of IMAGE_SIZE_640x480, IMAGE_SIZE_320x240, or 162 IMAGE_SIZE_160x120. 163 """ 164 if size not in (IMAGE_SIZE_640x480, IMAGE_SIZE_320x240, IMAGE_SIZE_160x120): 165 raise ValueError( 166 "Size must be one of IMAGE_SIZE_640x480, IMAGE_SIZE_320x240, or " 167 "IMAGE_SIZE_160x120!" 168 ) 169 return self._run_command( 170 _WRITE_DATA, bytes([0x05, 0x04, 0x01, 0x00, 0x19, size & 0xFF]), 5 171 ) 172 173 @property 174 def frame_length(self): 175 """Return the length in bytes of the currently capture frame/picture. 176 """ 177 if not self._run_command(_GET_FBUF_LEN, b"\x01\x00", 9): 178 return 0 179 frame_length = self._buffer[5] 180 frame_length <<= 8 181 frame_length |= self._buffer[6] 182 frame_length <<= 8 183 frame_length |= self._buffer[7] 184 frame_length <<= 8 185 frame_length |= self._buffer[8] 186 return frame_length 187 188 def take_picture(self): 189 """Tell the camera to take a picture. Returns True if successful. 190 """ 191 self._frame_ptr = 0 192 return self._run_command(_FBUF_CTRL, bytes([0x1, _STOPCURRENTFRAME]), 5) 193 194 def read_picture_into(self, buf): 195 """Read the next bytes of frame/picture data into the provided buffer. 196 Returns the number of bytes written to the buffer (might be less than 197 the size of the buffer). Buffer MUST be a multiple of 4 and 100 or 198 less. Suggested buffer size is 32. 199 """ 200 n = len(buf) 201 if n > 256 or n > (len(self._buffer) - 5): 202 raise ValueError("Buffer is too large!") 203 if n % 4 != 0: 204 raise ValueError("Buffer must be a multiple of 4! Try 32.") 205 args = bytes( 206 [ 207 0x0C, 208 0x0, 209 0x0A, 210 0, 211 0, 212 (self._frame_ptr >> 8) & 0xFF, 213 self._frame_ptr & 0xFF, 214 0, 215 0, 216 0, 217 n & 0xFF, 218 (_CAMERA_DELAY >> 8) & 0xFF, 219 _CAMERA_DELAY & 0xFF, 220 ] 221 ) 222 if not self._run_command(_READ_FBUF, args, 5, flush=False): 223 return 0 224 if self._read_response(self._buffer, n + 5) == 0: 225 return 0 226 self._frame_ptr += n 227 for i in range(n): 228 buf[i] = self._buffer[i] 229 return n 230 231 def _run_command(self, cmd, args, resplen, flush=True): 232 if flush: 233 self._read_response(self._buffer, len(self._buffer)) 234 self._send_command(cmd, args) 235 if self._read_response(self._buffer, resplen) != resplen: 236 return False 237 if not self._verify_response(cmd): 238 return False 239 return True 240 241 def _read_response(self, result, numbytes): 242 return self._uart.readinto(memoryview(result)[0:numbytes]) 243 244 def _verify_response(self, cmd): 245 return ( 246 self._buffer[0] == 0x76 247 and self._buffer[1] == _SERIAL 248 and self._buffer[2] == cmd & 0xFF 249 and self._buffer[3] == 0x00 250 ) 251 252 def _send_command(self, cmd, args=None): 253 self._command_header[0] = 0x56 254 self._command_header[1] = _SERIAL 255 self._command_header[2] = cmd & 0xFF 256 self._uart.write(self._command_header) 257 if args: 258 self._uart.write(args)