4

Can a system consisting of 10x Raspberry Pi's, 10x RPi Camera Modules + WiFi dongles be reliably & consistently synchronized over WiFi to take photos at the same time? Or are radio triggers a better option for simultaneous shooting. The system must remain scalable to 100+ Raspberries.

tiivik
  • 41
  • 1
  • 2

4 Answers4

7

You may be able to get pretty close. My idea goes something like:

  • sync time across all RPi's using NTP
  • broadcast a network message to shoot in the future
  • a listener calculates how long to wait and then performs the action

Using NTP you can get all RPi's to be in pretty good time sync with each other. From this Wikipedia article NTP can have sub-1ms resolution on a local network:

NTP can usually maintain time to within tens of milliseconds over the public Internet, and can achieve better than one millisecond accuracy in local area networks under ideal conditions

Put all your RPi's on a 192.168.0.0/16 network and you'll be set for thousands of devices; e.g. 192.168.0.1 - 192.168.254.254. My home network is a 192.168.1.0/24 network, which would support 254 devices.

The listener waits for a message with a timestamp, sleeps until that time and executes the desired action. For example, here's an implementation in Python. If your timing requirements are more stringent, maybe drop to C:

#!/usr/bin/env python
# original code from:
# http://www.prodigyproductionsllc.com/articles/programming/write-a-udp-client-and-server-with-python/

import datetime
import logging
import re
import socket
import sys
import time

SYNC_RE = re.compile(r'sync at (?P<timestamp>.*)')


logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger('listener')

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
s.bind(('', 5000))

logger.info('Listening for broadcasts...')

def do_action():
    now = datetime.datetime.utcnow()
    logger.info('syncd at {}'.format(now))


def sync_at(dt):
    delta = time.mktime(dt.timetuple()) - time.time()

    # broadcast blasts a few messages to ensure delivery
    # ignore broadcasts in the past
    if delta < 0:
        logger.debug('past')
        return

    time.sleep(delta)

    do_action()


while True:
    try:
        message, address = s.recvfrom(8192)

        matches = SYNC_RE.match(message)
        if not matches:
            continue

        dt = datetime.datetime.strptime(matches.group('timestamp'), "%Y-%m-%dT%H:%M:%S")
        logger.debug('dt={}, timestamp={}'.format(dt, matches.group('timestamp')))

        sync_at(dt)
    except (KeyboardInterrupt, SystemExit):
        break
    except Exception, exc:
        logger.exception(exc)

In the code above you can put your logic in do_action() and you're all set.

The broadcaster calculates a time in the future and blasts this on the network. I noticed sometimes the message would be missed, so I have it send it a few times. The listener above ignores any messages that appear in the past. The time delay is set to 2 seconds in the future.

#!/usr/bin/env python
# original code from:
# http://www.prodigyproductionsllc.com/articles/programming/write-a-udp-client-and-server-with-python/

import datetime
import logging
import socket
import sys
import time


logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logger = logging.getLogger('broadcast')

dest = ('<broadcast>', 5000)
delta = datetime.timedelta(seconds=2)

now = datetime.datetime.utcnow().replace(microsecond=0)

later = now + delta
later_iso = later.isoformat()

message = 'sync at {}\n'.format(later_iso)

logger.info(message)

for i in range(10):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
    s.sendto(message, dest)

    time.sleep(0.05)

In my quick tests, with a timestamp delay of 2 seconds, the devices are synced within a millisecond:

enter image description here

I like this approach because sync is handled by NTP and messaging is super basic networking. :)

berto
  • 1,251
  • 1
  • 9
  • 12
2

the time difference [...] should be as minimal as possible (preferrably under 1 millisecond)

That's not very feasible on a single pi sans networking, by which I mean, if you write a program to take a series of photographs spaced with millisecond granularity -- #1 at N, #2 at N + 1014 ms, #3 at N + 5135 ms, etc. -- this timing will fail badly. You might be able to get them within 10-100 ms of the target, but again, this is with regard to one pi, no network.

This is largely a constraint of the fact that the operating systems available for the pi are generally multi-tasking (as opposed to real-time).

However, before you go down the RTOS route, you might want to re-evaluate your requirements. Unless you are taking pictures of very fast moving objects, 1 ms is totally irrelevant:1 Consider an object moving 100 km/h * 1000 = 100000 m/h / 3600 = 27.777 m/s / 1000 = 2 cm/ms.

So, if this is for getting perfectly timed images at a racetrack for a science documentary, you probably want to invest in some much more expensive equipment. However, if 250-500 ms latency is at all feasible, you might be able to achieve that over wifi, although I think there will be some percentage of inconsistent shots. Using some kind of direct radio signal might get this down to 100 ms -- but I think a requirement for anything less than that is a requirement for completely different, very specialized, hardware.


1. Note that the shutter speed of the camera is probably going to be in excess of 1 ms.

goldilocks
  • 60,325
  • 17
  • 117
  • 234
1

Yes, no problem. It's done for creating 3d-scan of peaple.

Here is a site that shows how (and how exspensive it will be).

http://www.pi3dscan.com/

/Eric

1

I have built a system to do exactly this. I have a central Pi (called Pi0), with a bunch of slave Pi's (Pi2 - Pi12). Pi0 is set up as a wireless AP, and all the slave Pi's connect to Pi0 as stations on the same wireless network.

When a photo is triggered, Pi0 uses a network broadcast to send out a single udp packet to the entire subnet (to port 6789), to the subnet broadcast address. All of the slave Pi's are listening on that UDP port, so they all receive the trigger messages at the same time, and all take pictures.