commit 4b607beece2df806795e1e294d748b6fa433b5ca
parent 5fb5fa5f5b6ccbc20dda50a79043f15c1f5a23ae
Author: MTRNord <mtrnord1@gmail.com>
Date: Sat, 24 Jul 2021 21:58:42 +0200
Initial commit
Diffstat:
7 files changed, 230 insertions(+), 38 deletions(-)
diff --git a/Dockerfile.template b/Dockerfile.template
@@ -1,7 +1,11 @@
-FROM balenalib/%%BALENA_MACHINE_NAME%%-node
+FROM balenalib/%%BALENA_MACHINE_NAME%%-node:12
+
+ENV UDEV=on
# Install dependencies
-RUN install_packages libgphoto2-dev gphoto2
+RUN install_packages libgphoto2-dev gphoto2 python3 python2 build-essential uhubctl libudev-dev bluetooth bluez libbluetooth-dev libusb-1.0-0-dev
+
+RUN update-rc.d bluetooth remove
# Defines our working directory in container
WORKDIR /usr/src/app
@@ -11,11 +15,10 @@ COPY package.json ./
# This install npm dependencies on the balena build server,
# making sure to clean up the artifacts it creates in order to reduce the image size.
-RUN JOBS=MAX npm install --production --unsafe-perm && npm cache verify
+RUN JOBS=MAX npm install --unsafe-perm && npm cache verify
# This will copy all files in our root to the working directory in the container
COPY . ./
-
RUN npm run build
-CMD ["npm", "start"]
+CMD npm start
+\ No newline at end of file
diff --git a/README.md b/README.md
@@ -1,10 +1,22 @@
# balena-DSLR
-You can use this project to remotely control your DSLR camera from the balenaCloud dashboard.
+You can use this project to remotely control your DSLR camera via BLE using balena to manage the raspberry pi zero w.
## Hardware
| Hardware required | Notes |
| :--: | :--: |
-| A Raspberry Pi | You can use any of the devices listed [here](https://www.balena.io/docs/reference/hardware/devices/) - as we are only interested in the USB functionality of the Pi here|
+| A Raspberry Pi Zero W | You also need a way to connect your camera via usb and a phone capable of running Bluetooth Low Energy|
| A DSLR camera | This project uses `gphoto2` to communicate with the cameras. See supported cameras [here](http://gphoto.org/proj/libgphoto2/support.php)|
+
+## Notes
+
+This project is still very early in development please report any bugs.
+
+There isnt yet a client available. You will need to manually send the commands to the BLE service.
+
+The project is only tested on a Canon EOS 4000D/Rebel T100.
+
+This project eventually is going to get a rust rewrite to reduce performance overhead.
+
+The images get saved on to the cameras sd card as downloading them with the nodejs lib does lock up my camera. (gphoto2 cli doesnt)
+\ No newline at end of file
diff --git a/package.json b/package.json
@@ -13,12 +13,16 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@typedproject/gphoto2-driver": "^2.4.0",
- "lodash": "^4.17.15",
+ "@typedproject/gphoto2-driver": "^3.0.3",
+ "bleno": "npm:@abandonware/bleno@^0.5.1-4",
+ "lodash": "^4.17.21",
"segfault-handler": "^1.3.0",
- "typescript": "^3.6.4"
+ "typescript": "^4.3.5",
+ "usb": "^1.7.1"
},
"devDependencies": {
- "@types/node": "^13.13.4"
+ "@types/bleno": "^0.4.2",
+ "@types/node": "^12.20.1",
+ "@types/usb": "^1.5.3"
}
}
diff --git a/src/bluetoothService.ts b/src/bluetoothService.ts
@@ -0,0 +1,57 @@
+import { PrimaryService, Characteristic } from 'bleno';
+import CameraActions from './camera';
+class BLEService extends Characteristic {
+ private _value: Buffer;
+ private _updateValueCallback: any;
+ private camera: CameraActions;
+
+ constructor() {
+ super({
+ uuid: '14444444444444444444444444444448',
+ properties: ['write', 'notify'],
+ value: null
+ });
+ this.camera = new CameraActions();
+ this._value = Buffer.alloc(0);
+ this._updateValueCallback = null;
+ }
+
+ onWriteRequest(data: Buffer, _offset: number, _withoutResponse: boolean, callback: (result: number) => void) {
+ this._value = data;
+ console.log(this._value.toString());
+ // TODO use json instead
+ switch (this._value.toString()) {
+ case "capture":
+ this.camera.capture(
+ false,
+ false,
+ true
+ );
+ break;
+ case "timelapse":
+ this.camera.run_timelapse(
+ 20000,
+ 1500,
+ false
+ );
+ break;
+ }
+
+
+ if (this._updateValueCallback) {
+ this._updateValueCallback(this._value);
+ }
+
+ callback(Characteristic.RESULT_SUCCESS);
+ }
+
+ onSubscribe(_maxValueSize: number, updateValueCallback: any) {
+ this._updateValueCallback = updateValueCallback;
+ }
+
+ onUnsubscribefunction() {
+ this._updateValueCallback = null;
+ }
+}
+
+export const bleService = new PrimaryService({ uuid: '14444444444444444444444444444447', characteristics: [new BLEService()] })
+\ No newline at end of file
diff --git a/src/camera.ts b/src/camera.ts
@@ -0,0 +1,94 @@
+import { Camera, run } from '@typedproject/gphoto2-driver';
+import { sleep } from './utils';
+
+export default class CameraActions {
+ private camera: Camera;
+ constructor() {
+ this.camera = Camera.listen();
+ }
+ /**
+ * Do various capture actions
+ *
+ * @param autoFocus Should the camera start to autofocus? (May never finish and lock up camera!)
+ * @param preview Capture a preview image and save it to the sd card?
+ * @param capture Save a image in full quality to the sd card
+ */
+ public capture(autoFocus = false, preview = false, capture = false) {
+ if (this.camera.isClosed()) {
+ this.camera = Camera.listen();
+ }
+ return run(async () => {
+ await sleep(2000);
+
+ if (autoFocus) {
+ console.info("Autofocus =============================");
+ this.camera.autoFocus();
+
+ await sleep(20000);
+ }
+
+ if (preview) {
+ console.info("Preview ===============================");
+
+ this.camera.widgets.apply({
+ "/settings/capturetarget": "Memory card",
+ });
+
+ const res = await this.camera.capturePreviewAsync();
+ res?.close();
+
+ console.info("File saved");
+ }
+
+ if (capture) {
+ console.info("Capture ===============================");
+
+ this.camera.widgets.apply({
+ "/settings/capturetarget": "Memory card",
+ });
+
+ const res = await this.camera.captureImageAsync();
+ res?.close();
+ console.info("File saved");
+ }
+ });
+ }
+
+ /**
+ * Start a timelapse capture
+ *
+ * @param interval_ms The amount of time in between a capture being done in miliseconds
+ * @param frames The number of frames to overall take
+ * @param autoExpose If we should try to auto adjust for changing exposure
+ */
+ public async run_timelapse(interval_ms: number, frames: number, autoExpose: boolean) {
+ if (this.camera.isClosed()) {
+ this.camera = Camera.listen();
+ }
+
+ let current_frame = 1;
+
+ await sleep(2000);
+ return run(async () => {
+ while (current_frame <= frames) {
+
+ console.info("Capture ===============================");
+
+ this.camera.widgets.apply({
+ "/settings/capturetarget": "Memory card",
+ });
+
+ const res = await this.camera.captureImageAsync();
+ res?.close();
+
+ console.info("File saved ============================");
+
+ if (autoExpose) {
+ // TODO change exposure to the next step if needed
+ }
+
+ await sleep(interval_ms);
+ }
+ });
+ }
+}
+\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
@@ -1,27 +1,39 @@
-import SegfaultHandler from 'segfault-handler';
-import path from 'path';
-import { CameraList, closeQuietly } from '@typedproject/gphoto2-driver';
-
-SegfaultHandler.registerHandler('crash.log');
-
-const cameraList = new CameraList().load();
-
-console.log('Nb camera', cameraList.size);
-
-if (cameraList.size) {
- const camera = cameraList.getCamera(0);
-
-
- if (camera && !camera.isClosed()) {
- console.log('Camera =>', camera);
-
- const cameraFile = camera.captureImage()!;
-
- cameraFile.save(path.join(__dirname, '../.tmp/capture.jpeg'));
-
- closeQuietly(cameraFile);
- closeQuietly(camera);
- }
-}
-
-cameraList.close();
+import bleno from 'bleno';
+import { bleService } from './bluetoothService';
+
+
+const name = "CameraControlService"
+
+//
+// Wait until the BLE radio powers on before attempting to advertise.
+// If you don't have a BLE radio, then it will never power on!
+//
+bleno.on('stateChange', function (state) {
+ if (state === 'poweredOn') {
+ //
+ // We will also advertise the service ID in the advertising packet,
+ // so it's easier to find.
+ //
+ bleno.startAdvertising(name, [bleService.uuid], function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ }
+ else {
+ bleno.stopAdvertising();
+ }
+});
+
+bleno.on('advertisingStart', function (err) {
+ if (!err) {
+ console.log('advertising...');
+ //
+ // Once we are advertising, it's time to set up our services,
+ // along with our characteristics.
+ //
+ bleno.setServices([
+ bleService
+ ]);
+ }
+});
diff --git a/src/utils.ts b/src/utils.ts
@@ -0,0 +1,5 @@
+export function sleep(ms: number) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, ms);
+ });
+}
+\ No newline at end of file