JPEG camera + ESP8266

Read image from LinkSprite 2MP JPEG Camera and display in RoboRemo

JPEG camera + ESP8266

This page explains how to take a picture with the LinkSprite 2MP JPEG camera and display the image in RoboRemo.

Materials needed:
- HUZZAH ESP module
- LinkSprite 2MP JPEG camera
- breadboard
- wires
- Android phone with RoboRemo app installed

Introduction

The LinkSprite 2MP JPEG camera is a little camera module can take pictures, compress them to JPEG and send via UART. That combines perfectly with ESP8266 and RoboRemo, because ESP8266 can be programmed to interface the camera, and has UART port, and RoboRemo has image item that can receive and display images (in JPEG format and many others).
I used the HUZZAH ESP module and nodemcu_integer_0.9.6-dev_20150704.bin firmware. Previously I was trying with ESP-01 with older NodeMCU and it was showing out of memory error (the lua script for reading the camera is quite big).

Steps:

1.
Upload the init.lua script to the ESP:

wifi.setmode(wifi.SOFTAP)
cfg={}
cfg.ssid="mywifi"
cfg.pwd="qwerty123"
cfg.ip="192.168.0.1"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.0.1"
port=9876
wifi.ap.setip(cfg)
wifi.ap.config(cfg)

function stS(a,b)
  return string.sub(a,1,string.len(b))==b
end

function fhex(str)
  return (str:gsub('..', function(cc)
    return string.char(tonumber(cc,16))
  end))
end

function tohex(str)
  return (str:gsub('.',function(c)
    return string.format('%02X',string.byte(c))
  end))
end

function ith(a) return string.format('%06X',a) end
function nth() end
onSent=nth

function ascii(s,i) return string.byte(string.sub(s,i,i)) end

function getImg()
  bk = 240
  if bk>isz then bk=isz end
  uart.write(0,fhex("5600320C000A00"..ith(addr).."00"..ith(bk).."000A"))
  addr=addr+bk
  isz=isz-bk
  uart.on("data",bk+10,function(data)
    if isz==0 then
      onSent=nth
      uart.on("data")
    else
      onSent=getImg
    end
    send(string.sub(data,6,-6))
  end,0)   
end

function exe(st)
  rsp=5
  if stS(st,"56002600") then rsp=12 end
  if stS(st,"5600340100") then rsp=9 end
  if stS(st,"5600320C00") then
    addr=0
    onSent = getImg
    send("img "..isz.."\n")
    return
  end
  uart.write(0,fhex(st))
  uart.on("data",rsp,function(s)
    st=tohex(s).."\n"
    if(rsp==9) then
      isz=ascii(s,7)
      isz=bit.lshift(isz,8)+ascii(s,8)
      isz=bit.lshift(isz,8)+ascii(s,9)
      st=st.."size="..isz.."\n"
    end
    send(st)
    uart.on("data")
  end,0)
end

cmd=""
function rcvDat(c,data)
  cmd = cmd..data
  local a,b=string.find(cmd,"\n",1,true)
  while a do
    exe( string.sub(cmd,1,a-1))
    cmd=string.sub(cmd,a+1,string.len(cmd))
    a,b=string.find(cmd,"\n",1,true)
  end
end

print(":)")
srv=net.createServer(net.TCP,28800) 
srv:listen(port,function(conn)
  uart.setup(0,115200,8,0,1,1)
  function send(data) conn:send(data, onSent) end
  conn:on("receive",rcvDat) 
  conn:on("disconnection",function(c) uart.setup(0,9600,8,0,1,1) end)
end)

Same script, explained below:
(if you try to upload this script, you may get out of memory error as this explained file is bigger)

wifi.setmode(wifi.SOFTAP) -- set mode to software access point
cfg={}
cfg.ssid="mywifi"
cfg.pwd="qwerty123"
cfg.ip="192.168.0.1"
cfg.netmask="255.255.255.0"
cfg.gateway="192.168.0.1"
port=9876 -- port for TCP connection
wifi.ap.setip(cfg) -- configure ip, netmask and gateway
wifi.ap.config(cfg) -- configure ssid and password

function stS(a,b) -- checks if string a starts with string b
  return string.sub(a,1,string.len(b))==b
end

function fhex(str) -- converts string from hex (ex: "6A6B6C" -> "jkl")
  return (str:gsub('..', function(cc) -- substitute every 2 characters
    return string.char(tonumber(cc,16)) -- with the character that has
                        -- ASCII code equal to that number in hex
  end))
end

function tohex(str) -- converts string to hex (ex: "jkl" -> "6A6B6C")
  return (str:gsub('.',function(c) -- substitute each character
    return string.format('%02X',string.byte(c)) -- with the ASCII code
                      -- as a 2-digit hex number
  end))
end

function ith(a) return string.format('%06X',a) end -- converts an integer
                -- to a 6-digit hex number string (ex: 12345 -> "003039")
function nth() end -- function that does nothing
onSent=nth -- point the onSent callback to the function that does nothing

function ascii(s,i) return string.byte(string.sub(s,i,i)) end
         -- returns the ascii code (as integer)
         -- of the char at index i from string s (i starts from 1)

function getImg() -- get image data from camera and send it over TCP
  bk = 240 -- block size
  if bk>isz then bk=isz end -- if the remaining image data size
        -- is less than block size, then use a shorter block
  uart.write(0,fhex("5600320C000A00"..ith(addr).."00"..ith(bk).."000A"))
  -- send cmd to read image data starting from addr, with size bk

  addr=addr+bk -- increment address for next read
  isz=isz-bk -- decrease the remaining image data size
  uart.on("data",bk+10,function(data) -- after receiving bk+10 bytes
    -- +10 because camera sends 76 00 32 00 00 (5 bytes) before
    -- and after the actual image data
    if isz==0 then -- it that was the last block of image data
      onSent=nth -- send nothing after it
      uart.on("data") -- regain uart access to lua interpreter
    else -- if it was not the last block
      onSent=getImg -- recall this function after sending the block
                    -- so that it will continue sending
    end
    send(string.sub(data,6,-6)) -- now start sending the block
  end,0)   
end

function exe(st) -- execute command from st
  rsp=5 -- response from camera is usually 5 bytes
  if stS(st,"56002600") then rsp=12 end -- for reset cmd it is 12 bytes
  if stS(st,"5600340100") then rsp=9 end -- for get image data size
                                         -- is is 9 bytes
  if stS(st,"5600320C00") then -- if cmd is to get image
    addr=0 -- reset address to 0
    onSent = getImg -- set callback to getImg
    send("img "..isz.."\n") -- send to RoboRemo the id "img" followed
                            -- by space, image size and command ending
                            -- after sending that, the getImg will be called
    return -- return from exe(st)
  end
  uart.write(0,fhex(st)) -- convert command from hex string to actual bytes
                         -- and send it to camera
  uart.on("data",rsp,function(s) -- when response received
    st=tohex(s).."\n" -- convert it to hex string
                      -- and append RoboRemo cmd ending
    if(rsp==9) then -- is cmd was to get image size
      isz=ascii(s,7) -- compute image size from the 3 bytes
      isz=bit.lshift(isz,8)+ascii(s,8)
      isz=bit.lshift(isz,8)+ascii(s,9)
      st=st.."size="..isz.."\n" -- append to the string to
                                -- display in RoboRemo text log
    end
    send(st) -- send string to RoboRemo
    uart.on("data") -- regain uart access to lua interpreter
  end,0)
end

cmd="" -- declare cmd as empty string
function rcvDat(c,data) -- called when received some data from TCP
  cmd = cmd..data -- apend data to cmd
  local a,b=string.find(cmd,"\n",1,true) -- search for "\n"
  while a do -- if cmd contains "\n"
    exe( string.sub(cmd,1,a-1)) -- execute what is before "\n"
    cmd=string.sub(cmd,a+1,string.len(cmd)) -- and remove it
    a,b=string.find(cmd,"\n",1,true) -- search for next "\n"
  end
end

print(":)") -- for debug
srv=net.createServer(net.TCP,28800) -- create a TCP server with max timeout
srv:listen(port,function(conn) -- listen to port, function will be called
                               -- when RoboRemo connects
  uart.setup(0,115200,8,0,1,1) -- switch baud to 115200
  function send(data) conn:send(data, onSent) end -- declare function send
                                            -- and set the onSent callback
  conn:on("receive",rcvDat) -- set the callback for receiving data
  conn:on("disconnection",function(c) uart.setup(0,9600,8,0,1,1) end)
    -- when RoboRemo disconnects, switch back the baud to 9600
end)

2.
Connect camera UART to the ESP UART (Rx to Tx and Tx to Rx)

3.
Download RoboRemo interface file and send to your Android phone or tablet:

jpeg-cam-interface-phone.interface

jpeg-cam-interface-tablet.interface

Copy the interface file to your Android device, then import in RoboRemo using menu -> interface -> import.

4.
From Android WiFi settings connect to ESP (ssid: mywifi, password: qwerty123)

5.
Open RoboRemo, connect to TCP 192.168.0.1:9876 and enjoy :)