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.
- Proceed to installing/running the OpenScan Composer software.
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&