Push dev work to master #2
|
@ -1 +1,5 @@
|
|||
node_modules
|
||||
node_modules
|
||||
run_dev.sh
|
||||
state
|
||||
*/.DS_Store
|
||||
.DS_Store
|
||||
|
|
55
Readme.md
|
@ -1,2 +1,55 @@
|
|||
# intval3
|
||||
# INTVAL3
|
||||
|
||||
### What is this?
|
||||
|
||||
INTVAL3 is an open source intervalometer for the Bolex 16mm camera. The goal of the project is to create a cheap-to-make intervalometer that can be used to automate time-lapse or animation on the Bolex using mobile, web or physical controls.
|
||||
|
||||
This is the third incarnation of the INTVAL project, this time utilizing the [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/) for Wifi and Bluetooth control. Earlier versions, the [INTVAL](https://github.com/sixteenmillimeter/INTVAL) and [INTVAL2](https://github.com/sixteenmillimeter/intval2) were Arduino-based. The original INTVAL used a solenoid (!!!) to hammer a camera release cable, while the second attempt was a proving ground for the motor-and-key hardware used in this version.
|
||||
|
||||
The [INTVAL2](https://github.com/sixteenmillimeter/intval2) project should be used if you prefer a simpler, physical interface approach.
|
||||
|
||||
### Components
|
||||
|
||||
* [Firmware](#firmware) for the Raspberry Pi Zero W running [Node.js](https://nodejs.org) on Raspian
|
||||
* [Mobile app](#mobile) for controlling device using [Cordova](https://cordova.apache.org/) + [Bleno](https://github.com/sandeepmistry/bleno)
|
||||
* [Web app](#web) for controlling device using [Restify](http://restify.com/)
|
||||
* Hardware files, parts models for 3D printing, laser cutting and CNC
|
||||
* PCB design for a Raspberry Pi Zero W Bonnet
|
||||
* [Parts list](#parts-list)
|
||||
|
||||
<a name="firmware"></a>
|
||||
|
||||
## Firmware
|
||||
|
||||
The firmware of the INTVAL3 is a node.js application running on the Raspian OS intended for installation on the Raspberry Pi Zero W.
|
||||
|
||||
<a name="mobile"></a>
|
||||
|
||||
## Mobile App
|
||||
|
||||
The INTVAL3 mobile app controls the intervalometer over Bluetooth. It can be used to configure the settings on the intervalometer such as exposure length, delay between frames and the direction of the film. The app can also be used to trigger individual frames, as well as start and stop sequences. As an experimental feature, film exposure settings can be determined with the camera on a mobile device.
|
||||
|
||||
<a name="web"></a>
|
||||
|
||||
## Web App
|
||||
|
||||
As a function of the firmware, there is an embedded web application that is hosted on the INTVAL3. When connected to a wifi network (via the mobile app) users are able to control the intervalometer from a browser. Users are also able to trigger functions and change settings on the intervalometer firmware from the command line by using cURL or wget, so actions can be scripted and automated from an external machine.
|
||||
|
||||
<a name="hardware"></a>
|
||||
|
||||
## Hardware
|
||||
|
||||
All of the non-electronic hardware is generated from OpenSCAD scripts and built into either STL files for 3D printing or DXF files for laser cutting or CNCing.
|
||||
|
||||
Electronics designs are available in the form of a Fritzing file, a wiring diagram and a mask image that can be used to fabricate a board from a blank PCB. One of the easiest ways to
|
||||
|
||||
<a name="parts-list"></a>
|
||||
|
||||
## PARTS
|
||||
|
||||
1. Raspberry Pi Zero W - [[Adafruit](https://www.adafruit.com/product/3400)] [[Sparkfun](https://www.sparkfun.com/products/14277)]
|
||||
2. L298N Breakout Board - ?
|
||||
3. 120RPM 12VDC Motor - ?
|
||||
4. Microswitch w/ Roller - [[Adafruit](https://www.adafruit.com/product/819)]
|
||||
5. L7805 5V Regulator - [[Adafruit](https://www.adafruit.com/product/2164)] [Sparkfun](https://www.sparkfun.com/products/107)]
|
||||
6. (Optional) Proto Bonnet - [[Adafruit](https://www.adafruit.com/product/3203)]
|
|
@ -0,0 +1,5 @@
|
|||
*.DS_Store
|
||||
platforms/*
|
||||
plugins/*
|
||||
node_modules/*
|
||||
build.json
|
|
@ -0,0 +1,30 @@
|
|||
# INTVAL3
|
||||
|
||||
## Mobile App
|
||||
|
||||
The INTVAL3 mobile app is built using the Cordova framework for cross-platform deployment to iOS and Android.
|
||||
|
||||
## Requirements
|
||||
|
||||
* node.js
|
||||
* npm
|
||||
* Cordova
|
||||
* XCode (for iOS) and/or
|
||||
* Android Studio (for Android)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
All of the required plugins can be installed directly by executing the `install.sh` script on a system which supports bash. This script will use the `cordova` application to install the Cordova plugins. Cordova also supports the npm package.json format, so plugins may be alternately installed simply by running a `npm install` command from within the `app` directory.
|
||||
|
||||
## Building
|
||||
|
||||
Once all dependencies and plugins are installed, you can build the INTVAL3 app by running
|
||||
|
||||
```cordova build ios```
|
||||
|
||||
or
|
||||
|
||||
```cordova build android```
|
||||
|
||||
This generates the application source code in the `platforms` directory, under either the `ios` or `android` directory depending on your build target. The app can be built and run on your device by going to the project file and opening it in your IDE, either XCode or Android Studio. Alternately it can be run on your device using the `cordova run ios` or `cordova run android` commands.
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<widget id="com.sixteenmillimeter.intval3" version="1.0.3" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
||||
<name>INTVAL3</name>
|
||||
<description>
|
||||
Mobile control app for the INTVAL intervalometer for Bolex 16mm cameras
|
||||
</description>
|
||||
<author email="hi@mmcwilliams.com" href="https://sixteenmillimeter.com">
|
||||
M McWilliams
|
||||
</author>
|
||||
<content src="index.html" />
|
||||
<access origin="*" />
|
||||
<allow-intent href="http://*/*" />
|
||||
<allow-intent href="https://*/*" />
|
||||
<allow-intent href="tel:*" />
|
||||
<allow-intent href="sms:*" />
|
||||
<allow-intent href="mailto:*" />
|
||||
<allow-intent href="geo:*" />
|
||||
<platform name="android">
|
||||
<allow-intent href="market:*" />
|
||||
<icon density="mdpi" src="res/icon/android/mdpi.png" />
|
||||
<icon density="hdpi" src="res/icon/android/hdpi.png" />
|
||||
<icon density="xhdpi" src="res/icon/android/xhdpi.png" />
|
||||
<icon density="xxhdpi" src="res/icon/android/xxhdpi.png" />
|
||||
<icon density="xxxhdpi" src="res/icon/android/xxxhdpi.png" />
|
||||
</platform>
|
||||
<platform name="ios">
|
||||
<allow-intent href="itms:*" />
|
||||
<allow-intent href="itms-apps:*" />
|
||||
<icon height="180" src="res/icon/ios/icon-60@3x.png" width="180" />
|
||||
<icon height="120" src="res/icon/ios/icon-60@2x.png" width="120" />
|
||||
<icon height="60" src="res/icon/ios/icon-60.png" width="60" />
|
||||
<splash src="res/screen/ios/Default@2x~universal~anyany.png" />
|
||||
</platform>
|
||||
<preference name="DisallowOverscroll" value="true" />
|
||||
<preference name="StatusBarBackgroundColor" value="#212121" />
|
||||
<preference name="CameraUsesGeolocation" value="false" />
|
||||
<plugin name="cordova-plugin-whitelist" spec="1" />
|
||||
<plugin name="cordova-plugin-device" spec="^1.1.7" />
|
||||
<plugin name="cordova-plugin-dialogs" spec="^1.3.4" />
|
||||
<plugin name="cordova-plugin-statusbar" spec="^2.3.0" />
|
||||
<plugin name="cordova-plugin-splashscreen" spec="~4.1.0" />
|
||||
<plugin name="cordova-plugin-camera-with-exif" spec="^1.2.2" />
|
||||
<plugin name="cordova-plugin-ble-central" spec="^1.1.4">
|
||||
<variable name="BLUETOOTH_USAGE_DESCRIPTION" value="INTVAL3 intervalometer controls" />
|
||||
</plugin>
|
||||
<engine name="android" spec="^6.4.0" />
|
||||
<engine name="ios" spec="^4.5.4" />
|
||||
</widget>
|
|
@ -0,0 +1,23 @@
|
|||
<!--
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
-->
|
||||
# Cordova Hooks
|
||||
|
||||
Cordova Hooks represent special scripts which could be added by application and plugin developers or even by your own build system to customize cordova commands. See Hooks Guide for more details: http://cordova.apache.org/docs/en/edge/guide_appdev_hooks_index.md.html#Hooks%20Guide.
|
|
@ -0,0 +1,31 @@
|
|||
#!/bin/sh
|
||||
base=$1
|
||||
#convert "$base" -resize '29x29' -unsharp 1x4 "res/icon/ios/Icon-Small.png"
|
||||
#convert "$base" -resize '40x40' -unsharp 1x4 "res/icon/ios/Icon-Small-40.png"
|
||||
#convert "$base" -resize '50x50' -unsharp 1x4 "res/icon/ios/Icon-Small-50.png"
|
||||
#convert "$base" -resize '57x57' -unsharp 1x4 "res/icon/ios/Icon.png"
|
||||
#convert "$base" -resize '58x58' -unsharp 1x4 "res/icon/ios/Icon-Small@2x.png"
|
||||
convert "$base" -resize '60x60' -unsharp 1x4 "res/icon/ios/icon-60.png"
|
||||
#convert "$base" -resize '72x72' -unsharp 1x4 "res/icon/ios/Icon-72.png"
|
||||
#convert "$base" -resize '76x76' -unsharp 1x4 "res/icon/ios/Icon-76.png"
|
||||
#convert "$base" -resize '80x80' -unsharp 1x4 "res/icon/ios/Icon-Small-40@2x.png"
|
||||
#convert "$base" -resize '100x100' -unsharp 1x4 "res/icon/ios/Icon-Small-50@2x.png"
|
||||
#convert "$base" -resize '114x114' -unsharp 1x4 "res/icon/ios/Icon@2x.png"
|
||||
convert "$base" -resize '120x120' -unsharp 1x4 "res/icon/ios/icon-60@2x.png"
|
||||
#convert "$base" -resize '144x144' -unsharp 1x4 "res/icon/ios/Icon-72@2x.png"
|
||||
#convert "$base" -resize '152x152' -unsharp 1x4 "res/icon/ios/Icon-76@2x.png"
|
||||
convert "$base" -resize '180x180' -unsharp 1x4 "res/icon/ios/icon-60@3x.png"
|
||||
#convert "$base" -resize '512x512' -unsharp 1x4 "res/icon/ios/iTunesArtwork"
|
||||
#convert "$base" -resize '1024x1024' -unsharp 1x4 "res/icon/ios/iTunesArtwork@2x"
|
||||
|
||||
#convert "$base" -resize '36x36' -unsharp 1x4 "res/icon/android/Icon-ldpi.png"
|
||||
convert "$base" -resize '48x48' -unsharp 1x4 "res/icon/android/mdpi.png"
|
||||
convert "$base" -resize '72x72' -unsharp 1x4 "res/icon/android/hdpi.png"
|
||||
convert "$base" -resize '96x96' -unsharp 1x4 "res/icon/android/xhdpi.png"
|
||||
convert "$base" -resize '144x144' -unsharp 1x4 "res/icon/android/xxhdpi.png"
|
||||
convert "$base" -resize '192x192' -unsharp 1x4 "res/icon/android/xxxhdpi.png"
|
||||
|
||||
cd res/icon/ios/
|
||||
find -type f -name "*.png" -exec optipng -o7 {} \;
|
||||
cd ../android/
|
||||
find -type f -name "*.png" -exec optipng -o7 {} \;
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/sh
|
||||
base=$1
|
||||
|
||||
c="convert $1 -gravity center"
|
||||
|
||||
# iPhone
|
||||
$c -resize 320x480 "res/screen/ios/Default~iphone.png"
|
||||
$c -resize 640x960 "res/screen/ios/Default@2x~iphone.png"
|
||||
$c -resize 640x1136 "res/screen/ios/Default-568h@2x~iphone.png"
|
||||
|
||||
$c -resize 320x426 "res/screen/android/splash-portrait-ldpi.png"
|
||||
$c -resize 320x470 "res/screen/android/splash-portrait-mdpi.png"
|
||||
$c -resize 480x640 "res/screen/android/splash-portrait-hdpi.png"
|
||||
$c -resize 720x960 "res/screen/android/splash-portrait-xhdpi.png"
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
npm install
|
||||
cordova platform add ios
|
||||
cordova platform add android
|
||||
cordova plugin add cordova-plugin-device
|
||||
cordova plugin add cordova-plugin-dialogs
|
||||
cordova plugin add cordova-plugin-ble-central --variable BLUETOOTH_USAGE_DESCRIPTION="INTVAL3 intervalometer controls"
|
||||
cordova plugin add cordova-plugin-statusbar
|
||||
cordova plugin add cordova-plugin-camera-with-exif
|
|
@ -0,0 +1,570 @@
|
|||
{
|
||||
"name": "com.sixteenmillimeter.intval3",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"cordova-android": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-6.4.0.tgz",
|
||||
"integrity": "sha1-VK6NpXKKjX5e/MYXLT3MoXvH/n0=",
|
||||
"requires": {
|
||||
"android-versions": "1.2.1",
|
||||
"cordova-common": "2.1.0",
|
||||
"elementtree": "0.1.6",
|
||||
"nopt": "3.0.6",
|
||||
"properties-parser": "0.2.3",
|
||||
"q": "1.5.0",
|
||||
"shelljs": "0.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
},
|
||||
"android-versions": {
|
||||
"version": "1.2.1",
|
||||
"bundled": true
|
||||
},
|
||||
"ansi": {
|
||||
"version": "0.3.1",
|
||||
"bundled": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.25",
|
||||
"bundled": true
|
||||
},
|
||||
"bplist-parser": {
|
||||
"version": "0.1.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"big-integer": "1.6.25"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.8",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"cordova-common": {
|
||||
"version": "2.1.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ansi": "0.3.1",
|
||||
"bplist-parser": "0.1.1",
|
||||
"cordova-registry-mapper": "1.1.15",
|
||||
"elementtree": "0.1.6",
|
||||
"glob": "5.0.15",
|
||||
"minimatch": "3.0.4",
|
||||
"osenv": "0.1.4",
|
||||
"plist": "1.2.0",
|
||||
"q": "1.5.0",
|
||||
"semver": "5.4.1",
|
||||
"shelljs": "0.5.3",
|
||||
"underscore": "1.8.3",
|
||||
"unorm": "1.4.1"
|
||||
}
|
||||
},
|
||||
"cordova-registry-mapper": {
|
||||
"version": "1.1.15",
|
||||
"bundled": true
|
||||
},
|
||||
"elementtree": {
|
||||
"version": "0.1.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"sax": "0.3.5"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "5.0.15",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"once": "1.4.0",
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"bundled": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.8"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"abbrev": "1.1.0"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"os-homedir": "1.0.2",
|
||||
"os-tmpdir": "1.0.2"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"base64-js": "0.0.8",
|
||||
"util-deprecate": "1.0.2",
|
||||
"xmlbuilder": "4.0.0",
|
||||
"xmldom": "0.1.27"
|
||||
}
|
||||
},
|
||||
"properties-parser": {
|
||||
"version": "0.2.3",
|
||||
"bundled": true
|
||||
},
|
||||
"q": {
|
||||
"version": "1.5.0",
|
||||
"bundled": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "0.3.5",
|
||||
"bundled": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.5.3",
|
||||
"bundled": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.8.3",
|
||||
"bundled": true
|
||||
},
|
||||
"unorm": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "4.0.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"lodash": "3.10.1"
|
||||
}
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.1.27",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cordova-ios": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-4.5.4.tgz",
|
||||
"integrity": "sha1-yAZIBYlyloVw3BXalzFP+S0H3+c=",
|
||||
"requires": {
|
||||
"cordova-common": "2.1.0",
|
||||
"ios-sim": "6.1.2",
|
||||
"nopt": "3.0.6",
|
||||
"plist": "1.2.0",
|
||||
"q": "1.5.1",
|
||||
"shelljs": "0.5.3",
|
||||
"xcode": "0.9.3",
|
||||
"xml-escape": "1.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"abbrev": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"ansi": {
|
||||
"version": "0.3.1",
|
||||
"bundled": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.25",
|
||||
"bundled": true
|
||||
},
|
||||
"bplist-creator": {
|
||||
"version": "0.0.7",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"stream-buffers": "2.2.0"
|
||||
}
|
||||
},
|
||||
"bplist-parser": {
|
||||
"version": "0.1.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"big-integer": "1.6.25"
|
||||
}
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.8",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"cordova-common": {
|
||||
"version": "2.1.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"ansi": "0.3.1",
|
||||
"bplist-parser": "0.1.1",
|
||||
"cordova-registry-mapper": "1.1.15",
|
||||
"elementtree": "0.1.6",
|
||||
"glob": "5.0.15",
|
||||
"minimatch": "3.0.4",
|
||||
"osenv": "0.1.4",
|
||||
"plist": "1.2.0",
|
||||
"q": "1.5.1",
|
||||
"semver": "5.4.1",
|
||||
"shelljs": "0.5.3",
|
||||
"underscore": "1.8.3",
|
||||
"unorm": "1.4.1"
|
||||
}
|
||||
},
|
||||
"cordova-registry-mapper": {
|
||||
"version": "1.1.15",
|
||||
"bundled": true
|
||||
},
|
||||
"elementtree": {
|
||||
"version": "0.1.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"sax": "0.3.5"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "5.0.15",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"once": "1.4.0",
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"ios-sim": {
|
||||
"version": "6.1.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"bplist-parser": "0.0.6",
|
||||
"nopt": "1.0.9",
|
||||
"plist": "1.2.0",
|
||||
"simctl": "1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bplist-parser": {
|
||||
"version": "0.0.6",
|
||||
"bundled": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "1.0.9",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"abbrev": "1.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.1",
|
||||
"bundled": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.8"
|
||||
}
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"abbrev": "1.1.1"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"osenv": {
|
||||
"version": "0.1.4",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"os-homedir": "1.0.2",
|
||||
"os-tmpdir": "1.0.2"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"pegjs": {
|
||||
"version": "0.10.0",
|
||||
"bundled": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "1.2.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"base64-js": "0.0.8",
|
||||
"util-deprecate": "1.0.2",
|
||||
"xmlbuilder": "4.0.0",
|
||||
"xmldom": "0.1.27"
|
||||
}
|
||||
},
|
||||
"q": {
|
||||
"version": "1.5.1",
|
||||
"bundled": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "0.3.5",
|
||||
"bundled": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.5.3",
|
||||
"bundled": true
|
||||
},
|
||||
"simctl": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"shelljs": "0.2.6",
|
||||
"tail": "0.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"shelljs": {
|
||||
"version": "0.2.6",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"simple-plist": {
|
||||
"version": "0.2.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"bplist-creator": "0.0.7",
|
||||
"bplist-parser": "0.1.1",
|
||||
"plist": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"base64-js": {
|
||||
"version": "1.1.2",
|
||||
"bundled": true
|
||||
},
|
||||
"plist": {
|
||||
"version": "2.0.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"base64-js": "1.1.2",
|
||||
"xmlbuilder": "8.2.2",
|
||||
"xmldom": "0.1.27"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "8.2.2",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"stream-buffers": {
|
||||
"version": "2.2.0",
|
||||
"bundled": true
|
||||
},
|
||||
"tail": {
|
||||
"version": "0.4.0",
|
||||
"bundled": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.8.3",
|
||||
"bundled": true
|
||||
},
|
||||
"unorm": {
|
||||
"version": "1.4.1",
|
||||
"bundled": true
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"xcode": {
|
||||
"version": "0.9.3",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"pegjs": "0.10.0",
|
||||
"simple-plist": "0.2.1",
|
||||
"uuid": "3.0.1"
|
||||
}
|
||||
},
|
||||
"xml-escape": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "4.0.0",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"lodash": "3.10.1"
|
||||
}
|
||||
},
|
||||
"xmldom": {
|
||||
"version": "0.1.27",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"cordova-plugin-ble-central": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-ble-central/-/cordova-plugin-ble-central-1.1.4.tgz",
|
||||
"integrity": "sha1-rZA2mnla1wChuf3WbhnnzkSnicM="
|
||||
},
|
||||
"cordova-plugin-camera-with-exif": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-camera-with-exif/-/cordova-plugin-camera-with-exif-1.2.2.tgz",
|
||||
"integrity": "sha1-/kxfHWgga6QoOqtNM4MrVSwil3A="
|
||||
},
|
||||
"cordova-plugin-compat": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-compat/-/cordova-plugin-compat-1.2.0.tgz",
|
||||
"integrity": "sha1-C8ZXVyduvZIMASzpIOJ0F3V2Nz4="
|
||||
},
|
||||
"cordova-plugin-device": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-1.1.7.tgz",
|
||||
"integrity": "sha1-/JQRG+aTJijGaGiTjd89yCyfv+Y="
|
||||
},
|
||||
"cordova-plugin-dialogs": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-dialogs/-/cordova-plugin-dialogs-1.3.4.tgz",
|
||||
"integrity": "sha1-XMlm7nyZsvW1s934SQAmKLDacVc="
|
||||
},
|
||||
"cordova-plugin-splashscreen": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-4.1.0.tgz",
|
||||
"integrity": "sha1-gQKKt2Q+YVWT0n8q0CRFYR8ZRrY="
|
||||
},
|
||||
"cordova-plugin-statusbar": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-2.4.0.tgz",
|
||||
"integrity": "sha1-JOspc3ldEPbxrjIC90+Ix9mQzyA="
|
||||
},
|
||||
"cordova-plugin-whitelist": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.3.tgz",
|
||||
"integrity": "sha1-tehezbv+Wu3tQKG/TuI3LmfZb7Q="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "com.sixteenmillimeter.intval3",
|
||||
"displayName": "INTVAL3",
|
||||
"version": "1.0.3",
|
||||
"description": "Mobile control app for the INTVAL intervalometer for Bolex 16mm cameras",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "M McWilliams",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cordova-android": "^6.4.0",
|
||||
"cordova-ios": "^4.5.4",
|
||||
"cordova-plugin-ble-central": "^1.1.4",
|
||||
"cordova-plugin-camera-with-exif": "^1.2.2",
|
||||
"cordova-plugin-compat": "^1.2.0",
|
||||
"cordova-plugin-device": "^1.1.7",
|
||||
"cordova-plugin-dialogs": "^1.3.4",
|
||||
"cordova-plugin-splashscreen": "^4.1.0",
|
||||
"cordova-plugin-statusbar": "^2.4.0",
|
||||
"cordova-plugin-whitelist": "^1.3.3"
|
||||
},
|
||||
"cordova": {
|
||||
"plugins": {
|
||||
"cordova-plugin-whitelist": {},
|
||||
"cordova-plugin-device": {},
|
||||
"cordova-plugin-dialogs": {},
|
||||
"cordova-plugin-statusbar": {},
|
||||
"cordova-plugin-ble-central": {
|
||||
"BLUETOOTH_USAGE_DESCRIPTION": "INTVAL intervalometer controls"
|
||||
},
|
||||
"cordova-plugin-splashscreen": {},
|
||||
"cordova-plugin-camera-with-exif": {}
|
||||
},
|
||||
"platforms": [
|
||||
"android",
|
||||
"ios"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
-->
|
||||
|
||||
Note that these image resources are not copied into a project when a project
|
||||
is created with the CLI. Although there are default image resources in a
|
||||
newly-created project, those come from the platform-specific project template,
|
||||
which can generally be found in the platform's `template` directory. Until
|
||||
icon and splashscreen support is added to the CLI, these image resources
|
||||
aren't used directly.
|
||||
|
||||
See https://issues.apache.org/jira/browse/CB-5145
|
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 5.9 KiB |
|
@ -0,0 +1,3 @@
|
|||
/Icon-60.png
|
||||
/Icon-60@2x.png
|
||||
/Icon-60@3x.png
|
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 108 KiB |
|
@ -1,11 +1,181 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>intval 3</title>
|
||||
<title>INTVAL3</title>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
<meta name="viewport" content="width = 320, initial-scale = 1.0, user-scalable = no, minimal-ui, viewport-fit=cover">
|
||||
<!--<link rel="stylesheet" href="static/css/codemirror.css" />
|
||||
<link rel="stylesheet" href="static/css/monokai.css" />-->
|
||||
<link rel="stylesheet" href="static/css/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<form method="/frame">
|
||||
|
||||
</form>
|
||||
<div id="overlay">
|
||||
<div id="spinner"></div>
|
||||
<div id="msg"></div>
|
||||
</div>
|
||||
<div id="app" class="page selected">
|
||||
<h2>INTVAL3</h2>
|
||||
<div>
|
||||
<div class="label">Counter</div>
|
||||
<input type="number" id="counter" onclick="setCounter();" value="0" step="1" readonly />
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Direction</div>
|
||||
<span id="bwdLabel">BACKWARD</span>
|
||||
<span id="fwdLabel" class="selected">FORWARD</span>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="dir" onclick="setDir();">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Exposure <span id="str">1/5</span></div>
|
||||
<input type="number" id="exposure" value="630" min="0" onchange="setExposure();" />
|
||||
<select id="scale" onchange="setExposureScale();">
|
||||
<option value="ms" selected>ms</option>
|
||||
<option value="sec">sec</option>
|
||||
<option value="min">min</option>
|
||||
<option value="hour">hour</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Delay</div>
|
||||
<input type="number" id="delay" value="0" min="0" step="1" onchange="setDelay();" />
|
||||
<select id="delayScale" onchange="setDelayScale();">
|
||||
<option value="ms" selected>ms</option>
|
||||
<option value="sec">sec</option>
|
||||
<option value="min">min</option>
|
||||
<option value="hour">hour</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<button id="seq" onclick="sequence();">START SEQUENCE</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="frame" onclick="frame();">+1 FRAME</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="settings" class="page">
|
||||
<div class="ble">
|
||||
<h2>BLUETOOTH</h2>
|
||||
<select id="bluetooth">
|
||||
<option>N/A</option>
|
||||
</select>
|
||||
<button id="disconnect" onclick="mobile.ble.disconnect();">DISCONNECT</button>
|
||||
<button id="scan" class="active" onclick="mobile.ble.scan();">SCAN FOR DEVICE</button>
|
||||
</div>
|
||||
<div class="ble">
|
||||
<h2>WIFI</h2>
|
||||
<div id="ip">
|
||||
Local IP: null
|
||||
</div>
|
||||
<div>
|
||||
<select id="available" class="" onchange="mobile.editWifi();">
|
||||
<option>N/A</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<input type="password" id="password" class="" placeholder="Wifi Password" />
|
||||
</div>
|
||||
<button id="wifi" class="" onclick="mobile.setWifi();">CONNECT</button>
|
||||
<button id="wifiRefresh" class="" onclick="mobile.getWifi();">REFRESH WIFI</button>
|
||||
</div>
|
||||
<div>
|
||||
<button id="reset" onclick="reset();">RESET</button>
|
||||
<button id="restart" onclick="restart();">RESTART</button>
|
||||
<button id="update" onclick="update();">UPDATE</button>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div id="mscript" class="page">
|
||||
<h2>MSCRIPT</h2>
|
||||
<textarea id="mscript_editor"></textarea>
|
||||
<button id="compile">COMPILE</button>
|
||||
<button id="mscript_seq">START SEQUENCE</button>
|
||||
</div>-->
|
||||
<div id="camera" class="page">
|
||||
<h2>CAMERA</h2>
|
||||
<div class="clearfix">
|
||||
<div class="setting">
|
||||
<div class="label">ISO</div>
|
||||
<input type="number" class="iso" placeholder="100" value="100" onchange="mobile.refreshExposure();">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="label">F-stop</div>
|
||||
<input type="number" class="fstop" placeholder="5.6" value="5.6" onchange="mobile.refreshExposure();" />
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="label">Rex-o-fader</div>
|
||||
<select class="angle">
|
||||
<option value="133" selected>0 (Normal)</option>
|
||||
<option value="66">1 Stop</option>
|
||||
<option value="33">2 Stop</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button id="cameraBtn" class="ble" onclick="mobile.getCamera();">
|
||||
<i class="cameraIcon"></i>
|
||||
</button>
|
||||
<div class="clearfix ble">
|
||||
<div id="camera_exposure">
|
||||
<h3>PHONE</h3>
|
||||
<div>
|
||||
<label for="cam_exp">EXP</label>
|
||||
<input readonly id="cam_exp" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="cam_f">F</label>
|
||||
<input readonly id="cam_f" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="cam_iso">ISO</label>
|
||||
<input readonly id="cam_iso" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label>COMP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="bolex_exposure">
|
||||
<h3>BOLEX</h3>
|
||||
<div>
|
||||
<label id="bol_exp_diff"></label>
|
||||
<input readonly id="bol_exp" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label id="bol_f_diff"></label>
|
||||
<input readonly id="bol_f" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label id="bol_iso_diff"></label>
|
||||
<input readonly id="bol_iso" type="text" />
|
||||
</div>
|
||||
<div>
|
||||
<label><span class="pos">+0.8</span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<div id="settingsIcon" onclick="settingsPage();" class="icon">
|
||||
<div> </div>
|
||||
</div>
|
||||
<div id="appIcon" onclick="appPage();" class="icon selected">
|
||||
<div></div>
|
||||
</div>
|
||||
<!--<div id="mscriptIcon" onclick="mscriptPage();" class="icon">
|
||||
<div></div>
|
||||
</div>-->
|
||||
<div id="cameraIcon" onclick="cameraPage();" class="icon">
|
||||
<div class="cameraIcon"></div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="cordova.js"></script>
|
||||
<script src="static/js/spin.min.js"></script>
|
||||
<!--<script src="static/js/codemirror.js"></script>-->
|
||||
<script src="static/js/intval.core.js"></script>
|
||||
<script src="static/js/intval.web.js"></script>
|
||||
<script src="static/js/intval.mobile.js"></script>
|
||||
<!--<script src="static/js/intval.mscript.js"></script>-->
|
||||
<script src="static/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,346 @@
|
|||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
|
@ -0,0 +1,430 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
* {
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0); /* make transparent link selection, adjust last value opacity 0 to 1.0 */
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
|
||||
-webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
|
||||
-webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
|
||||
}
|
||||
html,body{
|
||||
background: #212121;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html,body,button,h2,label,input{
|
||||
color: #fff;
|
||||
font-family: 'Arial Neue', Helvetical, Arial, sans-serif;
|
||||
|
||||
}
|
||||
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table;
|
||||
}
|
||||
|
||||
body.mobile{
|
||||
padding-top: 5px;
|
||||
}
|
||||
.page{
|
||||
padding: 5px 10% 0 10%;
|
||||
display: none;
|
||||
}
|
||||
.page.selected{
|
||||
display: block;
|
||||
}
|
||||
.ble{
|
||||
display: none;
|
||||
}
|
||||
.ble.active{
|
||||
display: block;
|
||||
}
|
||||
h2{
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
}
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {display:none;}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #20ce45;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #f32121;
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
input[type=number],
|
||||
input[type=text],
|
||||
input[type=password],
|
||||
select{
|
||||
width: 100%;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
margin: 5px 0;
|
||||
padding: 5px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
option{
|
||||
color: #212121;
|
||||
}
|
||||
button{
|
||||
width: 100%;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
background: #363636;
|
||||
-webkit-appearance: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
margin: 5px 0;
|
||||
padding: 5px 0;
|
||||
text-align: center;
|
||||
}
|
||||
button:focus,
|
||||
button.focus{
|
||||
background-color: #20ce45;
|
||||
border-color: #20ce45;
|
||||
color: #212121;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#fwdLabel,#bwdLabel{
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
}
|
||||
#fwdLabel.selected,
|
||||
#bwdLabel.selected{
|
||||
color: #fff;
|
||||
}
|
||||
#fwdLabel.selected{
|
||||
text-shadow: 0px 0px 4px #20ce45;
|
||||
}
|
||||
#bwdLabel.selected{
|
||||
text-shadow: 0px 0px 4px #f32121;
|
||||
}
|
||||
#fwdLabel{
|
||||
float: left;
|
||||
}
|
||||
#bwdLabel{
|
||||
position: absolute;
|
||||
right: 10%;
|
||||
}
|
||||
.label{
|
||||
/*text-align: center;*/
|
||||
color: #666;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
/* MAIN */
|
||||
#app{
|
||||
|
||||
}
|
||||
|
||||
#app > h2{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#exposure,
|
||||
#delay{
|
||||
width: 70%;
|
||||
display: inline-block;
|
||||
}
|
||||
#scale,
|
||||
#delayScale{
|
||||
width: 25%;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
#str{
|
||||
color: #fff;
|
||||
}
|
||||
#counter{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#frame{
|
||||
padding: 20px 0;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#settingsIcon > div{
|
||||
background: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDU0IDU0IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NCA1NDsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSIzMnB4IiBoZWlnaHQ9IjMycHgiPgo8Zz4KCTxwYXRoIGQ9Ik01MS4yMiwyMWgtNS4wNTJjLTAuODEyLDAtMS40ODEtMC40NDctMS43OTItMS4xOTdzLTAuMTUzLTEuNTQsMC40Mi0yLjExNGwzLjU3Mi0zLjU3MSAgIGMwLjUyNS0wLjUyNSwwLjgxNC0xLjIyNCwwLjgxNC0xLjk2NmMwLTAuNzQzLTAuMjg5LTEuNDQxLTAuODE0LTEuOTY3bC00LjU1My00LjU1M2MtMS4wNS0xLjA1LTIuODgxLTEuMDUyLTMuOTMzLDBsLTMuNTcxLDMuNTcxICAgYy0wLjU3NCwwLjU3My0xLjM2NiwwLjczMy0yLjExNCwwLjQyMUMzMy40NDcsOS4zMTMsMzMsOC42NDQsMzMsNy44MzJWMi43OEMzMywxLjI0NywzMS43NTMsMCwzMC4yMiwwSDIzLjc4ICAgQzIyLjI0NywwLDIxLDEuMjQ3LDIxLDIuNzh2NS4wNTJjMCwwLjgxMi0wLjQ0NywxLjQ4MS0xLjE5NywxLjc5MmMtMC43NDgsMC4zMTMtMS41NCwwLjE1Mi0yLjExNC0wLjQyMWwtMy41NzEtMy41NzEgICBjLTEuMDUyLTEuMDUyLTIuODgzLTEuMDUtMy45MzMsMGwtNC41NTMsNC41NTNjLTAuNTI1LDAuNTI1LTAuODE0LDEuMjI0LTAuODE0LDEuOTY3YzAsMC43NDIsMC4yODksMS40NCwwLjgxNCwxLjk2NmwzLjU3MiwzLjU3MSAgIGMwLjU3MywwLjU3NCwwLjczLDEuMzY0LDAuNDIsMi4xMTRTOC42NDQsMjEsNy44MzIsMjFIMi43OEMxLjI0NywyMSwwLDIyLjI0NywwLDIzLjc4djYuNDM5QzAsMzEuNzUzLDEuMjQ3LDMzLDIuNzgsMzNoNS4wNTIgICBjMC44MTIsMCwxLjQ4MSwwLjQ0NywxLjc5MiwxLjE5N3MwLjE1MywxLjU0LTAuNDIsMi4xMTRsLTMuNTcyLDMuNTcxYy0wLjUyNSwwLjUyNS0wLjgxNCwxLjIyNC0wLjgxNCwxLjk2NiAgIGMwLDAuNzQzLDAuMjg5LDEuNDQxLDAuODE0LDEuOTY3bDQuNTUzLDQuNTUzYzEuMDUxLDEuMDUxLDIuODgxLDEuMDUzLDMuOTMzLDBsMy41NzEtMy41NzJjMC41NzQtMC41NzMsMS4zNjMtMC43MzEsMi4xMTQtMC40MiAgIGMwLjc1LDAuMzExLDEuMTk3LDAuOTgsMS4xOTcsMS43OTJ2NS4wNTJjMCwxLjUzMywxLjI0NywyLjc4LDIuNzgsMi43OGg2LjQzOWMxLjUzMywwLDIuNzgtMS4yNDcsMi43OC0yLjc4di01LjA1MiAgIGMwLTAuODEyLDAuNDQ3LTEuNDgxLDEuMTk3LTEuNzkyYzAuNzUxLTAuMzEyLDEuNTQtMC4xNTMsMi4xMTQsMC40MmwzLjU3MSwzLjU3MmMxLjA1MiwxLjA1MiwyLjg4MywxLjA1LDMuOTMzLDBsNC41NTMtNC41NTMgICBjMC41MjUtMC41MjUsMC44MTQtMS4yMjQsMC44MTQtMS45NjdjMC0wLjc0Mi0wLjI4OS0xLjQ0LTAuODE0LTEuOTY2bC0zLjU3Mi0zLjU3MWMtMC41NzMtMC41NzQtMC43My0xLjM2NC0wLjQyLTIuMTE0ICAgUzQ1LjM1NiwzMyw0Ni4xNjgsMzNoNS4wNTJjMS41MzMsMCwyLjc4LTEuMjQ3LDIuNzgtMi43OFYyMy43OEM1NCwyMi4yNDcsNTIuNzUzLDIxLDUxLjIyLDIxeiBNNTIsMzAuMjIgICBDNTIsMzAuNjUsNTEuNjUsMzEsNTEuMjIsMzFoLTUuMDUyYy0xLjYyNCwwLTMuMDE5LDAuOTMyLTMuNjQsMi40MzJjLTAuNjIyLDEuNS0wLjI5NSwzLjE0NiwwLjg1NCw0LjI5NGwzLjU3MiwzLjU3MSAgIGMwLjMwNSwwLjMwNSwwLjMwNSwwLjgsMCwxLjEwNGwtNC41NTMsNC41NTNjLTAuMzA0LDAuMzA0LTAuNzk5LDAuMzA2LTEuMTA0LDBsLTMuNTcxLTMuNTcyYy0xLjE0OS0xLjE0OS0yLjc5NC0xLjQ3NC00LjI5NC0wLjg1NCAgIGMtMS41LDAuNjIxLTIuNDMyLDIuMDE2LTIuNDMyLDMuNjR2NS4wNTJDMzEsNTEuNjUsMzAuNjUsNTIsMzAuMjIsNTJIMjMuNzhDMjMuMzUsNTIsMjMsNTEuNjUsMjMsNTEuMjJ2LTUuMDUyICAgYzAtMS42MjQtMC45MzItMy4wMTktMi40MzItMy42NGMtMC41MDMtMC4yMDktMS4wMjEtMC4zMTEtMS41MzMtMC4zMTFjLTEuMDE0LDAtMS45OTcsMC40LTIuNzYxLDEuMTY0bC0zLjU3MSwzLjU3MiAgIGMtMC4zMDYsMC4zMDYtMC44MDEsMC4zMDQtMS4xMDQsMGwtNC41NTMtNC41NTNjLTAuMzA1LTAuMzA1LTAuMzA1LTAuOCwwLTEuMTA0bDMuNTcyLTMuNTcxYzEuMTQ4LTEuMTQ4LDEuNDc2LTIuNzk0LDAuODU0LTQuMjk0ICAgQzEwLjg1MSwzMS45MzIsOS40NTYsMzEsNy44MzIsMzFIMi43OEMyLjM1LDMxLDIsMzAuNjUsMiwzMC4yMlYyMy43OEMyLDIzLjM1LDIuMzUsMjMsMi43OCwyM2g1LjA1MiAgIGMxLjYyNCwwLDMuMDE5LTAuOTMyLDMuNjQtMi40MzJjMC42MjItMS41LDAuMjk1LTMuMTQ2LTAuODU0LTQuMjk0bC0zLjU3Mi0zLjU3MWMtMC4zMDUtMC4zMDUtMC4zMDUtMC44LDAtMS4xMDRsNC41NTMtNC41NTMgICBjMC4zMDQtMC4zMDUsMC43OTktMC4zMDUsMS4xMDQsMGwzLjU3MSwzLjU3MWMxLjE0NywxLjE0NywyLjc5MiwxLjQ3Niw0LjI5NCwwLjg1NEMyMi4wNjgsMTAuODUxLDIzLDkuNDU2LDIzLDcuODMyVjIuNzggICBDMjMsMi4zNSwyMy4zNSwyLDIzLjc4LDJoNi40MzlDMzAuNjUsMiwzMSwyLjM1LDMxLDIuNzh2NS4wNTJjMCwxLjYyNCwwLjkzMiwzLjAxOSwyLjQzMiwzLjY0ICAgYzEuNTAyLDAuNjIyLDMuMTQ2LDAuMjk0LDQuMjk0LTAuODU0bDMuNTcxLTMuNTcxYzAuMzA2LTAuMzA1LDAuODAxLTAuMzA1LDEuMTA0LDBsNC41NTMsNC41NTNjMC4zMDUsMC4zMDUsMC4zMDUsMC44LDAsMS4xMDQgICBsLTMuNTcyLDMuNTcxYy0xLjE0OCwxLjE0OC0xLjQ3NiwyLjc5NC0wLjg1NCw0LjI5NGMwLjYyMSwxLjUsMi4wMTYsMi40MzIsMy42NCwyLjQzMmg1LjA1MkM1MS42NSwyMyw1MiwyMy4zNSw1MiwyMy43OFYzMC4yMnoiIGZpbGw9IiNGRkZGRkYiLz4KCTxwYXRoIGQ9Ik0yNywxOGMtNC45NjMsMC05LDQuMDM3LTksOXM0LjAzNyw5LDksOXM5LTQuMDM3LDktOVMzMS45NjMsMTgsMjcsMTh6IE0yNywzNGMtMy44NTksMC03LTMuMTQxLTctN3MzLjE0MS03LDctNyAgIHM3LDMuMTQxLDcsN1MzMC44NTksMzQsMjcsMzR6IiBmaWxsPSIjRkZGRkZGIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==) no-repeat;
|
||||
}
|
||||
#mscriptIcon > div{
|
||||
background: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjMycHgiIGhlaWdodD0iMzJweCIgdmlld0JveD0iMCAwIDUyMi40NjggNTIyLjQ2OSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTIyLjQ2OCA1MjIuNDY5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTMyNS43NjIsNzAuNTEzbC0xNy43MDYtNC44NTRjLTIuMjc5LTAuNzYtNC41MjQtMC41MjEtNi43MDcsMC43MTVjLTIuMTksMS4yMzctMy42NjksMy4wOTQtNC40MjksNS41NjhMMTkwLjQyNiw0NDAuNTMgICAgYy0wLjc2LDIuNDc1LTAuNTIyLDQuODA5LDAuNzE1LDYuOTk1YzEuMjM3LDIuMTksMy4wOSwzLjY2NSw1LjU2OCw0LjQyNWwxNy43MDEsNC44NTZjMi4yODQsMC43NjYsNC41MjEsMC41MjYsNi43MS0wLjcxMiAgICBjMi4xOS0xLjI0MywzLjY2Ni0zLjA5NCw0LjQyNS01LjU2NEwzMzIuMDQyLDgxLjkzNmMwLjc1OS0yLjQ3NCwwLjUyMy00LjgwOC0wLjcxNi02Ljk5OSAgICBDMzMwLjA4OCw3Mi43NDcsMzI4LjIzNyw3MS4yNzIsMzI1Ljc2Miw3MC41MTN6IiBmaWxsPSIjRkZGRkZGIi8+CgkJPHBhdGggZD0iTTE2Ni4xNjcsMTQyLjQ2NWMwLTIuNDc0LTAuOTUzLTQuNjY1LTIuODU2LTYuNTY3bC0xNC4yNzctMTQuMjc2Yy0xLjkwMy0xLjkwMy00LjA5My0yLjg1Ny02LjU2Ny0yLjg1NyAgICBzLTQuNjY1LDAuOTU1LTYuNTY3LDIuODU3TDIuODU2LDI1NC42NjZDMC45NSwyNTYuNTY5LDAsMjU4Ljc1OSwwLDI2MS4yMzNjMCwyLjQ3NCwwLjk1Myw0LjY2NCwyLjg1Niw2LjU2NmwxMzMuMDQzLDEzMy4wNDQgICAgYzEuOTAyLDEuOTA2LDQuMDg5LDIuODU0LDYuNTY3LDIuODU0czQuNjY1LTAuOTUxLDYuNTY3LTIuODU0bDE0LjI3Ny0xNC4yNjhjMS45MDMtMS45MDIsMi44NTYtNC4wOTMsMi44NTYtNi41NyAgICBjMC0yLjQ3MS0wLjk1My00LjY2MS0yLjg1Ni02LjU2M0w1MS4xMDcsMjYxLjIzM2wxMTIuMjA0LTExMi4yMDFDMTY1LjIxNywxNDcuMTMsMTY2LjE2NywxNDQuOTM5LDE2Ni4xNjcsMTQyLjQ2NXoiIGZpbGw9IiNGRkZGRkYiLz4KCQk8cGF0aCBkPSJNNTE5LjYxNCwyNTQuNjYzTDM4Ni41NjcsMTIxLjYxOWMtMS45MDItMS45MDItNC4wOTMtMi44NTctNi41NjMtMi44NTdjLTIuNDc4LDAtNC42NjEsMC45NTUtNi41NywyLjg1N2wtMTQuMjcxLDE0LjI3NSAgICBjLTEuOTAyLDEuOTAzLTIuODUxLDQuMDktMi44NTEsNi41NjdzMC45NDgsNC42NjUsMi44NTEsNi41NjdsMTEyLjIwNiwxMTIuMjA0TDM1OS4xNjMsMzczLjQ0MiAgICBjLTEuOTAyLDEuOTAyLTIuODUxLDQuMDkzLTIuODUxLDYuNTYzYzAsMi40NzgsMC45NDgsNC42NjgsMi44NTEsNi41N2wxNC4yNzEsMTQuMjY4YzEuOTA5LDEuOTA2LDQuMDkzLDIuODU0LDYuNTcsMi44NTQgICAgYzIuNDcxLDAsNC42NjEtMC45NTEsNi41NjMtMi44NTRMNTE5LjYxNCwyNjcuOGMxLjkwMy0xLjkwMiwyLjg1NC00LjA5NiwyLjg1NC02LjU3ICAgIEM1MjIuNDY4LDI1OC43NTUsNTIxLjUxNywyNTYuNTY1LDUxOS42MTQsMjU0LjY2M3oiIGZpbGw9IiNGRkZGRkYiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K) no-repeat;
|
||||
}
|
||||
#appIcon > div{
|
||||
background: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDYwIDYwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA2MCA2MDsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHdpZHRoPSIzMnB4IiBoZWlnaHQ9IjMycHgiPgo8Zz4KCTxwYXRoIGQ9Ik00NS41NjMsMjkuMTc0bC0yMi0xNWMtMC4zMDctMC4yMDgtMC43MDMtMC4yMzEtMS4wMzEtMC4wNThDMjIuMjA1LDE0LjI4OSwyMiwxNC42MjksMjIsMTV2MzAgICBjMCwwLjM3MSwwLjIwNSwwLjcxMSwwLjUzMywwLjg4NEMyMi42NzksNDUuOTYyLDIyLjg0LDQ2LDIzLDQ2YzAuMTk3LDAsMC4zOTQtMC4wNTksMC41NjMtMC4xNzRsMjItMTUgICBDNDUuODM2LDMwLjY0LDQ2LDMwLjMzMSw0NiwzMFM0NS44MzYsMjkuMzYsNDUuNTYzLDI5LjE3NHogTTI0LDQzLjEwN1YxNi44OTNMNDMuMjI1LDMwTDI0LDQzLjEwN3oiIGZpbGw9IiNGRkZGRkYiLz4KCTxwYXRoIGQ9Ik0zMCwwQzEzLjQ1OCwwLDAsMTMuNDU4LDAsMzBzMTMuNDU4LDMwLDMwLDMwczMwLTEzLjQ1OCwzMC0zMFM0Ni41NDIsMCwzMCwweiBNMzAsNThDMTQuNTYxLDU4LDIsNDUuNDM5LDIsMzAgICBTMTQuNTYxLDIsMzAsMnMyOCwxMi41NjEsMjgsMjhTNDUuNDM5LDU4LDMwLDU4eiIgZmlsbD0iI0ZGRkZGRiIvPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=) no-repeat;
|
||||
}
|
||||
.cameraIcon{
|
||||
background: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEwMCAxMDA7IiB4bWw6c3BhY2U9InByZXNlcnZlIiB3aWR0aD0iMzJweCIgaGVpZ2h0PSIzMnB4Ij4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNNTAsNDBjLTguMjg1LDAtMTUsNi43MTgtMTUsMTVjMCw4LjI4NSw2LjcxNSwxNSwxNSwxNWM4LjI4MywwLDE1LTYuNzE1LDE1LTE1ICAgIEM2NSw0Ni43MTgsNTguMjgzLDQwLDUwLDQweiBNOTAsMjVINzhjLTEuNjUsMC0zLjQyOC0xLjI4LTMuOTQ5LTIuODQ2bC0zLjEwMi05LjMwOUM3MC40MjYsMTEuMjgsNjguNjUsMTAsNjcsMTBIMzMgICAgYy0xLjY1LDAtMy40MjgsMS4yOC0zLjk0OSwyLjg0NmwtMy4xMDIsOS4zMDlDMjUuNDI2LDIzLjcyLDIzLjY1LDI1LDIyLDI1SDEwQzQuNSwyNSwwLDI5LjUsMCwzNXY0NWMwLDUuNSw0LjUsMTAsMTAsMTBoODAgICAgYzUuNSwwLDEwLTQuNSwxMC0xMFYzNUMxMDAsMjkuNSw5NS41LDI1LDkwLDI1eiBNNTAsODBjLTEzLjgwNywwLTI1LTExLjE5My0yNS0yNWMwLTEzLjgwNiwxMS4xOTMtMjUsMjUtMjUgICAgYzEzLjgwNSwwLDI1LDExLjE5NCwyNSwyNUM3NSw2OC44MDcsNjMuODA1LDgwLDUwLDgweiBNODYuNSw0MS45OTNjLTEuOTMyLDAtMy41LTEuNTY2LTMuNS0zLjVjMC0xLjkzMiwxLjU2OC0zLjUsMy41LTMuNSAgICBjMS45MzQsMCwzLjUsMS41NjgsMy41LDMuNUM5MCw0MC40MjcsODguNDMzLDQxLjk5Myw4Ni41LDQxLjk5M3oiIGZpbGw9IiNGRkZGRkYiLz4KCTwvZz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K) no-repeat;
|
||||
}
|
||||
|
||||
button i {
|
||||
display: block;
|
||||
width: 33px;
|
||||
height: 33px;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
footer{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
footer .icon {
|
||||
width: 33.33%;
|
||||
/*width: 50%;*/
|
||||
height: 50px;
|
||||
float: left;
|
||||
box-sizing: border-box;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
body.mobile footer .icon{
|
||||
/*width: 25%;*/
|
||||
width: 33.33%;
|
||||
}
|
||||
|
||||
footer .icon:last-child{
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.icon > div{
|
||||
display: block;
|
||||
line-height: 34px;
|
||||
height: 33px;
|
||||
width: 33px;
|
||||
opacity: 0.5;
|
||||
color: #fff;
|
||||
margin: 10px auto 0;
|
||||
}
|
||||
.icon.selected > div{
|
||||
opacity: 1.0;
|
||||
}
|
||||
|
||||
footer > div.selected{
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.CodeMirror{
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
#compile{
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#seq{
|
||||
margin-top: 40px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
#overlay{
|
||||
position: fixed;
|
||||
z-index: 2001;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
}
|
||||
#overlay.active{
|
||||
display: block;
|
||||
}
|
||||
#spinner{
|
||||
display: none;
|
||||
margin-top: 200px;
|
||||
}
|
||||
|
||||
#spinner.active{
|
||||
display: block;
|
||||
}
|
||||
|
||||
#disconnect,#scan{
|
||||
display: none;
|
||||
}
|
||||
#disconnect.active,
|
||||
#scan.active{
|
||||
display: block;
|
||||
}
|
||||
|
||||
#available.active{
|
||||
border-color: #20ce45;
|
||||
}
|
||||
#ip{
|
||||
color: #666;
|
||||
}
|
||||
#ip span{
|
||||
color: #20ce45;
|
||||
}
|
||||
#password,#wifi,#ip{
|
||||
display: none;
|
||||
}
|
||||
#password.active,
|
||||
#ip.active,
|
||||
#wifi.active{
|
||||
display: block;
|
||||
}
|
||||
.indicator {
|
||||
color: red;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
|
||||
border-bottom: 5px solid black;
|
||||
}
|
||||
.setting{
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
.setting input,
|
||||
.setting select{
|
||||
width: 90%;
|
||||
}
|
||||
.setting .label{
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
#cameraBtn{
|
||||
margin-top: 20px;
|
||||
}
|
||||
#camera_exposure,
|
||||
#bolex_exposure{
|
||||
width: 50%;
|
||||
float: left;
|
||||
}
|
||||
#camera_exposure input,
|
||||
#bolex_exposure input{
|
||||
margin: 12px auto;
|
||||
width: 69%;
|
||||
display: block;
|
||||
border-color: #666;
|
||||
}
|
||||
#camera_exposure h3,
|
||||
#bolex_exposure h3{
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
}
|
||||
#camera_exposure div label,
|
||||
#bolex_exposure div label{
|
||||
color: #666;
|
||||
position: absolute;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-left: -50%;
|
||||
}
|
||||
span.neg{
|
||||
color: #f32121;;
|
||||
}
|
||||
span.pos{
|
||||
color: #20ce45;
|
||||
}
|
||||
body.mobile footer{
|
||||
display: block;
|
||||
}
|
||||
|
||||
#reset{
|
||||
margin-top: 60px;
|
||||
}
|
||||
#msg{
|
||||
display: none;
|
||||
width: 200px;
|
||||
height: 44px;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
margin-top: 45px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 0px #000;
|
||||
}
|
||||
#msg.active{
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* Based on Sublime Text's Monokai theme */
|
||||
|
||||
.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; }
|
||||
.cm-s-monokai div.CodeMirror-selected { background: #49483E; }
|
||||
.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); }
|
||||
.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); }
|
||||
.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; }
|
||||
.cm-s-monokai .CodeMirror-guttermarker { color: white; }
|
||||
.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
|
||||
.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; }
|
||||
.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
|
||||
|
||||
.cm-s-monokai span.cm-comment { color: #75715e; }
|
||||
.cm-s-monokai span.cm-atom { color: #ae81ff; }
|
||||
.cm-s-monokai span.cm-number { color: #ae81ff; }
|
||||
|
||||
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
|
||||
.cm-s-monokai span.cm-keyword { color: #f92672; }
|
||||
.cm-s-monokai span.cm-builtin { color: #66d9ef; }
|
||||
.cm-s-monokai span.cm-string { color: #e6db74; }
|
||||
|
||||
.cm-s-monokai span.cm-variable { color: #f8f8f2; }
|
||||
.cm-s-monokai span.cm-variable-2 { color: #9effff; }
|
||||
.cm-s-monokai span.cm-variable-3, .cm-s-monokai span.cm-type { color: #66d9ef; }
|
||||
.cm-s-monokai span.cm-def { color: #fd971f; }
|
||||
.cm-s-monokai span.cm-bracket { color: #f8f8f2; }
|
||||
.cm-s-monokai span.cm-tag { color: #f92672; }
|
||||
.cm-s-monokai span.cm-header { color: #ae81ff; }
|
||||
.cm-s-monokai span.cm-link { color: #ae81ff; }
|
||||
.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; }
|
||||
|
||||
.cm-s-monokai .CodeMirror-activeline-background { background: #373831; }
|
||||
.cm-s-monokai .CodeMirror-matchingbracket {
|
||||
text-decoration: underline;
|
||||
color: white !important;
|
||||
}
|
|
@ -0,0 +1,436 @@
|
|||
/*!
|
||||
* QUnit 2.5.0
|
||||
* https://qunitjs.com/
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license
|
||||
* https://jquery.org/license
|
||||
*
|
||||
* Date: 2018-01-10T02:56Z
|
||||
*/
|
||||
|
||||
/** Font Family and Sizes */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
|
||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||
#qunit-tests { font-size: smaller; }
|
||||
|
||||
|
||||
/** Resets */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/** Header (excluding toolbar) */
|
||||
|
||||
#qunit-header {
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
|
||||
color: #8699A4;
|
||||
background-color: #0D3349;
|
||||
|
||||
font-size: 1.5em;
|
||||
line-height: 1em;
|
||||
font-weight: 400;
|
||||
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
#qunit-header a {
|
||||
text-decoration: none;
|
||||
color: #C2CCD1;
|
||||
}
|
||||
|
||||
#qunit-header a:hover,
|
||||
#qunit-header a:focus {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#qunit-banner {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
#qunit-filteredTest {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
color: #366097;
|
||||
background-color: #F4FF77;
|
||||
}
|
||||
|
||||
#qunit-userAgent {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
color: #FFF;
|
||||
background-color: #2B81AF;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
|
||||
|
||||
/** Toolbar */
|
||||
|
||||
#qunit-testrunner-toolbar {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
color: #5E740B;
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar .clearfix {
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar input[type=checkbox],
|
||||
#qunit-testrunner-toolbar input[type=radio] {
|
||||
margin: 3px;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar input[type=text] {
|
||||
box-sizing: border-box;
|
||||
height: 1.6em;
|
||||
}
|
||||
|
||||
.qunit-url-config,
|
||||
.qunit-filter,
|
||||
#qunit-modulefilter {
|
||||
display: inline-block;
|
||||
line-height: 2.1em;
|
||||
}
|
||||
|
||||
.qunit-filter,
|
||||
#qunit-modulefilter {
|
||||
float: right;
|
||||
position: relative;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.qunit-url-config label {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-search {
|
||||
box-sizing: border-box;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-search-container:after {
|
||||
position: absolute;
|
||||
right: 0.3em;
|
||||
content: "\25bc";
|
||||
color: black;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown {
|
||||
/* align with #qunit-modulefilter-search */
|
||||
box-sizing: border-box;
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
margin-top: 0.8em;
|
||||
|
||||
border: 1px solid #D3D3D3;
|
||||
border-top: none;
|
||||
border-radius: 0 0 .25em .25em;
|
||||
color: #000;
|
||||
background-color: #F5F5F5;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown .clickable.checked {
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
background-color: #D2E0E6;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown .clickable:hover {
|
||||
color: #FFF;
|
||||
background-color: #0D3349;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-actions {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
|
||||
/* align with #qunit-modulefilter-dropdown-list */
|
||||
font: smaller/1.5em sans-serif;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
|
||||
box-sizing: border-box;
|
||||
max-height: 2.8em;
|
||||
display: block;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
|
||||
float: right;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
|
||||
/* insert padding to align with checkbox margins */
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
margin: 0;
|
||||
border-top: 2px groove threedhighlight;
|
||||
padding: 0.4em 0 0;
|
||||
font: smaller/1.5em sans-serif;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown-list li {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown-list .clickable {
|
||||
display: block;
|
||||
padding-left: 0.15em;
|
||||
}
|
||||
|
||||
|
||||
/** Tests: Pass/Fail */
|
||||
|
||||
#qunit-tests {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li {
|
||||
padding: 0.4em 1em 0.4em 1em;
|
||||
border-bottom: 1px solid #FFF;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests > li {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests li.running,
|
||||
#qunit-tests li.pass,
|
||||
#qunit-tests li.fail,
|
||||
#qunit-tests li.skipped,
|
||||
#qunit-tests li.aborted {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass li.running,
|
||||
#qunit-tests.hidepass li.pass:not(.todo) {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#qunit-tests li strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#qunit-tests li.skipped strong {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#qunit-tests li a {
|
||||
padding: 0.5em;
|
||||
color: #C2CCD1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests li p a {
|
||||
padding: 0.25em;
|
||||
color: #6B6464;
|
||||
}
|
||||
#qunit-tests li a:hover,
|
||||
#qunit-tests li a:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#qunit-tests li .runtime {
|
||||
float: right;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.qunit-assert-list {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #FFF;
|
||||
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.qunit-source {
|
||||
margin: 0.6em 0 0.3em;
|
||||
}
|
||||
|
||||
.qunit-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests table {
|
||||
border-collapse: collapse;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
#qunit-tests th {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
padding: 0 0.5em 0 0;
|
||||
}
|
||||
|
||||
#qunit-tests td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#qunit-tests pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#qunit-tests del {
|
||||
color: #374E0C;
|
||||
background-color: #E0F2BE;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests ins {
|
||||
color: #500;
|
||||
background-color: #FFCACA;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** Test Counts */
|
||||
|
||||
#qunit-tests b.counts { color: #000; }
|
||||
#qunit-tests b.passed { color: #5E740B; }
|
||||
#qunit-tests b.failed { color: #710909; }
|
||||
|
||||
#qunit-tests li li {
|
||||
padding: 5px;
|
||||
background-color: #FFF;
|
||||
border-bottom: none;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/*** Passing Styles */
|
||||
|
||||
#qunit-tests li li.pass {
|
||||
color: #3C510C;
|
||||
background-color: #FFF;
|
||||
border-left: 10px solid #C6E746;
|
||||
}
|
||||
|
||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||
#qunit-tests .pass .test-name { color: #366097; }
|
||||
|
||||
#qunit-tests .pass .test-actual,
|
||||
#qunit-tests .pass .test-expected { color: #999; }
|
||||
|
||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||
|
||||
/*** Failing Styles */
|
||||
|
||||
#qunit-tests li li.fail {
|
||||
color: #710909;
|
||||
background-color: #FFF;
|
||||
border-left: 10px solid #EE5757;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#qunit-tests > li:last-child {
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
#qunit-tests .fail { color: #000; background-color: #EE5757; }
|
||||
#qunit-tests .fail .test-name,
|
||||
#qunit-tests .fail .module-name { color: #000; }
|
||||
|
||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
||||
#qunit-tests .fail .test-expected { color: #008000; }
|
||||
|
||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
||||
|
||||
|
||||
/*** Aborted tests */
|
||||
#qunit-tests .aborted { color: #000; background-color: orange; }
|
||||
/*** Skipped tests */
|
||||
|
||||
#qunit-tests .skipped {
|
||||
background-color: #EBECE9;
|
||||
}
|
||||
|
||||
#qunit-tests .qunit-todo-label,
|
||||
#qunit-tests .qunit-skipped-label {
|
||||
background-color: #F4FF77;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
color: #366097;
|
||||
line-height: 1.8em;
|
||||
padding: 0 0.5em;
|
||||
margin: -0.4em 0.4em -0.4em 0;
|
||||
}
|
||||
|
||||
#qunit-tests .qunit-todo-label {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
/** Result */
|
||||
|
||||
#qunit-testresult {
|
||||
color: #2B81AF;
|
||||
background-color: #D2E0E6;
|
||||
|
||||
border-bottom: 1px solid #FFF;
|
||||
}
|
||||
#qunit-testresult .clearfix {
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
#qunit-testresult .module-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
#qunit-testresult-display {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
width: 85%;
|
||||
float:left;
|
||||
}
|
||||
#qunit-testresult-controls {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
width: 10%;
|
||||
float:left;
|
||||
}
|
||||
|
||||
/** Fixture */
|
||||
|
||||
#qunit-fixture {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
var app = {
|
||||
// Application Constructor
|
||||
initialize: function() {
|
||||
document.addEventListener('deviceready', this.onDeviceReady.bind(this), false);
|
||||
document.addEventListener('resume', this.onDeviceResume.bind(this), false);
|
||||
document.addEventListener('DOMContentLoaded', event => {
|
||||
if (typeof cordova === 'undefined') {
|
||||
init();
|
||||
web.init();
|
||||
getState();
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// deviceready Event Handler
|
||||
//
|
||||
// Bind any cordova events here. Common events are:
|
||||
// 'pause', 'resume', etc.
|
||||
onDeviceReady: function() {
|
||||
init();
|
||||
mobile.init();
|
||||
},
|
||||
onDeviceResume : function () {
|
||||
getState();
|
||||
}
|
||||
};
|
||||
|
||||
app.initialize();
|
|
@ -0,0 +1,318 @@
|
|||
'use strict';
|
||||
const BOLEX = {
|
||||
angle : 133,
|
||||
prism : 0.8,
|
||||
iso : 100,
|
||||
fstop : 5.6,
|
||||
expected : 630
|
||||
};
|
||||
const STATE = {
|
||||
dir : true,
|
||||
exposure : 630, //always ms
|
||||
delay : 0,
|
||||
scale : 'ms',
|
||||
delayScale : 'ms',
|
||||
counter : 0,
|
||||
sequence : false
|
||||
};
|
||||
|
||||
//functions
|
||||
window.frame = null;
|
||||
window.getState = null;
|
||||
window.setDir = null;
|
||||
window.setExposure = null;
|
||||
window.setDelay = null;
|
||||
window.setCounter = null;
|
||||
window.sequence = null;
|
||||
window.reset = null;
|
||||
window.restart = null;
|
||||
window.update = null;
|
||||
|
||||
//ms
|
||||
var shutter = function (exposure) {
|
||||
let fraction = BOLEX.expected / 1000;
|
||||
let speed;
|
||||
let corrected;
|
||||
let str;
|
||||
if (exposure > BOLEX.expected) {
|
||||
//if exposure is explicitly set
|
||||
fraction = exposure / 1000;
|
||||
speed = fraction;
|
||||
} else {
|
||||
speed = fraction * (BOLEX.angle / 360);
|
||||
}
|
||||
corrected = speed * BOLEX.prism;
|
||||
if (corrected < 1.0) {
|
||||
//less than a second
|
||||
str = '1/' + Math.round(Math.pow(corrected, -1)) + ' sec';
|
||||
} else if (corrected >= 1.0 && corrected < 60) {
|
||||
//greater than a second, less than a minute
|
||||
str = '' + (Math.round(corrected * 10) / 10) + ' sec'
|
||||
} else if (corrected >= 60 && corrected < 60 * 60) {
|
||||
//greater than a minute, less than an hour
|
||||
str = '' + (Math.round(corrected / 6) / 10) + ' min';
|
||||
} else if (corrected >= 60 * 60 && corrected < 60 * 60 * 24) {
|
||||
//greater than an hour, less than a day
|
||||
str = '' + (Math.round(corrected / (6 * 60)) / 10) + ' hr';
|
||||
} else if (corrected >= 60 * 60 * 24) {
|
||||
//greater than a day
|
||||
str = '' + (Math.round(corrected / (6 * 60 * 24)) / 10) + ' day';
|
||||
}
|
||||
return { speed : speed, str : str }
|
||||
};
|
||||
|
||||
var scaleAuto = function (ms) {
|
||||
if (ms < 1000) {
|
||||
return 'ms'
|
||||
} else if (ms >= 1000 && ms < 1000 * 60) {
|
||||
return 'sec'
|
||||
} else if (ms >= 1000 * 60 && ms < 1000 * 60 * 60) {
|
||||
return 'min'
|
||||
} else if (ms >= 1000 * 60 * 60) {
|
||||
return 'hour'
|
||||
}
|
||||
};
|
||||
|
||||
var scaleTime = function (raw, scale) {
|
||||
if (scale === 'ms') {
|
||||
return raw
|
||||
} else if (scale === 'sec') {
|
||||
return raw * 1000;
|
||||
} else if (scale === 'min') {
|
||||
return raw * (1000 * 60);
|
||||
} else if (scale === 'hour') {
|
||||
return raw * (1000 * 60 * 60);
|
||||
}
|
||||
};
|
||||
|
||||
var setExposureScale = function () {
|
||||
const scale = document.getElementById('scale').value;
|
||||
const elem = document.getElementById('exposure');
|
||||
if (scale === 'ms') {
|
||||
elem.value = STATE.exposure;
|
||||
} else if (scale === 'sec') {
|
||||
elem.value = STATE.exposure / 1000;
|
||||
} else if (scale === 'min') {
|
||||
elem.value = STATE.exposure / (1000 * 60);
|
||||
} else if (scale === 'hour') {
|
||||
elem.value = STATE.exposure / (1000 * 60 * 60);
|
||||
}
|
||||
STATE.scale = scale;
|
||||
};
|
||||
|
||||
var setDelayScale = function () {
|
||||
const scale = document.getElementById('delayScale').value;
|
||||
const elem = document.getElementById('delay');
|
||||
if (scale === 'ms') {
|
||||
elem.value = STATE.delay;
|
||||
} else if (scale === 'sec') {
|
||||
elem.value = STATE.delay / 1000;
|
||||
} else if (scale === 'min') {
|
||||
elem.value = STATE.delay / (1000 * 60);
|
||||
} else if (scale === 'hour') {
|
||||
elem.value = STATE.delay / (1000 * 60 * 60);
|
||||
}
|
||||
STATE.delayScale = scale;
|
||||
};
|
||||
|
||||
var setDirLabel = function (dir) {
|
||||
const bwdLabel = document.getElementById('bwdLabel');
|
||||
const fwdLabel = document.getElementById('fwdLabel');
|
||||
const but = document.getElementById('frame');
|
||||
if (dir) {
|
||||
bwdLabel.classList.remove('selected');
|
||||
fwdLabel.classList.add('selected');
|
||||
but.innerHTML = '+1 FRAME';
|
||||
} else {
|
||||
fwdLabel.classList.remove('selected');
|
||||
bwdLabel.classList.add('selected');
|
||||
but.innerHTML = '-1 FRAME';
|
||||
}
|
||||
};
|
||||
var incCounter = function (val) {
|
||||
const elem = document.getElementById('counter');
|
||||
const current = elem.value;
|
||||
elem.value = (parseInt(current) + val);
|
||||
STATE.counter += val;
|
||||
};
|
||||
var forceCounter = function (val) {
|
||||
document.getElementById('counter').value = val;
|
||||
}
|
||||
var unsetPages = function () {
|
||||
const pages = document.querySelectorAll('.page');
|
||||
const icons = document.querySelectorAll('.icon');
|
||||
for (let icon of icons) {
|
||||
if (icon.classList.contains('selected')) icon.classList.remove('selected');
|
||||
};
|
||||
for (let page of pages){;
|
||||
if (page.classList.contains('selected')) page.classList.remove('selected');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var setState = function (res) {
|
||||
let exposure;
|
||||
let exposureScale;
|
||||
let delayScale;
|
||||
|
||||
if (res.frame.dir !== true) {
|
||||
document.getElementById('dir').checked = true;
|
||||
STATE.dir = res.frame.dir;
|
||||
setDirLabel(false);
|
||||
} else {
|
||||
document.getElementById('dir').checked = false;
|
||||
STATE.dir = res.frame.dir;
|
||||
setDirLabel(true);
|
||||
}
|
||||
document.getElementById('counter').value = res.counter;
|
||||
STATE.counter = res.counter;
|
||||
//Exposure
|
||||
if (res.frame.exposure === 0) {
|
||||
res.frame.exposure = BOLEX.expected;
|
||||
}
|
||||
STATE.exposure = res.frame.exposure;
|
||||
exposure = shutter(STATE.exposure);
|
||||
exposureScale = scaleAuto(STATE.exposure);
|
||||
|
||||
document.getElementById('str').innerHTML = exposure.str;
|
||||
document.getElementById('scale').value = exposureScale;
|
||||
setExposureScale();
|
||||
|
||||
STATE.delay = res.frame.delay;
|
||||
delayScale = scaleAuto(STATE.delay);
|
||||
document.getElementById('delayScale').value = delayScale;
|
||||
setDelayScale();
|
||||
|
||||
if (res.sequence == true) {
|
||||
STATE.sequence = true;
|
||||
if (mobile.ble) mobile.ble.active = true;
|
||||
seqState(true);
|
||||
} else {
|
||||
seqState(false);
|
||||
}
|
||||
};
|
||||
|
||||
var seqState = function (state) {
|
||||
const elem = document.getElementById('seq')
|
||||
if (state) {
|
||||
if (!elem.classList.contains('focus')) {
|
||||
elem.classList.add('focus');
|
||||
elem.innerHTML = 'STOP SEQUENCE';
|
||||
}
|
||||
} else {
|
||||
if (elem.classList.contains('focus')) {
|
||||
elem.classList.remove('focus');
|
||||
elem.innerHTML = 'START SEQUENCE';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var appPage = function () {
|
||||
unsetPages();
|
||||
document.getElementById('app').classList.add('selected');
|
||||
document.getElementById('appIcon').classList.add('selected');
|
||||
};
|
||||
var settingsPage = function () {
|
||||
unsetPages();
|
||||
document.getElementById('settings').classList.add('selected');
|
||||
document.getElementById('settingsIcon').classList.add('selected');
|
||||
};
|
||||
var mscriptPage = function () {
|
||||
unsetPages();
|
||||
document.getElementById('mscript').classList.add('selected');
|
||||
document.getElementById('mscriptIcon').classList.add('selected');
|
||||
editor.cm.refresh();
|
||||
};
|
||||
var cameraPage = function () {
|
||||
unsetPages();
|
||||
document.getElementById('camera').classList.add('selected');
|
||||
document.getElementById('cameraIcon').classList.add('selected');
|
||||
};
|
||||
|
||||
var isNumeric = function (n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
};
|
||||
|
||||
var UI = {};
|
||||
|
||||
UI.overlay = {
|
||||
elem : document.getElementById('overlay')
|
||||
}
|
||||
UI.overlay.show = function () {
|
||||
if (!UI.overlay.elem.classList.contains('active')) {
|
||||
UI.overlay.elem.classList.add('active');
|
||||
}
|
||||
};
|
||||
UI.overlay.hide = function () {
|
||||
if (UI.overlay.elem.classList.contains('active')) {
|
||||
UI.overlay.elem.classList.remove('active');
|
||||
}
|
||||
};
|
||||
UI.spinner = {
|
||||
elem : document.getElementById('spinner')
|
||||
}
|
||||
UI.spinner.opts = {
|
||||
lines: 13 // The number of lines to draw
|
||||
, length: 33 // The length of each line
|
||||
, width: 11 // The line thickness
|
||||
, radius: 30 // The radius of the inner circle
|
||||
, scale: 0.5 // Scales overall size of the spinner
|
||||
, corners: 1 // Corner roundness (0..1)
|
||||
, color: '#fff' // #rgb or #rrggbb or array of colors
|
||||
, opacity: 0.25 // Opacity of the lines
|
||||
, rotate: 0 // The rotation offset
|
||||
, direction: 1 // 1: clockwise, -1: counterclockwise
|
||||
, speed: 1 // Rounds per second
|
||||
, trail: 60 // Afterglow percentage
|
||||
, fps: 20 // Frames per second when using setTimeout() as a fallback for CSS
|
||||
, zIndex: 2e9 // The z-index (defaults to 2000000000)
|
||||
, className: 'spinner' // The CSS class to assign to the spinner
|
||||
, top: '50%' // Top position relative to parent
|
||||
, left: '50%' // Left position relative to parent
|
||||
, shadow: true // Whether to render a shadow
|
||||
, hwaccel: true // Whether to use hardware acceleration
|
||||
, position: 'relative' // Element positioning
|
||||
};
|
||||
UI.spinner.init = function () {
|
||||
const spinner = new Spinner(UI.spinner.opts).spin(UI.spinner.elem);
|
||||
};
|
||||
UI.spinner.show = function (text) {
|
||||
if (!UI.spinner.elem.classList.contains('active')) {
|
||||
UI.spinner.elem.classList.add('active');
|
||||
}
|
||||
if (text) {
|
||||
UI.message.show(text)
|
||||
}
|
||||
};
|
||||
UI.spinner.hide = function () {
|
||||
if (UI.spinner.elem.classList.contains('active')) {
|
||||
UI.spinner.elem.classList.remove('active');
|
||||
}
|
||||
|
||||
};
|
||||
UI.message = {
|
||||
elem : document.getElementById('msg')
|
||||
};
|
||||
UI.message.show = function (text) {
|
||||
UI.message.elem.innerHTML = text
|
||||
if (!UI.message.elem.classList.contains('active')) {
|
||||
UI.message.elem.classList.add('active');
|
||||
}
|
||||
};
|
||||
UI.message.hide = function () {
|
||||
if (UI.message.elem.classList.contains('active')) {
|
||||
UI.message.elem.classList.remove('active');
|
||||
}
|
||||
};
|
||||
|
||||
var init = function () {
|
||||
document.querySelector('.angle').oninput = function () {
|
||||
BOLEX.angle = parseInt(this.value);
|
||||
};
|
||||
document.querySelector('.iso').oninput = function () {
|
||||
BOLEX.iso = parseInt(this.value);
|
||||
};
|
||||
document.querySelector('.fstop').oninput = function () {
|
||||
BOLEX.fstop = parseFloat(this.value);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,781 @@
|
|||
/* jshint esversion:6, strict:true, browser:true*/
|
||||
/* global console, alert */
|
||||
|
||||
|
||||
'use strict';
|
||||
var mobile = {};
|
||||
|
||||
mobile.ble = {
|
||||
BLENO_DEVICE_NAME : 'intval3',
|
||||
DEVICE_ID : 'intval3',
|
||||
SERVICE_ID : '149582bd-d49d-4b5c-acd1-1ae503d09e7a',
|
||||
CHAR_ID : '47bf69fb-f62f-4ef8-9be8-eb727a54fae4', //general data
|
||||
WIFI_ID : '3fe7d9cf-7bd2-4ff0-97c5-ebe87288c2cc', //wifi only
|
||||
devices : [],
|
||||
device : {},
|
||||
connected : false,
|
||||
active : false
|
||||
};
|
||||
|
||||
mobile.wifi = {
|
||||
current : 'null',
|
||||
available : [],
|
||||
ip : null
|
||||
};
|
||||
|
||||
mobile.ble.scan = function () {
|
||||
UI.spinner.show('Scanning for INTVAL3...');
|
||||
UI.overlay.show();
|
||||
ble.scan([], 5, mobile.ble.onDiscover, mobile.ble.onError);
|
||||
mobile.ble.devices = [];
|
||||
setTimeout(() => {
|
||||
UI.spinner.hide();
|
||||
UI.overlay.hide();
|
||||
if (!mobile.ble.connected) {
|
||||
mobile.alert('No devices found.')
|
||||
settingsPage();
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
mobile.ble.onDiscover = function (device) {
|
||||
if (device && device.name && device.name.indexOf('intval3') !== -1) {
|
||||
console.log('BLE - Discovered INTVAL3');
|
||||
console.dir(device);
|
||||
mobile.ble.devices.push(device);
|
||||
if (!mobile.ble.connected) {
|
||||
mobile.ble.connect(device);
|
||||
}
|
||||
} else {
|
||||
//console.log(`BLE - Discovered Other ${device.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
mobile.ble.connect = function (device) {
|
||||
console.log(`BLE - Connecting to ${device.id}`);
|
||||
ble.connect(device.id, (peripheral) => {
|
||||
mobile.ble.onConnect(peripheral, device);
|
||||
}, mobile.ble.onError);
|
||||
};
|
||||
|
||||
mobile.ble.onConnect = function (peripheral, device) {
|
||||
const elem = document.getElementById('bluetooth');
|
||||
const option = document.createElement('option');
|
||||
const disconnect = document.getElementById('disconnect');
|
||||
const scan = document.getElementById('scan');
|
||||
|
||||
UI.spinner.hide();
|
||||
UI.overlay.hide();
|
||||
console.log(`BLE - Connected to ${device.id}`);
|
||||
console.log(peripheral);
|
||||
console.dir(device);
|
||||
|
||||
mobile.ble.device = device;
|
||||
mobile.ble.connected = true;
|
||||
|
||||
elem.innerHTML = '';
|
||||
option.text = device.name;
|
||||
option.value = device.id;
|
||||
elem.add(option);
|
||||
|
||||
disconnect.classList.add('active');
|
||||
scan.classList.remove('active');
|
||||
|
||||
getState();
|
||||
mobile.getWifi();
|
||||
};
|
||||
|
||||
mobile.ble.disconnect = function () {
|
||||
const elem = document.getElementById('bluetooth');
|
||||
const option = document.createElement('option');
|
||||
const disconnect = document.getElementById('disconnect');
|
||||
const scan = document.getElementById('scan');
|
||||
let device;
|
||||
if (!mobile.ble.connected) {
|
||||
console.warn('Not connected to any device');
|
||||
return false;
|
||||
}
|
||||
device = mobile.ble.device;
|
||||
console.log(`BLE - Disconnecting from ${device.id}`);
|
||||
ble.disconnect(device.id, mobile.ble.onDisconnect, mobile.ble.onDisconnect);
|
||||
|
||||
elem.innerHTML = '';
|
||||
option.text = 'N/A';
|
||||
elem.add(option);
|
||||
|
||||
disconnect.classList.remove('active');
|
||||
scan.classList.add('active');
|
||||
UI.spinner.hide();
|
||||
UI.overlay.hide();
|
||||
};
|
||||
|
||||
mobile.ble.onDisconnect = function (res) {
|
||||
console.log(`BLE - Disconnected from ${res}`);
|
||||
mobile.ble.connected = false;
|
||||
mobile.ble.device = {};
|
||||
};
|
||||
|
||||
mobile.ble.onError = function (err) {
|
||||
if (err.errorMessage && err.errorMessage === 'Peripheral Disconnected') {
|
||||
console.log('Device disconnected');
|
||||
mobile.ble.disconnect()
|
||||
} else {
|
||||
mobile.alert(JSON.stringify(err));
|
||||
}
|
||||
/*
|
||||
Object
|
||||
errorDescription: "The specified device has disconnected from us."
|
||||
errorMessage: "Peripheral Disconnected"
|
||||
id: "E8EF4B8B-0B5E-4E96-B337-E878DB1E3C4B"
|
||||
name: "intval3_b827ebc7461d"
|
||||
*/
|
||||
};
|
||||
|
||||
mobile.init = function () {
|
||||
const bleInputs = document.querySelectorAll('.ble');
|
||||
const bolIso = document.querySelector('.iso');
|
||||
const bolF = document.querySelector('.fstop');
|
||||
|
||||
document.querySelector('body').classList.add('mobile');
|
||||
|
||||
window.frame = mobile.frame;
|
||||
window.getState = mobile.getState;
|
||||
window.setDir = mobile.setDir;
|
||||
window.setExposure = mobile.setExposure;
|
||||
window.setDelay = mobile.setDelay;
|
||||
window.setCounter = mobile.setCounter;
|
||||
window.sequence = mobile.sequence;
|
||||
window.reset = mobile.reset;
|
||||
window.restart = mobile.restart;
|
||||
window.update = mobile.update;
|
||||
|
||||
//show ble-specific fields in settings
|
||||
for (let i of bleInputs) {
|
||||
i.classList.add('active');
|
||||
}
|
||||
UI.spinner.init()
|
||||
mobile.ble.scan();
|
||||
mobile.cameraValues();
|
||||
|
||||
};
|
||||
|
||||
mobile.getState = function () {
|
||||
if (!mobile.ble.connected) {
|
||||
//returning here will prevent error alert
|
||||
}
|
||||
ble.read(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
mobile.stateSuccess,
|
||||
mobile.ble.onError);
|
||||
};
|
||||
mobile.stateSuccess = function (data) {
|
||||
let str = bytesToString(data);
|
||||
let res = JSON.parse(str);
|
||||
setState(res);
|
||||
};
|
||||
|
||||
mobile.frame = function () {
|
||||
const opts = {
|
||||
type : 'frame'
|
||||
};
|
||||
if (!mobile.ble.connected) {
|
||||
return mobile.alert('Not connected to an INTVAL3 device.');
|
||||
}
|
||||
if (mobile.ble.active) {
|
||||
return false;
|
||||
}
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)), //check length?
|
||||
mobile.frameSuccess,
|
||||
mobile.ble.onError);
|
||||
document.getElementById('frame').classList.add('focus');
|
||||
mobile.ble.active = true;
|
||||
};
|
||||
|
||||
|
||||
mobile.frameSuccess = function () {
|
||||
if (STATE.exposure < 5000) {
|
||||
console.log('Frame finished, getting state.');
|
||||
mobile.ble.active = false;
|
||||
document.getElementById('frame').classList.remove('focus');
|
||||
mobile.getState();
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
console.log('Frame finished, getting state.');
|
||||
mobile.ble.active = false;
|
||||
document.getElementById('frame').classList.remove('focus');
|
||||
mobile.getState();
|
||||
}, STATE.exposure + 500)
|
||||
}
|
||||
}
|
||||
mobile.setDir = function () {
|
||||
const opts = {
|
||||
type : 'dir',
|
||||
dir : !document.getElementById('dir').checked
|
||||
};
|
||||
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)), //check length?
|
||||
mobile.dirSuccess,
|
||||
mobile.ble.onError);
|
||||
};
|
||||
mobile.dirSuccess = function () {
|
||||
console.log('Set direction');
|
||||
mobile.getState();
|
||||
setTimeout(() => {
|
||||
setDirLabel(STATE.dir);
|
||||
}, 50);
|
||||
};
|
||||
mobile.setExposure = function () {
|
||||
let exposure = document.getElementById('exposure').value;
|
||||
let scaledExposure;
|
||||
let opts = {
|
||||
type : 'exposure'
|
||||
};
|
||||
if (exposure === '' || exposure === null) {
|
||||
exposure = 0;
|
||||
}
|
||||
scaledExposure = scaleTime(exposure, STATE.scale);
|
||||
opts.exposure = scaledExposure;
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)), //check length?
|
||||
mobile.exposureSuccess,
|
||||
mobile.ble.onError);
|
||||
};
|
||||
mobile.exposureSuccess = function () {
|
||||
console.log('Set exposure');
|
||||
mobile.getState();
|
||||
};
|
||||
|
||||
mobile.setDelay = function () {
|
||||
const delay = document.getElementById('delay').value;
|
||||
const scaledDelay = scaleTime(delay, STATE.delayScale);
|
||||
let opts = {
|
||||
type : 'delay',
|
||||
delay : scaledDelay
|
||||
};
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)), //check length?
|
||||
mobile.delaySuccess,
|
||||
mobile.ble.onError);
|
||||
}
|
||||
|
||||
mobile.delaySuccess = function () {
|
||||
console.log('Set delay');
|
||||
mobile.getState();
|
||||
};
|
||||
|
||||
mobile.setCounter = function () {
|
||||
let opts = {
|
||||
type : 'counter',
|
||||
counter : null
|
||||
};
|
||||
const counter = document.getElementById('counter').value;
|
||||
function counterPrompt (results) {
|
||||
let change = results.input1
|
||||
if (results.buttonIndex === 1) {
|
||||
if (change === null || !isNumeric(change)) return false;
|
||||
opts.counter = change;
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)), //check length?
|
||||
mobile.counterSuccess,
|
||||
mobile.ble.onError);
|
||||
}
|
||||
}
|
||||
navigator.notification.prompt(
|
||||
`Change counter value?`,
|
||||
counterPrompt,
|
||||
'INTVAL3',
|
||||
['Okay', 'Cancel'],
|
||||
counter);
|
||||
};
|
||||
|
||||
mobile.counterSuccess = function () {
|
||||
console.log('Set counter');
|
||||
mobile.getState();
|
||||
};
|
||||
|
||||
mobile.sequence = function () {
|
||||
const opts = {
|
||||
type : 'sequence'
|
||||
};
|
||||
const elem = document.getElementById('seq');
|
||||
if (!mobile.ble.connected) {
|
||||
return mobile.alert('Not connected to an INTVAL3 device.');
|
||||
}
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)), //check length?
|
||||
mobile.sequenceSuccess,
|
||||
mobile.ble.onError);
|
||||
|
||||
if (!elem.classList.contains('focus')) {
|
||||
elem.classList.add('focus');
|
||||
}
|
||||
|
||||
mobile.ble.active = true;
|
||||
};
|
||||
|
||||
mobile.sequenceSuccess = function () {
|
||||
console.log('Sequence state changed');
|
||||
mobile.getState();
|
||||
setTimeout(() => {
|
||||
if (STATE.sequence) {
|
||||
mobile.ble.active = true;
|
||||
seqState(true);
|
||||
} else {
|
||||
mobile.ble.active = false;
|
||||
seqState(false);
|
||||
}
|
||||
}, 20);
|
||||
};
|
||||
|
||||
|
||||
//retreive object with list of available Wifi APs,
|
||||
//and state of current connection, if available
|
||||
mobile.getWifi = function () {
|
||||
UI.spinner.show('Refreshing WIFI...');
|
||||
UI.overlay.show();
|
||||
|
||||
ble.read(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.WIFI_ID,
|
||||
mobile.getWifiSuccess,
|
||||
mobile.ble.onError);
|
||||
};
|
||||
|
||||
mobile.getWifiSuccess = function (data) {
|
||||
const elem = document.getElementById('available');
|
||||
const wifi = document.getElementById('wifi');
|
||||
const password = document.getElementById('password');
|
||||
const ip = document.getElementById('ip');
|
||||
let option = document.createElement('option');
|
||||
let str = bytesToString(data);
|
||||
let res = JSON.parse(str);
|
||||
|
||||
UI.spinner.hide();
|
||||
UI.overlay.hide();
|
||||
elem.innerHTML = ''
|
||||
if (!res.available || res.available.length === 0) {
|
||||
if (elem.classList.contains('active')) {
|
||||
elem.classList.remove('active');
|
||||
}
|
||||
option.text = 'N/A'
|
||||
elem.add(option);
|
||||
elem.value = '';
|
||||
} else {
|
||||
for (let ap of res.available) {
|
||||
option = document.createElement('option');
|
||||
option.text = ap;
|
||||
option.value = ap;
|
||||
elem.add(option);
|
||||
}
|
||||
if (res.current && res.available.indexOf(res.current) !== -1) {
|
||||
elem.value = res.current
|
||||
if (!elem.classList.contains('active')) {
|
||||
elem.classList.add('active');
|
||||
}
|
||||
if (wifi.classList.contains('active')) {
|
||||
wifi.classList.remove('active');
|
||||
}
|
||||
if (password.classList.contains('active')) {
|
||||
password.classList.remove('active');
|
||||
}
|
||||
} else {
|
||||
if (!wifi.classList.contains('active')) {
|
||||
wifi.classList.add('active');
|
||||
}
|
||||
if (!password.classList.contains('active')) {
|
||||
password.classList.add('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof res.ip !== 'undefined' && res.ip != null ) {
|
||||
ip.innerHTML = `Local IP: <span onclick="window.open('http://${res.ip}', '_system', 'location=yes');">${res.ip}</span>`
|
||||
if (!ip.classList.contains('active')) {
|
||||
ip.classList.add('active');
|
||||
}
|
||||
} else {
|
||||
ip.innerHTML = 'Local IP: null'
|
||||
if (ip.classList.contains('active')) {
|
||||
ip.classList.remove('active');
|
||||
}
|
||||
}
|
||||
mobile.wifi.current = res.current;
|
||||
mobile.wifi.available = res.available;
|
||||
mobile.wifi.ip = res.ip;
|
||||
};
|
||||
|
||||
mobile.editWifi = function () {
|
||||
const available = document.getElementById('available');
|
||||
const wifi = document.getElementById('wifi');
|
||||
const password = document.getElementById('password');
|
||||
if (!wifi.classList.contains('active')) {
|
||||
wifi.classList.add('active');
|
||||
}
|
||||
if (!password.classList.contains('active')) {
|
||||
password.classList.add('active');
|
||||
}
|
||||
password.focus();
|
||||
if (available.value !== mobile.wifi.current && available.classList.contains('active')) {
|
||||
available.classList.remove('active');
|
||||
}
|
||||
};
|
||||
|
||||
mobile.setWifi = function () {
|
||||
const ssid = document.getElementById('available').value;
|
||||
const pwd = document.getElementById('password').value;
|
||||
const opts = {
|
||||
ssid : ssid,
|
||||
pwd : pwd
|
||||
};
|
||||
UI.spinner.show('Setting WIFI...');
|
||||
UI.overlay.show();
|
||||
|
||||
if (ssid === '' || ssid === null || ssid === undefined) {
|
||||
return mobile.alert('Cannot set wireless credentials with a blank SSID');
|
||||
}
|
||||
if (pwd === '' || pwd === null || pwd === undefined) {
|
||||
return mobile.alert('Cannot set wireless credentials with a blank passphrase');
|
||||
}
|
||||
if (pwd.length < 8 || pwd.length > 63) {
|
||||
return mobile.alert('Passphrase must be 8..63 characters');
|
||||
}
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.WIFI_ID,
|
||||
stringToBytes(JSON.stringify(opts)),
|
||||
mobile.setWifiSuccess,
|
||||
mobile.ble.onError);
|
||||
};
|
||||
|
||||
mobile.setWifiSuccess = function () {
|
||||
UI.spinner.hide();
|
||||
UI.overlay.hide();
|
||||
console.log('Set new wifi credentials');
|
||||
setTimeout(mobile.getWifi, 100);
|
||||
};
|
||||
mobile.exif = {}
|
||||
|
||||
mobile.getCamera = function () {
|
||||
const opts = {
|
||||
quality: 30,
|
||||
sourceType: Camera.PictureSourceType.CAMERA,
|
||||
destinationType: Camera.DestinationType.FILE_URI
|
||||
};
|
||||
navigator.camera.getPicture(mobile.cameraSuccess, mobile.cameraError, opts);
|
||||
};
|
||||
mobile.cameraSuccess = function (result) {
|
||||
const thisResult = JSON.parse(result);
|
||||
const metadata = JSON.parse(thisResult.json_metadata);
|
||||
|
||||
mobile.cameraExposure(metadata.Exif);
|
||||
};
|
||||
mobile.cameraError = function (err) {
|
||||
console.error(err);
|
||||
mobile.alert(JSON.stringify(err));
|
||||
};
|
||||
|
||||
mobile.cameraExposure = function (exif) {
|
||||
const cam_exp = document.getElementById('cam_exp');
|
||||
const cam_f = document.getElementById('cam_f');
|
||||
const cam_iso = document.getElementById('cam_iso');
|
||||
const bol_exp = document.getElementById('bol_exp');
|
||||
const bol_f = document.getElementById('bol_f');
|
||||
const bol_iso = document.getElementById('bol_iso');
|
||||
const bol_f_diff = document.getElementById('bol_f_diff');
|
||||
const bol_iso_diff = document.getElementById('bol_iso_diff');
|
||||
const bol_exp_diff = document.getElementById('bol_exp_diff');
|
||||
|
||||
const fstop = BOLEX.fstop || 5.6;
|
||||
const iso = BOLEX.iso || 100;
|
||||
const prism = BOLEX.prism || 0.8;
|
||||
|
||||
const cFstop = exif.ApertureValue || exif.FNumber;
|
||||
const cExposure = exif.ExposureTime * 1000;
|
||||
const cIso = exif.ISOSpeedRatings[0];
|
||||
|
||||
//convert fstop to "fnumber", an absolute scale where stops are scaled to 1.0
|
||||
const f = fnumber(cFstop);
|
||||
const target = fnumber(fstop); //bolex
|
||||
|
||||
let exposure = cExposure;
|
||||
let isoStops = 0;
|
||||
let fStops = 0;
|
||||
let expDiff;
|
||||
|
||||
let scale_elem;
|
||||
let exposure_elem;
|
||||
|
||||
let proceed;
|
||||
let e1;
|
||||
let e2;
|
||||
|
||||
mobile.exif = exif;
|
||||
|
||||
//Determine if fstop of phone camera "f"
|
||||
if (target !== f) {
|
||||
fStops = f - target;
|
||||
exposure = exposure / Math.pow(2, fStops);
|
||||
}
|
||||
|
||||
if (cIso != iso) {
|
||||
isoStops = (Math.log(cIso) / Math.log(2)) - (Math.log(iso) / Math.log(2));
|
||||
}
|
||||
|
||||
//Double or halve exposure based on the differences in ISO stops
|
||||
exposure = exposure * Math.pow(2, isoStops);
|
||||
|
||||
//Compensate for Bolex prism
|
||||
exposure = exposure * Math.pow(2, prism);
|
||||
|
||||
exposure = Math.round(exposure) //round to nearest millisecond
|
||||
|
||||
bol_f.value = fstop;
|
||||
bol_iso.value = iso;
|
||||
bol_exp.value = exposure;
|
||||
|
||||
//Total difference in exposure from phone camera to Bolex
|
||||
expDiff = (Math.log(exposure) / Math.log(2)) - (Math.log(cExposure) / Math.log(2));
|
||||
|
||||
bol_exp_diff.innerHTML = floatDisplay(expDiff);
|
||||
bol_iso_diff.innerHTML = floatDisplay(isoStops);
|
||||
bol_f_diff.innerHTML = floatDisplay(-fStops);
|
||||
|
||||
cam_exp.value = cExposure;
|
||||
cam_f.value = cFstop;
|
||||
cam_iso.value = cIso;
|
||||
|
||||
function exposureConfirm (index) {
|
||||
if (index === 1) {
|
||||
e1 = new Event('change');
|
||||
e2 = new Event('change');
|
||||
|
||||
scale_elem = document.getElementById('scale');
|
||||
exposure_elem = document.getElementById('exposure');
|
||||
|
||||
scale_elem.value = 'ms';
|
||||
scale_elem.dispatchEvent(e1);
|
||||
|
||||
exposure_elem.value = exposure;
|
||||
exposure_elem.dispatchEvent(e2);
|
||||
}
|
||||
}
|
||||
|
||||
if (exposure > 500) {
|
||||
navigator.notification.confirm(
|
||||
`Set camera exposure to ${exposure}ms to match photo?`,
|
||||
exposureConfirm,
|
||||
'INTVAL3',
|
||||
['Okay', 'Cancel']
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"Exif": {
|
||||
"DateTimeOriginal": "2018:02:02 16:59:13",
|
||||
"ExposureBiasValue": 0,
|
||||
"SensingMethod": 2,
|
||||
"BrightnessValue": -0.9969016228800144,
|
||||
"LensMake": "Apple",
|
||||
"FNumber": 1.8,
|
||||
"FocalLength": 3.99,
|
||||
"ShutterSpeedValue": 2.049355412374274,
|
||||
"SceneType": 1,
|
||||
"ApertureValue": 1.6959938131099002,
|
||||
"SubjectArea": [
|
||||
2015,
|
||||
1511,
|
||||
2217,
|
||||
1330
|
||||
],
|
||||
"ColorSpace": 65535,
|
||||
"LensSpecification": [
|
||||
3.99,
|
||||
3.99,
|
||||
1.8,
|
||||
1.8
|
||||
],
|
||||
"PixelYDimension": 3024,
|
||||
"WhiteBalance": 0,
|
||||
"DateTimeDigitized": "2018:02:02 16:59:13",
|
||||
"ExposureMode": 0,
|
||||
"ISOSpeedRatings": [
|
||||
100
|
||||
],
|
||||
"PixelXDimension": 4032,
|
||||
"LensModel": "iPhone 8 back camera 3.99mm f/1.8",
|
||||
"ExposureTime": 0.25,
|
||||
"Flash": 24,
|
||||
"SubsecTimeDigitized": "209",
|
||||
"SubsecTimeOriginal": "209",
|
||||
"ExposureProgram": 2,
|
||||
"FocalLenIn35mmFilm": 28,
|
||||
"MeteringMode": 5
|
||||
}
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
mobile.refreshExposure = function () {
|
||||
if (typeof mobile.exif.ExposureTime !== 'undefined') {
|
||||
mobile.cameraExposure(mobile.exif);
|
||||
}
|
||||
};
|
||||
|
||||
mobile.EV = function (fstop, shutter) {
|
||||
const sec = shutter / 1000; //shutter in ms => seconds
|
||||
const square = Math.pow(fstop, 2);
|
||||
return Math.log(square / sec);
|
||||
};
|
||||
|
||||
mobile.reset = function () {
|
||||
let opts = {
|
||||
type : 'reset'
|
||||
};
|
||||
function resetConfirm (index) {
|
||||
if (index === 1) {
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)),
|
||||
mobile.resetSuccess,
|
||||
mobile.ble.onError);
|
||||
}
|
||||
}
|
||||
navigator.notification.confirm(
|
||||
`Reset INTVAL3 to default settings and clear counter?`,
|
||||
resetConfirm,
|
||||
'INTVAL3',
|
||||
['Okay', 'Cancel']
|
||||
);
|
||||
};
|
||||
|
||||
mobile.resetSuccess = function () {
|
||||
console.log('Reset to default settings');
|
||||
setTimeout(() => {
|
||||
mobile.getState();
|
||||
}, 100)
|
||||
};
|
||||
|
||||
mobile.update = function () {
|
||||
let opts = {
|
||||
type : 'update'
|
||||
};
|
||||
function updateConfirm (index) {
|
||||
if (index === 1) {
|
||||
UI.spinner.show('Updating INTVAL3...');
|
||||
UI.overlay.show();
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)),
|
||||
mobile.updateSuccess,
|
||||
mobile.ble.onError);
|
||||
}
|
||||
}
|
||||
navigator.notification.confirm(
|
||||
`Check for updates? You will be disconnected from the INTVAL3 during this process.`,
|
||||
updateConfirm,
|
||||
'INTVAL3',
|
||||
['Okay', 'Cancel']
|
||||
);
|
||||
};
|
||||
|
||||
mobile.updateSuccess = function () {
|
||||
console.log('Finished updating firmware, restarting...');
|
||||
};
|
||||
|
||||
mobile.restart = function () {
|
||||
let opts = {
|
||||
type : 'restart'
|
||||
};
|
||||
function restartConfirm (index) {
|
||||
if (index === 1) {
|
||||
UI.spinner.show('Restarting INTVAL3...');
|
||||
UI.overlay.show();
|
||||
ble.write(mobile.ble.device.id,
|
||||
mobile.ble.SERVICE_ID,
|
||||
mobile.ble.CHAR_ID,
|
||||
stringToBytes(JSON.stringify(opts)),
|
||||
mobile.restartSuccess,
|
||||
mobile.ble.onError);
|
||||
}
|
||||
}
|
||||
navigator.notification.confirm(
|
||||
`Restart the INTVAL3? You will be disconnected from it during this process.`,
|
||||
restartConfirm,
|
||||
'INTVAL3',
|
||||
['Okay', 'Cancel']
|
||||
);
|
||||
};
|
||||
mobile.restartSuccess = function () {
|
||||
console.log('Restarting... ');
|
||||
}
|
||||
|
||||
mobile.alert = function (msg) {
|
||||
if (navigator && navigator.notification) {
|
||||
navigator.notification.alert(
|
||||
msg,
|
||||
() => {},
|
||||
'INTVAL3',
|
||||
'Okay'
|
||||
);
|
||||
} else {
|
||||
alert(msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Mobile helper functions
|
||||
*/
|
||||
|
||||
function bytesToString (buffer) {
|
||||
return String.fromCharCode.apply(null, new Uint8Array(buffer));
|
||||
}
|
||||
|
||||
function stringToBytes(string) {
|
||||
var array = new Uint8Array(string.length);
|
||||
for (var i = 0, l = string.length; i < l; i++) {
|
||||
array[i] = string.charCodeAt(i);
|
||||
}
|
||||
return array.buffer;
|
||||
}
|
||||
|
||||
function fnumber (fstop) {
|
||||
return Math.log(fstop) / Math.log(Math.sqrt(2));
|
||||
}
|
||||
|
||||
function floatDisplay (value) {
|
||||
let str = value + '';
|
||||
const period = str.indexOf('.');
|
||||
if (period === -1) {
|
||||
str = str + '.0';
|
||||
} else {
|
||||
str = roundTenth(value) + '';
|
||||
}
|
||||
if (value < 0) {
|
||||
str = `<span class="neg">${(str + '')}</span>`;
|
||||
} else if (value > 0) {
|
||||
str = `<span class="pos">+${(str + '')}</span>`;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function roundTenth (value) {
|
||||
return Math.round((value * 10) / 10)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
'use strict'
|
||||
const editor = {}
|
||||
|
||||
editor.init = function () {
|
||||
const elem = document.getElementById('mscript_editor');
|
||||
editor.cm = CodeMirror.fromTextArea(elem, {
|
||||
lineNumbers: true,
|
||||
theme : 'monokai',
|
||||
gutters: ['CodeMirror-linenumbers']
|
||||
});
|
||||
setTimeout(() => {
|
||||
editor.cm.setValue('CF');
|
||||
editor.cm.refresh();
|
||||
}, 10);
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', event => {
|
||||
editor.init();
|
||||
});
|
|
@ -0,0 +1,232 @@
|
|||
'use strict'
|
||||
const web = {};
|
||||
web._header = new Headers({ 'content-type' : 'application/json' })
|
||||
web.frame = function () {
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header
|
||||
};
|
||||
fetch('/frame', opts)
|
||||
.then(res => {
|
||||
return res.json()
|
||||
})
|
||||
.then(web.frameSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error triggering frame')
|
||||
console.error(err)
|
||||
});
|
||||
}
|
||||
web.frameSuccess = function (res) {
|
||||
document.getElementById('frame').blur();
|
||||
console.log(`Frame ${res.dir ? 'forward' : 'backward'} took ${res.len}ms`)
|
||||
if (res.dir === true) {
|
||||
incCounter(1);
|
||||
} else {
|
||||
incCounter(-1);
|
||||
}
|
||||
};
|
||||
web.setDir = function () {
|
||||
const dir = !document.getElementById('dir').checked;
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({ dir : dir })
|
||||
};
|
||||
fetch('/dir', opts)
|
||||
.then(res => {
|
||||
return res.json()
|
||||
})
|
||||
.then(web.setDirSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error setting direction')
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
web.setDirSuccess = function (res) {
|
||||
STATE.dir = res.dir;
|
||||
setDirLabel(res.dir);
|
||||
console.log(`setDir to ${res.dir}`);
|
||||
};
|
||||
web.getState = function () {
|
||||
const opts = {
|
||||
method : 'GET'
|
||||
}
|
||||
fetch('/status', opts)
|
||||
.then(res => {
|
||||
return res.json();
|
||||
})
|
||||
.then(setState)
|
||||
.catch(err => {
|
||||
console.error('Error getting state');
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
web.setExposure = function () {
|
||||
let exposure = document.getElementById('exposure').value;
|
||||
let scaledExposure;
|
||||
let opts
|
||||
if (exposure === '' || exposure === null) {
|
||||
exposure = 0;
|
||||
}
|
||||
scaledExposure = scaleTime(exposure, STATE.scale);
|
||||
opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({ exposure : scaledExposure })
|
||||
}
|
||||
fetch('/exposure', opts)
|
||||
.then(web.useJson)
|
||||
.then(web.setExposureSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error setting exposure');
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
web.setExposureSuccess = function (res) {
|
||||
let exposure;
|
||||
if (res.exposure < BOLEX.expected) {
|
||||
res.exposure = BOLEX.expected;
|
||||
}
|
||||
STATE.exposure = res.exposure;
|
||||
exposure = shutter(STATE.exposure);
|
||||
document.getElementById('str').innerHTML = exposure.str;
|
||||
console.log(`setExposure to ${res.exposure}`);
|
||||
};
|
||||
web.setDelay = function () {
|
||||
const delay = document.getElementById('delay').value;
|
||||
const scaledDelay = scaleTime(delay, STATE.delayScale)
|
||||
let opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({ delay : scaledDelay })
|
||||
}
|
||||
fetch('/delay', opts)
|
||||
.then(web.useJson)
|
||||
.then(web.setDelaySuccess)
|
||||
.catch(err => {
|
||||
console.error('Error setting delay');
|
||||
console.error(err);
|
||||
})
|
||||
};
|
||||
web.setDelaySuccess = function (res) {
|
||||
STATE.delay = res.delay;
|
||||
console.log(`setDelay to ${res.delay}`);
|
||||
};
|
||||
web.setCounter = function () {
|
||||
const counter = document.getElementById('counter').value;
|
||||
const change = prompt(`Change counter value?`, counter);
|
||||
if (change === null || !isNumeric(change)) return false;
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({ counter : change })
|
||||
}
|
||||
fetch('/counter', opts)
|
||||
.then(web.useJson)
|
||||
.then(web.setCounterSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error setting counter');
|
||||
console.error(err);
|
||||
})
|
||||
};
|
||||
web.setCounterSuccess = function (res) {
|
||||
STATE.counter = res.counter;
|
||||
forceCounter(res.counter);
|
||||
console.log(`setCounter to ${res.counter}`);
|
||||
};
|
||||
web.sequence = function () {
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({})
|
||||
}
|
||||
fetch('/sequence', opts)
|
||||
.then(web.useJson)
|
||||
.then(web.sequenceSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error getting /sequence');
|
||||
console.error(err);
|
||||
})
|
||||
};
|
||||
web.sequenceSuccess = function (res) {
|
||||
if (res.started && res.started != false) {
|
||||
STATE.sequence = true;
|
||||
document.getElementById('seq').focus();
|
||||
seqState(true);
|
||||
} else if (res.stopped) {
|
||||
STATE.sequence = false;
|
||||
document.getElementById('seq').blur();
|
||||
seqState(false);
|
||||
mobile.getState();
|
||||
}
|
||||
};
|
||||
web.reset = function () {
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({})
|
||||
}
|
||||
const proceed = confirm(`Reset INTVAL3 to default settings and clear counter?`);
|
||||
if (!proceed) return false
|
||||
fetch('/reset', opts)
|
||||
.then(web.useJson)
|
||||
.then(setState)
|
||||
.catch(err => {
|
||||
console.error('Error posting to /reset');
|
||||
console.error(err);
|
||||
})
|
||||
};
|
||||
web.restart = function () {
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({})
|
||||
}
|
||||
const proceed = confirm(`Restart the INTVAL3? You will be disconnected from it during this process.`);
|
||||
if (!proceed) return false;
|
||||
fetch('/restart', opts)
|
||||
.then(web.useJson)
|
||||
.then(web.restartSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error posting to /restart');
|
||||
console.error(err);
|
||||
})
|
||||
};
|
||||
web.restartSuccess = function (res) {
|
||||
console.dir(res)
|
||||
};
|
||||
web.update = function () {
|
||||
const opts = {
|
||||
method : 'POST',
|
||||
headers : web._header,
|
||||
body : JSON.stringify({})
|
||||
}
|
||||
const proceed = confirm(`Check for updates? You will be disconnected from the INTVAL3 during this process.`);
|
||||
if (!proceed) return false;
|
||||
fetch('/update', opts)
|
||||
.then(web.useJson)
|
||||
.then(web.updateSuccess)
|
||||
.catch(err => {
|
||||
console.error('Error posting to /update');
|
||||
console.error(err);
|
||||
})
|
||||
};
|
||||
web.updateSuccess = function (res) {
|
||||
console.dir(res)
|
||||
};
|
||||
web.useJson = function (res) {
|
||||
return res.json();
|
||||
};
|
||||
web.init = function () {
|
||||
window.frame = web.frame;
|
||||
window.getState = web.getState;
|
||||
window.setDir = web.setDir;
|
||||
window.setDelay = web.setDelay;
|
||||
window.setExposure = web.setExposure;
|
||||
window.setCounter = web.setCounter;
|
||||
window.sequence = web.sequence;
|
||||
window.reset = web.reset;
|
||||
window.restart = web.restart;
|
||||
window.update = web.update;
|
||||
console.log('started web')
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
// http://spin.js.org/#v2.3.2
|
||||
!function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d<l.length;d++)if(c=l[d]+b,void 0!==e[c])return c}function e(a,b){for(var c in b)a.style[d(a,c)||c]=b[c];return a}function f(a){for(var b=1;b<arguments.length;b++){var c=arguments[b];for(var d in c)void 0===a[d]&&(a[d]=c[d])}return a}function g(a,b){return"string"==typeof a?a:a[b%a.length]}function h(a){this.opts=f(a||{},h.defaults,n)}function i(){function c(b,c){return a("<"+b+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d<e.childNodes.length&&(e=e.childNodes[b+d],e=e&&e.firstChild,e=e&&e.firstChild,e&&(e.opacity=c))}}var j,k,l=["webkit","Moz","ms","O"],m={},n={lines:12,length:7,width:5,radius:10,scale:1,corners:1,color:"#000",opacity:.25,rotate:0,direction:1,speed:1,trail:100,fps:20,zIndex:2e9,className:"spinner",top:"50%",left:"50%",shadow:!1,hwaccel:!1,position:"absolute"};if(h.defaults={},f(h.prototype,{spin:function(b){this.stop();var c=this,d=c.opts,f=c.el=a(null,{className:d.className});if(e(f,{position:d.position,width:0,zIndex:d.zIndex,left:d.left,top:d.top}),b&&b.insertBefore(f,b.firstChild||null),f.setAttribute("role","progressbar"),c.lines(f,c.opts),!j){var g,h=0,i=(d.lines-1)*(1-d.direction)/2,k=d.fps,l=k/d.speed,m=(1-d.opacity)/(l*d.trail/100),n=l/d.lines;!function o(){h++;for(var a=0;a<d.lines;a++)g=Math.max(1-(h+(d.lines-a)*n)%l*m,d.opacity),c.opacity(f,a*d.direction+i,g,d);c.timeout=c.el&&setTimeout(o,~~(1e3/k))}()}return c},stop:function(){var a=this.el;return a&&(clearTimeout(this.timeout),a.parentNode&&a.parentNode.removeChild(a),this.el=void 0),this},lines:function(d,f){function h(b,c){return e(a(),{position:"absolute",width:f.scale*(f.length+f.width)+"px",height:f.scale*f.width+"px",background:b,boxShadow:c,transformOrigin:"left",transform:"rotate("+~~(360/f.lines*k+f.rotate)+"deg) translate("+f.scale*f.radius+"px,0)",borderRadius:(f.corners*f.scale*f.width>>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k<f.lines;k++)i=e(a(),{position:"absolute",top:1+~(f.scale*f.width/2)+"px",transform:f.hwaccel?"translate3d(0,0,0)":"",opacity:f.opacity,animation:j&&c(f.opacity,f.trail,l+k*f.direction,f.lines)+" "+1/f.speed+"s linear infinite"}),f.shadow&&b(i,e(h("#000","0 0 4px #000"),{top:"2px"})),b(d,b(i,h(g(f.color,k),"0 0 1px rgba(0,0,0,.1)")));return d},opacity:function(a,b,c){b<a.childNodes.length&&(a.childNodes[b].style.opacity=c)}}),"undefined"!=typeof document){k=function(){var c=a("style",{type:"text/css"});return b(document.getElementsByTagName("head")[0],c),c.sheet||c.styleSheet}();var o=e(a("group"),{behavior:"url(#default#VML)"});!d(o,"transform")&&o.adj?i():j=d(o,"animation")}return h});
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>INTVAL3 client tests</title>
|
||||
<link href="../static/css/qunit-2.5.0.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
<script src="../static/js/qunit-2.5.0.js"></script>
|
||||
<script src="../static/js/intval.core.js"></script>
|
||||
<script src="./tests.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,3 @@
|
|||
QUnit.test('hello world', function (assert) {
|
||||
assert.ok(true, 'this is ok')
|
||||
})
|
|
@ -0,0 +1,24 @@
|
|||
# interfaces(5) file used by ifup(8) and ifdown(8)
|
||||
|
||||
# Please note that this file is written to be used with dhcpcd
|
||||
# For static IP, consult /etc/dhcpcd.conf and 'man dhcpcd.conf'
|
||||
|
||||
# Include files from /etc/network/interfaces.d:
|
||||
source-directory /etc/network/interfaces.d
|
||||
|
||||
auto lo
|
||||
iface lo inet loopback
|
||||
|
||||
iface eth0 inet manual
|
||||
|
||||
auto wlan0
|
||||
allow-hotplug wlan0
|
||||
iface wlan0 inet manual
|
||||
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
wireless-power off
|
||||
|
||||
auto wlan1
|
||||
allow-hotplug wlan1
|
||||
iface wlan1 inet manual
|
||||
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
wireless-power off
|
|
@ -0,0 +1,28 @@
|
|||
#blootstrap nginx conf
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:6699/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
#gzip on;
|
||||
#gzip_comp_level 5;
|
||||
#gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
|
||||
}
|
||||
#uncomment for static file server
|
||||
location /static/ {
|
||||
#uncomment to turn on caching
|
||||
#expires modified 1y;
|
||||
#access_log off;
|
||||
#add_header Cache-Control "public";
|
||||
#gzip on;
|
||||
#gzip_comp_level 5;
|
||||
#gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
|
||||
#use project location
|
||||
alias /home/pi/intval3/app/www/static/;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Starting in dev mode"
|
||||
|
||||
rm run_dev.sh
|
||||
jq -r ".apps[0].env | keys[]" ./process.json | while read key ; do
|
||||
echo -n "$key=\"">> run_dev.sh
|
||||
echo -n "$(jq ".apps[0].env.$key" ./process.json)" >> run_dev.sh
|
||||
echo -n "\" ">> run_dev.sh
|
||||
done
|
||||
echo -n " node ." >> run_dev.sh
|
||||
|
||||
#cat run_dev.sh
|
||||
sh run_dev.sh
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
libs="./lib/*"
|
||||
for l in $libs
|
||||
do
|
||||
echo "Generating documentation for $l"
|
||||
./node_modules/.bin/jsdoc2md $l/index.js > $l/Readme.md
|
||||
done
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"dir" : false }' http://localhost:6699/dir
|
|
@ -0,0 +1,118 @@
|
|||
'use strict'
|
||||
|
||||
const Gpio = require('onoff').Gpio
|
||||
|
||||
let release
|
||||
let micro
|
||||
let fwd
|
||||
let bwd
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
if (fwd && fwd.writeSync) {
|
||||
console.log(`Setting fwd to 0`)
|
||||
fwd.writeSync(0)
|
||||
}
|
||||
if (bwd && bwd.writeSync) {
|
||||
console.log(`Setting bwd to 0`)
|
||||
bwd.writeSync(0)
|
||||
}
|
||||
process.exit()
|
||||
})
|
||||
|
||||
function releaseTest () {
|
||||
const PIN = 6
|
||||
release = Gpio(PIN, 'in', 'both')
|
||||
console.log(`Watching input on GPIO 0${PIN}`)
|
||||
let saveTime = 0
|
||||
let active = false
|
||||
release.watch((err, val) => {
|
||||
const NOW = +new Date()
|
||||
/* Button + 10K ohm resistor */
|
||||
/* 1 = open */
|
||||
/* 0 = closed */
|
||||
if (err) {
|
||||
return console.error(err)
|
||||
}
|
||||
//console.log(`Release switch val: ${val}`)
|
||||
//console.log(`RELEASE: ${val} ${active} ${NOW} ${saveTime}`)
|
||||
if (val === 0) {
|
||||
//console.log('closed')
|
||||
} else if (val === 1) {
|
||||
//console.log('open')
|
||||
}
|
||||
if (val === 0) {
|
||||
//closed
|
||||
if ((!active && saveTime === 0) || (active && NOW - saveTime > 10 * 1000)) {
|
||||
saveTime = NOW
|
||||
active = true //maybe unncecessary
|
||||
} else {
|
||||
//saveTime = 0
|
||||
//active = false
|
||||
}
|
||||
} else if (val === 1) {
|
||||
//open
|
||||
if (active) {
|
||||
if (NOW - saveTime > 50 && NOW - saveTime < 1000) {
|
||||
console.log('Started Frame')
|
||||
} else if (NOW - saveTime >= 1000) {
|
||||
console.log('Started Sequence')
|
||||
}
|
||||
//console.log(`Release closed for ${NOW - saveTime}`)
|
||||
saveTime = 0
|
||||
active = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function microTest () {
|
||||
const PIN = 5
|
||||
micro = Gpio(PIN, 'in', 'both')
|
||||
console.log(`Watching input on GPIO 0${PIN}`)
|
||||
let saveTime = 0
|
||||
let frameActive = true //this._state.frame.active
|
||||
let primed = false //this._state.primed
|
||||
micro.watch((err, val) => {
|
||||
const NOW = +new Date()
|
||||
if (err) {
|
||||
return console.error(err)
|
||||
}
|
||||
console.log(`Micro switch val: ${val}`)
|
||||
if (val === 0) {
|
||||
//console.log('closed')
|
||||
} else if (val === 1) {
|
||||
//console.log('open')
|
||||
}
|
||||
if (val === 0 && frameActive) {
|
||||
if (!primed) {
|
||||
primed = true
|
||||
saveTime = NOW
|
||||
console.log('Primed')
|
||||
}
|
||||
} else if (val === 1 && frameActive) {
|
||||
if (primed) {
|
||||
primed = false
|
||||
setTimeout( () => {
|
||||
console.log(`Stop Frame after ${NOW - saveTime}`)
|
||||
}, 10)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//test stepping up of 3.3V RPI logic via
|
||||
//Sparkfun PRT-10968 (NPC1402)
|
||||
function stepupTest () {
|
||||
const FWD = 13 // RPIO PIN 13
|
||||
const BWD = 19
|
||||
fwd = Gpio(FWD, 'out')
|
||||
bwd = Gpio(BWD, 'out')
|
||||
|
||||
console.log(`Setting pin ${BWD} high`)
|
||||
fwd.writeSync(0)
|
||||
bwd.writeSync(1)
|
||||
}
|
||||
|
||||
releaseTest()
|
||||
microTest()
|
||||
//stepupTest()
|
|
@ -0,0 +1,169 @@
|
|||
'use strict'
|
||||
const log = require('../lib/log')('mscript-tests')
|
||||
const mscript = require('../lib/mscript')
|
||||
//TODO: rewrite for mocha
|
||||
|
||||
const tests = function tests () {
|
||||
log.info('Running mscript tests')
|
||||
console.time('Tests took')
|
||||
|
||||
mscript.alts_unique(); //perform check only during tests
|
||||
var fail = function (script, obj) {
|
||||
log.error('...Failed :(')
|
||||
log.error('script', script)
|
||||
log.error('err', obj)
|
||||
process.exit(1)
|
||||
}
|
||||
let script =
|
||||
`CF
|
||||
PF
|
||||
CB
|
||||
PB
|
||||
BF
|
||||
BB`
|
||||
log.info('Basic function test...');
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 0
|
||||
&& obj.proj === 0
|
||||
&& obj.arr.length === 6) {
|
||||
log.info('...Passed!')
|
||||
} else {
|
||||
fail(script, obj)
|
||||
}
|
||||
})
|
||||
|
||||
script =
|
||||
`CF
|
||||
PF
|
||||
CB
|
||||
PB
|
||||
BF
|
||||
BB`
|
||||
log.info('Functions with integers test...')
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 0
|
||||
&& obj.proj === 0
|
||||
&& obj.arr.length === 6) {
|
||||
log.info('...Passed!')
|
||||
} else {
|
||||
fail(script, obj)
|
||||
}
|
||||
})
|
||||
|
||||
script =
|
||||
`CF 1000
|
||||
CB 1000
|
||||
SET PROJ 200
|
||||
PB 200`
|
||||
log.info('Basic state test...')
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 0
|
||||
&& obj.proj === 0) {
|
||||
log.info('...Passed!')
|
||||
} else {
|
||||
fail(script, obj)
|
||||
}
|
||||
})
|
||||
|
||||
script =
|
||||
`LOOP 10
|
||||
CF 3
|
||||
PF 1
|
||||
END LOOP`
|
||||
log.info('Basic loop test...')
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 30
|
||||
&& obj.proj === 10
|
||||
&& obj.arr.length === 40) {
|
||||
log.info('...Passed!')
|
||||
} else {
|
||||
fail(script, obj)
|
||||
}
|
||||
});
|
||||
|
||||
script = `LOOP 4\nLOOP 4\nPF\nBF\nEND LOOP\nEND LOOP`
|
||||
log.info('Recursive loop test...');
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 16
|
||||
&& obj.proj === 16
|
||||
&& obj.arr.length === 32) {
|
||||
log.info('...Passed!');
|
||||
} else {
|
||||
fail(script, obj);
|
||||
}
|
||||
});
|
||||
|
||||
//Lighting tests
|
||||
script = `L 255,255,255\nCF\nPF`
|
||||
log.info('Basic light test...');
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 1
|
||||
&& obj.proj === 1
|
||||
&& obj.arr.length === 2
|
||||
&& obj.light.length === 2
|
||||
&& obj.light[0] === '255,255,255'
|
||||
&& obj.light[1] === '') {
|
||||
log.info('...Passed!');
|
||||
} else {
|
||||
fail(script, obj);
|
||||
}
|
||||
});
|
||||
script = 'L 255,255,255\nCF\nPF\nBF';
|
||||
log.info('Basic black test...');
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 2
|
||||
&& obj.proj === 1
|
||||
&& obj.arr.length === 3
|
||||
&& obj.light.length === 3
|
||||
&& obj.light[0] === '255,255,255'
|
||||
&& obj.light[1] === ''
|
||||
&& obj.light[2] === mscript.black) {
|
||||
log.info('...Passed!');
|
||||
} else {
|
||||
fail(script, obj);
|
||||
}
|
||||
});
|
||||
script = 'LOOP 2\nL 1,1,1\nCF\nL 2,2,2\nCF\nEND';
|
||||
log.info('Basic light loop test...');
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 4
|
||||
&& obj.proj === 0
|
||||
&& obj.arr.length === 4
|
||||
&& obj.light.length === 4
|
||||
&& obj.light[0] === '1,1,1'
|
||||
&& obj.light[3] === '2,2,2') {
|
||||
log.info('...Passed!');
|
||||
} else {
|
||||
fail(script, obj);
|
||||
}
|
||||
});
|
||||
|
||||
//LOOP W/ CAM and PROJ
|
||||
script = 'LOOP 2\nCAM 4\nPROJ 4\nEND';
|
||||
log.info('Basic cam/proj loop test...');
|
||||
mscript.interpret(script, function (obj) {
|
||||
if (obj.success === true
|
||||
&& obj.cam === 8
|
||||
&& obj.proj === 8
|
||||
&& obj.arr.length === 16
|
||||
&& obj.light.length === 16
|
||||
&& obj.light[0] === mscript.black) {
|
||||
log.info('...Passed!');
|
||||
} else {
|
||||
fail(script, obj);
|
||||
}
|
||||
});
|
||||
|
||||
log.info('All tests completed');
|
||||
console.timeEnd('Tests took');
|
||||
}
|
||||
|
||||
tests()
|
|
@ -0,0 +1,4 @@
|
|||
'use strict'
|
||||
|
||||
const log = require('../lib/log')('wifi-tests')
|
||||
const wifi = require('../lib/wifi')
|
|
@ -0,0 +1,466 @@
|
|||
include <./modules.scad>
|
||||
include <./variables.scad>
|
||||
|
||||
module l289N_holes (r = 3/2 - .2) {
|
||||
$fn = 60;
|
||||
DISTANCE = 36.5;
|
||||
H = 50;
|
||||
translate([0, 0, 0]) cylinder(r = r, h = H * 5, center = true);
|
||||
translate([DISTANCE, 0, 0]) cylinder(r = r, h = H * 5, center = true);
|
||||
translate([DISTANCE, DISTANCE, 0]) cylinder(r = r, h = H * 5, center = true);
|
||||
translate([0, DISTANCE, 0]) cylinder(r = r, h = H * 5, center = true);
|
||||
}
|
||||
|
||||
module l289N_hole_test () {
|
||||
$fn = 40;
|
||||
difference () {
|
||||
cube([140, 40, 3], center = true);
|
||||
cylinder(r = 3/2, h = 50, center = true);
|
||||
translate([7, 0, 0]) cylinder(r = 3/2, h = 50, center = true);
|
||||
translate([7 * 2, 0, 0]) cylinder(r = 3/2 - .1, h = 50, center = true);
|
||||
translate([7 * 3, 0, 0]) cylinder(r = 3/2 - .2, h = 50, center = true);
|
||||
translate([7 * 4, 0, 0]) cylinder(r = 3/2 - .3, h = 50, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module intval_panel_laser () {
|
||||
$fn = 40;
|
||||
difference () {
|
||||
union () {
|
||||
difference () {
|
||||
translate ([0, 0, 8.5]) {
|
||||
union () {
|
||||
translate([12 - 10, , 0]) {
|
||||
rotate([0, 0, -13]) {
|
||||
rounded_cube([panel_2_x + 20 + 20, panel_2_y, 25.4/8], d = 20, center = true);
|
||||
}
|
||||
}
|
||||
//reinforces
|
||||
//translate([54, -12, -3]) rotate([0, 0, 89]) rounded_cube([110, 20, 4], 20, center = true);
|
||||
//translate([-17, 2, -3]) rotate([0, 0, 72]) rounded_cube([94, 13, 4], 13, center = true);
|
||||
}
|
||||
}
|
||||
for (i = [0 : len(xArray) - 1]) {
|
||||
bolex_pin_inner_laser(xArray[i], yArray[i]);
|
||||
}
|
||||
}
|
||||
//onetoone(26, 10, 4.5);
|
||||
//extends for onetoone
|
||||
|
||||
|
||||
}
|
||||
//onetoone(9, 14, 8.5);
|
||||
bearing_laser(54.5, 12, 6, width= 18, hole=false);
|
||||
translate([-38, -1, 0]) rotate([0, 0, -13]) l289N_holes();
|
||||
//translate ([6, -9, height + 3.5]) cylinder(r = bolt_inner, h = 50, center = true); //cover standoff hole
|
||||
//frame_counter_access(); //use the space
|
||||
m_p_access();
|
||||
remove_front();
|
||||
translate([6, 18, 0]) rotate([0, 0, -13]) cube([15, 25, 40], center=true); //motor wind key hole
|
||||
|
||||
for (i = [0 : len(mm_x) - 1]) {
|
||||
translate([mm_x[i], mm_y[i], 0]) cylinder(r = bolt_inner, h = 100, center = true);
|
||||
}
|
||||
translate([0, 0, .25]) intval_laser_panel_cover();
|
||||
translate([-35, -24, 15]) rotate([0, 0, -13]) {
|
||||
translate([58 / 2, 23 / 2, 0]) cylinder(r = 3/2 - .2, h = 30, center = true, $fn = 20);
|
||||
translate([-58 / 2, 23 / 2, 0]) cylinder(r = 3/2 - .2, h = 30, center = true, $fn = 20);
|
||||
translate([58 / 2, -23 / 2, 0]) cylinder(r = 3/2 - .2, h = 30, center = true, $fn = 20);
|
||||
translate([-58 / 2, -23 / 2, 0]) cylinder(r = 3/2 - .2, h = 30, center = true, $fn = 20);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module intval_panel_laser_debug () {
|
||||
$fn = 40;
|
||||
difference () {
|
||||
union () {
|
||||
difference () {
|
||||
translate ([0, 0, 8.5]) {
|
||||
union () {
|
||||
translate([12 - 32.5, -5 + 9, 0]) {
|
||||
rotate([0, 0, -13]) {
|
||||
rounded_cube([panel_2_x + 20 + 65, panel_2_y, 25.4/8], d = 20, center = true);
|
||||
}
|
||||
}
|
||||
//reinforces
|
||||
//translate([54, -12, -3]) rotate([0, 0, 89]) rounded_cube([110, 20, 4], 20, center = true);
|
||||
//translate([-17, 2, -3]) rotate([0, 0, 72]) rounded_cube([94, 13, 4], 13, center = true);
|
||||
}
|
||||
}
|
||||
for (i = [0 : len(xArray) - 1]) {
|
||||
bolex_pin_inner_laser(xArray[i], yArray[i]);
|
||||
}
|
||||
}
|
||||
//onetoone(26, 10, 4.5);
|
||||
//extends for onetoone
|
||||
|
||||
|
||||
}
|
||||
//onetoone(9, 14, 8.5);
|
||||
bearing_laser(54.5, 12, 6, width= 18, hole=false);
|
||||
translate([-38, -1, 0]) rotate([0, 0, -13]) l289N_holes();
|
||||
//translate ([6, -9, height + 3.5]) cylinder(r = bolt_inner, h = 50, center = true); //cover standoff hole
|
||||
//frame_counter_access(); //use the space
|
||||
m_p_access();
|
||||
remove_front();
|
||||
translate([6, 18, 0]) rotate([0, 0, -13]) cube([15, 25, 40], center=true); //motor wind key hole
|
||||
|
||||
for (i = [0 : len(mm_x) - 1]) {
|
||||
translate([mm_x[i], mm_y[i], 0]) cylinder(r = bolt_inner, h = 100, center = true);
|
||||
}
|
||||
intval_laser_panel_cover(DEBUG = true);
|
||||
translate ([4, 12, 0]) {
|
||||
translate([-51.5, -8.5, 0]) cylinder(r = 2.8/2, h = 100, center = true);
|
||||
translate([-51.5 - 66, -8.5 + 15, 0]) cylinder(r = 2.8/2, h = 100, center = true);
|
||||
translate([-51.5 + 11.5, -8 + 49, 0]) cylinder(r = 2.8/2, h = 100, center = true);
|
||||
translate([-51.5 - 54.5, -8.5 + 49 + 16, 0]) cylinder(r = 2.8/2, h = 100, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module bolex_pin_laser (x, y) {
|
||||
in = innerD;
|
||||
$fn = 120;
|
||||
translate ([x, y, 1]) {
|
||||
difference () {
|
||||
union () {
|
||||
translate([0, 0, (height / 2) - 3]) cylinder(r = (outerD + 5) / 2, h = 2, center = true);
|
||||
translate([0, 0, 1.175/2]) cylinder(r = outerD / 2, h = height + 1.175 , center = true);
|
||||
}
|
||||
cylinder(r = in / 2, h = height * 2, center = true);
|
||||
translate([0, 0, (height / 2) - 1.9]) cylinder(r1 =4.5 / 2, r2 = 6.7 / 2, h = 2, center = true);
|
||||
translate([0, 0, (height / 2) + 1]) cylinder(r = 6.7 / 2, h = 4, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module intval_laser_standoffs () {
|
||||
$fn = 40;
|
||||
for (i = [0 : len(xArray) - 1]) {
|
||||
bolex_pin_laser(xArray[i], yArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
module intval_laser_standoffs_plate () {
|
||||
$fn = 40;
|
||||
rotate ([0, 180, 0]) {
|
||||
bolex_pin_laser(0, 0);
|
||||
bolex_pin_laser(15, 0);
|
||||
bolex_pin_laser(0, 15);
|
||||
bolex_pin_laser(15, 15);
|
||||
}
|
||||
//decoys
|
||||
//translate([7, 7, 0]) decoys(23, 5.5, 6);
|
||||
}
|
||||
|
||||
module bolex_pin_inner_laser (x, y) {
|
||||
$fn = 40;
|
||||
//innerD = 6.75;
|
||||
innerD = 9;
|
||||
translate ([x, y, 1]) {
|
||||
cylinder(r = innerD / 2, h = height * 2, center = true);
|
||||
//translate([0, 0, (height / 2) - 1]) cylinder(r1 =4.5 / 2, r2 = 6.5 / 2, h = 2, center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module bearing_laser (x, y, z, width= 8, hole = true) {
|
||||
innerD = 8.05;
|
||||
outerD = 22.1 - .4;
|
||||
fuzz = 0.1;
|
||||
translate ([x, y, z]) {
|
||||
difference () {
|
||||
cylinder(r = outerD / 2 + fuzz, h = width, center = true);
|
||||
if (hole) {
|
||||
cylinder(r = innerD / 2 - fuzz, h = width, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module intval_laser_panel_cover (LASER = false, DEBUG = false, ALL_RED = false) {
|
||||
$fn = 60;
|
||||
cover_h = 16 + 3 + 4 + 10;
|
||||
MATERIAL = 25.4 / 8;
|
||||
|
||||
module top () {
|
||||
difference () {
|
||||
rotate([0, 0, -13]) {
|
||||
translate([-10, 0, 0]) rounded_cube([120, panel_2_y, MATERIAL], d = 20, center = true);
|
||||
}
|
||||
translate([53, 12, 0]) cylinder(r = 30, h = 60, center = true); //hole for motor mount
|
||||
translate([22, 20, 0]) cylinder(r = 8, h = 60, center = true); // hole for moto mount bolt holder
|
||||
translate([53, 42, 0]) cylinder(r = 15, h = 60, center = true); //removes pointy part
|
||||
|
||||
translate([-44 - 20, 8 + 5, -(cover_h / 2 ) - MATERIAL - 1]) rotate([0, 0, -13]) rotate([0, 90, 0]) back_side();
|
||||
translate([2, 49, -(cover_h / 2 ) - MATERIAL - 1]) rotate([0, 0, -13]) rotate([90, 0, 0]) top_side();
|
||||
translate([-22, -45, -(cover_h / 2 ) - MATERIAL - 1]) rotate([0, 0, -13]) rotate([90, 0, 0]) bottom_side();
|
||||
translate([xArray[0], yArray[0], 0]) cylinder(r = 7 / 2, h = height * 20, center = true); //top standoff access
|
||||
translate ([8, -9, height + 3.5]) cylinder(r = bolt_inner - .5, h = 50, center = true); //bottom mount attach
|
||||
}
|
||||
|
||||
}
|
||||
module back_side () {
|
||||
difference () {
|
||||
translate([0, 1.75, 0]) cube([cover_h + 2 + (MATERIAL * 2) + 1 + 3, panel_2_y - 10, MATERIAL], center = true);
|
||||
//top negatives (strange)
|
||||
translate([-(cover_h / 2) - (MATERIAL * 1.5), 20, 0]) cube([MATERIAL, 20, MATERIAL], center = true);
|
||||
translate([-(cover_h / 2) - (MATERIAL * 1.5), -20, 0]) cube([MATERIAL, 20, MATERIAL], center = true);
|
||||
|
||||
translate([-(cover_h / 2) - (MATERIAL * 1.5), 47, 0]) cube([MATERIAL, 10, MATERIAL], center = true);
|
||||
translate([-(cover_h / 2) - (MATERIAL * 1.5), -47, 0]) cube([MATERIAL, 10, MATERIAL], center = true);
|
||||
|
||||
//bottom negatives
|
||||
translate([(cover_h / 2) + (MATERIAL * 1.5), 20, 0]) cube([MATERIAL, 20, MATERIAL], center = true);
|
||||
translate([(cover_h / 2) + (MATERIAL * 1.5), -20 - 11, 0]) cube([MATERIAL, 35, MATERIAL], center = true);
|
||||
|
||||
translate([18 , -30 , 0]) cube([10, 15, 30], center = true); //access for microSD
|
||||
|
||||
translate([0, 50.5, 0]) cube([17.5, MATERIAL, MATERIAL], center = true);
|
||||
translate([0, -50.5 + (1.75 / 2) + MATERIAL - 0.25, 0]) cube([17.5, MATERIAL, MATERIAL], center = true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module top_side () {
|
||||
difference () {
|
||||
translate([-2.5 - 10 - 1, 0, 0]) cube([ panel_2_x - 41 + 20, cover_h + 2 + (MATERIAL * 2) + 1 + 3, MATERIAL], center = true);
|
||||
//top and bottom negatives
|
||||
translate([28 - 5, -(cover_h / 2) - (MATERIAL * 1.5), 0]) cube([35, MATERIAL, MATERIAL], center = true);
|
||||
translate([28 - 5, (cover_h / 2) + (MATERIAL * 1.5), 0]) cube([35, MATERIAL, MATERIAL], center = true);
|
||||
translate([-28 - 10 - 2, -(cover_h / 2) - (MATERIAL * 1.5), 0]) cube([30, MATERIAL, MATERIAL], center = true);
|
||||
translate([-28 - 10 - 2, (cover_h / 2) + (MATERIAL * 1.5), 0]) cube([30, MATERIAL, MATERIAL], center = true);
|
||||
//back side negatives
|
||||
translate([-35.5 - 20 - 1, -13 - 8.1, 0]) cube([MATERIAL, 25, MATERIAL], center = true); //side tabs
|
||||
translate([-35.5 - 20 - 1, 13 + 8.1, 0]) cube([MATERIAL, 25, MATERIAL], center = true); //side tabs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module bottom_side () {
|
||||
difference () {
|
||||
//main piece
|
||||
translate([.25 - 10 - 1, 0, 0]) cube([ panel_2_x - 39.5 + 20, cover_h + 2 + (MATERIAL * 2) + 1 + 3, MATERIAL], center = true);
|
||||
//top and bottom negatives
|
||||
translate([25 - 27.5, -(cover_h / 2) - (MATERIAL * 1.5), 0]) cube([35, MATERIAL, MATERIAL], center = true);
|
||||
translate([-25 - 29, -(cover_h / 2) - (MATERIAL * 1.5), 0]) cube([30, MATERIAL, MATERIAL], center = true);
|
||||
translate([30, -(cover_h / 2) - (MATERIAL * 1.5), 0]) cube([15, MATERIAL, MATERIAL], center = true);
|
||||
//
|
||||
translate([30, (cover_h / 2) + (MATERIAL * 1.5), 0]) cube([35, MATERIAL, MATERIAL], center = true);
|
||||
translate([-30 - 10 - 2, (cover_h / 2) + (MATERIAL * 1.5), 0]) cube([30, MATERIAL, MATERIAL], center = true);
|
||||
|
||||
//back side negatives
|
||||
translate([-33.5 - 20 - 1, 17.3, 0]) cube([MATERIAL, 17.5, MATERIAL], center = true);
|
||||
translate([-33.5 - 20 - 1, -17.3, 0]) cube([MATERIAL, 17.5, MATERIAL], center = true);
|
||||
|
||||
//hole for audio jack -> add countersink
|
||||
translate([7, 10, 0]) cylinder(r = 6/2, h = 50, center = true);
|
||||
//hole for female DC power jack, 12vdc
|
||||
//translate([-15 - 20, 1 + 5, 0]) cylinder(r = 8/2, h = 20, center = true); //smaller DC jack
|
||||
translate([23, 8, 0]) cylinder(r = 12/2, h = 20, center = true); //larger DC jack
|
||||
|
||||
//usb negative
|
||||
translate([0, -15, 0]) cube([30, 10, 20], center = true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (LASER) {
|
||||
projection() top();
|
||||
if (!DEBUG) {
|
||||
translate([-95, 20, 0]) rotate([0, 0, -13]) projection() back_side();
|
||||
}
|
||||
translate([20, 80, 0]) rotate([0, 0, -13]) projection() top_side();
|
||||
translate([-20, -80, 0]) rotate([0, 0, -13]) projection() bottom_side();
|
||||
} else {
|
||||
//translate([0, 0, height + cover_h]) top();
|
||||
if (!DEBUG) {
|
||||
translate([-44 - 20, 8 + 5, height + (cover_h / 2 ) - 4.25]) rotate([0, 0, -13]) rotate([0, 90, 0]) back_side();
|
||||
}
|
||||
translate([2, 49, height + (cover_h / 2 ) - 4.25]) rotate([0, 0, -13]) rotate([90, 0, 0]) top_side();
|
||||
translate([-22, -45, height + (cover_h / 2 ) - 4.25]) rotate([0, 0, -13]) rotate([90, 0, 0]) bottom_side();
|
||||
}
|
||||
}
|
||||
|
||||
module intval_laser_panel_cover_standoff (DECOYS = false) {
|
||||
tight = 0.2;
|
||||
cover_h = 21;
|
||||
$fn = 40;
|
||||
translate ([6, -9, height + 3.5]) {
|
||||
difference() {
|
||||
cylinder(r = bolt_inner + 1.4, h = cover_h - .5, center = true);
|
||||
cylinder(r = bolt_inner - tight, h = cover_h, center = true);
|
||||
}
|
||||
if (DECOYS) {
|
||||
decoys(12, -(cover_h / 2) + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module remove_front () {
|
||||
|
||||
translate([87, 0, 4]) rotate([0, 0, 89]) cube([170, 40, 40], center = true);
|
||||
}
|
||||
module onetoone (size, height, z) {
|
||||
translate ([one_to_one_x, one_to_one_y, z]) {
|
||||
cylinder(r = size / 2, h = height, center = true);
|
||||
}
|
||||
}
|
||||
module m_p_access () {
|
||||
translate ([18, -44, 0]) {
|
||||
rounded_cube([35, 17, 50], 17, true);
|
||||
}
|
||||
}
|
||||
module bolex_pin (x, y) {
|
||||
in = innerD;
|
||||
translate ([x, y, 1]) {
|
||||
difference () {
|
||||
union () {
|
||||
translate([0, 0, (height / 2) - 2]) cylinder(r = (outerD + 4) / 2, h = 4, center = true);
|
||||
cylinder(r = outerD / 2, h = height, center = true);
|
||||
}
|
||||
cylinder(r = in / 2, h = height, center = true);
|
||||
translate([0, 0, (height / 2) - 1]) cylinder(r1 =4.5 / 2, r2 = 6.5 / 2, h = 2, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
module bolex_pin_inner (x, y) {
|
||||
translate ([x, y, 1]) {
|
||||
cylinder(r = innerD / 2, h = height * 2, center = true);
|
||||
translate([0, 0, (height / 2) - 1]) cylinder(r1 =4.5 / 2, r2 = 6.5 / 2, h = 2, center = true);
|
||||
}
|
||||
}
|
||||
module intval_pins () {
|
||||
for (i = [0 : len(xArray) - 1]) {
|
||||
bolex_pin(xArray[i], yArray[i]);
|
||||
}
|
||||
}
|
||||
module key () {
|
||||
tighten = 0.25;
|
||||
difference () {
|
||||
cylinder(r = 6.7 / 2, h = 5, center = true);
|
||||
cylinder(r = (4.76 -+ tighten) / 2, h = 5, center = true);
|
||||
}
|
||||
translate ([0, 0, -7.5]) {
|
||||
cylinder(r = 6.7 / 2, h = 10, center = true);
|
||||
}
|
||||
}
|
||||
module keyHole () {
|
||||
translate ([0, 0, 1.75]) {
|
||||
cube([10, 2, 3.5], center = true);
|
||||
}
|
||||
}
|
||||
module key_end (rotArr = [0, 0, 0], transArr = [0, 0, 0], ALT = false) {
|
||||
translate(transArr) {
|
||||
rotate (rotArr) {
|
||||
difference () {
|
||||
key();
|
||||
keyHole();
|
||||
if (ALT) {
|
||||
translate([-2.5, 0, 1.75]) cube([5, 3, 3.5], center= true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
module frame_counter_access () {
|
||||
x = 37.5;
|
||||
y = 39;
|
||||
translate([x, y, 8.5]) {
|
||||
difference () {
|
||||
union () {
|
||||
rotate ([0, 0, 19]) {
|
||||
translate([0, 9, 0]) {
|
||||
cube([12, 16, 4], center = true);
|
||||
}
|
||||
}
|
||||
rotate ([0, 0, -19]) {
|
||||
translate([0, 9, 0]) {
|
||||
cube([12, 16, 4], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
translate([0, 15.5, 0]) {
|
||||
cube([17, 6, 4], center = true);
|
||||
}
|
||||
}
|
||||
cylinder(r = 6.2, h = 4, center = true);
|
||||
}
|
||||
}
|
||||
module bearing (x, y, z, width= 8, hole = true, calval = 0) {
|
||||
innerD = 8.05;
|
||||
outerD = 22.1;
|
||||
fuzz = 0.1;
|
||||
translate ([x, y, z]) {
|
||||
difference () {
|
||||
cylinder(r = outerD / 2 + fuzz + calval, h = width, center = true);
|
||||
if (hole) {
|
||||
cylinder(r = innerD / 2 - fuzz, h = width, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module key_cap () {
|
||||
$fn = 60;
|
||||
thickness = .75;
|
||||
innerD = 22.1;
|
||||
outerD = innerD + (thickness * 2);
|
||||
h = 18 - 2.5;
|
||||
|
||||
difference () {
|
||||
cylinder(r = outerD / 2, h = h, center = true);
|
||||
translate([0, 0, -1.01]) cylinder(r = innerD / 2, h = h - thickness, center = true);
|
||||
//translate([100, 0, 0]) cube([200, 200, 200], center = true);
|
||||
}
|
||||
//decoys(23, 7);
|
||||
}
|
||||
|
||||
module motor_cap_120 (HALF = false) {
|
||||
$fn = 60;
|
||||
base_d = 47;
|
||||
base_inner = 29;
|
||||
inner_h = 57;
|
||||
difference () {
|
||||
union () {
|
||||
translate([-6, 0, 24]) cylinder(r = base_d/2, h = 15, center = true);
|
||||
translate([0, 0, inner_h]) cylinder(r=(base_inner / 2) + 3, h=inner_h, center = true);
|
||||
}
|
||||
translate([-6, 0, -5.75]) cylinder(r = base_d/2 - 1, h = 50, center = true); //to grip edge of
|
||||
translate([-6, 0, 3]) cylinder(r = base_d/2 - 3, h = 50, center = true);
|
||||
translate([-25, 0, 19]) cube([10, 10, 15], center = true); //wire access
|
||||
|
||||
//120 motor
|
||||
|
||||
translate([0, 0, inner_h - 2]) cylinder(r=base_inner / 2, h=inner_h, center = true); //inner cylinder
|
||||
|
||||
if (HALF){
|
||||
translate([100, 0, 0]) cube([200, 200, 200], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module bearing_calibrate (val = 0) {
|
||||
mat = 25.4/8;
|
||||
difference () {
|
||||
cube([40, 40, mat], center = true);
|
||||
bearing(0, 0, 0, hole = false, calval = val);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
//rpi zero w
|
||||
translate([-35, -24, 15]) rotate([0, 0, -13]) {
|
||||
difference () {
|
||||
cube([67, 31, 3], center = true);
|
||||
translate([58 / 2, 23 / 2, 0]) cylinder(r = 1.6, h = 3 + 1, center = true, $fn = 20);
|
||||
translate([-58 / 2, 23 / 2, 0]) cylinder(r = 1.6, h = 3 + 1, center = true, $fn = 20);
|
||||
translate([58 / 2, -23 / 2, 0]) cylinder(r = 1.6, h = 3 + 1, center = true, $fn = 20);
|
||||
translate([-58 / 2, -23 / 2, 0]) cylinder(r = 1.6, h = 3 + 1, center = true, $fn = 20);
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,58 @@
|
|||
include <./case.scad>
|
||||
include <./mount.scad>
|
||||
include <./plunger.scad>
|
||||
/*
|
||||
|
||||
INTVAL 3
|
||||
|
||||
*/
|
||||
|
||||
module stl_plate () {
|
||||
//translate([0, 0, -0.5]) cube([150, 150, 1], center = true);
|
||||
translate([-38, 41, 7.5]) rotate([0, 180, 0]) intval_laser_standoffs_plate();
|
||||
translate([-27, 40, -9.5]) rotate([0, 0, 13]) translate([-40 + 2, -1, 14]) rotate([0, 0, -13]) l289N_mount();
|
||||
|
||||
translate([23, 1, -5.75]) rotate([0, 0, 90]) motor_mount_bottom();
|
||||
translate([48, -13, 9]) rotate([0, 180, 0]) key_cap();
|
||||
translate([-5, -11, 3]) rotate([0, 0, 190]) geared_motor_mount_120();
|
||||
translate([65, 44, 22.5]) rotate([0, 180, 0]) motor_key();
|
||||
translate([0, -42, 15]) plunger_plate();
|
||||
translate([-52, -20, 66]) rotate([0, 180, 0]) motor_cap(false);
|
||||
};
|
||||
|
||||
module dxf_plate () {
|
||||
translate([125, 0, 0]) rotate([0, 0, 13]) projection() intval_panel_laser();
|
||||
rotate([0, 0, 13]) intval_laser_panel_cover(LASER=true, ALL_RED=true);
|
||||
};
|
||||
|
||||
module exploded_view () {
|
||||
intval_panel_laser();
|
||||
translate([0, 0, 5]) translate([-40 + 2, -1, 14]) rotate([0, 0, -13]) l289N_mount();
|
||||
translate([0, 0, 5]) motor_mount_bottom();
|
||||
translate([0, 0, 20]) motor_key_120();
|
||||
translate([one_to_one_x, one_to_one_y, 50]) geared_motor_mount_120();
|
||||
translate([one_to_one_x, one_to_one_y, 50]) motor_cap_120(false);
|
||||
translate([0, 0, 60]) intval_laser_panel_cover(false, ALL_RED=true);
|
||||
}
|
||||
|
||||
|
||||
//bolex_pin_laser(0, 0);
|
||||
//intval_laser_standoffs_plate();
|
||||
//intval_electronics_mount("METRO");
|
||||
//motor_mount_bottom();
|
||||
//projection () intval_panel_laser();
|
||||
//intval_laser_panel_cover(true, ALL_RED=true);
|
||||
//rotate([0, 0, 13]) intval_panel_laser();
|
||||
//rotate([0, 0, 13]) intval_laser_panel_cover();
|
||||
key_cap();
|
||||
//geared_motor_mount_120();
|
||||
//motor_key();
|
||||
//motor_key_120();
|
||||
//plunger_plate();
|
||||
//motor_cap(false);
|
||||
//motor_cap_120(false);
|
||||
//translate([0, 0, 39 / 2 + 5.75]) bolt_guide();
|
||||
|
||||
//exploded_view();
|
||||
//stl_plate();
|
||||
//dxf_plate();
|
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 188 KiB |
|
@ -0,0 +1,213 @@
|
|||
module tube(o = 1, i = 0, h = 1, center = false, $fn = 12) {
|
||||
$fn = $fn;
|
||||
union () {
|
||||
difference () {
|
||||
cylinder(r = o, h = h, center = center);
|
||||
cylinder(r = i, h = h, center = center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module rounded_cube (cube_arr = [1, 1, 1], d = 0, center = false) {
|
||||
off_x = 0;
|
||||
off_y = 0;
|
||||
r = d/2;
|
||||
union () {
|
||||
cube([cube_arr[0] - d, cube_arr[1], cube_arr[2]], center = center);
|
||||
cube([cube_arr[0], cube_arr[1] - d, cube_arr[2]], center = center);
|
||||
translate ([1 * (cube_arr[0] / 2) - r , 1 * (cube_arr[1] / 2)- r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||
translate ([-1 * (cube_arr[0] / 2) + r, -1 * (cube_arr[1] / 2) + r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||
translate ([1 * (cube_arr[0] / 2) - r, -1 * (cube_arr[1] / 2) + r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||
translate ([-1 * (cube_arr[0] / 2) + r, 1 * (cube_arr[1] / 2)- r, 0]) cylinder(r = r, h = cube_arr[2], center = center);
|
||||
}
|
||||
}
|
||||
|
||||
module c_battery () {
|
||||
/* C Cell battery, 26.1 × 50 */
|
||||
x = 26.1;
|
||||
x_fuzz = .3;
|
||||
y = 50;
|
||||
y_fuzz = 2;
|
||||
cylinder(r = (x + x_fuzz) / 2, h = y + y_fuzz, center = true);
|
||||
}
|
||||
|
||||
module sub_c_battery () {
|
||||
/* Sub C Cell battery, 22.2 × 42.9 */
|
||||
x = 22.2;
|
||||
x_fuzz = .3;
|
||||
y = 42.9;
|
||||
y_fuzz = 2;
|
||||
cylinder(r = (x + x_fuzz) / 2, h = y + y_fuzz, center = true);
|
||||
}
|
||||
|
||||
module hex (r = 1, h = 1, center = false) {
|
||||
cylinder(r = r, h = h, center = center, $fn = 6);
|
||||
}
|
||||
|
||||
module triangle (a = 1, b = 1, c = 1, h = 1, center = false) {
|
||||
|
||||
}
|
||||
|
||||
module cone_45 (d = 1, center = false) {
|
||||
cylinder(r1 = d/2, r2 = 0, h = d, center = center);
|
||||
}
|
||||
|
||||
module decoys (d = 10, z = 0, number = 4, cube_size = 4, debug = false) {
|
||||
for (i = [0: number]) {
|
||||
rotate([0, 0, (360/number) * i]) translate([d, 0, z]) cube([cube_size, cube_size, cube_size], center = true);
|
||||
if (debug && i == 0) {
|
||||
rotate([0, 0, (360/number) * i]) translate([d, 0, z]) cube([cube_size * 5, cube_size* 5, cube_size], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Paraboloid module for OpenScad
|
||||
//
|
||||
// Copyright (C) 2013 Lochner, Juergen
|
||||
// http://www.thingiverse.com/Ablapo/designs
|
||||
//
|
||||
// This program is free software. It is
|
||||
// licensed under the Attribution - Creative Commons license.
|
||||
// http://creativecommons.org/licenses/by/3.0/
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
module paraboloid (y=10, f=5, rfa=0, fc=1, detail=44){
|
||||
// y = height of paraboloid
|
||||
// f = focus distance
|
||||
// fc : 1 = center paraboloid in focus point(x=0, y=f); 0 = center paraboloid on top (x=0, y=0)
|
||||
// rfa = radius of the focus area : 0 = point focus
|
||||
// detail = $fn of cone
|
||||
|
||||
hi = (y+2*f)/sqrt(2); // height and radius of the cone -> alpha = 45° -> sin(45°)=1/sqrt(2)
|
||||
x =2*f*sqrt(y/f); // x = half size of parabola
|
||||
|
||||
translate([0,0,-f*fc]) // center on focus
|
||||
rotate_extrude(convexity = 10,$fn=detail ) // extrude paraboild
|
||||
translate([rfa,0,0]) // translate for fokus area
|
||||
difference(){
|
||||
union(){ // adding square for focal area
|
||||
projection(cut = true) // reduce from 3D cone to 2D parabola
|
||||
translate([0,0,f*2]) rotate([45,0,0]) // rotate cone 45° and translate for cutting
|
||||
translate([0,0,-hi/2])cylinder(h= hi, r1=hi, r2=0, center=true, $fn=detail); // center cone on tip
|
||||
translate([-(rfa+x ),0]) square ([rfa+x , y ]); // focal area square
|
||||
}
|
||||
translate([-(2*rfa+x ), -1/2]) square ([rfa+x ,y +1] ); // cut of half at rotation center
|
||||
}
|
||||
}
|
||||
|
||||
//Spiral Notes
|
||||
//-------------------------------------------------------------------
|
||||
//Height = center to center height of the end spheres which form the spirals. Ends will need to be flattened by the user as desired. Actual height of the rendering is Height+2*baseRadius
|
||||
//Radius = the maximum distance from the axis of the spiral (the z axis) to the center of the sphere(s) forming the spiral
|
||||
//baseRadius = cross sectional radius of the spiral
|
||||
//frequency = the number of complete revolutions about the axis made by the spiral, whole numbers will result in spirals whose tops end directly above their bases
|
||||
//resolution = integer number of spheres, not to be confused with $fn. The greater the number of spheres, the smoother the spiral will be (also longer render times!). Recommended that this number be 8*frequency or greater.
|
||||
//numSpirals = integer number of spirals used in the spiralMulti modules spaced evenly around the axis (3 spirals are spaced 120 degrees apart, 4 spirals: 90 degrees apart, etc.)
|
||||
|
||||
//Instructions
|
||||
//------------------------------------------------------------------
|
||||
//1. Place spiral.scad in the "libraries" folder of your openscad installation. Find the libraries folder by File -> Show Library Folder...
|
||||
//2. Then create a new or open one of your existing scad files and include spiral.scad with the following code:
|
||||
//use<spiral.scad>;
|
||||
//3. Then call the modules in your files with code similar to the following:
|
||||
//spiral(20,20,3,1,25);
|
||||
//spiralCone(20,20,3,1,25);
|
||||
//spiralEllipse(20,20,3,1,25);
|
||||
//spiralMulti(20,20,3,1,25,3);
|
||||
//spiralMultiCone(20,20,3,1,25,3);
|
||||
//spiralMultiEllipse(40,60,3,1,32,3);
|
||||
|
||||
//-------------------------------------------------------------
|
||||
//simple spiral
|
||||
module spiral (height = 20, Radius = 20, baseRadius = 3, frequency = 1, resolution = 25, $fn=50) {
|
||||
union(){
|
||||
translate ([0,0,-(height/2)]) {
|
||||
for(i=[0:resolution-2]){
|
||||
hull(){
|
||||
rotate ([0,0,frequency*360/(resolution-1)*i]) translate ([Radius,0,i*height/(resolution-1)]) sphere(r=baseRadius, center=true);
|
||||
rotate ([0,0,frequency*360/(resolution-1)*(i+1)]) translate ([Radius,0,(i+1)*height/(resolution-1)]) sphere(r=baseRadius,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//cone spiral
|
||||
module spiralCone(height=20,Radius=20,baseRadius=3,frequency=1,resolution=25, $fn=50) {
|
||||
union(){
|
||||
translate ([0,0,-(height/2)]) {
|
||||
for(i=[0:resolution-2]){
|
||||
hull(){
|
||||
rotate ([0,0,frequency*360/(resolution-1)*i]) translate ([Radius-(i-1)*Radius/resolution,0,i*height/(resolution-1)]) sphere(r=baseRadius, center=true);
|
||||
rotate ([0,0,frequency*360/(resolution-1)*(i+1)]) translate ([Radius-i*Radius/resolution,0,(i+1)*height/(resolution-1)]) sphere(r=baseRadius,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//ellipse spiral
|
||||
module spiralEllipse(height=20,Radius=20,baseRadius=3,frequency=1,resolution=25, $fn=50) {
|
||||
union(){
|
||||
translate ([0,0,-(height/2)]) {
|
||||
for(i=[0:resolution-2]){
|
||||
hull(){
|
||||
rotate ([0,0,frequency*360/(resolution-1)*i]) translate ([Radius*sqrt(1-(i/(resolution-1)*(i/(resolution-1)))),0,i*height/(resolution-1)]) sphere(r=baseRadius, center=true);
|
||||
rotate ([0,0,frequency*360/(resolution-1)*(i+1)]) translate ([Radius*sqrt(1-((i+1)/(resolution-1)*((i+1)/(resolution-1)))),0,(i+1)*height/(resolution-1)]) sphere(r=baseRadius,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple spirals arranged radially around the axis
|
||||
module spiralMulti(height=20,Radius=20,baseRadius=3,frequency=1,resolution=25,numSpirals=3,$fn=50) {
|
||||
shiftAngle=360/numSpirals;
|
||||
for(total=[0:numSpirals-1]) {
|
||||
union(){
|
||||
translate ([0,0,-(height/2)]) {
|
||||
for(i=[0:resolution-2]){
|
||||
hull(){
|
||||
rotate ([0,0,frequency*360/(resolution-1)*i+shiftAngle*total]) translate ([Radius,0,i*height/(resolution-1)]) sphere(r=baseRadius, center=true);
|
||||
rotate ([0,0,frequency*360/(resolution-1)*(i+1)+shiftAngle*total]) translate ([Radius,0,(i+1)*height/(resolution-1)]) sphere(r=baseRadius,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple spirals arranged radially around the axis tapering in towards the axis
|
||||
module spiralMultiCone(height=20,Radius=20,baseRadius=3,frequency=1,resolution=25,numSpirals=3,$fn=50) {
|
||||
shiftAngle=360/numSpirals;
|
||||
for(total=[0:numSpirals-1]) {
|
||||
union(){
|
||||
translate ([0,0,-(height/2)]) {
|
||||
for(i=[0:resolution-2]){
|
||||
hull(){
|
||||
rotate ([0,0,frequency*360/(resolution-1)*i+shiftAngle*total]) translate ([Radius-(i-1)*Radius/resolution,0,i*height/(resolution-1)]) sphere(r=baseRadius, center=true);
|
||||
rotate ([0,0,frequency*360/(resolution-1)*(i+1)+shiftAngle*total]) translate ([Radius-i*Radius/resolution,0,(i+1)*height/(resolution-1)]) sphere(r=baseRadius,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//multiple ellipse spiral
|
||||
module spiralMultiEllipse(height=20,Radius=20,baseRadius=3,frequency=1,resolution=25,numSpirals=3,$fn=50) {
|
||||
shiftAngle=360/numSpirals;
|
||||
for(total=[0:numSpirals-1]) {
|
||||
union(){
|
||||
translate ([0,0,-(height/2)]) {
|
||||
for(i=[0:resolution-2]){
|
||||
hull(){
|
||||
rotate ([0,0,frequency*360/(resolution-1)*i+shiftAngle*total]) translate ([Radius*sqrt(1-(i/(resolution-1)*(i/(resolution-1)))),0,i*height/(resolution-1)]) sphere(r=baseRadius, center=true);
|
||||
rotate ([0,0,frequency*360/(resolution-1)*(i+1)+shiftAngle*total]) translate ([Radius*sqrt(1-((i+1)/(resolution-1)*((i+1)/(resolution-1)))),0,(i+1)*height/(resolution-1)]) sphere(r=baseRadius,center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
include <./modules.scad>
|
||||
include <./variables.scad>
|
||||
|
||||
module motor_key_120 (half = false, DECOYS = false, sides = 1, ALT = false) {
|
||||
innerD = 7.85;
|
||||
outer_d = 27.5 + 2;
|
||||
notch_d = 10;
|
||||
height = 7 + 5 + 4;
|
||||
diff = 14 + 2.5 + 2;
|
||||
$fn = 60;
|
||||
difference () {
|
||||
union () {
|
||||
translate([one_to_one_x, one_to_one_y, 12.1]) cylinder(r1 = 12 / 2, r2 = 12/2 + 4, h = 5, center = true);// padding against bearing
|
||||
translate([one_to_one_x, one_to_one_y, diff + 1]) cylinder(r=outer_d/2, h= height -2, center= true, $fn=200); //large cylinder
|
||||
translate([one_to_one_x, one_to_one_y, 6]) cylinder(r=innerD/2, h= 10, center= true);
|
||||
//key_end([0, 180, 0], [one_to_one_x, one_to_one_y, -2.5]); //thicker-than-key_end cylinder for inner bearing
|
||||
key_end([0, 180, -20], [one_to_one_x, one_to_one_y, -3.5], ALT = ALT); // longer for laser cut board
|
||||
//key_end([0, 180, 0], [one_to_one_x, one_to_one_y, -4.5]); //experimental length
|
||||
}
|
||||
//1 notch
|
||||
translate([one_to_one_x, one_to_one_y, diff]) {
|
||||
translate ([-outer_d/2 - 2.5, 0, 0]) cylinder(r=notch_d/2, h= height, center= true); //notch
|
||||
}
|
||||
translate([one_to_one_x, one_to_one_y, diff]) {
|
||||
translate ([-outer_d/2 -.5, -3.5 , 0]) rotate([0, 0, 100]) cube([15, 5, height], center = true); // smooth notch
|
||||
translate ([-outer_d/2 -.5, 3.5, 0]) rotate([0, 0, -100]) cube([15, 5, height], center = true); // smooth notch
|
||||
}
|
||||
|
||||
if (sides == 2) {
|
||||
//2 notch
|
||||
translate([one_to_one_x, one_to_one_y, diff]) {
|
||||
translate ([outer_d/2 + 2.5, 0, 0]) cylinder(r=notch_d/2, h= height, center= true); //notch
|
||||
}
|
||||
translate([one_to_one_x, one_to_one_y, diff]) {
|
||||
translate ([outer_d/2 +.5, -3.5, 0]) rotate([0, 0, -100]) cube([15, 5, height], center = true); // smooth notch
|
||||
translate ([outer_d/2 +.5, 3.5, 0]) rotate([0, 0, 100]) cube([15, 5, height], center = true); // smooth notch
|
||||
}
|
||||
}
|
||||
|
||||
//slot for hobbled(?) end
|
||||
translate([one_to_one_x, one_to_one_y, 17 + 2]) {
|
||||
translate([0, 0, 6.5]) hobbled_rod_120(12);
|
||||
//translate([6.42, 0, 6 - 1.7]) motor_set_screw_120();
|
||||
translate([6.42 - .2, 0, 4.3 - 1]) rotate([0, 90, 0]) motor_set_screw_120_alt();
|
||||
translate([14, 0, 4.3 - 1]) rotate([0, 90, 0]) cylinder(r2 = 6 / 2, r1 = 5.8 / 2, h = 6, center = true); //extension
|
||||
|
||||
}
|
||||
//translate([one_to_one_x, one_to_one_y, 20.5]) cylinder(r = 11.5/2, h = 10, center = true);
|
||||
|
||||
translate([one_to_one_x, one_to_one_y, 17.5]) {
|
||||
difference() {
|
||||
//cylinder(r = 7.5/2, h = 2, center = true);
|
||||
//translate([5, 0, 0]) cube([10, 10, 10], center = true);
|
||||
}
|
||||
}
|
||||
if (half) {
|
||||
translate([one_to_one_x - 50 , one_to_one_y, -50]) cube([100, 100, 200]);
|
||||
}
|
||||
}
|
||||
// translate([one_to_one_x, one_to_one_y, 17]) translate([6.42 - .2, 0, 6 - 1.7]) rotate([0, 90, 0]) motor_set_screw_120_alt();
|
||||
if (DECOYS) {
|
||||
translate([one_to_one_x, one_to_one_y, 20.5]) decoys(24);
|
||||
}
|
||||
}
|
||||
|
||||
module motor_set_screw_120 () {
|
||||
cube([10.19, 2.95, 2.95], center = true);
|
||||
translate([(10.19 / 2) - (2.56 / 2), 0, 0]) cube([2.56, 5.8, 5.8], center = true);
|
||||
}
|
||||
|
||||
module motor_set_screw_120_alt () {
|
||||
$fn = 60;
|
||||
cylinder(r = 2.95 / 2, h = 10.19, center= true);
|
||||
translate([0, 0, (10.19 / 2) - (2.56 / 2)]) cylinder(r = 5.8 / 2, h = 2.56, center = true);
|
||||
}
|
||||
|
||||
module hobbled_rod_120 (h = 10) {
|
||||
d = 4.00;
|
||||
diff = 3.33;
|
||||
difference () {
|
||||
|
||||
cylinder(r = d/2, h = h, center = true, $fn = 60);
|
||||
translate([d/2 + ((d/2) - (d - diff)), 0, 0]) cube([d, d, h + 1], center = true);
|
||||
}
|
||||
}
|
||||
|
||||
module motor_12v () {
|
||||
motor_d = 37;
|
||||
motor_h = 63;
|
||||
end = 11.5;
|
||||
len = 17;
|
||||
cylinder(r = motor_d/2, h = motor_h, center=true);
|
||||
translate([0, 0, (motor_h / 2) + (len / 2)]) cylinder(r = end/2, h = len, center=true);
|
||||
}
|
||||
|
||||
module geared_motor_mount_120 (DECOYS = false) {
|
||||
$fn = 160;
|
||||
base_d = 45;
|
||||
base_inner = 25.2;
|
||||
base_thickness = 3;
|
||||
hole_d = 7;
|
||||
screw_d = 3.2;
|
||||
bolt_end = 5.4;
|
||||
height = 6;
|
||||
screw_distance = 17;
|
||||
difference () {
|
||||
difference () {
|
||||
translate([-6, 0, 2.5]) cylinder(r=base_d/2, h=height + 5, center = true); //outer cylinder
|
||||
//translate([-6, 0, base_thickness + 2.5]) cylinder(r=base_inner/2, h=height + 5, center = true); //inner cylinder
|
||||
translate([0, 0, base_thickness + 1.5]) cylinder(r=base_inner/2, h=height + 5, center = true); //inner cylinder
|
||||
}
|
||||
cylinder(r=hole_d/2, h=29, center = true); //center hole
|
||||
//screw holes
|
||||
translate([0, 0, 0]) {
|
||||
translate([0, screw_distance/2, 0]) cylinder(r=screw_d/2, h=29, center = true);
|
||||
translate([0, -screw_distance/2, 0]) cylinder(r=screw_d/2, h=29, center = true);
|
||||
|
||||
//bolt ends
|
||||
translate([0, screw_distance/2, -3]) cylinder(r=bolt_end/2, h=2, center = true);
|
||||
translate([0, -screw_distance/2, -3]) cylinder(r=bolt_end/2, h=2, center = true);
|
||||
}
|
||||
translate([2, 19, 0]) cylinder(r=5, h = 100, center = true); //hole for panel bolt access
|
||||
}
|
||||
//wings
|
||||
translate ([-one_to_one_x, -one_to_one_y, 0]) bolt_holder([mm_x[0], mm_y[0], 0], mm_r[0], height, mm_l[0]);
|
||||
translate ([-one_to_one_x, -one_to_one_y, 0]) bolt_holder([mm_x[1], mm_y[1], 0], mm_r[1], height, mm_l[1]);
|
||||
//translate ([-one_to_one_x, -one_to_one_y, 0]) bolt_holder([mm_x[5] , mm_y[5], 0], mm_r[5], height, mm_l[5] - 1);
|
||||
if (DECOYS) {
|
||||
translate([-7, -6, 0]) decoys(40, -1, 4);
|
||||
translate([-9, -2, 0]) rotate([0, 0, 49]) decoys(37, -1, 4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module motor_mount_bottom () {
|
||||
$fn = 60;
|
||||
mount_d = 45;
|
||||
base_d = 45;
|
||||
outer_d = 28 + 2.3 + 4;
|
||||
height = 19 + 3.5 + 4;
|
||||
bolt_h = 22.3;
|
||||
shelf_h = 6; //match to motor_mount
|
||||
screw_d = 4;
|
||||
module motor_mount_core () {
|
||||
translate ([one_to_one_x, one_to_one_y, (height / 2 ) + 5.75]) {
|
||||
difference() {
|
||||
translate([-6, 0, 0]) cylinder(r = mount_d / 2, h = height, center = true); //main block
|
||||
translate([0, 0, (height / 2) - (shelf_h / 2)]) cylinder(r = base_d / 2 + 7, h = shelf_h, center = true); //shelf for motor_mount
|
||||
cylinder(r = outer_d / 2, h = 50, center = true); //space for spinning
|
||||
translate ([-one_to_one_x, -one_to_one_y, 0]) remove_front(); //flatten side
|
||||
translate([-32, -17, -19]) cube([40, 40, 40], center= true); //hole for notch
|
||||
translate([-42, 0, -19]) rotate([0, 0, -39]) cube([40, 40, 40], center= true); //hole for notch
|
||||
translate([2.5, 19.5, 0]) cylinder(r=10/2, h = 60, center=true); // hole for panel bolt
|
||||
translate([22.5, 19.5, 0]) cube([40, 40, 60], center = true); //remove front entirely
|
||||
translate([-6.5, 0, 7.5]) {
|
||||
translate([0, screw_distance/2, 0]) sphere(r=screw_d, center = true);
|
||||
translate([0, -screw_distance/2, 0]) sphere(r=screw_d, center = true);
|
||||
}
|
||||
}
|
||||
translate ([-one_to_one_x, -one_to_one_y, 0]) bolt_holder([mm_x[0], mm_y[0], -shelf_h / 2], mm_r[0], height - shelf_h, mm_l[0], tight = 0.2); //Bottom bolt holder
|
||||
translate ([-one_to_one_x, -one_to_one_y, 0]) bolt_holder([mm_x[1] , mm_y[1], -shelf_h / 2], mm_r[1], height - shelf_h, mm_l[1], tight = 0.2); //Left bolt holder
|
||||
|
||||
translate ([-one_to_one_x, -one_to_one_y, -2]) bolt_holder([mm_x[5] , mm_y[5], -shelf_h / 2], mm_r[5], height - shelf_h - 4, mm_l[5]); //Top bolt holder
|
||||
}
|
||||
}
|
||||
module microswitch_holder () {
|
||||
difference () {
|
||||
translate([29, -1, 14]) cube([36, 65, height - shelf_h - 4], center = true);//Base shape
|
||||
translate ([25.5, -14, 15]) {
|
||||
cube([17, 28, 39.5], center = true); //rectangle hole for center
|
||||
translate([4.5, 5.6, 0]) rotate([0, 0, -23]) cube([17, 25, 39.5], center = true); //bottom right inner
|
||||
translate([-2, -18, -3.5]) cube([7, 11, 12], center = true); // hole for bottom pins
|
||||
translate([-9.5, -1, -3.5]) cube([30, 4, 12], center = true); //hole for side pin
|
||||
}
|
||||
translate ([14, 37.5, 15]) rotate([0, 0, 44]) cube([55, 30, 30], center= true); //top left outer
|
||||
translate ([one_to_one_x, one_to_one_y, 18]) {
|
||||
cylinder(r = outer_d / 2, h = 50, center = true); //space for spinning
|
||||
}
|
||||
translate ([32, 6, 15]) {
|
||||
difference () {
|
||||
translate([3, 0, 0]) rotate([0, 0, 0]) cube([20, 25, 39.5], center = true); //removes area for microswitch arm
|
||||
translate([-2, 16, 0]) rotate([0, 0, -55]) cube([20, 50, 39.5], center = true);
|
||||
}
|
||||
}
|
||||
translate ([58, -25, 15]) {
|
||||
rotate([0, 0, 75]) cube([45, 30, 30], center= true); //bottom right outer
|
||||
}
|
||||
translate([mm_x[4], mm_y[4], 0]) cylinder(r = bolt_inner, h = 100, center = true); // extra bolt hole
|
||||
translate([mm_x[1], mm_y[1], 0]) cylinder(r = 4, h = 100, center = true); //clear out top left bolt hole
|
||||
}
|
||||
}
|
||||
module panel_attachment () {
|
||||
difference () {
|
||||
union() {
|
||||
translate([0, 0, 7.75 + 3]) cylinder(r = 10/2, h = 44 - shelf_h, center = true);
|
||||
translate([3.5, 0, 0]) cube([7, 7, height - shelf_h - 4], center = true);
|
||||
}
|
||||
translate([0, 0, 25]) cylinder(r = 3.2/2, h = 50, center = true);
|
||||
}
|
||||
}
|
||||
translate([8, -9, (height - shelf_h) / 2 + 3.75]) panel_attachment();
|
||||
motor_mount_core();
|
||||
microswitch_holder();
|
||||
bolt_holder([mm_x[2], mm_y[2], ((height - shelf_h)/ 2) + 3.75], 0, height - shelf_h - 4, 6); //bottom left mount
|
||||
bolt_holder([mm_x[3], mm_y[3], ((height - shelf_h)/ 2) + 3.75], 180, height - shelf_h - 4, 6); //bottom right mount
|
||||
if (DECOYS) {
|
||||
difference () {
|
||||
translate([35, 0 , 0]) decoys(44, 8, 6);
|
||||
}
|
||||
translate([0, 0, 8]) cube([4, 4, 4], center = true);
|
||||
translate([40, 55, 8]) cube([4, 4, 4], center = true);
|
||||
}
|
||||
}
|
||||
module bolt_holder (position = [0, 0, 0], rotate_z = 0, h = 17, length = 4.5, hole = true, tight = 0) {
|
||||
bolt_r = 6;
|
||||
|
||||
translate (position) {
|
||||
difference () {
|
||||
union() {
|
||||
cylinder(r = bolt_r + 0, h = h, center = true);
|
||||
rotate([0, 0, rotate_z]) translate([length/2, 0, 0]) cube([length, bolt_r * 2, h], center=true);
|
||||
}
|
||||
if (hole) {
|
||||
cylinder(r = bolt_inner - tight, h = h + 2, center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
module microswitch (position = [0, 0, 0], rotation = [0, 0, 0]) {
|
||||
translate(position) {
|
||||
rotate(rotation) {
|
||||
cube([16, 28, 9.5], center = true);
|
||||
translate([10, 8, 0]) rotate([0, 0, -7]) cube([1, 28, 4], center = true);
|
||||
translate([8 + 7, 14 + 8, 0]) cylinder(r = 2.5, h = 4, center = true);
|
||||
translate([0, -19, 0]) cube([6, 11, 9.5], center = true);
|
||||
}
|
||||
}
|
||||
}
|
||||
module l289N_mount () {
|
||||
$fn = 60;
|
||||
DISTANCE = 36.5;
|
||||
H = 4;
|
||||
THICKNESS = 3;
|
||||
module stand () {
|
||||
difference () {
|
||||
cylinder(r1 = 4, r2 = 3, h = H, center = true);
|
||||
cylinder(r = 1.5, h = H, center = true);
|
||||
}
|
||||
}
|
||||
translate([0, 0, 0]) stand();
|
||||
translate([DISTANCE, 0, 0]) stand();
|
||||
translate([DISTANCE, DISTANCE, 0]) stand();
|
||||
translate([0, DISTANCE, 0]) stand();
|
||||
difference () {
|
||||
translate([DISTANCE/2, DISTANCE/2, -3]) rounded_cube([DISTANCE + 8, DISTANCE + 8, THICKNESS], 8, center = true); //base
|
||||
translate([DISTANCE/2, DISTANCE/2, -3]) rounded_cube([DISTANCE - 5, DISTANCE - 5, THICKNESS], 10, center = true); //base
|
||||
translate([0, 0, 0]) cylinder(r = 1.5, h = H * 5, center = true);
|
||||
translate([DISTANCE, 0, 0]) cylinder(r = 1.5, h = H * 5, center = true);
|
||||
translate([DISTANCE, DISTANCE, 0]) cylinder(r = 1.5, h = H * 5, center = true);
|
||||
translate([0, DISTANCE, 0]) cylinder(r = 1.5, h = H * 5, center = true);
|
||||
}
|
||||
}
|
||||
module pcb_mount () {
|
||||
DISTANCE_X = 41;
|
||||
DISTANCE_Y = 66;
|
||||
OUTER = 10;
|
||||
H = 8;
|
||||
|
||||
module stand () {
|
||||
difference () {
|
||||
cylinder(r1 = 5, r2 = 4, h = H, center = true);
|
||||
cylinder(r = 1.75, h = H, center = true);
|
||||
}
|
||||
}
|
||||
translate([0, 0, 0]) stand();
|
||||
translate([DISTANCE_X, 0, 0]) stand();
|
||||
translate([DISTANCE_X, DISTANCE_Y, 0]) stand();
|
||||
translate([0, DISTANCE_Y, 0]) stand();
|
||||
translate([DISTANCE_X/2, DISTANCE_Y/2, -4]) rounded_cube([DISTANCE_X + OUTER, DISTANCE_Y + OUTER, 4], OUTER, center = true);
|
||||
}
|
||||
|
||||
module bolt_guide () {
|
||||
$fn = 60;
|
||||
H = 39;
|
||||
difference () {
|
||||
union() {
|
||||
cylinder(r = 10 / 2, h = H, center = true);
|
||||
translate([0, 0, -(H / 2) + 1]) cylinder(r = 14 / 2, h = 2, center = true);
|
||||
}
|
||||
cylinder(r = 7 / 2, h = H + 1, center = true);
|
||||
translate([12, 0, -(H / 2) + 1]) cube([14, 14, 3], center = true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
include <./modules.scad>
|
||||
include <./variables.scad>
|
||||
|
||||
module plunger () {
|
||||
$fn = 60;
|
||||
FINGER = 39;
|
||||
CYL_D = 9;
|
||||
WALL = 3;
|
||||
difference () {
|
||||
union () {
|
||||
cylinder(r1 = CYL_D, r2 = CYL_D - 1, h = 30, center = true); //outer cylinder
|
||||
difference () {
|
||||
translate([0, 0, -9]) rotate([90, 0, 0]) rounded_cube([50, 12, 10], d = 5, center = true);
|
||||
translate([23, 0, 9]) rotate([90, 0, 0]) cylinder(r = FINGER/2, h = 20, center = true);
|
||||
translate([-23, 0, 9]) rotate([90, 0, 0]) cylinder(r = FINGER/2, h = 20, center = true);
|
||||
}
|
||||
}
|
||||
translate([0, 0, 2]) cylinder( r = CYL_D - WALL, h = 30, center = true); //inner cylinder
|
||||
cylinder(r = 7/2, h = 50, center = true); // button hole
|
||||
|
||||
}
|
||||
|
||||
//cylinder(r= 5, h = 50, center = true); button
|
||||
}
|
||||
|
||||
module plunger_top () {
|
||||
$fn = 60;
|
||||
CYL_D = 9;
|
||||
WALL = 3;
|
||||
|
||||
difference () {
|
||||
union () {
|
||||
cylinder(r = CYL_D - WALL - 0.015, h =6, center = true);
|
||||
translate([0, 0, 2]) cylinder (r = CYL_D - 1, h = 2, center = true);
|
||||
}
|
||||
translate([0, 0, -2]) cylinder(r = CYL_D - WALL - 0.015 - 1, h =6, center = true);
|
||||
//cylinder(r = 3/2, h = 50, center = true); // wire
|
||||
cylinder(r = 3.9/2, h = 50, center = true); //3.5mm wire
|
||||
}
|
||||
}
|
||||
|
||||
module plunger_plate () {
|
||||
translate([40, 0, -12]) rotate([180, 0, 0]) plunger_top();
|
||||
plunger();
|
||||
|
||||
//decoys
|
||||
/*translate([44,20,-13]) cube([4, 4, 4], center = true);
|
||||
translate([44,-20,-13]) cube([4, 4, 4], center = true);
|
||||
translate([-23,20,-13]) cube([4, 4, 4], center = true);
|
||||
translate([-23,-20,-13]) cube([4, 4, 4], center = true);*/
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
mm_x = [61.5, 21.5, 6, 45.5, 18, 39];
|
||||
mm_y = [-18, 21, -27.5, -27.5, 7, 39];
|
||||
mm_r = [110, -15, 0, 0, 0, -70];
|
||||
mm_l = [13, 9, 0, 0, 0, 8];
|
||||
|
||||
xArray = [-3, 57, 55, -26]; //NO MIDDLE PIN
|
||||
yArray = [38, 31, -56, -33]; //NO MIDDLE PIN
|
||||
|
||||
outerD = 9;
|
||||
innerD = 4.5;
|
||||
height = 17;
|
||||
|
||||
panel_2_x = 110;
|
||||
panel_2_y = 110;
|
||||
|
||||
one_to_one_x = 54.5;
|
||||
one_to_one_y = 12;
|
||||
|
||||
bolt_inner = 2.55;
|
||||
|
||||
screw_distance = 31;
|
605
index.js
|
@ -1,57 +1,616 @@
|
|||
'use strict'
|
||||
|
||||
const ble = require('./lib/blootstrap')
|
||||
const intval = require('./lib/intval')
|
||||
const restify = require('restify')
|
||||
const logger = require('winston')
|
||||
const log = require('./lib/log')('main')
|
||||
const fs = require('fs')
|
||||
const pin = {}
|
||||
const { exec } = require('child_process')
|
||||
|
||||
const BLE = require('./lib/ble')
|
||||
const intval = require('./lib/intval')
|
||||
const sequence = require('./lib/sequence')
|
||||
|
||||
const PACKAGE = require('./package.json')
|
||||
const PORT = process.env.PORT || 6699
|
||||
const APPNAME = PACKAGE.name
|
||||
const INDEX = fs.readFileSync('./app/www/index.html', 'utf8')
|
||||
const INDEXPATH = './app/www/index.html'
|
||||
|
||||
let app = restify.createServer({
|
||||
name: APPNAME,
|
||||
version: '0.0.1'
|
||||
version: PACKAGE.version
|
||||
})
|
||||
|
||||
let ble
|
||||
|
||||
function createServer () {
|
||||
app.get('/', index)
|
||||
app.get('/frame', rFrame)
|
||||
app.use(restify.plugins.queryParser())
|
||||
app.use(restify.plugins.bodyParser({ mapParams: false }))
|
||||
app.get( '/', index)
|
||||
app.get( '/dir', rDir)
|
||||
app.post('/dir', rDir)
|
||||
app.get( '/exposure', rExposure)
|
||||
app.post('/exposure', rExposure)
|
||||
app.get( '/delay', rDelay)
|
||||
app.post('/delay', rDelay)
|
||||
app.get( '/counter', rCounter)
|
||||
app.post('/counter', rCounter)
|
||||
app.get( '/frame', rFrame)
|
||||
app.post('/frame', rFrame)
|
||||
app.get('/sequence', () => {})
|
||||
app.post('/sequence', () => {})
|
||||
app.get('/status', rStatus)
|
||||
app.get( '/sequence', rSequence)
|
||||
app.post('/sequence', rSequence)
|
||||
|
||||
app.get( '/status', rStatus)
|
||||
app.post('/reset', rReset)
|
||||
app.post('/update', rUpdate)
|
||||
app.post('/restart', rRestart)
|
||||
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`${APPNAME} listening on port ${PORT}!`)
|
||||
log.info('server', { name : APPNAME, port : PORT })
|
||||
})
|
||||
}
|
||||
|
||||
function rFrame (req, res, next) {
|
||||
res.send({})
|
||||
function createBLE () {
|
||||
ble = new BLE(() => {
|
||||
return intval.status()
|
||||
})
|
||||
ble.on('frame', bFrame)
|
||||
ble.on('dir', bDir)
|
||||
ble.on('exposure', bExposure)
|
||||
ble.on('delay', bDelay)
|
||||
ble.on('counter', bCounter)
|
||||
ble.on('sequence', bSequence)
|
||||
|
||||
ble.on('reset', bReset)
|
||||
ble.on('update', bUpdate)
|
||||
ble.on('restart', bRestart)
|
||||
}
|
||||
|
||||
//Restify functions
|
||||
function rDir (req, res, next) {
|
||||
let dir = true
|
||||
let set = false
|
||||
if (req.query && typeof req.query.dir !== 'undefined') {
|
||||
if (typeof req.query.dir === 'string') {
|
||||
dir = (req.query.dir === 'true')
|
||||
} else {
|
||||
dir = req.query.dir
|
||||
}
|
||||
set = true
|
||||
} else if (req.body && typeof req.body.dir !== 'undefined') {
|
||||
if (typeof req.body.dir === 'string') {
|
||||
dir = (req.body.dir === 'true')
|
||||
} else {
|
||||
dir = req.body.dir
|
||||
}
|
||||
set = true
|
||||
}
|
||||
if (set) {
|
||||
intval.setDir(dir)
|
||||
} else {
|
||||
dir = intval._state.frame.dir
|
||||
}
|
||||
log.info('/dir', { method: req.method, set : set, dir : dir})
|
||||
res.send({ dir : dir })
|
||||
return next()
|
||||
}
|
||||
|
||||
function rExposure (req, res, next) {
|
||||
let exposure = 0
|
||||
let set = false
|
||||
if (req.query && typeof req.query.exposure !== 'undefined') {
|
||||
if (typeof req.query.exposure === 'string') {
|
||||
exposure = parseInt(req.query.exposure)
|
||||
} else {
|
||||
exposure = req.query.exposure
|
||||
}
|
||||
set = true
|
||||
} else if (req.body && typeof req.body.exposure !== 'undefined') {
|
||||
if (typeof req.body.exposure === 'string') {
|
||||
exposure = parseInt(req.body.exposure)
|
||||
} else {
|
||||
exposure = req.body.exposure
|
||||
}
|
||||
set = true
|
||||
}
|
||||
if (set) {
|
||||
if (exposure <= intval._frame.expected) {
|
||||
exposure = 0;
|
||||
}
|
||||
intval.setExposure(exposure)
|
||||
} else {
|
||||
exposure = intval._state.frame.exposure
|
||||
}
|
||||
log.info('/exposure', { method: req.method, set : set, exposure : exposure })
|
||||
res.send({ exposure : exposure })
|
||||
return next()
|
||||
}
|
||||
|
||||
function rDelay (req, res, next) {
|
||||
let delay = 0
|
||||
let set = false
|
||||
if (req.query && typeof req.query.delay !== 'undefined') {
|
||||
if (typeof req.query.delay === 'string') {
|
||||
delay = parseInt(req.query.delay)
|
||||
} else {
|
||||
delay = req.query.delay
|
||||
}
|
||||
set = true
|
||||
}
|
||||
if (req.body && typeof req.body.delay !== 'undefined') {
|
||||
if (typeof req.body.delay === 'string') {
|
||||
delay = parseInt(req.body.delay)
|
||||
} else {
|
||||
delay = req.body.delay
|
||||
}
|
||||
set = true
|
||||
}
|
||||
if (set) {
|
||||
intval.setDelay(delay)
|
||||
} else {
|
||||
delay = intval._state.frame.delay
|
||||
}
|
||||
log.info('/delay', { method: req.method, set : set, delay : delay })
|
||||
res.send({ delay : delay })
|
||||
return next()
|
||||
}
|
||||
|
||||
function rCounter (req, res, next) {
|
||||
let counter = 0
|
||||
let set = false
|
||||
if (req.query && typeof req.query.counter !== 'undefined') {
|
||||
if (typeof req.query.counter === 'string') {
|
||||
counter = parseInt(req.query.counter)
|
||||
} else {
|
||||
counter = req.query.counter
|
||||
}
|
||||
set = true
|
||||
}
|
||||
if (req.body && typeof req.body.counter !== 'undefined') {
|
||||
if (typeof req.body.counter !== 'string') {
|
||||
counter = parseInt(req.body.counter)
|
||||
} else {
|
||||
counter = req.body.counter
|
||||
}
|
||||
set = true
|
||||
}
|
||||
if (set) {
|
||||
intval.setCounter(counter)
|
||||
} else {
|
||||
counter = intval._state.counter
|
||||
}
|
||||
log.info('/counter', { method : req.method, set : set, counter : counter })
|
||||
res.send({ counter : counter })
|
||||
return next()
|
||||
}
|
||||
|
||||
function rFrame (req, res, next) {
|
||||
let dir = true
|
||||
let exposure = 0
|
||||
if (intval._state.frame.dir !== true) {
|
||||
dir = false
|
||||
}
|
||||
if (intval._state.frame.exposure !== 0) {
|
||||
exposure = intval._state.frame.exposure
|
||||
}
|
||||
if (req.query && typeof req.query.dir !== 'undefined') {
|
||||
if (typeof req.query.dir === 'string') {
|
||||
dir = (req.query.dir === 'true')
|
||||
} else {
|
||||
dir = req.query.dir
|
||||
}
|
||||
}
|
||||
if (req.body && typeof req.body.dir !== 'undefined') {
|
||||
if (typeof req.body.dir === 'string') {
|
||||
dir = (req.body.dir === 'true')
|
||||
} else {
|
||||
dir = req.body.dir
|
||||
}
|
||||
}
|
||||
if (req.query && typeof req.query.exposure !== 'undefined') {
|
||||
if (typeof req.query.exposure === 'string') {
|
||||
exposure = parseInt(req.query.exposure)
|
||||
} else {
|
||||
exposure = req.query.exposure
|
||||
}
|
||||
}
|
||||
if (req.body && typeof req.body.exposure !== 'undefined') {
|
||||
if (typeof req.body.exposure === 'string') {
|
||||
exposure = parseInt(req.body.exposure)
|
||||
} else {
|
||||
exposure = req.body.exposure
|
||||
}
|
||||
}
|
||||
if (req.query && typeof req.query.delay !== 'undefined') {
|
||||
if (typeof req.query.delay === 'string') {
|
||||
delay = parseInt(req.query.delay)
|
||||
} else {
|
||||
delay = req.query.delay
|
||||
}
|
||||
}
|
||||
if (req.body && typeof req.body.delay !== 'undefined') {
|
||||
if (typeof req.body.delay === 'string') {
|
||||
delay = parseInt(req.body.delay)
|
||||
} else {
|
||||
delay = req.body.delay
|
||||
}
|
||||
}
|
||||
log.info('/frame', { method : req.method, dir : dir, exposure : exposure })
|
||||
intval.frame(dir, exposure, (len) => {
|
||||
res.send({ dir : dir, len : len})
|
||||
return next()
|
||||
})
|
||||
}
|
||||
|
||||
function rStatus (req, res, next) {
|
||||
const obj = intval.status()
|
||||
res.send({})
|
||||
res.send(obj)
|
||||
return next()
|
||||
}
|
||||
|
||||
function index (req, res, next) {
|
||||
res.end(INDEX)
|
||||
return next()
|
||||
function rSequence (req, res, next) {
|
||||
let dir = true
|
||||
let exposure = 0
|
||||
let delay = 0
|
||||
|
||||
if (intval._state.frame.dir !== true) {
|
||||
dir = false
|
||||
}
|
||||
if (intval._state.frame.exposure !== 0) {
|
||||
exposure = intval._state.frame.exposure
|
||||
}
|
||||
if (intval._state.frame.delay !== 0) {
|
||||
delay = intval._state.frame.delay
|
||||
}
|
||||
|
||||
if (req.query && typeof req.query.dir !== 'undefined') {
|
||||
if (typeof req.query.dir === 'string') {
|
||||
dir = (req.query.dir === 'true')
|
||||
} else {
|
||||
dir = req.query.dir
|
||||
}
|
||||
}
|
||||
if (req.body && typeof req.body.dir !== 'undefined') {
|
||||
if (typeof req.body.dir === 'string') {
|
||||
dir = (req.body.dir === 'true')
|
||||
} else {
|
||||
dir = req.body.dir
|
||||
}
|
||||
}
|
||||
if (req.query && typeof req.query.exposure !== 'undefined') {
|
||||
if (typeof req.query.exposure === 'string') {
|
||||
exposure = parseInt(req.query.exposure)
|
||||
} else {
|
||||
exposure = req.query.exposure
|
||||
}
|
||||
}
|
||||
if (req.body && typeof req.body.exposure !== 'undefined') {
|
||||
if (typeof req.body.exposure === 'string') {
|
||||
exposure = parseInt(req.body.exposure)
|
||||
} else {
|
||||
exposure = req.body.exposure
|
||||
}
|
||||
}
|
||||
if (req.query && typeof req.query.delay !== 'undefined') {
|
||||
if (typeof req.query.delay === 'string') {
|
||||
delay = parseInt(req.query.delay)
|
||||
} else {
|
||||
delay = req.query.delay
|
||||
}
|
||||
}
|
||||
if (req.body && typeof req.body.delay!== 'undefined') {
|
||||
if (typeof req.body.delay === 'string') {
|
||||
delay = parseInt(req.body.delay)
|
||||
} else {
|
||||
delay = req.body.delay
|
||||
}
|
||||
}
|
||||
if (intval._state.sequence && sequence._state.active) {
|
||||
sequence.setStop()
|
||||
intval._state.sequence = false
|
||||
res.send({ stopped : true })
|
||||
return next()
|
||||
} else {
|
||||
console.time('sequence time')
|
||||
intval._state.sequence = true
|
||||
let seq_id = sequence.start({
|
||||
loop : [ (next) => {
|
||||
intval.frame(dir, exposure, (len) => {
|
||||
next()
|
||||
})
|
||||
}, (next) => {
|
||||
setTimeout(() => {
|
||||
next()
|
||||
}, delay)
|
||||
}]
|
||||
}, (seq) => {
|
||||
console.timeEnd('sequence time')
|
||||
})
|
||||
|
||||
if (seq_id === false) {
|
||||
res.send({ started : false })
|
||||
} else {
|
||||
res.send({ started : true , id : seq_id })
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
function rReset (req, res, next) {
|
||||
log.info(`/reset`, {time : +new Date()})
|
||||
intval.reset()
|
||||
setTimeout(() => {
|
||||
const obj = intval.status()
|
||||
res.send(obj)
|
||||
return next()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
function init () {
|
||||
createServer()
|
||||
|
||||
ble.on('data', (str) => {
|
||||
console.log(str)
|
||||
function rUpdate (req, res, next) {
|
||||
log.info(`/update`, { time : +new Date() })
|
||||
exec('sh ./scripts/update.sh', (err, stdio, stderr) => {
|
||||
if (err) {
|
||||
log.error(err)
|
||||
}
|
||||
log.info(`/update`, { git : stdio })
|
||||
res.send({ success : true, action : 'update', output : stdio })
|
||||
res.end()
|
||||
next()
|
||||
setTimeout(() => {
|
||||
process.exit(0)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
function rRestart (req, res, next) {
|
||||
log.info(`/restart`, { time : +new Date() })
|
||||
res.send({ success : true, action : 'restart' })
|
||||
res.end()
|
||||
next()
|
||||
setTimeout(() => {
|
||||
process.exit(0)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
//Ble functions
|
||||
|
||||
function bFrame (obj, cb) {
|
||||
let dir = true
|
||||
let exposure = 0
|
||||
|
||||
if (intval._state.frame.dir !== true) {
|
||||
dir = false
|
||||
}
|
||||
if (intval._state.frame.exposure !== 0) {
|
||||
exposure = intval._state.frame.exposure
|
||||
}
|
||||
if (typeof obj.dir !== 'undefined') {
|
||||
if (typeof obj.dir === 'string') {
|
||||
dir = (obj.dir === 'true')
|
||||
} else {
|
||||
dir = obj.dir
|
||||
}
|
||||
}
|
||||
if (typeof obj.exposure !== 'undefined') {
|
||||
if (typeof obj.exposure === 'string') {
|
||||
exposure = parseInt(obj.exposure)
|
||||
} else {
|
||||
exposure = obj.exposure
|
||||
}
|
||||
}
|
||||
log.info('frame', { method : 'ble', dir : dir, exposure : exposure })
|
||||
|
||||
if (exposure < 5000) {
|
||||
intval.frame(dir, exposure, (len) => {
|
||||
return cb()
|
||||
})
|
||||
} else {
|
||||
intval.frame(dir, exposure, (len) => {})
|
||||
return cb()
|
||||
}
|
||||
|
||||
//setTimeout(cb, exposure === 0 ? 630 : exposure)
|
||||
}
|
||||
|
||||
function bDir (obj, cb) {
|
||||
let dir = true
|
||||
let set = false
|
||||
if (obj.dir !== 'undefined') {
|
||||
if (typeof obj.dir === 'string') {
|
||||
dir = (obj.dir === 'true')
|
||||
} else {
|
||||
dir = obj.dir
|
||||
}
|
||||
}
|
||||
intval.setDir(dir)
|
||||
log.info('dir', { method: 'ble', dir : dir })
|
||||
cb()
|
||||
}
|
||||
|
||||
function bExposure (obj, cb) {
|
||||
let exposure = 0
|
||||
if (typeof obj.exposure !== 'undefined') {
|
||||
if (typeof obj.exposure === 'string') {
|
||||
exposure = parseInt(obj.exposure)
|
||||
} else {
|
||||
exposure = obj.exposure
|
||||
}
|
||||
}
|
||||
intval.setExposure(exposure)
|
||||
log.info('exposure', { method: 'ble', exposure : exposure })
|
||||
return cb()
|
||||
}
|
||||
|
||||
function bDelay (obj, cb) {
|
||||
let delay = 0
|
||||
let set = false
|
||||
if (typeof obj.delay !== 'undefined') {
|
||||
if (typeof obj.delay === 'string') {
|
||||
delay = parseInt(obj.delay)
|
||||
} else {
|
||||
delay = obj.delay
|
||||
}
|
||||
set = true
|
||||
}
|
||||
intval.setDelay(delay)
|
||||
log.info('delay', { method: 'ble', delay : delay })
|
||||
return cb()
|
||||
}
|
||||
|
||||
function bCounter (obj, cb) {
|
||||
let counter = 0
|
||||
if (typeof obj.counter !== 'undefined') {
|
||||
if (typeof obj.counter !== 'string') {
|
||||
counter = parseInt(obj.counter)
|
||||
} else {
|
||||
counter = obj.counter
|
||||
}
|
||||
}
|
||||
intval.setCounter(counter)
|
||||
log.info('counter', { method : 'ble', counter : counter })
|
||||
return cb()
|
||||
}
|
||||
|
||||
function bSequence (obj, cb) {
|
||||
let dir = true
|
||||
let exposure = 0
|
||||
let delay = 0
|
||||
|
||||
if (intval._state.frame.dir !== true) {
|
||||
dir = false
|
||||
}
|
||||
if (intval._state.frame.exposure !== 0) {
|
||||
exposure = intval._state.frame.exposure
|
||||
}
|
||||
if (intval._state.frame.delay !== 0) {
|
||||
delay = intval._state.frame.delay
|
||||
}
|
||||
|
||||
if (typeof obj.dir !== 'undefined') {
|
||||
if (typeof obj.dir === 'string') {
|
||||
dir = (obj.dir === 'true')
|
||||
} else {
|
||||
dir = obj.dir
|
||||
}
|
||||
}
|
||||
if (typeof obj.exposure !== 'undefined') {
|
||||
if (typeof obj.exposure === 'string') {
|
||||
exposure = parseInt(obj.exposure)
|
||||
} else {
|
||||
exposure = obj.exposure
|
||||
}
|
||||
}
|
||||
if (intval._state.sequence && sequence._state.active) {
|
||||
//should not occur with single client
|
||||
sequence.setStop()
|
||||
intval._state.sequence = false
|
||||
log.info('sequence stop', { method : 'ble' })
|
||||
return cb()
|
||||
} else {
|
||||
console.time('sequence time')
|
||||
intval._state.sequence = true
|
||||
let seq_id = sequence.start({
|
||||
loop : [ (next) => {
|
||||
intval.frame(dir, exposure, (len) => {
|
||||
next()
|
||||
})
|
||||
}, (next) => {
|
||||
setTimeout(() => {
|
||||
next()
|
||||
}, delay)
|
||||
}]
|
||||
}, (seq) => {
|
||||
console.timeEnd('sequence time')
|
||||
})
|
||||
if (seq_id !== false) {
|
||||
log.info('sequence start', { method : 'ble', id : seq_id })
|
||||
}
|
||||
return cb()
|
||||
}
|
||||
}
|
||||
|
||||
function bSequenceStop (obj, cb) {
|
||||
//
|
||||
if (intval._state.sequence && sequence._state.active) {
|
||||
sequence.setStop()
|
||||
intval._state.sequence = false
|
||||
log.info('sequence stop', { method : 'ble' })
|
||||
return cb()
|
||||
}
|
||||
}
|
||||
|
||||
function bReset (obj, cb) {
|
||||
log.info(`reset`, { method: 'ble' })
|
||||
intval.reset()
|
||||
setTimeout(cb, 10)
|
||||
}
|
||||
|
||||
function bUpdate (obj, cb) {
|
||||
log.info('update', { method : 'ble' })
|
||||
exec('sh ./scripts/update.sh', (err, stdio, stderr) => {
|
||||
if (err) {
|
||||
log.error('update', err)
|
||||
}
|
||||
log.info('update', { stdio : stdio })
|
||||
cb()
|
||||
setTimeout(() => {
|
||||
process.exit(0)
|
||||
}, 20)
|
||||
})
|
||||
}
|
||||
function bRestart (obj, cb) {
|
||||
log.info('restart', { method : 'ble' })
|
||||
cb()
|
||||
setTimeout(() => {
|
||||
process.exit(0)
|
||||
}, 20)
|
||||
}
|
||||
|
||||
function seq () {
|
||||
let dir = intval._state.frame.dir
|
||||
let exposure = intval._state.frame.exposure
|
||||
let delay = intval._state.frame.delay
|
||||
|
||||
if (intval._state.sequence && sequence._state.active) {
|
||||
log.info('sequence', { method : 'release' , stop: true })
|
||||
sequence.setStop()
|
||||
intval._state.sequence = false
|
||||
return cb()
|
||||
} else {
|
||||
console.time('sequence time')
|
||||
log.info('sequence', { method : 'release', start : true })
|
||||
intval._state.sequence = true
|
||||
sequence.start({
|
||||
loop : [ (next) => {
|
||||
intval.frame(dir, exposure, (len) => {
|
||||
next()
|
||||
})
|
||||
}, (next) => {
|
||||
setTimeout(() => {
|
||||
next()
|
||||
}, delay)
|
||||
}]
|
||||
}, (seq) => {
|
||||
console.timeEnd('sequence time')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function index (req, res, next) {
|
||||
fs.readFile(INDEXPATH, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
return next(err)
|
||||
}
|
||||
res.end(data)
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
function init () {
|
||||
intval.init()
|
||||
intval.sequence = seq
|
||||
createServer()
|
||||
createBLE()
|
||||
}
|
||||
|
||||
init()
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<a name="module_ble"></a>
|
||||
|
||||
## ble
|
||||
|
||||
* [ble](#module_ble)
|
||||
* [~BLE](#module_ble..BLE)
|
||||
* [new BLE()](#new_module_ble..BLE_new)
|
||||
* [.on(eventName, callback)](#module_ble..BLE+on)
|
||||
* [~os](#module_ble..os)
|
||||
|
||||
<a name="module_ble..BLE"></a>
|
||||
|
||||
### ble~BLE
|
||||
Class representing the bluetooth interface
|
||||
|
||||
**Kind**: inner class of [<code>ble</code>](#module_ble)
|
||||
|
||||
* [~BLE](#module_ble..BLE)
|
||||
* [new BLE()](#new_module_ble..BLE_new)
|
||||
* [.on(eventName, callback)](#module_ble..BLE+on)
|
||||
|
||||
<a name="new_module_ble..BLE_new"></a>
|
||||
|
||||
#### new BLE()
|
||||
Establishes Bluetooth Low Energy services, accessible to process through this class
|
||||
|
||||
<a name="module_ble..BLE+on"></a>
|
||||
|
||||
#### blE.on(eventName, callback)
|
||||
Binds functions to events that are triggered by BLE messages
|
||||
|
||||
**Kind**: instance method of [<code>BLE</code>](#module_ble..BLE)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| eventName | <code>string</code> | Name of the event to to bind |
|
||||
| callback | <code>function</code> | Invoked when the event is triggered |
|
||||
|
||||
<a name="module_ble..os"></a>
|
||||
|
||||
### ble~os
|
||||
Bluetooth Low Energy module
|
||||
|
||||
**Kind**: inner constant of [<code>ble</code>](#module_ble)
|
|
@ -0,0 +1,254 @@
|
|||
'use strict'
|
||||
|
||||
/** @module ble */
|
||||
/** Bluetooth Low Energy module */
|
||||
|
||||
const util = require('util')
|
||||
const os = require('os')
|
||||
|
||||
const log = require('../log')('ble')
|
||||
const wifi = require('../wifi')
|
||||
|
||||
const DEVICE_NAME = process.env.DEVICE_NAME || 'intval3'
|
||||
const SERVICE_ID = process.env.SERVICE_ID || 'intval3_ble'
|
||||
const CHAR_ID = process.env.CHAR_ID || 'intval3char'
|
||||
const WIFI_ID = process.env.WIFI_ID || 'wifichar'
|
||||
const NETWORK = os.networkInterfaces()
|
||||
const MAC = getMac() || spoofMac()
|
||||
|
||||
//Give the device a unique device name, needs to be in env
|
||||
process.env.BLENO_DEVICE_NAME += '_' + MAC
|
||||
const bleno = require('bleno')
|
||||
|
||||
|
||||
let currentWifi = 'disconnected'
|
||||
let currentAddr = null
|
||||
let getState
|
||||
|
||||
const chars = []
|
||||
|
||||
function createChar(name, uuid, prop, write, read) {
|
||||
function characteristic () {
|
||||
bleno.Characteristic.call(this, {
|
||||
uuid : uuid,
|
||||
properties: prop
|
||||
})
|
||||
}
|
||||
util.inherits(characteristic, bleno.Characteristic)
|
||||
if (prop.indexOf('read') !== -1) {
|
||||
//data, offset, withoutResponse, callback
|
||||
characteristic.prototype.onReadRequest = read
|
||||
}
|
||||
if (prop.indexOf('write') !== -1) {
|
||||
characteristic.prototype.onWriteRequest = write
|
||||
}
|
||||
chars.push(new characteristic())
|
||||
}
|
||||
|
||||
function createChars (onWrite, onRead) {
|
||||
createChar('intval3', CHAR_ID, ['read', 'write'], onWrite, onRead)
|
||||
createChar('wifi', WIFI_ID, ['read', 'write'], onWifiWrite, onWifiRead)
|
||||
}
|
||||
|
||||
function onWifiWrite (data, offset, withoutResponse, callback) {
|
||||
let result
|
||||
let utf8
|
||||
let obj
|
||||
let ssid
|
||||
let pwd
|
||||
if (offset) {
|
||||
log.warn(`Offset scenario`)
|
||||
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
||||
return callback(result)
|
||||
}
|
||||
utf8 = data.toString('utf8')
|
||||
obj = JSON.parse(utf8)
|
||||
ssid = obj.ssid
|
||||
pwd = obj.pwd
|
||||
log.info(`connecting to AP`, { ssid : ssid })
|
||||
return wifi.createPSK(ssid, pwd, (err, hash, plaintext) => {
|
||||
if (err) {
|
||||
log.error('Error hashing wifi password', err)
|
||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||
return callback(result)
|
||||
}
|
||||
return wifi.setNetwork(ssid, plaintext, hash, (err, data) => {
|
||||
if (err) {
|
||||
log.error('Error configuring wifi', err)
|
||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||
return callback(result)
|
||||
}
|
||||
currentWifi = ssid
|
||||
currentAddr = getIp()
|
||||
log.info(`Connected to AP`, { ssid : ssid, ip : currentAddr })
|
||||
result = bleno.Characteristic.RESULT_SUCCESS
|
||||
return callback(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function onWifiRead (offset, callback) {
|
||||
const result = bleno.Characteristic.RESULT_SUCCESS
|
||||
const wifiRes = {}
|
||||
let data
|
||||
wifi.list((err, list) => {
|
||||
if (err) {
|
||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||
return callback(result)
|
||||
}
|
||||
wifiRes.available = list
|
||||
wifiRes.current = currentWifi
|
||||
wifiRes.ip = currentAddr
|
||||
log.info('Discovered available APs', { found : list.length })
|
||||
data = new Buffer(JSON.stringify(wifiRes))
|
||||
callback(result, data.slice(offset, data.length))
|
||||
})
|
||||
}
|
||||
|
||||
function getMac () {
|
||||
const colonRe = new RegExp(':', 'g')
|
||||
if (NETWORK && NETWORK.wlan0 && NETWORK.wlan0[0] && NETWORK.wlan0[0].mac) {
|
||||
return NETWORK.wlan0[0].mac.replace(colonRe, '')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function spoofMac () {
|
||||
const fs = require('fs')
|
||||
const FSPATH = require.resolve('uuid')
|
||||
const IDFILE = os.homedir() + '/.intval3id'
|
||||
let uuid
|
||||
let UUIDPATH
|
||||
let TMP
|
||||
let MACTMP
|
||||
let dashRe
|
||||
delete require.cache[FSPATH]
|
||||
if (fs.existsSync(IDFILE)) {
|
||||
return fs.readFileSync(IDFILE, 'utf8')
|
||||
}
|
||||
uuid = require('uuid').v4
|
||||
UUIDPATH = require.resolve('uuid')
|
||||
delete require.cache[UUIDPATH]
|
||||
TMP = uuid()
|
||||
MACTMP = TMP.replace(dashRe, '').substring(0, 12)
|
||||
dashRe = new RegExp('-', 'g')
|
||||
fs.writeFileSync(IDFILE, MACTMP, 'utf8')
|
||||
return MACTMP
|
||||
}
|
||||
|
||||
function getIp () {
|
||||
let addr = null
|
||||
let ipv4
|
||||
const ifaces = os.networkInterfaces()
|
||||
if (ifaces && ifaces.wlan0) {
|
||||
ipv4 = ifaces.wlan0.filter(iface => {
|
||||
if (iface.family === 'IPv4') {
|
||||
return iface
|
||||
}
|
||||
})
|
||||
if (ipv4.length === 1) {
|
||||
addr = ipv4[0].address
|
||||
}
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
|
||||
function capitalize (s) {
|
||||
return s[0].toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
/** Class representing the bluetooth interface */
|
||||
class BLE {
|
||||
/**
|
||||
* Establishes Bluetooth Low Energy services, accessible to process through this class
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
constructor (bleGetState) {
|
||||
log.info('Starting bluetooth service')
|
||||
|
||||
getState = bleGetState
|
||||
|
||||
bleno.on('stateChange', state => {
|
||||
log.info('stateChange', { state : state })
|
||||
if (state === 'poweredOn') {
|
||||
log.info('Starting advertising', { DEVICE_NAME: DEVICE_NAME, DEVICE_ID : process.env.BLENO_DEVICE_NAME })
|
||||
bleno.startAdvertising(DEVICE_NAME, [CHAR_ID])
|
||||
} else {
|
||||
bleno.stopAdvertising()
|
||||
}
|
||||
})
|
||||
|
||||
bleno.on('advertisingStart', err => {
|
||||
log.info('advertisingStart', { res : (err ? 'error ' + err : 'success') })
|
||||
createChars(this._onWrite.bind(this), this._onRead.bind(this))
|
||||
if (!err) {
|
||||
bleno.setServices([
|
||||
new bleno.PrimaryService({
|
||||
uuid : SERVICE_ID, //hardcoded across panels
|
||||
characteristics : chars
|
||||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
bleno.on('accept', clientAddress => {
|
||||
log.info('accept', { clientAddress : clientAddress })
|
||||
})
|
||||
|
||||
bleno.on('disconnect', clientAddress => {
|
||||
log.info('disconnect', { clientAddress : clientAddress })
|
||||
})
|
||||
|
||||
wifi.getNetwork((err, ssid) => {
|
||||
if (err) {
|
||||
return log.error('wifi.getNetwork', err)
|
||||
}
|
||||
currentWifi = ssid
|
||||
currentAddr = getIp()
|
||||
log.info('wifi.getNetwork', {ssid : ssid, ip : currentAddr })
|
||||
})
|
||||
}
|
||||
_onWrite (data, offset, withoutResponse, callback) {
|
||||
let result = {}
|
||||
let utf8
|
||||
let obj
|
||||
let fn
|
||||
if (offset) {
|
||||
log.warn(`Offset scenario`)
|
||||
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
||||
return callback(result)
|
||||
}
|
||||
utf8 = data.toString('utf8')
|
||||
obj = JSON.parse(utf8)
|
||||
result = bleno.Characteristic.RESULT_SUCCESS
|
||||
fn = `_on${capitalize(obj.type)}`
|
||||
if (obj.type && this[fn]) {
|
||||
return this[fn](obj, () => {
|
||||
callback(result)
|
||||
})
|
||||
} else {
|
||||
return callback(result)
|
||||
}
|
||||
|
||||
}
|
||||
_onRead (offset, callback) {
|
||||
const result = bleno.Characteristic.RESULT_SUCCESS
|
||||
const state = getState()
|
||||
const data = new Buffer(JSON.stringify( state ))
|
||||
callback(result, data.slice(offset, data.length))
|
||||
}
|
||||
/**
|
||||
* Binds functions to events that are triggered by BLE messages
|
||||
*
|
||||
* @param {string} eventName Name of the event to to bind
|
||||
* @param {function} callback Invoked when the event is triggered
|
||||
*/
|
||||
on (eventName, callback) {
|
||||
this[`_on${capitalize(eventName)}`] = callback
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = BLE
|
|
@ -1,32 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
const ipc = require('node-ipc')
|
||||
|
||||
function capitalize (s) {
|
||||
return s[0].toUpperCase() + s.slice(1)
|
||||
}
|
||||
|
||||
class Blootstrap {
|
||||
constructor () {
|
||||
this._onData = () => {}
|
||||
ipc.connectTo('blootstrap_ble', () => {
|
||||
ipc.of.blootstrap_ble.on('connect', () => {
|
||||
ipc.log(`Connected to the blootstrap_ble service`)
|
||||
|
||||
})
|
||||
ipc.of.blootstrap_ble.on('data', data => {
|
||||
const str = data.toString()
|
||||
ipc.log(str)
|
||||
this._onData(str)
|
||||
})
|
||||
ipc.of.blootstrap_ble.on('disconnect', () => {
|
||||
ipc.log(`Disconnected from the blootstrap_ble service`)
|
||||
})
|
||||
})
|
||||
}
|
||||
on (eventName, callback) {
|
||||
this[`_on${capitalize(eventName)}`] = callback
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Blootstrap()
|
|
@ -0,0 +1,52 @@
|
|||
'use strict'
|
||||
|
||||
const fs = require('fs')
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const sqlite3 = require('sqlite3').verbose()
|
||||
const squel = require('squel')
|
||||
|
||||
const DB_FILE = path.join(os.homedir(), '.intval3.db')
|
||||
const db = new sqlite3.Database(DB_FILE)
|
||||
|
||||
class DB {
|
||||
constructor () {
|
||||
this._table = 'frames'
|
||||
this.createTable()
|
||||
}
|
||||
createTable () {
|
||||
const query = `CREATE TABLE
|
||||
IF NOT EXISTS ${this._table} (
|
||||
dir INTEGER,
|
||||
exposure INTEGER,
|
||||
start INTEGER,
|
||||
stop INTEGER,
|
||||
len INTEGER,
|
||||
counter INTEGER,
|
||||
sequence INTEGER
|
||||
);`
|
||||
db.run(query)
|
||||
}
|
||||
insert (obj) {
|
||||
const query = squel.insert()
|
||||
.into(this._table)
|
||||
.setFields(obj) //dir, exposure, start, stop, len, counter
|
||||
.toString()
|
||||
db.run(query)
|
||||
}
|
||||
find (where, cb) {
|
||||
const query = squel.select()
|
||||
.from(this._table)
|
||||
.where(where)
|
||||
.toString()
|
||||
db.all(query, cb)
|
||||
}
|
||||
list (cb) {
|
||||
const query = squel.select()
|
||||
.from(this._table)
|
||||
.toString()
|
||||
db.all(query, cb)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new DB()
|
|
@ -0,0 +1,123 @@
|
|||
<a name="intval"></a>
|
||||
|
||||
## intval
|
||||
Object representing the intval3 features
|
||||
|
||||
**Kind**: global constant
|
||||
|
||||
* [intval](#intval)
|
||||
* [._declarePins()](#intval._declarePins)
|
||||
* [._undeclarePins()](#intval._undeclarePins)
|
||||
* [._startFwd()](#intval._startFwd)
|
||||
* [._startBwd()](#intval._startBwd)
|
||||
* [._stop()](#intval._stop)
|
||||
* [._watchMicro(err, val)](#intval._watchMicro)
|
||||
* [._watchRelease(err, val)](#intval._watchRelease)
|
||||
* [.setDir([dir])](#intval.setDir)
|
||||
* [.frame([dir], [time])](#intval.frame)
|
||||
* [.sequence()](#intval.sequence)
|
||||
|
||||
<a name="intval._declarePins"></a>
|
||||
|
||||
### intval._declarePins()
|
||||
(internal function) Declares all Gpio pins that will be used
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
<a name="intval._undeclarePins"></a>
|
||||
|
||||
### intval._undeclarePins()
|
||||
(internal function) Undeclares all Gpio in event of uncaught error
|
||||
that interupts the node process
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
<a name="intval._startFwd"></a>
|
||||
|
||||
### intval._startFwd()
|
||||
Start motor in forward direction by setting correct pins in h-bridge
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
<a name="intval._startBwd"></a>
|
||||
|
||||
### intval._startBwd()
|
||||
Start motor in backward direction by setting correct pins in h-bridge
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
<a name="intval._stop"></a>
|
||||
|
||||
### intval._stop()
|
||||
Stop motor by setting both motor pins to 0 (LOW)
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
<a name="intval._watchMicro"></a>
|
||||
|
||||
### intval._watchMicro(err, val)
|
||||
Callback for watching relese switch state changes.
|
||||
Using GPIO 06 on Raspberry Pi Zero W.
|
||||
|
||||
1) If closed AND frame active, start timer, set state primed to `true`.
|
||||
1) If opened AND frame active, stop frame
|
||||
|
||||
Microswitch + 10K ohm resistor
|
||||
* 1 === open
|
||||
* 0 === closed
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>object</code> | Error object present if problem reading pin |
|
||||
| val | <code>integer</code> | Current value of the pin |
|
||||
|
||||
<a name="intval._watchRelease"></a>
|
||||
|
||||
### intval._watchRelease(err, val)
|
||||
Callback for watching relese switch state changes.
|
||||
Using GPIO 05 on Raspberry Pi Zero W.
|
||||
|
||||
1) If closed, start timer.
|
||||
2) If opened, check timer AND
|
||||
3) If `press` (`now - intval._state.release.time`) greater than minimum and less than `intval._release.seq`, start frame
|
||||
4) If `press` greater than `intval._release.seq`, start sequence
|
||||
|
||||
Button + 10K ohm resistor
|
||||
* 1 === open
|
||||
* 0 === closed
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>object</code> | Error object present if problem reading pin |
|
||||
| val | <code>integer</code> | Current value of the pin |
|
||||
|
||||
<a name="intval.setDir"></a>
|
||||
|
||||
### intval.setDir([dir])
|
||||
Set the default direction of the camera.
|
||||
* forward = true
|
||||
* backward = false
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [dir] | <code>boolean</code> | <code>true</code> | Direction of the camera |
|
||||
|
||||
<a name="intval.frame"></a>
|
||||
|
||||
### intval.frame([dir], [time])
|
||||
Begin a single frame with set variables or defaults
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [dir] | <code>boolean</code> | <code>"null"</code> | (optional) Direction of the frame |
|
||||
| [time] | <code>integer</code> | <code>"null"</code> | (optional) Exposure time, 0 = minimum |
|
||||
|
||||
<a name="intval.sequence"></a>
|
||||
|
||||
### intval.sequence()
|
||||
Start a sequence of frames, using defaults or explicit instructions
|
||||
|
||||
**Kind**: static method of [<code>intval</code>](#intval)
|
|
@ -1,24 +1,423 @@
|
|||
'use strict'
|
||||
|
||||
const gpio = require('gpio')
|
||||
const db = require('../db')
|
||||
const log = require('../log')('intval')
|
||||
const storage = require('node-persist')
|
||||
const fs = require('fs')
|
||||
|
||||
class Intval {
|
||||
constructor () {
|
||||
this._pin = {}
|
||||
this._declarePins()
|
||||
}
|
||||
_declarePins () {
|
||||
this._pin.four = gpio.export(4, {
|
||||
direction: 'out',
|
||||
interval: 100,
|
||||
ready : () => {
|
||||
console.info(`Set pin 4 to OUTPUT`)
|
||||
}
|
||||
})
|
||||
}
|
||||
status () {
|
||||
return {}
|
||||
let Gpio
|
||||
try {
|
||||
Gpio = require('onoff').Gpio
|
||||
} catch (e) {
|
||||
log.warn('Failed including Gpio, using sim')
|
||||
Gpio = require('../../lib/onoffsim').Gpio
|
||||
}
|
||||
|
||||
|
||||
const PINS = {
|
||||
fwd : {
|
||||
pin : 13,
|
||||
dir : 'out'
|
||||
},
|
||||
bwd : {
|
||||
pin : 19,
|
||||
dir : 'out'
|
||||
},
|
||||
micro : {
|
||||
pin : 5,
|
||||
dir : 'in',
|
||||
edge : 'both'
|
||||
},
|
||||
release : {
|
||||
pin : 6,
|
||||
dir : 'in',
|
||||
edge : 'both'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Intval()
|
||||
/** Object representing the intval3 features */
|
||||
const intval = {}
|
||||
|
||||
intval._frame = {
|
||||
open : 250, //delay before pausing frame in open state
|
||||
openBwd : 400,
|
||||
closed : 100, //time that frame actually remains closed for
|
||||
expected : 630 //expected length of frame, in ms
|
||||
}
|
||||
intval._release = {
|
||||
min : 20,
|
||||
seq : 1000
|
||||
}
|
||||
intval._microDelay = 10 // delay after stop signal before stopping motors
|
||||
intval._pin = {}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
intval.init = function () {
|
||||
if (!fs.existsSync('./state')) fs.mkdirSync('./state')
|
||||
storage.init({
|
||||
dir: './state',
|
||||
stringify: JSON.stringify,
|
||||
parse: JSON.parse,
|
||||
encoding: 'utf8',
|
||||
logging: false, // can also be custom logging function
|
||||
continuous: true, // continously persist to disk
|
||||
interval: false, // milliseconds, persist to disk on an interval
|
||||
ttl: false, // ttl* [NEW], can be true for 24h default or a number in MILLISECONDS
|
||||
expiredInterval: 2 * 60 * 1000, // [NEW] every 2 minutes the process will clean-up the expired cache
|
||||
forgiveParseErrors: false // [NEW]
|
||||
}).then(intval._restoreState).catch((err) => {
|
||||
log.warn('init', err)
|
||||
intval.reset()
|
||||
intval._declarePins()
|
||||
})
|
||||
|
||||
process.on('SIGINT', intval._undeclarePins)
|
||||
process.on('uncaughtException', intval._undeclarePins)
|
||||
}
|
||||
|
||||
intval._restoreState = function (res) {
|
||||
storage.getItem('_state', 'test').then(intval._setState).catch((err) => {
|
||||
intval._setState(undefined)
|
||||
log.error('_restoreState', err)
|
||||
})
|
||||
intval._declarePins()
|
||||
}
|
||||
|
||||
intval._setState = function (data) {
|
||||
if (typeof data !== 'undefined') {
|
||||
intval._state = data
|
||||
intval._state.frame.cb = () => {}
|
||||
log.info('_setState', 'Restored intval state from disk')
|
||||
return true
|
||||
}
|
||||
log.info('_setState', 'Setting state from defaults')
|
||||
intval._state = {
|
||||
frame : {
|
||||
dir : true, //forward
|
||||
start : 0, //time frame started, timestamp
|
||||
active : false, //should frame be running
|
||||
paused : false,
|
||||
exposure : 0, //length of frame exposure, in ms
|
||||
delay : 0, //delay before start of frame, in ms
|
||||
current : {}, //current settings
|
||||
cb : () => {}
|
||||
},
|
||||
release : {
|
||||
time: 0,
|
||||
active : false //is pressed
|
||||
},
|
||||
micro : {
|
||||
time : 0,
|
||||
primed : false //is ready to stop frame
|
||||
},
|
||||
counter : 0,
|
||||
sequence : false
|
||||
}
|
||||
intval._storeState()
|
||||
}
|
||||
|
||||
intval._storeState = function () {
|
||||
storage.setItem('_state', intval._state)
|
||||
.then(() => {})
|
||||
.catch((err) => {
|
||||
log.error('_storeState', err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* (internal function) Declares all Gpio pins that will be used
|
||||
*
|
||||
*/
|
||||
intval._declarePins = function () {
|
||||
let pin
|
||||
for (let p in PINS) {
|
||||
pin = PINS[p]
|
||||
if (pin.edge) intval._pin[p] = Gpio(pin.pin, pin.dir, pin.edge)
|
||||
if (!pin.edge) intval._pin[p] = Gpio(pin.pin, pin.dir)
|
||||
log.info('_declarePins', { pin : pin.pin, dir : pin.dir, edge : pin.edge })
|
||||
}
|
||||
intval._pin.release.watch(intval._watchRelease)
|
||||
}
|
||||
/**
|
||||
* (internal function) Undeclares all Gpio in event of uncaught error
|
||||
* that interupts the node process
|
||||
*
|
||||
*/
|
||||
intval._undeclarePins = function (e) {
|
||||
log.error(e)
|
||||
if (!intval._pin) {
|
||||
log.warn('_undeclarePins', { reason : 'No pins'})
|
||||
return process.exit()
|
||||
}
|
||||
log.warn('_undeclarePins', { pin : PINS.fwd.pin, val : 0, reason : 'exiting'})
|
||||
intval._pin.fwd.writeSync(0)
|
||||
log.warn('_undeclarePins', { pin : PINS.bwd.pin, val : 0, reason : 'exiting'})
|
||||
intval._pin.bwd.writeSync(0)
|
||||
intval._pin.fwd.unexport()
|
||||
intval._pin.bwd.unexport()
|
||||
intval._pin.micro.unexport()
|
||||
intval._pin.release.unexport()
|
||||
process.exit()
|
||||
}
|
||||
/**
|
||||
* Start motor in forward direction by setting correct pins in h-bridge
|
||||
*
|
||||
*/
|
||||
intval._startFwd = function () {
|
||||
intval._pin.fwd.writeSync(1)
|
||||
intval._pin.bwd.writeSync(0)
|
||||
}
|
||||
/**
|
||||
* Start motor in backward direction by setting correct pins in h-bridge
|
||||
*
|
||||
*/
|
||||
intval._startBwd = function () {
|
||||
intval._pin.fwd.writeSync(0)
|
||||
intval._pin.bwd.writeSync(1)
|
||||
}
|
||||
|
||||
intval._pause = function () {
|
||||
intval._pin.fwd.writeSync(0)
|
||||
intval._pin.bwd.writeSync(0)
|
||||
//log.info('_pause', 'frame paused')
|
||||
}
|
||||
/**
|
||||
* Stop motor by setting both motor pins to 0 (LOW)
|
||||
*
|
||||
*/
|
||||
intval._stop = function () {
|
||||
const entry = {}
|
||||
const now = +new Date()
|
||||
const len = now - intval._state.frame.start
|
||||
|
||||
intval._pin.fwd.writeSync(0)
|
||||
intval._pin.bwd.writeSync(0)
|
||||
|
||||
log.info(`_stop`, { frame : len })
|
||||
|
||||
intval._pin.micro.unwatch()
|
||||
intval._state.frame.active = false
|
||||
|
||||
if (intval._state.frame.cb) intval._state.frame.cb(len)
|
||||
|
||||
entry.start = intval._state.frame.start
|
||||
entry.stop = now
|
||||
entry.len = len
|
||||
entry.dir = intval._state.frame.current.dir ? 1 : 0
|
||||
entry.exposure = intval._state.frame.current.exposure
|
||||
entry.counter = intval._state.counter
|
||||
entry.sequence = intval._state.sequence ? 1 : 0
|
||||
|
||||
db.insert(entry)
|
||||
|
||||
intval._state.frame.current = {}
|
||||
}
|
||||
/**
|
||||
* Callback for watching relese switch state changes.
|
||||
* Using GPIO 06 on Raspberry Pi Zero W.
|
||||
*
|
||||
* 1) If closed AND frame active, start timer, set state primed to `true`.
|
||||
* 1) If opened AND frame active, stop frame
|
||||
*
|
||||
* Microswitch + 10K ohm resistor
|
||||
* * 1 === open
|
||||
* * 0 === closed
|
||||
*
|
||||
*
|
||||
* @param {object} err Error object present if problem reading pin
|
||||
* @param {integer} val Current value of the pin
|
||||
*
|
||||
*/
|
||||
intval._watchMicro = function (err, val) {
|
||||
const now = +new Date()
|
||||
if (err) {
|
||||
log.error('_watchMicro', err)
|
||||
}
|
||||
//log.info(`Microswitch val: ${val}`)
|
||||
//determine when to stop
|
||||
if (val === 0 && intval._state.frame.active) {
|
||||
if (!intval._state.micro.primed) {
|
||||
intval._state.micro.primed = true
|
||||
intval._state.micro.time = now
|
||||
log.info('Microswitch primed to stop motor')
|
||||
}
|
||||
} else if (val === 1 && intval._state.frame.active) {
|
||||
if (intval._state.micro.primed && !intval._state.micro.paused && (now - intval._state.frame.start) > intval._frame.open) {
|
||||
intval._state.micro.primed = false
|
||||
intval._state.micro.time = 0
|
||||
setTimeout( () => {
|
||||
intval._stop()
|
||||
}, intval._microDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Callback for watching relese switch state changes.
|
||||
* Using GPIO 05 on Raspberry Pi Zero W.
|
||||
*
|
||||
* 1) If closed, start timer.
|
||||
* 2) If opened, check timer AND
|
||||
* 3) If `press` (`now - intval._state.release.time`) greater than minimum and less than `intval._release.seq`, start frame
|
||||
* 4) If `press` greater than `intval._release.seq`, start sequence
|
||||
*
|
||||
* Button + 10K ohm resistor
|
||||
* * 1 === open
|
||||
* * 0 === closed
|
||||
*
|
||||
* @param {object} err Error object present if problem reading pin
|
||||
* @param {integer} val Current value of the pin
|
||||
*
|
||||
*/
|
||||
intval._watchRelease = function (err, val) {
|
||||
const now = +new Date()
|
||||
let press = 0
|
||||
if (err) {
|
||||
return log.error(err)
|
||||
}
|
||||
//log.info(`Release switch val: ${val}`)
|
||||
if (val === 0) {
|
||||
//closed
|
||||
if (intval._releaseClosedState(now)) {
|
||||
intval._state.release.time = now
|
||||
intval._state.release.active = true //maybe unncecessary
|
||||
}
|
||||
} else if (val === 1) {
|
||||
//opened
|
||||
if (intval._state.release.active) {
|
||||
press = now - intval._state.release.time
|
||||
if (press > intval._release.min && press < intval._release.seq) {
|
||||
intval.frame()
|
||||
} else if (press >= intval._release.seq) {
|
||||
intval.sequence()
|
||||
}
|
||||
//log.info(`Release closed for ${press}ms`)
|
||||
intval._state.release.time = 0
|
||||
intval._state.release.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intval._releaseClosedState = function (now) {
|
||||
if (!intval._state.release.active && intval._state.release.time === 0) {
|
||||
return true
|
||||
}
|
||||
if (intval._state.release.active && (now - intval._state.release.time) > (intval._release.seq * 10)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
intval.reset = function () {
|
||||
intval._setState()
|
||||
intval._storeState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default direction of the camera.
|
||||
* * forward = true
|
||||
* * backward = false
|
||||
*
|
||||
* @param {boolean} [dir=true] Direction of the camera
|
||||
*
|
||||
*/
|
||||
intval.setDir = function (val = true) {
|
||||
if (typeof val !== 'boolean') {
|
||||
return log.warn('Direction must be represented as either true or false')
|
||||
}
|
||||
intval._state.frame.dir = val
|
||||
intval._storeState()
|
||||
log.info('setDir', { direction : val ? 'forward' : 'backward' })
|
||||
}
|
||||
|
||||
intval.setExposure = function (val = 0) {
|
||||
intval._state.frame.exposure = val
|
||||
intval._storeState()
|
||||
log.info('setExposure', { exposure : val })
|
||||
}
|
||||
|
||||
intval.setDelay = function (val = 0) {
|
||||
intval._state.frame.delay = val
|
||||
intval._storeState()
|
||||
log.info('setDelay', { delay : val })
|
||||
}
|
||||
intval.setCounter = function (val = 0) {
|
||||
intval._state.counter = val
|
||||
intval._storeState()
|
||||
log.info('setCounter', { counter : val })
|
||||
}
|
||||
/**
|
||||
* Begin a single frame with set variables or defaults
|
||||
*
|
||||
* @param {?boolean} [dir="null"] (optional) Direction of the frame
|
||||
* @param {?integer} [exposure="null"] (optional) Exposure time, 0 = minimum
|
||||
*
|
||||
*/
|
||||
intval.frame = function (dir = null, exposure = null, cb = () => {}) {
|
||||
if (dir === true || (dir === null && intval._state.frame.dir === true) ) {
|
||||
dir = true
|
||||
} else {
|
||||
dir = false
|
||||
}
|
||||
|
||||
if (exposure === null && intval._state.frame.exposure !== 0) {
|
||||
exposure = intval._state.frame.exposure
|
||||
} else if (exposure === null) {
|
||||
exposure = 0 //default speed
|
||||
}
|
||||
|
||||
intval._state.frame.start = +new Date()
|
||||
intval._state.frame.active = true
|
||||
intval._pin.micro.watch(intval._watchMicro)
|
||||
|
||||
log.info('frame', {dir : dir ? 'forward' : 'backward', exposure : exposure})
|
||||
|
||||
if (dir) {
|
||||
intval._startFwd()
|
||||
} else {
|
||||
intval._startBwd()
|
||||
}
|
||||
if (exposure !== 0) {
|
||||
intval._state.frame.paused = true
|
||||
if (dir) {
|
||||
setTimeout(intval._pause, intval._frame.open)
|
||||
//log.info('frame', { pausing : time + intval._frame.open })
|
||||
setTimeout( () => {
|
||||
intval._state.frame.paused = false
|
||||
intval._startFwd()
|
||||
}, exposure + intval._frame.closed)
|
||||
} else {
|
||||
setTimeout(intval._pause, intval._frame.openBwd)
|
||||
setTimeout( () => {
|
||||
//log.info('frame', 'restarting')
|
||||
intval._state.frame.paused = false
|
||||
intval._startBwd()
|
||||
}, exposure + intval._frame.closed)
|
||||
}
|
||||
}
|
||||
if (dir) {
|
||||
intval._state.frame.cb = (len) => {
|
||||
intval._state.counter++
|
||||
intval._storeState()
|
||||
cb(len)
|
||||
}
|
||||
} else {
|
||||
intval._state.frame.cb = (len) => {
|
||||
intval._state.counter--
|
||||
intval._storeState()
|
||||
cb(len)
|
||||
}
|
||||
}
|
||||
intval._state.frame.current = {
|
||||
dir: dir,
|
||||
exposure: exposure
|
||||
}
|
||||
}
|
||||
|
||||
intval.status = function () {
|
||||
return intval._state
|
||||
}
|
||||
|
||||
module.exports = intval
|
|
@ -0,0 +1,13 @@
|
|||
<a name="createLog"></a>
|
||||
|
||||
## createLog(label, filename) ⇒ <code>object</code>
|
||||
createLog() - Returns a winston logger configured to service
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>object</code> - Winston logger
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| label | <code>string</code> | | Label appearing on logger |
|
||||
| filename | <code>string</code> | <code>null</code> | Optional file to write log to |
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
'use strict'
|
||||
|
||||
const winston = require('winston')
|
||||
|
||||
/**
|
||||
* createLog() - Returns a winston logger configured to service
|
||||
*
|
||||
* @param {string} label Label appearing on logger
|
||||
* @param {string} filename Optional file to write log to
|
||||
* @returns {object} Winston logger
|
||||
*/
|
||||
function createLog (label, filename = null) {
|
||||
const transports = [ new (winston.transports.Console)({ label : label }) ]
|
||||
if (filename !== null) {
|
||||
transports.push( new (winston.transports.File)({ label : label, filename : filename }) )
|
||||
}
|
||||
return new (winston.Logger)({
|
||||
transports: transports
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = createLog
|
|
@ -0,0 +1,35 @@
|
|||
<a name="mscript"></a>
|
||||
|
||||
## mscript
|
||||
Object representing mscript parser
|
||||
|
||||
**Kind**: global constant
|
||||
|
||||
* [mscript](#mscript)
|
||||
* [.arg(shrt, lng)](#mscript.arg)
|
||||
* [.arg_pos(shrt, lng)](#mscript.arg_pos)
|
||||
|
||||
<a name="mscript.arg"></a>
|
||||
|
||||
### mscript.arg(shrt, lng)
|
||||
Determine whether or not argument flag has been set
|
||||
|
||||
**Kind**: static method of [<code>mscript</code>](#mscript)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| shrt | <code>string</code> | Short flag name (ie `-a`) |
|
||||
| lng | <code>string</code> | Long flag name (ie `--apple`) |
|
||||
|
||||
<a name="mscript.arg_pos"></a>
|
||||
|
||||
### mscript.arg_pos(shrt, lng)
|
||||
Determine position of flag, in argument array
|
||||
|
||||
**Kind**: static method of [<code>mscript</code>](#mscript)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| shrt | <code>string</code> | Short flag name (ie `-a`) |
|
||||
| lng | <code>string</code> | Long flag name (ie `--apple`) |
|
||||
|
|
@ -0,0 +1,441 @@
|
|||
'use strict'
|
||||
|
||||
let fs
|
||||
let input;
|
||||
|
||||
/** Object representing mscript parser */
|
||||
const mscript = {}
|
||||
|
||||
/**
|
||||
* Determine whether or not argument flag has been set
|
||||
*
|
||||
*
|
||||
* @param {string} shrt Short flag name (ie `-a`)
|
||||
* @param {string} lng Long flag name (ie `--apple`)
|
||||
*
|
||||
*/
|
||||
mscript.arg = function arg (shrt, lng) {
|
||||
if (process.argv.indexOf(shrt) !== -1 ||
|
||||
process.argv.indexOf(lng) !== -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
/**
|
||||
* Determine position of flag, in argument array
|
||||
*
|
||||
*
|
||||
* @param {string} shrt Short flag name (ie `-a`)
|
||||
* @param {string} lng Long flag name (ie `--apple`)
|
||||
*
|
||||
*/
|
||||
mscript.arg_pos = function arg_pos (shrt, lng) {
|
||||
let pos = process.argv.indexOf(shrt)
|
||||
if (pos === -1) {
|
||||
pos = process.argv.indexOf(lng)
|
||||
}
|
||||
return pos
|
||||
}
|
||||
|
||||
mscript.black = '0,0,0'
|
||||
|
||||
mscript.cmd = [
|
||||
'CF',
|
||||
'PF',
|
||||
'BF',
|
||||
'CB',
|
||||
'PB',
|
||||
'BB',
|
||||
'D'
|
||||
]
|
||||
mscript.alts = {
|
||||
'CF' : ['CAMERA FORWARD', 'CAM FORWARD', 'C'],
|
||||
'PF' : ['PROJECTOR FORWARD', 'PROJ FORWARD', 'P'],
|
||||
'BF' : ['BLACK FORWARD'],
|
||||
'CB' : ['CAMERA BACKWARD', 'CAM BACKWARD', 'CAMERA BACK', 'CAM BACK'],
|
||||
'PB' : ['PROJECTOR FORWARD', 'PROJ FORWARD', 'PROJECTOR BACK', 'PROJ BACK'],
|
||||
'BB' : ['BLACK BACKWARD', 'BLACK BACK'],
|
||||
'L ' : ['LIGHT', 'COLOR', 'LAMP']
|
||||
}
|
||||
|
||||
mscript.state = {}
|
||||
//TODO: This will memory leak
|
||||
mscript.state_clear = function state_clear () {
|
||||
mscript.state = {
|
||||
cam : 0,
|
||||
proj : 0,
|
||||
color : '',
|
||||
loops : [],
|
||||
rec : -1
|
||||
}
|
||||
}
|
||||
|
||||
mscript.alts_unique = function alts_unique () {
|
||||
const ids = Object.keys(mscript.alts)
|
||||
const all = []
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
if (all.indexOf(ids[i]) === -1) {
|
||||
all.push(ids[i])
|
||||
} else {
|
||||
mscript.fail(1, "Can't parse")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mscript.interpret = function interpret (text, callback) {
|
||||
mscript.state_clear()
|
||||
if (typeof text === 'undefined') {
|
||||
mscript.fail(2, 'No input')
|
||||
}
|
||||
const lines = text.split('\n')
|
||||
const arr = []
|
||||
const light = []
|
||||
const output = {}
|
||||
|
||||
let two = ''
|
||||
let target = 0
|
||||
let dist = 0 //?
|
||||
let x
|
||||
|
||||
//loop through all lines
|
||||
for (let line of lines) {
|
||||
//preprocess line
|
||||
line = mscript.preprocess(line)
|
||||
two = line.substring(0, 2)
|
||||
|
||||
if (mscript.cmd.indexOf(two) !== -1) {
|
||||
if (mscript.state.loops.length > 0) {
|
||||
//hold generated arr in state loop array
|
||||
mscript.state.loops[mscript.state.rec].arr.push.apply(mscript.state.loops[mscript.state.rec].arr, mscript.str_to_arr(line, two))
|
||||
mscript.state.loops[mscript.state.rec].light.push.apply(mscript.state.loops[mscript.state.rec].light, mscript.light_to_arr(line, two))
|
||||
} else {
|
||||
arr.push.apply(arr, mscript.str_to_arr(line, two))
|
||||
light.push.apply(light, mscript.light_to_arr(line, two))
|
||||
}
|
||||
|
||||
} else if (line.substring(0, 4) === 'LOOP') {
|
||||
mscript.state.rec++
|
||||
mscript.state.loops[mscript.state.rec] = {
|
||||
arr : [],
|
||||
light : [],
|
||||
cam : 0,
|
||||
proj : 0,
|
||||
cmd : line + ''
|
||||
}
|
||||
} else if (line.substring(0, 2) === 'L ') {
|
||||
mscript.light_state(line)
|
||||
} else if (line.substring(0, 3) === 'END') {
|
||||
for (x = 0; x < mscript.loop_count(mscript.state.loops[mscript.state.rec].cmd); x++) {
|
||||
if (mscript.state.rec === 0) {
|
||||
arr.push.apply(arr, mscript.state.loops[mscript.state.rec].arr);
|
||||
light.push.apply(light, mscript.state.loops[mscript.state.rec].light);
|
||||
} else if (mscript.state.rec >= 1) {
|
||||
mscript.state.loops[mscript.state.rec - 1].arr.push.apply(mscript.state.loops[mscript.state.rec - 1].arr, mscript.state.loops[mscript.state.rec].arr)
|
||||
mscript.state.loops[mscript.state.rec - 1].light.push.apply(mscript.state.loops[mscript.state.rec - 1].light, mscript.state.loops[mscript.state.rec].light)
|
||||
}
|
||||
}
|
||||
mscript.state_update('END', mscript.loop_count(mscript.state.loops[mscript.state.rec].cmd));
|
||||
delete mscript.state.loops[mscript.state.rec]
|
||||
mscript.state.rec--
|
||||
} else if (line.substring(0, 3) === 'CAM') { //directly go to that frame (black?)
|
||||
target = parseInt(line.split('CAM ')[1])
|
||||
if (mscript.state.loops.length > 0) {
|
||||
if (target > mscript.state.cam) {
|
||||
dist = target - mscript.state.cam
|
||||
for (x = 0; x < dist; x++) {
|
||||
mscript.state.loops[mscript.state.rec].arr.push('BF')
|
||||
mscript.state.loops[mscript.state.rec].light.push(mscript.black)
|
||||
mscript.state_update('BF')
|
||||
}
|
||||
} else {
|
||||
dist = mscript.state.cam - target
|
||||
for (x = 0; x < dist; x++) {
|
||||
mscript.state.loops[mscript.state.rec].arr.push('BB')
|
||||
mscript.state.loops[mscript.state.rec].light.push(mscript.black)
|
||||
mscript.state_update('BB')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (target > mscript.state.cam) {
|
||||
dist = target - mscript.state.cam
|
||||
for (x = 0; x < dist; x++) {
|
||||
arr.push('BF')
|
||||
light.push(mscript.black)
|
||||
mscript.state_update('BF')
|
||||
}
|
||||
} else {
|
||||
dist = mscript.state.cam - target
|
||||
for (x = 0; x < dist; x++) {
|
||||
arr.push('BB')
|
||||
light.push(mscript.black)
|
||||
mscript.state_update('BB')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line.substring(0, 4) === 'PROJ') { //directly go to that frame
|
||||
target = parseInt(line.split('PROJ ')[1])
|
||||
if (mscript.state.loops.length > 0) {
|
||||
if (target > mscript.state.proj) {
|
||||
dist = target - mscript.state.proj
|
||||
for (x = 0; x < dist; x++) {
|
||||
mscript.state.loops[mscript.state.rec].arr.push('PF')
|
||||
mscript.state.loops[mscript.state.rec].light.push('')
|
||||
mscript.state_update('PF')
|
||||
}
|
||||
} else {
|
||||
dist = mscript.state.proj - target
|
||||
for (x = 0; x < dist; x++) {
|
||||
mscript.state.loops[mscript.state.rec].arr.push('PB')
|
||||
mscript.state.loops[mscript.state.rec].light.push('')
|
||||
mscript.state_update('PB')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (target > mscript.state.proj) {
|
||||
dist = target - mscript.state.proj
|
||||
for (x = 0; x < dist; x++) {
|
||||
arr.push('PF')
|
||||
light.push('')
|
||||
mscript.state_update('PF')
|
||||
}
|
||||
} else {
|
||||
dist = mscript.state.proj - target
|
||||
for (x = 0; x < dist; x++) {
|
||||
arr.push('PB')
|
||||
light.push('')
|
||||
mscript.state_update('PB');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line.substring(0, 3) === 'SET') { //set that state
|
||||
if (line.substring(0, 7) === 'SET CAM') {
|
||||
mscript.state.cam = parseInt(line.split('SET CAM')[1]);
|
||||
} else if (line.substring(0, 8) === 'SET PROJ') {
|
||||
mscript.state.proj = parseInt(line.split('SET PROJ')[1]);
|
||||
}
|
||||
} else if (line.substring(0, 1) === '#' || line.substring(0, 2) === '//') {
|
||||
//comments
|
||||
//ignore while parsing
|
||||
}
|
||||
}
|
||||
output.success = true
|
||||
output.arr = arr
|
||||
output.light = light
|
||||
output.cam = mscript.state.cam
|
||||
output.proj = mscript.state.proj
|
||||
if (typeof callback !== 'undefined') {
|
||||
//should only be invoked by running mscript.tests()
|
||||
callback(output)
|
||||
} else {
|
||||
return mscript.output(output)
|
||||
}
|
||||
}
|
||||
|
||||
mscript.preprocess = function preprocess (line) {
|
||||
line = line.replace(/\t+/g, '') //strip tabs
|
||||
line = line.trim() //remove excess whitespace before and after command
|
||||
line = line.toUpperCase()
|
||||
return line
|
||||
}
|
||||
|
||||
mscript.last_loop = function last_loop () {
|
||||
return mscript.state.loops[mscript.state.loops.length - 1]
|
||||
}
|
||||
|
||||
mscript.parent_loop = function parent_loop () {
|
||||
return mscript.state.loops[mscript.state.loops.length - 2]
|
||||
}
|
||||
|
||||
mscript.state_update = function state_update (cmd, val) {
|
||||
if (cmd === 'END') {
|
||||
for (var i = 0; i < val; i++) {
|
||||
if (mscript.state.rec === 0) {
|
||||
mscript.state.cam += mscript.state.loops[mscript.state.rec].cam
|
||||
mscript.state.proj += mscript.state.loops[mscript.state.rec].proj
|
||||
} else if (mscript.state.rec >= 1) {
|
||||
mscript.state.loops[mscript.state.rec - 1].cam += mscript.state.loops[mscript.state.rec].cam
|
||||
mscript.state.loops[mscript.state.rec - 1].proj += mscript.state.loops[mscript.state.rec].proj
|
||||
}
|
||||
}
|
||||
} else if (cmd === 'CF') {
|
||||
if (mscript.state.loops.length < 1) {
|
||||
mscript.state.cam++
|
||||
} else {
|
||||
mscript.state.loops[mscript.state.rec].cam++
|
||||
}
|
||||
} else if (cmd === 'CB') {
|
||||
if (mscript.state.loops.length < 1) {
|
||||
mscript.state.cam--
|
||||
} else {
|
||||
mscript.state.loops[mscript.state.rec].cam--
|
||||
}
|
||||
} else if (cmd === 'PF') {
|
||||
if (mscript.state.loops.length < 1) {
|
||||
mscript.state.proj++
|
||||
} else {
|
||||
mscript.state.loops[mscript.state.rec].proj++
|
||||
}
|
||||
} else if (cmd === 'PB') {
|
||||
if (mscript.state.loops.length < 1) {
|
||||
mscript.state.proj--
|
||||
} else {
|
||||
mscript.state.loops[mscript.state.rec].proj--
|
||||
}
|
||||
} else if (cmd === 'BF') {
|
||||
if (mscript.state.loops.length < 1) {
|
||||
mscript.state.cam++
|
||||
} else {
|
||||
mscript.state.loops[mscript.state.rec].cam++
|
||||
}
|
||||
} else if (cmd === 'BB') {
|
||||
if (mscript.state.loops.length < 1) {
|
||||
mscript.state.cam--
|
||||
} else {
|
||||
mscript.state.loops[mscript.state.rec].cam++
|
||||
}
|
||||
} else if (cmd === 'L ') {
|
||||
//TODO : ????
|
||||
}
|
||||
}
|
||||
|
||||
mscript.str_to_arr = function str_to_arr (str, cmd) {
|
||||
const cnt = str.split(cmd)
|
||||
let arr = []
|
||||
let c = parseInt(cnt[1])
|
||||
|
||||
if (cnt[1] === '') {
|
||||
c = 1
|
||||
} else {
|
||||
c = parseInt(cnt[1])
|
||||
}
|
||||
for (var i = 0; i < c; i++) {
|
||||
arr.push(cmd)
|
||||
mscript.state_update(cmd)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
mscript.light_state = function light_state (str) {
|
||||
//add parsers for other color spaces
|
||||
const color = str.replace('L ', '').trim()
|
||||
mscript.state.color = color
|
||||
}
|
||||
|
||||
mscript.light_to_arr = function light_to_arr (str, cmd) {
|
||||
const cnt = str.split(cmd)
|
||||
const arr = []
|
||||
let c = parseInt(cnt[1])
|
||||
|
||||
if (cnt[1] === '') {
|
||||
c = 1
|
||||
} else {
|
||||
c = parseInt(cnt[1])
|
||||
}
|
||||
for (var i = 0; i < c; i++) {
|
||||
if (cmd === 'CF' || cmd === 'CB') {
|
||||
arr.push(mscript.state.color)
|
||||
} else if (cmd === 'BF' || cmd === 'BB') {
|
||||
arr.push(mscript.black)
|
||||
} else {
|
||||
arr.push('')
|
||||
}
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
mscript.loop_count = function loop_count (str) {
|
||||
return parseInt(str.split('LOOP ')[1])
|
||||
}
|
||||
|
||||
mscript.fail = function fail (code, reason) {
|
||||
const obj = { success: false, error: true, msg : reason }
|
||||
console.error(JSON.stringify(obj))
|
||||
if (process) process.exit()
|
||||
}
|
||||
|
||||
mscript.output = function output (data) {
|
||||
let json = true; //default
|
||||
if (mscript.arg('-j', '--json')) {
|
||||
json = true
|
||||
}
|
||||
|
||||
if (mscript.arg('-t', '--text')) {
|
||||
json = false
|
||||
}
|
||||
|
||||
if (json) {
|
||||
console.log(JSON.stringify(data))
|
||||
} else {
|
||||
var ids = Object.keys(data)
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
console.log(ids[i] + ': ' + data[ids[i]])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mscript.init = function init () {
|
||||
if (mscript.arg('-t', '--tests')) {
|
||||
return mscript.tests()
|
||||
}
|
||||
|
||||
if (mscript.arg('-v', '--verbose')) {
|
||||
console.time('mscript')
|
||||
}
|
||||
|
||||
if (mscript.arg('-c', '--cam')) {
|
||||
mscript.state.cam = parseInt(process.argv[mscript.arg_pos('-c', '--cam') + 1])
|
||||
}
|
||||
|
||||
if (mscript.arg('-p', '--proj')) {
|
||||
mscript.state.proj = parseInt(process.argv[mscript.arg_pos('-p', '--proj') + 1])
|
||||
}
|
||||
|
||||
if (mscript.arg('-f', '--file')) {
|
||||
input = process.argv[mscript.arg_pos('-f', '--file') + 1]
|
||||
mscript.interpret(fs.readFileSync(input, 'utf8'))
|
||||
} else {
|
||||
mscript.interpret(input)
|
||||
}
|
||||
|
||||
if (mscript.arg('-v', '--verbose')) {
|
||||
console.timeEnd('mscript')
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document === 'undefined' && typeof module !== 'undefined' && !module.parent) {
|
||||
//node script
|
||||
fs = require('fs')
|
||||
input = process.argv[2]
|
||||
mscript.init()
|
||||
} else if (typeof module !== 'undefined' && module.parent) {
|
||||
//module
|
||||
fs = require('fs')
|
||||
module.exports = mscript
|
||||
} else {
|
||||
//web
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
CAM # - go to camera frame #
|
||||
PROJ # - go to projector frame #
|
||||
|
||||
SET CAM # - sets camera count to #
|
||||
SET PROJ # - sets projector count to #
|
||||
|
||||
LOOP # - begin loop, can nest recursively, # times
|
||||
END LOOP - (or END) closes loop
|
||||
|
||||
L #RGB - sets light to rgb value
|
||||
|
||||
FADE
|
||||
|
||||
CF - Camera forwards
|
||||
PF - Projector forwards
|
||||
BF - Black forwards
|
||||
CB - Camera backwards
|
||||
PB - Projector backwards
|
||||
BB - Black backwards
|
||||
|
||||
*/
|
|
@ -0,0 +1,20 @@
|
|||
<a name="onoffsim"></a>
|
||||
|
||||
## onoffsim
|
||||
Object representing a fake onoff Gpio class
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="onoffsim.Gpio"></a>
|
||||
|
||||
### onoffsim.Gpio(no, dir, additional) ⇒ <code>object</code>
|
||||
Returns a Gpio class in the case of running on a dev machine
|
||||
|
||||
**Kind**: static method of [<code>onoffsim</code>](#onoffsim)
|
||||
**Returns**: <code>object</code> - Fake Gpio object
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| no | <code>integer</code> | Number of the GPIO pin |
|
||||
| dir | <code>string</code> | Dirction of the pin, 'input' or 'output' |
|
||||
| additional | <code>string</code> | Additional instructions for the GPIO pin, for 'input' type |
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
'use strict'
|
||||
|
||||
/** Object representing a fake onoff Gpio class */
|
||||
const onoffsim = {
|
||||
/**
|
||||
* Returns a Gpio class in the case of running on a dev machine
|
||||
*
|
||||
* @param {integer} no Number of the GPIO pin
|
||||
* @param {string} dir Dirction of the pin, 'input' or 'output'
|
||||
* @param {string} additional Additional instructions for the GPIO pin, for 'input' type
|
||||
* @returns {object} Fake Gpio object
|
||||
*/
|
||||
Gpio : function (no, dir = 'in', additional = 'none') {
|
||||
return {
|
||||
no : no,
|
||||
dir : dir,
|
||||
additional : additional,
|
||||
val : null,
|
||||
watchFunc : null,
|
||||
set : function (val) {
|
||||
console.log(`onoffsim set ${this.no} to ${val}`)
|
||||
},
|
||||
get : function () {
|
||||
return this.val
|
||||
},
|
||||
watch : function (cb) {
|
||||
this.watchFunc = cb
|
||||
},
|
||||
unexport : function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = onoffsim
|
|
@ -0,0 +1,114 @@
|
|||
'use strict'
|
||||
|
||||
const uuid = require('uuid').v4
|
||||
const log = require('../log')('seq')
|
||||
|
||||
/** Object sequence features */
|
||||
const sequence = {};
|
||||
|
||||
sequence._state = {
|
||||
arr : [],
|
||||
active : false,
|
||||
paused : false,
|
||||
frame: false,
|
||||
delay : false,
|
||||
count : 0,
|
||||
stop : null
|
||||
}
|
||||
|
||||
sequence._loop = {
|
||||
arr : [],
|
||||
count : 0,
|
||||
max : 0
|
||||
}
|
||||
|
||||
sequence.start = function (options, cb) {
|
||||
if (sequence._state.active) {
|
||||
return false
|
||||
}
|
||||
|
||||
sequence._state.active = true
|
||||
sequence._state.count = 0
|
||||
|
||||
if (options.arr) {
|
||||
sequence._state.arr = options.arr
|
||||
}
|
||||
|
||||
if (options.loop) {
|
||||
sequence._loop.arr = options.loop
|
||||
sequence._loop.count = 0
|
||||
}
|
||||
|
||||
if (options.maxLoop) {
|
||||
sequence._loop.max = options.maxLoop
|
||||
} else {
|
||||
sequence._loop.max = 0
|
||||
}
|
||||
sequence._state.stop = cb
|
||||
sequence.step()
|
||||
sequence._state.id = uuid()
|
||||
return sequence._state.id
|
||||
}
|
||||
|
||||
sequence.setStop = function () {
|
||||
sequence._state.active = false
|
||||
}
|
||||
|
||||
sequence.stop = function () {
|
||||
sequence._state.active = false
|
||||
sequence._state.count = 0
|
||||
sequence._state.arr = []
|
||||
|
||||
sequence._loop.count = 0
|
||||
sequence._loop.max = 0
|
||||
sequence._loop.arr = []
|
||||
|
||||
if (sequence._state.stop) sequence._state.stop()
|
||||
|
||||
sequence._state.stop = null
|
||||
}
|
||||
|
||||
sequence.pause = function () {
|
||||
sequence._state.paused = true
|
||||
}
|
||||
|
||||
sequence.resume = function () {
|
||||
sequence._state.paused = false
|
||||
sequence.step()
|
||||
}
|
||||
|
||||
sequence.step = function () {
|
||||
if (sequence._state.active && !sequence._state.paused) {
|
||||
if (sequence._state.arr.length > 0) {
|
||||
if (sequence._state.count > sequence._state.arr.length - 1) {
|
||||
return sequence.stop()
|
||||
}
|
||||
log.info('step', { count : sequence._state.count, id : sequence._state.id })
|
||||
return sequence._state.arr[sequence._state.count](() => {
|
||||
sequence._state.count++
|
||||
sequence.step()
|
||||
})
|
||||
} else if (sequence._loop.arr.length > 0) {
|
||||
if (sequence._state.count > sequence._loop.arr.length - 1) {
|
||||
sequence._state.count = 0
|
||||
sequence._loop.count++
|
||||
}
|
||||
if (sequence._loop.max > 0 && sequence._loop.count > sequence._loop.max) {
|
||||
return sequence.stop()
|
||||
}
|
||||
log.info('step', { count : sequence._state.count, id : sequence._state.id })
|
||||
return sequence._loop.arr[sequence._state.count](() => {
|
||||
sequence._state.count++
|
||||
sequence.step()
|
||||
})
|
||||
} else{
|
||||
return sequence.stop()
|
||||
}
|
||||
} else if (sequence._state.paused) {
|
||||
log.info('step', 'Sequence paused', { loop : sequence._loop.count, count : sequence._state.count })
|
||||
} else if (!sequence._state.active) {
|
||||
log.info('step', 'Sequence stopped', { loop : sequence._loop.count, count : sequence._state.count })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = sequence
|
|
@ -0,0 +1,102 @@
|
|||
<a name="Wifi"></a>
|
||||
|
||||
## Wifi
|
||||
Class representing the wifi features
|
||||
|
||||
**Kind**: global class
|
||||
|
||||
* [Wifi](#Wifi)
|
||||
* [.list(callback)](#Wifi+list)
|
||||
* [._readConfigCb(err, data)](#Wifi+_readConfigCb)
|
||||
* [._writeConfigCb(err)](#Wifi+_writeConfigCb)
|
||||
* [._reconfigureCb(err, stdout, stderr)](#Wifi+_reconfigureCb)
|
||||
* [._refreshCb(err, stdout, stderr)](#Wifi+_refreshCb)
|
||||
* [.setNetwork(ssid, pwd, callback)](#Wifi+setNetwork)
|
||||
* [.getNetwork(callback)](#Wifi+getNetwork)
|
||||
|
||||
<a name="Wifi+list"></a>
|
||||
|
||||
### wifi.list(callback)
|
||||
List available wifi access points
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| callback | <code>function</code> | Function which gets invoked after list is returned |
|
||||
|
||||
<a name="Wifi+_readConfigCb"></a>
|
||||
|
||||
### wifi._readConfigCb(err, data)
|
||||
(internal function) Invoked after config file is read,
|
||||
then invokes file write on the config file
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>object</code> | (optional) Error object only present if problem reading config file |
|
||||
| data | <code>string</code> | Contents of the config file |
|
||||
|
||||
<a name="Wifi+_writeConfigCb"></a>
|
||||
|
||||
### wifi._writeConfigCb(err)
|
||||
(internal function) Invoked after config file is written,
|
||||
then executes reconfiguration command
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>object</code> | (optional) Error object only present if problem writing config file |
|
||||
|
||||
<a name="Wifi+_reconfigureCb"></a>
|
||||
|
||||
### wifi._reconfigureCb(err, stdout, stderr)
|
||||
(internal function) Invoked after reconfiguration command is complete
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>object</code> | (optional) Error object only present if configuration command fails |
|
||||
| stdout | <code>string</code> | Standard output from reconfiguration command |
|
||||
| stderr | <code>string</code> | Error output from command if fails |
|
||||
|
||||
<a name="Wifi+_refreshCb"></a>
|
||||
|
||||
### wifi._refreshCb(err, stdout, stderr)
|
||||
(internal function) Invoked after wifi refresh command is complete
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| err | <code>object</code> | (optional) Error object only present if refresh command fails |
|
||||
| stdout | <code>string</code> | Standard output from refresh command |
|
||||
| stderr | <code>string</code> | Error output from command if fails |
|
||||
|
||||
<a name="Wifi+setNetwork"></a>
|
||||
|
||||
### wifi.setNetwork(ssid, pwd, callback)
|
||||
Function which initializes the processes for adding a wifi access point authentication
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ssid | <code>string</code> | SSID of network to configure |
|
||||
| pwd | <code>string</code> | Password of access point, plaintext |
|
||||
| callback | <code>function</code> | Function invoked after process is complete, or fails |
|
||||
|
||||
<a name="Wifi+getNetwork"></a>
|
||||
|
||||
### wifi.getNetwork(callback)
|
||||
Executes command which gets the currently connected network
|
||||
|
||||
**Kind**: instance method of [<code>Wifi</code>](#Wifi)
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| callback | <code>function</code> | Function which is invoked after command is completed |
|
||||
|
|
@ -9,15 +9,24 @@ const refresh = '/sbin/ifdown wlan0 && /sbin/ifup --force wlan0'
|
|||
const iwlist = '/sbin/iwlist wlan0 scanning | grep "ESSID:"'
|
||||
const iwgetid = '/sbin/iwgetid'
|
||||
|
||||
const log = require('../log')('wifi')
|
||||
const exec = require('child_process').exec
|
||||
const fs = require('fs')
|
||||
|
||||
class wifi {
|
||||
let _entry = null
|
||||
let _ssid = null
|
||||
let _cb = null
|
||||
|
||||
/** Class representing the wifi features */
|
||||
class Wifi {
|
||||
constructor () {
|
||||
this._callback = () => {}
|
||||
this._entry = null
|
||||
this._ssid = null
|
||||
|
||||
}
|
||||
/**
|
||||
* List available wifi access points
|
||||
*
|
||||
* @param {function} callback Function which gets invoked after list is returned
|
||||
*/
|
||||
list (callback) {
|
||||
exec(iwlist, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
|
@ -25,69 +34,175 @@ class wifi {
|
|||
return callback(err)
|
||||
}
|
||||
const lines = stdout.split('\n')
|
||||
const output = []
|
||||
let output = []
|
||||
let line
|
||||
let i = 0
|
||||
for (let l of lines) {
|
||||
line = l.replace('ESSID:', '').trim()
|
||||
if (line !== '""') {
|
||||
if (line !== '""' && i < 5) {
|
||||
line = line.replace(quoteRe, '')
|
||||
output.push(line)
|
||||
}
|
||||
i++
|
||||
}
|
||||
output = output.filter(ap => {
|
||||
if (ap !== '') return ap
|
||||
})
|
||||
return callback(null, output)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* (internal function) Invoked after config file is read,
|
||||
* then invokes file write on the config file
|
||||
*
|
||||
* @param {object} err (optional) Error object only present if problem reading config file
|
||||
* @param {string} data Contents of the config file
|
||||
*/
|
||||
_readConfigCb (err, data) {
|
||||
let parsed
|
||||
let current
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return this._callback(err)
|
||||
return _cb(err)
|
||||
}
|
||||
if (data.search(networkPattern) === -1) {
|
||||
data += `\n${this._entry}`
|
||||
parsed = this._parseConfig(data)
|
||||
current = parsed.find(network => {
|
||||
return network.ssid === _ssid
|
||||
})
|
||||
if (typeof current !== 'undefined') {
|
||||
data = data.replace(current.raw, _entry)
|
||||
} else {
|
||||
data = data.replace(networkPattern, this._entry)
|
||||
data += '\n\n' + _entry
|
||||
}
|
||||
this._entry = null
|
||||
fs.writeFile(filePath, data, 'utf8', this._writeConfigCb)
|
||||
_entry = null
|
||||
fs.writeFile(filePath, data, 'utf8', this._writeConfigCb.bind(this))
|
||||
}
|
||||
/**
|
||||
* (internal function) Invoked after config file is written,
|
||||
* then executes reconfiguration command
|
||||
*
|
||||
* @param {object} err (optional) Error object only present if problem writing config file
|
||||
*/
|
||||
_writeConfigCb (err) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return this._callback(err)
|
||||
return _cb(err)
|
||||
}
|
||||
exec(reconfigure, this._reconfigureCb)
|
||||
exec(reconfigure, this._reconfigureCb.bind(this))
|
||||
}
|
||||
/**
|
||||
* (internal function) Invoked after reconfiguration command is complete
|
||||
*
|
||||
* @param {object} err (optional) Error object only present if configuration command fails
|
||||
* @param {string} stdout Standard output from reconfiguration command
|
||||
* @param {string} stderr Error output from command if fails
|
||||
*/
|
||||
_reconfigureCb (err, stdout, stderr) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return this._callback(err)
|
||||
return _cb(err)
|
||||
}
|
||||
console.log('Wifi reconfigured')
|
||||
exec(refresh, this._refreshCb)
|
||||
exec(refresh, this._refreshCb.bind(this))
|
||||
}
|
||||
/**
|
||||
* (internal function) Invoked after wifi refresh command is complete
|
||||
*
|
||||
* @param {object} err (optional) Error object only present if refresh command fails
|
||||
* @param {string} stdout Standard output from refresh command
|
||||
* @param {string} stderr Error output from command if fails
|
||||
*/
|
||||
_refreshCb (err, stdout, stderr) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
return this._callback(err)
|
||||
return _cb(err)
|
||||
}
|
||||
console.log('Wifi refreshed')
|
||||
//this._callback(null, { ssid : ssid, pwd : pwd.length })
|
||||
this._callback = () => {}
|
||||
_cb(null, { ssid : _ssid })
|
||||
_cb = () => {}
|
||||
}
|
||||
setNetwork (ssid, pwd, callback) {
|
||||
this._entry = `network={\n\tssid="${ssid}"\n\tpsk="${pwd}"\n}\n`
|
||||
this._callback = callback
|
||||
this._ssid = ssid
|
||||
fs.readFile(filePath, 'utf8', this._readConfigCb)
|
||||
_parseConfig (str) {
|
||||
const networks = []
|
||||
const lines = str.split('\n')
|
||||
let network = {}
|
||||
for (let line of lines) {
|
||||
if (line.substring(0, 9) === 'network={') {
|
||||
network = {}
|
||||
network.raw = line
|
||||
} else if (network.raw && line.indexOf('ssid=') !== -1) {
|
||||
network.ssid = line.replace('ssid=', '').trim().replace(quoteRe, '')
|
||||
if (network.raw) {
|
||||
network.raw += '\n' + line
|
||||
}
|
||||
} else if (network.raw && line.substring(0, 1) === '}') {
|
||||
network.raw += '\n' + line
|
||||
networks.push(network)
|
||||
network = {}
|
||||
} else if (network.raw) {
|
||||
network.raw += '\n' + line
|
||||
}
|
||||
}
|
||||
return networks
|
||||
}
|
||||
/**
|
||||
* Create sanitized wpa_supplicant.conf stanza for
|
||||
* configuring wifi without storing plaintext passwords
|
||||
* @example
|
||||
* network={
|
||||
* ssid="YOUR_SSID"
|
||||
* #psk="YOUR_PASSWORD"
|
||||
* psk=6a24edf1592aec4465271b7dcd204601b6e78df3186ce1a62a31f40ae9630702
|
||||
* }
|
||||
*
|
||||
* @param {string} ssid SSID of wifi network
|
||||
* @param {string} pwd Plaintext passphrase of wifi network
|
||||
* @param {function} callback Function called after psk hash is generated
|
||||
*/
|
||||
createPSK (ssid, pwd, callback) {
|
||||
const cmd = `wpa_passphrase "${ssid}" "${pwd}" | grep "psk="`
|
||||
let lines
|
||||
let hash
|
||||
let plaintext
|
||||
exec(cmd, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
lines = stdout.replace('#psk=', '').split('psk=')
|
||||
hash = lines[1]
|
||||
plaintext = lines[0]
|
||||
callback(null, hash.trim(), plaintext.trim())
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Function which initializes the processes for adding a wifi access point authentication
|
||||
*
|
||||
* @param {string} ssid SSID of network to configure
|
||||
* @param {string} pwd Password of access point, plaintext to be masked
|
||||
* @param {string} hash Password/SSID of access point, securely hashed
|
||||
* @param {function} callback Function invoked after process is complete, or fails
|
||||
*/
|
||||
setNetwork (ssid, pwd, hash, callback) {
|
||||
let masked = pwd.split('').map(char => { return char !== '"' ? '*' : '"' }).join('')
|
||||
_entry = `network={\n\tssid="${ssid}"\n\t#psk=${masked}\n\tpsk=${hash}\n}\n`
|
||||
_cb = callback
|
||||
_ssid = ssid
|
||||
fs.readFile(filePath, 'utf8', this._readConfigCb.bind(this))
|
||||
}
|
||||
/**
|
||||
* Executes command which gets the currently connected network
|
||||
*
|
||||
* @param {function} callback Function which is invoked after command is completed
|
||||
*/
|
||||
getNetwork (callback) {
|
||||
let output
|
||||
exec(iwgetid, (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
callback(null, stdout)
|
||||
output = stdout.split('ESSID:')[1].replace(quoteRe, '').trim()
|
||||
callback(null, output)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new wifi()
|
||||
module.exports = new Wifi()
|
48
nginx.conf
|
@ -1,48 +0,0 @@
|
|||
#blootstrap nginx conf
|
||||
|
||||
#uncomment and modify following files for ssl
|
||||
#server {
|
||||
|
||||
#listen 80;
|
||||
#server_name my_project;
|
||||
#return 301 https://$server_name$request_uri;
|
||||
|
||||
#}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
#listen 443 ssl;
|
||||
|
||||
#ssl on;
|
||||
#ssl_certificate {{SSL_CERT_PATH}};
|
||||
#ssl_certificate_key {{SSL_KEY_PATH}};
|
||||
|
||||
#ssl_session_timeout 5m;
|
||||
#ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
|
||||
#ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
|
||||
#ssl_prefer_server_ciphers on;
|
||||
|
||||
#server_name my_project;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:6699/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
gzip on;
|
||||
gzip_comp_level 5;
|
||||
gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
|
||||
}
|
||||
#uncomment for static file server
|
||||
#location /static/ {
|
||||
#uncomment to turn on caching
|
||||
#expires modified 1y;
|
||||
#access_log off;
|
||||
#add_header Cache-Control "public";
|
||||
#gzip on;
|
||||
#gzip_comp_level 5;
|
||||
#gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json;
|
||||
#use project location
|
||||
#alias /var/node/intval3/static/;
|
||||
#}
|
||||
}
|
14
package.json
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"name": "intval3",
|
||||
"version": "0.0.1",
|
||||
"version": "3.0.1",
|
||||
"description": "Intervalometer for the Bolex",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "./node_modules/.bin/qunit",
|
||||
"docs": "sh docs.sh"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -27,8 +28,17 @@
|
|||
"cron": "^1.2.1",
|
||||
"gpio": "^0.2.7",
|
||||
"node-ipc": "^9.1.0",
|
||||
"node-persist": "^2.1.0",
|
||||
"onoff": "^1.1.5",
|
||||
"restify": "^5.2.0",
|
||||
"rpio": "^0.9.20",
|
||||
"sqlite3": "^3.1.13",
|
||||
"squel": "^5.12.0",
|
||||
"uuid": "^3.1.0",
|
||||
"winston": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jsdoc-to-markdown": "^3.0.0",
|
||||
"qunit": "^2.5.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"apps" : [
|
||||
{
|
||||
"name" : "ble",
|
||||
"script" : "./services/bluetooth/index.js",
|
||||
"name" : "intval3",
|
||||
"script" : "./index.js",
|
||||
"watch" : false,
|
||||
"env" : {
|
||||
"BLENO_DEVICE_NAME" : "intval3",
|
||||
"DEVICE_ID" : "intval3",
|
||||
"DEVICE_NAME" : "intval3",
|
||||
"SERVICE_ID" : "149582bd-d49d-4b5c-acd1-1ae503d09e7a",
|
||||
"CHAR_ID" : "47bf69fb-f62f-4ef8-9be8-eb727a54fae4",
|
||||
"WIFI_ID" : "3fe7d9cf-7bd2-4ff0-97c5-ebe87288c2cc",
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Running blootstrap install script"
|
||||
echo "Running intval3 dependency install script"
|
||||
apt-get update
|
||||
apt-get install git ufw nginx -y
|
||||
apt-get install git ufw nginx jq -y
|
||||
|
||||
echo "Installing node.js dependencies.."
|
||||
apt-get install nodejs npm -y
|
||||
npm install -g n
|
||||
n latest
|
||||
npm install -g npm@latest
|
||||
npm install -g pm2
|
||||
npm install -g pm2 node-gyp
|
||||
|
||||
echo "Installing bluetooth dependencies..."
|
||||
apt-get install bluetooth bluez libbluetooth-dev libudev-dev -y
|
||||
|
||||
echo "Finished installing blootstrap dependencies"
|
||||
echo "Finished installing intval3 dependencies"
|
|
@ -1,18 +1,20 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Running blootstrap install script"
|
||||
echo "Running intval3 install script"
|
||||
apt-get update
|
||||
apt-get install git ufw nginx -y
|
||||
apt-get install git ufw nginx jq -y
|
||||
|
||||
echo "Installing node.js dependencies.."
|
||||
apt-get install nodejs npm -y
|
||||
npm install -g n
|
||||
n latest
|
||||
n 9.1.0
|
||||
npm install -g npm@latest
|
||||
npm install -g pm2
|
||||
npm install -g pm2 node-gyp
|
||||
|
||||
echo "Installing bluetooth dependencies..."
|
||||
apt-get install bluetooth bluez libbluetooth-dev libudev-dev -y
|
||||
systemctl disable bluetooth
|
||||
hciconfig hci0 up
|
||||
|
||||
echo "Configuring ufw (firewall)..."
|
||||
ufw default deny incoming
|
||||
|
@ -22,13 +24,13 @@ ufw allow http
|
|||
ufw allow https
|
||||
ufw enable
|
||||
|
||||
echo "Installing blootstrap project..."
|
||||
wget https://github.com/mattmcw/blootstrap/archive/master.zip
|
||||
unzip master.zip -d blootstrap/
|
||||
echo "Installing intval3 project..."
|
||||
wget https://github.com/sixteenmillimeter/intval3/archive/master.zip
|
||||
unzip master.zip -d intval3/
|
||||
rm master.zip
|
||||
|
||||
cd blootstrap
|
||||
cd intval3
|
||||
npm install
|
||||
pm2 start process.json
|
||||
|
||||
echo "Finished installing blootstrap"
|
||||
echo "Finished installing intval3"
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
URL=$1
|
||||
COUNTER=0
|
||||
FRAMES=25
|
||||
while [ $COUNTER -lt $FRAMES ]; do
|
||||
echo The counter is $COUNTER
|
||||
curl "$URL/frame"
|
||||
sleep 60
|
||||
((COUNTER++))
|
||||
done
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
sudo -u pi -i<< EOF
|
||||
cd /home/pi/intval3 && git pull
|
||||
EOF
|
|
@ -1,160 +0,0 @@
|
|||
'use strict'
|
||||
|
||||
const ipc = require('node-ipc')
|
||||
const os = require('os')
|
||||
const bleno = require('bleno')
|
||||
const util = require('util')
|
||||
const wifi = require('../../lib/wifi')
|
||||
|
||||
const BLENO_DEVICE_NAME = process.env.BLENO_DEVICE_NAME || 'my_project'
|
||||
const DEVICE_ID = process.env.DEVICE_ID || 'my_project_id'
|
||||
const SERVICE_ID = process.env.SERVICE_ID || 'blootstrap'
|
||||
const CHAR_ID = process.env.CHAR_ID || 'blootstrapchar'
|
||||
const WIFI_ID = process.env.WIFI_ID || 'blootstrapwifi'
|
||||
const NETWORK = os.networkInterfaces()
|
||||
const MAC = getMac() || spoofMac()
|
||||
|
||||
let currentWifi = 'disconnected'
|
||||
|
||||
const chars = []
|
||||
|
||||
ipc.config.id = 'blootstrap_ble'
|
||||
ipc.config.retry = 1500
|
||||
ipc.config.rawBuffer = true
|
||||
ipc.config.encoding = 'hex'
|
||||
|
||||
function createChar(name, uuid, prop, write, read) {
|
||||
function characteristic () {
|
||||
bleno.Characteristic.call(this, {
|
||||
uuid : uuid,
|
||||
properties: prop
|
||||
})
|
||||
}
|
||||
util.inherits(characteristic, bleno.Characteristic)
|
||||
if (prop.indexOf('read') !== -1) {
|
||||
//data, offset, withoutResponse, callback
|
||||
characteristic.prototype.onReadRequest = read
|
||||
}
|
||||
if (prop.indexOf('write') !== -1) {
|
||||
characteristic.prototype.onWriteRequest = write
|
||||
}
|
||||
chars.push(new characteristic())
|
||||
}
|
||||
|
||||
function createChars () {
|
||||
createChar('wifi', WIFI_ID, ['read', 'write'], onWifiWrite, onWifiRead)
|
||||
}
|
||||
|
||||
function onWifiWrite (data, offset, withoutResponse, callback) {
|
||||
let result
|
||||
let utf8
|
||||
let obj
|
||||
let ssid
|
||||
let pwd
|
||||
if (offset) {
|
||||
console.warn(`Offset scenario`)
|
||||
result = bleno.Characteristic.RESULT_ATTR_NOT_LONG
|
||||
return callback(result)
|
||||
}
|
||||
utf8 = data.toString('utf8')
|
||||
obj = JSON.parse(utf8)
|
||||
ssid = obj.ssid
|
||||
pwd = obj.pwd
|
||||
console.log(`Connecting to AP: ${ssid}...`)
|
||||
return wifi.setNetwork(ssid, pwd, (err, data) => {
|
||||
if (err) {
|
||||
console.error('Error configuring wifi', err)
|
||||
result = bleno.Characteristic.RESULT_UNLIKELY_ERROR
|
||||
return callback(result)
|
||||
}
|
||||
currentWifi = ssid
|
||||
console.log(`Connected to ${ssid}`)
|
||||
result = bleno.Characteristic.RESULT_SUCCESS
|
||||
return callback(result)
|
||||
})
|
||||
}
|
||||
|
||||
function onWifiRead (offset, callback) {
|
||||
const result = bleno.Characteristic.RESULT_SUCCESS
|
||||
const data = new Buffer(JSON.stringify(currentWifi))
|
||||
callback(result, data.slice(offset, data.length))
|
||||
}
|
||||
|
||||
function getMac () {
|
||||
const colonRe = new RegExp(':', 'g')
|
||||
if (NETWORK && NETWORK.wlan0 && NETWORK.wlan0[0] && NETWORK.wlan0[0].mac) {
|
||||
return NETWORK.wlan0[0].mac.replace(colonRe, '')
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function spoofMac () {
|
||||
const fs = require('fs')
|
||||
const FSPATH = require.resolve('uuid')
|
||||
const IDFILE = os.homedir() + '/.intval3id'
|
||||
let uuid
|
||||
let UUIDPATH
|
||||
let TMP
|
||||
let MACTMP
|
||||
let dashRe
|
||||
delete require.cache[FSPATH]
|
||||
if (fs.existsSync(IDFILE)) {
|
||||
return fs.readFileSync(IDFILE, 'utf8')
|
||||
}
|
||||
uuid = require('uuid').v4
|
||||
UUIDPATH = require.resolve('uuid')
|
||||
delete require.cache[UUIDPATH]
|
||||
TMP = uuid()
|
||||
MACTMP = TMP.replace(dashRe, '').substring(0, 12)
|
||||
dashRe = new RegExp('-', 'g')
|
||||
fs.writeFileSync(IDFILE, MACTMP, 'utf8')
|
||||
return MACTMP
|
||||
}
|
||||
|
||||
console.log('Starting bluetooth service')
|
||||
|
||||
bleno.on('stateChange', state => {
|
||||
const BLE_ID = `${DEVICE_ID}_${MAC}`
|
||||
console.log(`on -> stateChange: ${state}`)
|
||||
if (state === 'poweredOn') {
|
||||
console.log(`Started advertising BLE serveses as ${BLE_ID}`)
|
||||
bleno.startAdvertising(BLENO_DEVICE_NAME, [BLE_ID])
|
||||
} else {
|
||||
bleno.stopAdvertising()
|
||||
}
|
||||
})
|
||||
|
||||
bleno.on('advertisingStart', err => {
|
||||
console.log('on -> advertisingStart: ' + (err ? 'error ' + err : 'success'))
|
||||
createChars()
|
||||
if (!err) {
|
||||
bleno.setServices([
|
||||
new bleno.PrimaryService({
|
||||
uuid : SERVICE_ID, //hardcoded across panels
|
||||
characteristics : chars
|
||||
})
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
bleno.on('accept', clientAddress => {
|
||||
console.log(`${clientAddress} accepted`)
|
||||
})
|
||||
|
||||
bleno.on('disconnect', clientAddress => {
|
||||
console.log(`${clientAddress} disconnected`)
|
||||
})
|
||||
|
||||
ipc.serve(() => {
|
||||
ipc.server.on('connect', socket => {
|
||||
ipc.log('Client connected to socket')
|
||||
})
|
||||
ipc.server.on('disconnect', () => {
|
||||
ipc.log('Client disconnected from socket')
|
||||
})
|
||||
ipc.server.on('data', (data, socket) => {
|
||||
ipc.server.emit(socket, JSON.stringify({}))
|
||||
})
|
||||
})
|
||||
|
||||
ipc.server.start()
|
|
@ -0,0 +1,16 @@
|
|||
'use strict'
|
||||
|
||||
QUnit.test('hello world', function (assert) {
|
||||
assert.ok(true, 'this is true')
|
||||
})
|
||||
|
||||
//sequence tests
|
||||
//db tests
|
||||
//onoffsim
|
||||
|
||||
//intval tests
|
||||
//ble tests
|
||||
//mscript tests
|
||||
//wifi tests
|
||||
|
||||
//log tests (tricky)
|