The OpenScan Composer Firmware image contains a stable version of Raspbian OS packaged with a custom network daemon used to control the OpenScan hardware via the standard GPIO pin connections. This replaces the default OpenScan firmware and web based interface.

Download

This software is provided “as is”, without any warranty, express or implied. Use of this software may cause hardware damage and is entirely at your own risk!

10/23/2024 – OSC Firmware BETA v0.9.0.3 – Raspberry Pi OS Lite 64 bit (Bookworm 6.6.31+rpt-rpi-v8) (~0.98gb)

Change Log

v0.9.0.3 (10/23/2024)
Improved processing of framebuffer captures from camera (increased capture rates approximately 10-20%).
Reduced memory requirements when capturing for all cameras.

v0.9.0.2 (10/13/2024)
Added support for Raspberry Pi Camera 3 (12mp).

v0.9.0.1 (10/11/2024)
Bugfixes for initial OpenScan Composer release.

v0.9.0.0 (10/4/2024)
Initial release for testing.

Installation

The OpenScan Composer Firmware requires an SD card of at least 8GB in size, and preferably 32GB+ for best operation.

  • Insert the SD card into your PC and flash the firmware package using Raspberry PI Imager. Please note that this process will completely erase the contents of your SD card.

If you intend to exclusively use a wired connection (recommended), skip this step.

  • Insert the SD card into your Raspberry Pi.
  • For optimal photo transfer speed connect the Raspberry Pi to your network via the ethernet port. OpenScan Composer can work over wifi, but for best transfer speeds a gigabit ethernet connection is recommended.
  • Power on the OpenScan device. If you configured WIFI settings your device may require a restart before it will boot. If after approximately 10 seconds the Pi shows a solid red light and no green light then cycle the power to continue.
  • After approximately one minute the ringlight will flash (currently greenshield only) to indicate that a successful firmware boot has taken place. On the blackshield the motors will be disabled shortly after the boot completes. After the first boot, subsequent boots should complete in around 30 seconds.
  • Navigate your browser to http://openscan. If everything is working you should see the OpenScan Composer log page.

Technical Details

The firmware package consists of the following components.

  • Official Raspbian OS x64 Lite distribution.
  • OpenScan Composer network daemon.
  • Arducam libcamera (IMX519/Hawkeye Support)
  • wiringpi (GPIO/SPI control)
  • nginx/php (web/debugging interface)

Network Protocol (Direct Connection)

The OpenScan Composer daemon accepts TCP connections on port 2050 to provide hardware setup, motor/light control, and photo capture capabilities. The default hostname of the device after installing the firmware is “openscan”.

All network packets are defined as follows:

struct Packet
{
    struct PacketHeader
    {
        uint32 packetType;             // Network packet type, see below
        uint32 packetLength;           // Packet size in bytes (including header)
    };

    PacketHeader header;               // 8 byte header structure
    ...                                // Payload data depending on packet type
};

The following packet types are currently supported:

enum PacketType
{
    PacketType_Connect    = 0,            // Perform connection handshake
    PacketType_Disconnect = 1,	          // Disconnect
    PacketType_Config     = 2,            // Configure hardware state
    PacketType_Pin        = 3,            // Test GPIO pin
    PacketType_Light      = 4,            // Set ringlight state
    PacketType_Motor      = 5,            // Control motor
    PacketType_Camera     = 6,            // Configure camera
    PacketType_Photo      = 7,            // Request photo
    PacketType_Capture    = 8,            // Photo request result
    PacketType_Video      = 9,            // Request start or stop video capture
    PacketType_Stream     = 10,           // Video request result
    PacketType_Metadata   = 11,           // Change in capture states
    PacketType_Params     = 12,           // Update camera parameters
    PacketType_Data       = 13,           // Photo data header
    PacketType_Chunk      = 14,           // Photo data transfer chunk
    PacketType_Command    = 15,           // Non-capture command
    PacketType_Info       = 16,           // Log message
    PacketType_Hardware   = 17,           // Hardware details
    PacketType_Status     = 18            // Device status
};

Packet_Connect

Application -> Firmware

8 bytes (no payload)

Packet sent by the application to the firmware immediately upon connection.

struct Packet_Connect : Packet
{
    uint32 protocolVersion;               // Client's network protocol version (currently 1)
    bool   enableLogging;                 // Enable daemon debug logging
};

Packet_Disconnect

Application -> Firmware

12 bytes (4 byte payload)

Packet sent by the application to the firmware as disconnect notification, restart request, or shutdown request.

struct Packet_Disconnect : Packet
{
    uint32 disconnectAction;                // 0: Disconnect notification, 1: Restart device, 2: Shutdown device
};

Packet_Config

Application -> Firmware

137 bytes (129 byte payload)

Packet sent by the application to the firmware to configure hardware settings immediately after connection, or when changed by the user, firmware replies with PacketType_Hardware containing details of the device hardware setup.

struct Packet_Config : Packet
{
    uint32 controllerType;                  // Controller type (0: Auto, 1: Pi3, 2: Pi4, 3: Pi5)
    uint32 cameraType;                      // Device camera type (1: IMX519, 2: Hawkeye, 3: Pi Camera v3)
    uint32 pinExternalCamera;               // GPIO pin used to trigger external camera           (output)
    uint32 pinLight1;                       // GPIO pin used to toggle the inner ringlight LEDs   (output)
    uint32 pinLight2;                       // GPIO pin used to toggle the outer ringlight LEDs   (output)
    uint32 pinRotorDirection;               // GPIO pin used to switch the rotor direction        (output)
    uint32 pinRotorStep;                    // GPIO pin used to step the rotor motor              (output)
    uint32 pinRotorEnable;                  // GPIO pin used to enable the rotor motor            (output)
    uint32 pinTurntableDirection;           // GPIO pin used to switch the turntable direction    (output)
    uint32 pinTurntableStep;                // GPIO pin used to step the turntable                (output)
    uint32 pinTurntableEnable;              // GPIO pin used to enable the turntable motor        (output)
    uint32 pinSliderDirection;              // GPIO pin used to switch the slider direction       (output)
    uint32 pinSliderStep;                   // GPIO pin used to step the slider motor             (output)
    uint32 pinSliderEnable;                 // GPIO pin used to enable the slider motor           (output)
    uint32 pinEndstopRotorLo;               // GPIO pin used to detect rotor low end-stop status  (input)
    uint32 pinEndstopRotorHi;               // GPIO pin used to detect rotor high end-stop status (input)
    uint32 pinEndstopSlider;                // GPIO pin used to detect slider end-stop status     (input)
    uint32 pinLightFan;                     // GPIO pin used to enable the ringlight fan          (output)
    uint32 pinCaseFan;                      // GPIO pin used to enable the case fan               (output)
    uint32 rotorStepsPerRotation;           // Number of rotor motor steps per 360 degree rotation
    uint32 rotorInitialDelay;               // Rotor motor step delay in microseconds
    float  rotorAcceleration;               // Rotor motor acceleration
    uint32 rotorRamp;                       // Rotor motor acceleration ramp step count
    bool   rotorReversed;                   // True if rotor direction should be reversed
    uint32 turntableStepsPerRotation;       // Number of turntable motor steps per 360 degree rotation
    uint32 turntableInitialDelay;           // Turntable motor step delay in microseconds
    float  turntableAcceleration;           // Turntable motor acceleration
    uint32 turntableRamp;                   // Turntable motor acceleration ramp step count
    bool   turntableReversed;               // True if turntable direction should be reversed
    uint32 sliderStepsPerRotation;          // Number of slider motor steps per 360 degree rotation
    uint32 sliderInitialDelay;              // Slider motor step delay in microseconds
    float  sliderAcceleration;              // Slider motor acceleration
    uint32 sliderRamp;                      // Slider motor acceleration ramp step count
    bool   sliderReversed;                  // True if slider direction should be reversed
    uint32 caseFanThreshold;                // CPU temperature in degrees celcius to turn on case fan
    bool   transferCompression;             // Compress photo data on device before sending over network
    bool   announceDevice;                  // Announce device presence on network via port 1981
};

Packet_Pin

Application <-> Firmware

28 bytes (20 byte payload)

Packet sent by the application to directly read or write a GPIO pin, or sent by the firmware in response to a pin read request.

struct Packet_Pin : Packet
{
    uint32 pinIndex;                        // GPIO pin id to read or write
    uint32 pinValue;                        // Pin value to write if this is a write request, or the value of the pin if this is a response to a read request
    uint32 writePeriod;                     // Time in microseconds to hold the pin at 'pinValue' if writing, ignored/zero if reading
    uint32 repeatCount;                     // Number of times to pulse pin at 'pinValue' if writing, ignored/zero if reading
    bool   readOrWrite;                     // True to change a pin state, false to request the status of a pin
};

Packet_Light

Application -> Firmware

16 bytes (8 byte payload)

Packet sent by the application to turn on or turn off a light.

struct Packet_Light : Packet
{
    uint32 lightIndex;                       // Light index to change (0: ringlight inner LEDs, 1: ringlight outer LEDs, 2: ringlight all LEDs [v0904+])
    bool   lightState;                       // True to turn light on, false to turn light off
};

Packet_Motor

Application -> Firmware

24 bytes (16 byte payload)

Packet sent by the application to control the turntable or rotor motors.

struct Packet_Motor : Packet
{
     uint32 motorIndex;                      // Motor index to control (0: rotor, 1: turntable)
     float  rotationAngle;                   // Relative angle to rotate or absolute angle to rotate to depending on 'isRelative'
     uint32 isRelative;                      // True to perform a relative rotation, false to perform an absolute rotation
     uint32 setZero;                         // True to set the final motor position as the new zero position
};

Packet_Camera

Application -> Firmware

44 bytes (36 byte payload)

Packet sent by the application to configure the camera for photo captures.

struct Packet_Camera : Packet
{
    uint32 captureMode;                      // Capture mode (0: photo, 1: video)
    uint32 xResolution;			     // X resolution in pixels
    uint32 yResolution;			     // Y resolution in pixels
    uint32 autofocusMode;		     // Autofocus mode (0: manual focus, 1: auto focus, 2: low res auto focus, 3: continuous auto focus)
    uint32 captureFormat;		     // Capture data format (0: YUV420, 1: RGB888, 2: BGR888, : RAW10)
    uint32 imageRotation;                    // Image rotation post capture (0: None, 1: 90 deg, 2: 180 deg, 3: 270 deg)
    uint32 cropX;                            // Crop rectangle top left (x)
    uint32 cropY;                            // Crop rectangle top left (y)
    uint32 cropWidth;                        // Crop rectangle width
    uint32 cropHeight;                       // Crop rectangle height
    uint32 shutterPeriod;                    // Shutter period in milliseconds
    float  gainLevel;                        // Gain level specified in scan settings
    float  saturationLevel;                  // Saturation level
    float  contrastLevel;                    // Contrast level
    float  awbRed;                           // Auto-white-balance red level
    float  awbBlue;                          // Auto-white-balance blue level
};

Packet_Photo

Application -> Firmware

68 bytes (60 byte payload)

Packet sent by the application to capture a photo.

struct Packet_Photo : Packet
{
    uint32 photoId;                          // Unique photo id
    uint32 stackIndex;                       // Focus stack index
    float  focusDiopters;                    // Focus distance in diopters
    uint32 lensPosition;                     // Expected hardware lens position
    bool   moveMotors;                       // Move motors to the angles specified
    float  turntableAngle;                   // Turntable angle to set if 'moveMotors' is true
    float  rotorAngle;                       // Rotor angle to set if 'moveMotors' is true
    uint32 delayBefore;                      // Idle delay before capture, in milliseconds
    uint32 delayAfter;                       // Idle delay after capture, in milliseconds
};

Packet_Capture

Firmware -> Application

20 bytes (12 byte payload)

Packet sent by the firmware to report the result of a photo capture request.

struct Packet_Capture : Packet
{
    uint32 photoId;                           // Photo id of request
    uint32 stackIndex;                        // Stack index of request
    bool   captureResult;                     // True if capture succeeded, otherwise false
};

Packet_Video

Application -> Firmware

12 bytes (4 byte payload)

Packet sent by the application to start or stop a video capture stream.

struct Packet_Video : Packet
{
    bool  captureState;                       // Start or stop video capture
    float focusDiopters;                      // Focus distance in diopters
};

Packet_Stream

Packet sent by the firmware as a response to a request to start a video stream.

Firmware -> Application

12 bytes (4 byte payload)

struct Packet_Stream : Packet
{
    uint32 streamWidth;                        // Stream width in pixels
    uint32 streamHeight;                       // Stream height in pixels
    uint32 encodingType;                       // Stream encoding type (0: H264)
};

Packet_Metadata

Firmware -> Application

16 bytes (8 byte payload)

Packet sent by the firmware to notify the client about changes to metadata states during capture.

struct Packet_Metadata : Packet
{
    uint32 photoId;                            // Photo id for the associated metadata
    uint32 stackIndex;                         // Stack index for the associated metadata
    float  focusDiopters;                      // Focus distance in diopters
    uint32 lensPosition;                       // Actual hardware lens position;
    uint32 focusState;                         // Current camera focus state (0: Focusing, 1: Focused)
};

Packet_Params

Application -> Firmware

36 bytes (28 byte payload)

Packet sent by the client to change photo capture parameters.

struct Packet_Params : Packet
{
    uint32 shutterPeriod;                     // Shutter period in microseconds
    float  gainLevel;                         // Gain level
    float  saturationLevel;                   // Saturation level
    float  constrastLevel;                    // Contrast level
    float  awbRed;                            // Auto-white-balance red adjustment
    float  awbBlue;                           // Auto-white-balance blue adjustment
    float  focusDiopters;                     // Focus range (0 = autofocus)
};

Packet_Data

Firmware -> Application

48 bytes (40 byte payload)

Packet sent by the firmware to initiate transfer of captured photo data to the application.

struct Packet_Data : Packet
{
    uint32 photoId;                           // Photo id of request
    uint32 stackIndex;                        // Stack index of request
    float  focusDiopters;                     // Focus distance in diopters
    uint32 lensPosition;                      // Actual hardware lens position
    uint32 elapsedTime;                       // Capture time in milliseconds
    uint32 dataWidth;                         // Width of photo in pixels
    uint32 dataHeight;                        // Height of photo in pixels
    uint32 dataFormat;                        // Data format (0: YUV420, 1: RGB888, 2: BGR888, : RAW10)
    uint32 dataSize;                          // Size of photo data being transferred
    uint32 uncompressedSize;                  // Uncompressed size of photo data
};

Packet_Chunk

Firmware -> Application

8+X bytes (X byte payload)

Packet sent by the firmware to transfer a chunk of data.

struct Packet_Chunk : Packet
{
    byte[] chunkData;                         // Variable sized array of data
};

Packet_Command

Application -> Firmware

12 bytes (4 byte payload)

Packet sent by the application to perform a generic command on the device.

struct Packet_Command : Packet
{
    uint32 commandId;                         // Command identifier (0: request device status)
};

Packet_Info

Firmware -> Application

1032 bytes (1024 byte payload)

Packet sent by the firmware to deliver an info message to the client.

struct Packet_Info : Packet
{
    byte infoData[1024];                      // Info message
};

Packet_Hardware

Firmware -> Application

80 bytes (72 byte payload)

Packet sent by the firmware upon connection to notify the application of device capabilities.

struct Packet_Hardware : Packet
{
    uint32 controllerType;                    // Device type (1: Pi3, 2: Pi4, 3: Pi5)
    uint32 protocolVersion;                   // Firmware protocol version (currently 0)
    char   deviceVersion[40];                 // Device version string
    char   osVersion[20];                     // Operation system version string
    char   firmwareVersion[16];               // Firmware version string
    uint32 cameraType;                        // Camera installed on device (0: none, 1: IMX519, 2: Hawkeye, 3: Pi Camera v3) 
};

Packet_Status

Firmware -> Application

48bytes (40 byte payload)

Packet sent by the firmware in response to an application request for device status information.

struct Packet_Status : Packet
{
    uint64 totalMemory;                       // Total memory on device in bytes
    uint64 freeMemory;                        // Free memory available in bytes
    uint64 totalDisk;                         // Total disk space on device in bytes
    uint64 freeDisk;                          // Free disk space available in bytes
    float  cpuTemp;                           // Current CPU temperature in degrees celcius
    float  gpuTemp;                           // Current GPU temperature in degrees celcius
};

Network Announce

The firmware announces the device presence on the network (when enabled) via a special packet periodically broadcast on UDP port 1981. Note that this packet does not contain a header.

Broadcast_Announce

Firmware -> Application

6 bytes

struct Broadcast_Announce
{
    uint32 magicId;                           // The magic identifier 0x4E43534F
    uint16 connectPort;                       // Port to use to connect to this device
};

WebSockets (v0.9.0.4+)

Firmware version 0.9.0.4 adds initial support for control via a WebSocket service on port 8000. This control interface becomes read-only when a direct connection to the firmware is active. Data requests and responses use JSON format, with request type specified in the “request” field, and with parameters identical to the direct connection packets (all variable names are expected to be lowercase).

Javascript Example:

// Connect to WebSocket service
var websocket = new WebSocket("ws://openscan.local:8000");

// Configure hardware before doing anything else...
websocket.send(JSON.stringify({}));

// Get device status (firmware will respond with JSON encoded device status)
websocket.send(JSON.stringify({ request: "command", commandid: 0 }));

// Turn ringlight on
websocket.send(JSON.stringify({ request: "light", lightindex: 2, lightstate: true }));

// Move rotor
websocket.send(JSON.stringify({ request: "motor", motorindex: 0, rotationangle: 10, isrelative: true }));

// Move turntable
websocket.send(JSON.stringify({ request: "motor", motorindex: 1, rotationangle: 90, isrelative: true }));

// Configure camera for capture
websocket.send(JSON.stringify({}));

// Request photo
websocket.send(JSON.stringify({}));

REST API (v0.9.0.4+)

Firmware version 0.9.0.4 adds initial support for control via a REST API endpoint at “http://openscan.local/api.php?request=xxx”. This control interface becomes read-only when a direct connection to the firmware is active.
Requests can be made to the endpoint above, with request type specified as “request=xxx”, and with parameters identical to the direct connection packets (all variable names are expected to be lowercase).

// Configure hardware before doing anything else...
GET: http://openscan.local/api.php?request=config&cameratype=0&pinlight1=17&pinlight2=27&pinrotordirection=5&pinrotorstep=6&pinturntabledirection=9&pinturntablestep=11

// Get device status (firmware will respond with JSON encoded device status)
GET: http://openscan.local/api.php?request=command&commandid=0

// Turn ringlight on
GET: http://openscan.local/api.php?request=light&lightindex=2&lightstate=true

// Move rotor
GET: http://openscan.local/api.php?request=motor&motorindex=0&rotationangle=10&isrelative=true

// Move turntable
GET: http://openscan.local/api.php?request=motor&motorindex=1&rotationangle=90&isrelative=true

// Configure camera for capture
GET: http://openscan.local/api.php?request=camera&

// Request photo
GET: http://openscan.local/api.php?request=photo&