├── custom_firmware ├── file_system │ ├── VERSION │ └── PASSWORD ├── .browserslistrc ├── postcss.config.js ├── babel.config.js ├── public │ ├── favicon.ico │ └── index.html ├── lowsync.config.json ├── src │ ├── assets │ │ └── logo.png │ ├── store │ │ └── index.js │ ├── main.js │ ├── router │ │ └── index.js │ ├── Login.vue │ ├── App.vue │ └── views │ │ ├── Update.vue │ │ └── Settings.vue ├── server_buildenv │ └── babel.config.js ├── .eslintrc.js ├── lowbuild.config.json ├── test_server │ ├── update.js │ ├── settings.js │ └── index.js ├── package.json ├── Makefile ├── server │ ├── update.js │ ├── settings.js │ └── index.js └── README.txt ├── .gitmodules ├── general ├── chat_ws_webserver │ ├── www │ │ ├── favicon.png │ │ ├── styles.css │ │ └── index.html │ ├── node_modules │ │ ├── ws │ │ │ ├── browser.js │ │ │ ├── index.js │ │ │ └── lib │ │ │ │ ├── constants.js │ │ │ │ ├── validation.js │ │ │ │ ├── buffer-util.js │ │ │ │ ├── event-target.js │ │ │ │ ├── extension.js │ │ │ │ ├── sender.js │ │ │ │ └── websocket-server.js │ │ └── async-limiter │ │ │ ├── index.js │ │ │ └── coverage │ │ │ └── lcov-report │ │ │ └── sorter.js │ ├── README.md │ ├── server.crt │ ├── server.key │ └── index.js ├── repl_uart.js ├── uart_loop.js ├── can_self_test.js └── opcua_print_tree.js ├── neonious_one ├── table_radar │ ├── www │ │ ├── favicon.png │ │ ├── index.html │ │ └── index.js │ ├── README.md │ └── src │ │ └── index.js ├── gpio_opendrain_additional_leds.pdf ├── web_led_preinstalled_example │ ├── www │ │ ├── favicon.png │ │ ├── styles.css │ │ └── index.html │ ├── README.txt │ └── src │ │ └── index.js └── cellphone_controlled_rc_car │ ├── README.md │ ├── src │ └── index.js │ └── www │ └── index.html ├── .gitignore ├── README.md ├── esp32_wrover_kit └── hsv_rgb.js ├── LICENSE └── drivers ├── input ├── dinDebounce.ts └── i2cRotaryEncoder.ts ├── sensors ├── hc_sr04.js ├── dht11_22.js └── nmea_gps.js ├── displays └── ssd1306_sh1106.js └── industry ├── tmcm-3212-tmcl.js └── X20BC008U.js /custom_firmware/file_system/VERSION: -------------------------------------------------------------------------------- 1 | v1.0.0 2 | -------------------------------------------------------------------------------- /custom_firmware/file_system/PASSWORD: -------------------------------------------------------------------------------- 1 | customfirmware -------------------------------------------------------------------------------- /custom_firmware/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /custom_firmware/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /custom_firmware/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /custom_firmware/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonious/lowjs_esp32_examples/HEAD/custom_firmware/public/favicon.ico -------------------------------------------------------------------------------- /custom_firmware/lowsync.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ip": "192.168.0.1", 3 | "syncDir": "file_system", 4 | "transpile": false 5 | } 6 | -------------------------------------------------------------------------------- /custom_firmware/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonious/lowjs_esp32_examples/HEAD/custom_firmware/src/assets/logo.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "native/low_native_api"] 2 | path = native/low_native_api 3 | url = https://www.github.com/neonious/low_native_api 4 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/www/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonious/lowjs_esp32_examples/HEAD/general/chat_ws_webserver/www/favicon.png -------------------------------------------------------------------------------- /neonious_one/table_radar/www/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonious/lowjs_esp32_examples/HEAD/neonious_one/table_radar/www/favicon.png -------------------------------------------------------------------------------- /neonious_one/gpio_opendrain_additional_leds.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonious/lowjs_esp32_examples/HEAD/neonious_one/gpio_opendrain_additional_leds.pdf -------------------------------------------------------------------------------- /neonious_one/web_led_preinstalled_example/www/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neonious/lowjs_esp32_examples/HEAD/neonious_one/web_led_preinstalled_example/www/favicon.png -------------------------------------------------------------------------------- /custom_firmware/server_buildenv/babel.config.js: -------------------------------------------------------------------------------- 1 | const presets = [ 2 | [ 3 | "@babel/env", 4 | { 5 | useBuiltIns: false, 6 | }, 7 | ], 8 | ]; 9 | 10 | module.exports = { presets }; 11 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/browser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function () { 3 | throw new Error('ws does not work in the browser. Browser clients must use the native ' + 4 | 'WebSocket object'); 5 | }; 6 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var WebSocket = require('./lib/websocket'); 3 | WebSocket.Server = require('./lib/websocket-server'); 4 | WebSocket.Receiver = require('./lib/receiver'); 5 | WebSocket.Sender = require('./lib/sender'); 6 | module.exports = WebSocket; 7 | -------------------------------------------------------------------------------- /neonious_one/cellphone_controlled_rc_car/README.md: -------------------------------------------------------------------------------- 1 | # Cell phone controlled RC car built with JavaScript 2 | 3 | With fine grained controls. Built with some electronics and programmed 4 | with just a few lines of JavaScript code. 5 | 6 | See https://www.neonious.com/Blog/index.php/2019/04/05/cell-phone-controlled-rc-car/ for a description of the project! 7 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'], 4 | GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 5 | kStatusCode: Symbol('status-code'), 6 | kWebSocket: Symbol('websocket'), 7 | EMPTY_BUFFER: Buffer.alloc(0), 8 | NOOP: function () { } 9 | }; 10 | -------------------------------------------------------------------------------- /custom_firmware/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | rules: { 11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 13 | }, 14 | parserOptions: { 15 | parser: 'babel-eslint' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | /custom_firmware/firmware.dat 5 | 6 | /custom_firmware/dist 7 | /custom_firmware/file_system/dist 8 | /custom_firmware/file_system/server 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /general/repl_uart.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Provides the Node.JS REPL (read-eval-print loop) 3 | * on the serial port most generic ESP32 boards have. 4 | * 5 | * Note: low.js commands run asyncronly! Try copying together 6 | * two examples for more fun! 7 | */ 8 | 9 | 'use strict'; 10 | 11 | let uart = require('uart'); 12 | let repl = require('repl'); 13 | 14 | let stream = new uart.UART({pinRX: 3, pinTX: 1, baud: 115200}); 15 | repl.start({input: stream, output: stream, terminal: true}); 16 | -------------------------------------------------------------------------------- /custom_firmware/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import router from '../router' 4 | 5 | Vue.use(Vuex) 6 | 7 | export default new Vuex.Store({ 8 | state: { 9 | loginToken: null 10 | }, 11 | mutations: { 12 | goLoginOnLoginPage(state, token) { 13 | state.loginToken = token; 14 | }, 15 | doLogoutOnLoginPage(state) { 16 | state.loginToken = null; 17 | } 18 | }, 19 | actions: { 20 | logout() { 21 | router.replace({name: "login"}); 22 | } 23 | } 24 | }) 25 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/README.md: -------------------------------------------------------------------------------- 1 | Simple webserver with chat functionality via WebSocket made for low.js/Node.JS 2 | 3 | Run with bin/low lowjs-webserver-example/index.js from lowjs directory 4 | and then open the links given with the web browser. 5 | 6 | The https link will give a certificate warning, which is correct, because 7 | the certificate is self-signed. 8 | 9 | On low.js based microcontrollers you can alternatively not copy node_modules and install ws 10 | from the graphical package manager. This has the side effect of faster 11 | program startup as the packages from the package manager are packed more 12 | efficiently than separate files. 13 | -------------------------------------------------------------------------------- /custom_firmware/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import App from './App.vue' 4 | import router from './router' 5 | import store from './store' 6 | 7 | Vue.config.productionTip = false 8 | 9 | Vue.prototype.$http = axios; 10 | axios.defaults.validateStatus = (status) => { return status == 200 || status == 401; } 11 | 12 | /* 13 | axios.defaults.timeout = 20000; 14 | Vue.config.errorHandler = (err) => { 15 | alert('An error occurred. Please make sure the connection to the device is stable and try again!'); 16 | }; 17 | */ 18 | 19 | new Vue({ 20 | router, 21 | store, 22 | render: h => h(App) 23 | }).$mount('#app') 24 | -------------------------------------------------------------------------------- /custom_firmware/lowbuild.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "lowjs": { 3 | "version": "20200105", 4 | "pro": true, 5 | "system_flash_size": 8388608, 6 | "ide_support": false, 7 | "ota_update_support": true 8 | }, 9 | "static_files": "file_system/", 10 | "factory_files": null, 11 | "modules": {}, 12 | "settings": { 13 | "code": { 14 | "main": "/server/index.js", 15 | "auto_restart_on_fatal": true, 16 | "console_kb": 0, 17 | "only_static_files": true 18 | }, 19 | "wifi": { 20 | "ssid": "Custom Firmware Example", 21 | "password": "customfirmware" 22 | }, 23 | "web": { 24 | "http_enabled": false, 25 | "https_enabled": false 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /neonious_one/web_led_preinstalled_example/README.txt: -------------------------------------------------------------------------------- 1 | Welcome to your neonious one! 2 | 3 | The program entry point is /src/index.js . 4 | 5 | /www/ contains the static files your program serves to the user. The contents 6 | of this directory are not read by the JavaScript runtime on the board, but 7 | rather is read by the program (/src/index.js) and served to the user. Thus, all 8 | JS files in /www/ run in the browser, not on neonious one. 9 | 10 | The official documentation of the board can be found at 11 | 12 | https://www.neonious.com/Documentation 13 | 14 | For any questions, we are happy to help you! Contact us at info@neonious.com at 15 | any time. 16 | 17 | -- The neonious team 18 | -------------------------------------------------------------------------------- /custom_firmware/test_server/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const VERSION = 'v1.0.0 test server'; 4 | 5 | exports.handleGetVersion = function(res) { 6 | res.end(JSON.stringify({version: VERSION})); 7 | } 8 | 9 | exports.handleFirmware = function(req, res) { 10 | res.end(JSON.stringify({error: 'No support for updating the test server!'})); 11 | return; 12 | 13 | // In real life, data will be streamed to lowsys.createFirmwareStream 14 | let len = 0; 15 | req.on('data', (data) => { 16 | len += data.length; 17 | }); 18 | req.on('end', () => { 19 | console.log("Firmware length: ", len); 20 | res.end(JSON.stringify({success: true})); 21 | }); 22 | } -------------------------------------------------------------------------------- /neonious_one/table_radar/www/index.html: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | Table Radar 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /custom_firmware/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Login from '../Login.vue' 4 | import Settings from '../views/Settings.vue' 5 | import Update from '../views/Update.vue' 6 | 7 | Vue.use(VueRouter); 8 | 9 | const routes = [ 10 | { 11 | path: '/', 12 | redirect: { 13 | name: "login" 14 | } 15 | }, 16 | { 17 | name: 'login', 18 | path: '/Login', 19 | component: Login 20 | }, 21 | { 22 | name: 'settings', 23 | path: '/Settings', 24 | component: Settings 25 | }, 26 | { 27 | name: 'update', 28 | path: '/Update', 29 | component: Update 30 | } 31 | ] 32 | 33 | const router = new VueRouter({ 34 | mode: 'history', 35 | base: process.env.BASE_URL, 36 | routes 37 | }) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /custom_firmware/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "custom_firmware", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@babel/cli": "^7.7.7", 12 | "axios": "^0.19.0", 13 | "core-js": "^3.3.2", 14 | "vue": "^2.6.10", 15 | "vue-router": "^3.1.3", 16 | "vuex": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "^4.0.0", 20 | "@vue/cli-plugin-eslint": "^4.0.0", 21 | "@vue/cli-plugin-router": "^4.0.0", 22 | "@vue/cli-plugin-vuex": "^4.0.0", 23 | "@vue/cli-service": "^4.0.0", 24 | "babel-eslint": "^10.0.3", 25 | "eslint": "^5.16.0", 26 | "eslint-plugin-vue": "^5.0.0", 27 | "vue-template-compiler": "^2.6.10" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/validation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | try { 3 | var isValidUTF8 = require('utf-8-validate'); 4 | exports.isValidUTF8 = typeof isValidUTF8 === 'object' 5 | ? isValidUTF8.Validation.isValidUTF8 // utf-8-validate@<3.0.0 6 | : isValidUTF8; 7 | } 8 | catch (e) /* istanbul ignore next */ { 9 | exports.isValidUTF8 = function () { return true; }; 10 | } 11 | /** 12 | * Checks if a status code is allowed in a close frame. 13 | * 14 | * @param {Number} code The status code 15 | * @return {Boolean} `true` if the status code is valid, else `false` 16 | * @public 17 | */ 18 | exports.isValidStatusCode = function (code) { 19 | return ((code >= 1000 && 20 | code <= 1013 && 21 | code !== 1004 && 22 | code !== 1005 && 23 | code !== 1006) || 24 | (code >= 3000 && code <= 4999)); 25 | }; 26 | -------------------------------------------------------------------------------- /custom_firmware/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | low.js Example Program 9 | 18 | 19 | 20 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lowjs_esp32_examples 2 | Example projects and drivers for neonious one and low.js for ESP32 3 | 4 | 5 | # lowjs 6 | 7 | low.js is a free to use and open sourced port of the JavaScript runtime Node.js with far lower system requirements, allowing it to run on cheap, power-efficient microcontroller boards based on the ESP32-WROVER module. 8 | It also runs on Linux based systems as a smaller, faster booting alternative to Node.JS. 9 | 10 | For more information on low.js, please visit http://www.lowjs.org/ . 11 | 12 | 13 | ## Native API 14 | 15 | For an example on how to use the native API, see 16 | 17 | https://github.com/neonious/low_native_api 18 | 19 | 20 | ## Contributing 21 | 22 | We appreciate every person or company who is willing to contribute to low.js and its related products. We will gladly accept any code contribution which helps the cause after an appropriate review. Bug reports and suggestions are also welcome! 23 | -------------------------------------------------------------------------------- /custom_firmware/Makefile: -------------------------------------------------------------------------------- 1 | # You need to set the following parameters according to the documentation 2 | PRO_KEY=... 3 | 4 | FLASH_PORT=... 5 | 6 | # Flashing is a lot faster with this setting 7 | # Only enable if you have a perfectly fine USB connection 8 | #FLASH_BAUD=-b 921600 9 | FLASH_BAUD= 10 | 11 | 12 | all: build 13 | 14 | clean: 15 | rm -rf dist file_system/dist file_system/server 16 | 17 | # Build firmware for flashing or OTA updating 18 | build-parts: clean 19 | npm run build 20 | (cd server_buildenv; npx babel ../server --out-dir ../file_system/server) 21 | ln -s ../dist file_system/dist 22 | 23 | build: build-parts 24 | lowsync build --firmware-config=lowbuild.config.json firmware.dat 25 | 26 | # Flash firmware 27 | flash: build 28 | lowsync flash --init --firmware-file=firmware.dat --pro-key=$(PRO_KEY) --port=$(FLASH_PORT) $(FLASH_BAUD) 29 | 30 | # Run local test version 31 | run-test: build-parts 32 | node test_server/ 33 | -------------------------------------------------------------------------------- /esp32_wrover_kit/hsv_rgb.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Animates the RGB LEDs on the back of the ESP-WROVER-KIT 3 | * from red -> green -> blue -> red 4 | * 5 | * Note: low.js commands run asyncronly! Try copying together 6 | * two examples for more fun! 7 | */ 8 | 9 | 'use strict'; 10 | 11 | let gpio = require('gpio'); 12 | 13 | const FRAME_MS = 30; 14 | const FRAME_DIST = 1 / FRAME_MS; 15 | 16 | gpio.setFrequency(60); 17 | gpio.pins[0].setType(gpio.OUTPUT); // red 18 | gpio.pins[2].setType(gpio.OUTPUT); // green 19 | gpio.pins[4].setType(gpio.OUTPUT); // blue 20 | 21 | let at = 0; 22 | let dist = 0; 23 | 24 | function animate() { 25 | while (true) { 26 | dist += FRAME_DIST; 27 | if (dist <= 1) break; 28 | 29 | gpio.pins[at * 2].setValue(0); 30 | 31 | dist -= 1; 32 | at = (at + 1) % 3; 33 | } 34 | 35 | gpio.pins[at * 2].setValue(1 - dist); 36 | gpio.pins[((at + 1) % 3) * 2].setValue(dist); 37 | } 38 | setInterval(animate, FRAME_MS); -------------------------------------------------------------------------------- /general/uart_loop.js: -------------------------------------------------------------------------------- 1 | /* 2 | * uart_loop.js 3 | * 4 | * This program demonstrates the ability to communicate via UART 5 | * and the ability of to connect pins internally to a loop back. 6 | * It writes "Hello world!" via one UART and reads it via another 7 | * UART from the same pin. 8 | */ 9 | 10 | let uart = require('uart'); 11 | 12 | let pipeIn = new uart.UART({ 13 | pinRX: 14, 14 | pinTX: 13 15 | }); 16 | // Pipe out must be created after pipe in, because creating 17 | // pipe in later would set the pin mode wrong. Nothing to worry 18 | // about in real life, as nobody would use UART to simply loop 19 | // back on one pin... 20 | let pipeOut = new uart.UART({ 21 | pinRX: 13, 22 | pinTX: 14 23 | }); 24 | 25 | pipeOut.setEncoding('utf8'); 26 | pipeIn.setEncoding('utf8'); 27 | 28 | let txt = ''; 29 | pipeIn.on('data', (chunk) => { 30 | txt += chunk; 31 | }); 32 | 33 | pipeOut.write('Hello world!'); 34 | setTimeout(() => { 35 | console.log(txt); 36 | 37 | // Close application 38 | pipeIn.destroy(); 39 | pipeOut.destroy(); 40 | }, 1000); 41 | -------------------------------------------------------------------------------- /custom_firmware/src/Login.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC0DCCAbgCCQDiFEBwjK/Q7zANBgkqhkiG9w0BAQsFADAqMREwDwYDVQQKDAhu 3 | ZW9uaW91czEVMBMGA1UEAwwMbmVvbmlvdXMub25lMB4XDTE4MDUxMDE2MTcwNloX 4 | DTI4MDUwNzE2MTcwNlowKjERMA8GA1UECgwIbmVvbmlvdXMxFTATBgNVBAMMDG5l 5 | b25pb3VzLm9uZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKjCzMcr 6 | bFuy6YImpDNPEyYxw+nQAmrITFDS8qSk4L4OPU1MV9Epv9ehLNGFleEnVJVs021W 7 | 5dxy15tznQriI1t2tLU/Mz0gnxenY2UAAOd9njUcjgimehY+/mRbQXGQCkvoeRRs 8 | noAroP1jUJbOMtSFkB2gwCyxFeivWEAqKZJSLI1OAq1BmexoKGPT7QRN5ynWv1TM 9 | zyX0/DGbL+ghWKr2FkueSokiYJNfj9Kl6KbT5nVj+bTZgtSfUSv26EHKZB5NciJv 10 | 2Jl2392sMk8PhkTU7K8L4v6d50bzc8v6X8P5SvPINTdfCo28y0HUc1bIUhVgIQi4 11 | t/Dbc/xCxEo/zNUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEASUFHGGPsAOx6fmCA 12 | CPkvc/Y5ud9+vrleCzGXmUcYikChBTXw3KJQtvpDUfJ12PkLAEwUfhMWD7vholOO 13 | q+DqTzZao9sDFasQg2cpUFhFXj/YL/nWMq3L8rlrccmmKB8Cc/iiUa6e0ukhaoUK 14 | 6MPwwGj7eL5cUXguVgOmlhvgzyfO/SSqc+LKL46l16EB18bpETS07aeW13vKZTeC 15 | d5GVkvhbyn2m63HRsGjikq1NOwmH4Ror6TTb5u/BvBX60dVExpK8spFJSafINPW4 16 | jXqzfSuyaIhVHQyTdpsmQSFf9cdcAwq28eYIYUaMO95QEYmA/95fCnUoCCNteSIV 17 | gX3TQQ== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 neonious GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /custom_firmware/server/update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const lowsys = require('lowsys'); 5 | 6 | let version = fs.readFileSync('/VERSION', 'utf8'); 7 | 8 | exports.handleGetVersion = function(res) { 9 | res.end(JSON.stringify({version})); 10 | } 11 | 12 | exports.handleFirmware = function(req, res) { 13 | let stream; 14 | try { 15 | stream = lowsys.createFirmwareStream(); 16 | } catch(e) { 17 | res.end(JSON.stringify({ 18 | error: e.message 19 | })); 20 | return; 21 | } 22 | 23 | req.on('data', (data) => { 24 | stream.write(data); 25 | }); 26 | req.on('end', () => { 27 | stream.end(); 28 | }); 29 | 30 | let done = false; 31 | stream.on('error', (e) => { 32 | if(done) 33 | return; 34 | done = true; 35 | stream.destroy(); 36 | 37 | res.end(JSON.stringify({error: e.message})); 38 | }); 39 | stream.on('finish', () => { 40 | if(done) 41 | return; 42 | done = true; 43 | stream.destroy(); 44 | 45 | res.end(JSON.stringify({success: true})); 46 | setTimeout(() => { 47 | lowsys.restart(true); 48 | }, 5000); 49 | }); 50 | } -------------------------------------------------------------------------------- /custom_firmware/src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 39 | 40 | -------------------------------------------------------------------------------- /neonious_one/table_radar/README.md: -------------------------------------------------------------------------------- 1 | # Table Radar 2 | 3 | This is the table radar, an example project for the neonious one microcontroller board. 4 | 5 | It implements an radar for your table, which detects items in short range. You can view the radar through your web browser. 6 | 7 | A video of the table radar in action: https://drive.google.com/file/d/1G6cykg5DXdzrsAG8MDHQRWeSNzXArNSn/view 8 | 9 | 10 | ## Hardware setup 11 | 12 | The hardware consists of 13 | 14 | - a neonious one microcontroller board serving the website and controlling motor + sensor. More information about the neonious one board: http://www.neonious.com/ 15 | 16 | - a servo motor (a motor which you can position at any angle) 17 | - a distance sensor mounted onto the servo motor with glue 18 | - a 5 V power supply 19 | 20 | The servo motor is just one we picked of the shelf. You can take any one, as the protocol is standarized (one pulse of about 1 ms to position the servo). 21 | 22 | The distance sensor is the Pololu Carrier with Sharp GP2Y0A60SZLF Analog Distance Sensor 10-150cm, 3V. It gives out an analog signal which we can read out easily with the ADC (analog to digital converter) of the board. There are cheaper options, but I had one left from an older project. 23 | 24 | The servo motor is connected to the board via pin 6. The distance sensor is connected via pin 8 (which supports ADC). 25 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/www/styles.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | * { 6 | font-size: 14pt; 7 | font-family: Arial, Helvetica, sans-serif; 8 | text-align: center; 9 | } 10 | 11 | body { 12 | position: absolute; 13 | left: 0; 14 | top: 0; 15 | width: 100%; 16 | height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | body>div { 23 | background: #eeeeff; 24 | box-shadow: 0 0 220px 125px #eeeeff; 25 | max-width: 790px; 26 | border-radius: 30px; 27 | padding: 20px; 28 | } 29 | 30 | 31 | small { 32 | font-size: 12pt; 33 | } 34 | 35 | input[type=text] { 36 | border: 1px solid black; 37 | width: 460px; 38 | text-align: left; 39 | padding: 5px; 40 | } 41 | input[type=submit] { 42 | -webkit-appearance: none; 43 | margin-left: 5px; 44 | } 45 | 46 | a { 47 | text-decoration: none; 48 | } 49 | a:hover { 50 | color: #8888ff; 51 | } 52 | 53 | .welcome { 54 | font-size: 30pt; 55 | font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; 56 | letter-spacing: 5px; 57 | } 58 | #messages { 59 | background-color: white; 60 | padding: 5px; 61 | width: 460px; 62 | height: 100px; 63 | border: 1px solid black; 64 | text-align: left; 65 | overflow-y: scroll; 66 | } -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/async-limiter/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | function Queue(options) { 3 | if (!(this instanceof Queue)) { 4 | return new Queue(options); 5 | } 6 | options = options || {}; 7 | this.concurrency = options.concurrency || Infinity; 8 | this.pending = 0; 9 | this.jobs = []; 10 | this.cbs = []; 11 | this._done = done.bind(this); 12 | } 13 | var arrayAddMethods = [ 14 | 'push', 15 | 'unshift', 16 | 'splice' 17 | ]; 18 | arrayAddMethods.forEach(function (method) { 19 | Queue.prototype[method] = function () { 20 | var methodResult = Array.prototype[method].apply(this.jobs, arguments); 21 | this._run(); 22 | return methodResult; 23 | }; 24 | }); 25 | Object.defineProperty(Queue.prototype, 'length', { 26 | get: function () { 27 | return this.pending + this.jobs.length; 28 | } 29 | }); 30 | Queue.prototype._run = function () { 31 | if (this.pending === this.concurrency) { 32 | return; 33 | } 34 | if (this.jobs.length) { 35 | var job = this.jobs.shift(); 36 | this.pending++; 37 | job(this._done); 38 | this._run(); 39 | } 40 | if (this.pending === 0) { 41 | while (this.cbs.length !== 0) { 42 | var cb = this.cbs.pop(); 43 | process.nextTick(cb); 44 | } 45 | } 46 | }; 47 | Queue.prototype.onDone = function (cb) { 48 | if (typeof cb === 'function') { 49 | this.cbs.push(cb); 50 | this._run(); 51 | } 52 | }; 53 | function done() { 54 | this.pending--; 55 | this._run(); 56 | } 57 | module.exports = Queue; 58 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAqMLMxytsW7LpgiakM08TJjHD6dACashMUNLypKTgvg49TUxX 3 | 0Sm/16Es0YWV4SdUlWzTbVbl3HLXm3OdCuIjW3a0tT8zPSCfF6djZQAA532eNRyO 4 | CKZ6Fj7+ZFtBcZAKS+h5FGyegCug/WNQls4y1IWQHaDALLEV6K9YQCopklIsjU4C 5 | rUGZ7GgoY9PtBE3nKda/VMzPJfT8MZsv6CFYqvYWS55KiSJgk1+P0qXoptPmdWP5 6 | tNmC1J9RK/boQcpkHk1yIm/YmXbf3awyTw+GRNTsrwvi/p3nRvNzy/pfw/lK88g1 7 | N18KjbzLQdRzVshSFWAhCLi38Ntz/ELESj/M1QIDAQABAoIBAAvqLPEG5NCYkhnp 8 | RoCE3bI9ozpXwEPvvkuaiR0Msv1YHgXeFPkkfHHp5DMqvkk2MKeYFllK4FGvHf/Q 9 | qs7A5oQnnlyJiMGREwplfAP+exHc8ZWIXZLIxifZ7LYDJ1ywMbnoQNwrSwl7ixM4 10 | ttDe4AHXKDSfS9Ib8pMKBi4JjAPTgg3LfYcui6mcELqt/sd3Cv1GKNDmIxXpxc5Q 11 | eDy43/bBAJx597GkpLW0mmpmx3EvlrEaRjC0BxCKg/NrTiUnRJW/zfyS6gKOeZWd 12 | mGgNbxrbErW7vNDbrMt742QhRkqyqwvkf10/RRTlLfOaQJz4Bu7AO3912A1diU2q 13 | 1WB+80UCgYEA0fcN5jm9/sEbhgV8n6TBD6Sk5k65Wvqb9g7sQFaixZqtITqu/tuA 14 | 6qua51FN115ATXwJGdhSJfyUFdDSK3Sd4b+21leIv9JoU3c5bYFTV0nn5W7J5rGG 15 | STPRtjMf8nIWH6jOFEBW6nV62euKeUHTKduIkdGNyaM0AMaER/JnMB8CgYEAzcMJ 16 | GQoAKCKEcN1VLKY2j7AQs56BchBpQB0ipnhenNJgHokEwNP0NfudCX50yoWLsUel 17 | KbmMvgXSqPaZcniqgRiT21fFl2VgiS+qCiX9vOEF1st9/ESnw75xa0TUUVNiKOH9 18 | p8/hUpl87iPf2NFeEDnjYr/Ouh40uCL7Au/B1IsCgYEAow/cYo07ierNBlOiipU4 19 | Pn7edeJzvz1b/EdwoTLnbNGIXYMmvtUYFKg7QCR7cKRJCiQnKUQ/4DJ9i0fGwWrq 20 | 5pSuWV5X3Kl6zj+MgQfsAqFqEFvvP1Nld9pfpsGjsPV7SxEdIsso9SNRyoZL34oI 21 | hbxyHtUA6bTuwR9rQjj2fH8CgYEAuw5SqAOybsoqctyUARo9wdTC6jfv3/1RYarb 22 | BFGrWY/bOAdCXfaSLb1HXwkqLa7Idur2GH+Jlnp+r4I3r5xJDUGFsUMS63aLE0xU 23 | nhM5oEtFXqbyKrNTfJ0WSPlyt1hBhX8ldTiD0+6bZ3cPmfGEPOYTdIOA8Sxiqv+K 24 | CHpkUM8CgYATpSv5J+p5xTlcWk2gkX4JTT44EP31RXYRvBzwOm3DiJRB78JU6aOg 25 | 6aqo5/Qv3JqwAz/X2mMcRw+DuuvOXyGYXryqbzr3e82bY4Yp0rFyi1LrM72v6HIT 26 | b72rJqOKu+TZjzvp8qgPcQtiJ/qZsUmPNfXIMVm9IizAsfByg9wL+A== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /neonious_one/web_led_preinstalled_example/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * neonious one example program 3 | * 4 | * Written by neonious, modify as you wish. 5 | */ 6 | 7 | let http = require('http'); 8 | let fs = require('fs'); 9 | let gpio = require('gpio'); 10 | 11 | // Just thrown in to show how to detect a button press 12 | // Note: This is for the left button, the right button is reset! 13 | gpio.pins[gpio.BUTTON].on('fall', () => { 14 | console.log('Button pressed!') 15 | }); 16 | 17 | function writeStatic(path, res) { 18 | fs.readFile('/www/' + path, (err, data) => { 19 | if (err) { 20 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 21 | res.end('fs.readFile error: ' + err); 22 | return; 23 | } 24 | 25 | let contentType = 'text/html'; 26 | if (path.substr(-4) == '.png') 27 | contentType = 'image/png'; 28 | else if (path.substr(-3) == '.js') 29 | contentType = 'text/javascript'; 30 | 31 | res.writeHead(200, { 'Content-Type': contentType }); 32 | res.end(data); 33 | }); 34 | } 35 | 36 | let ledRedState = false, ledGreenState = false; 37 | 38 | http.createServer(function (req, res) { 39 | // Callbacks 40 | if (req.url == '/ToggleGreen') { 41 | res.end(); 42 | console.log("Toggling green LED"); 43 | ledGreenState = !ledGreenState; 44 | gpio.pins[gpio.LED_GREEN].setValue(ledGreenState); 45 | } else if (req.url == '/ToggleRed') { 46 | res.end(); 47 | console.log("Toggling red LED"); 48 | ledRedState = !ledRedState; 49 | gpio.pins[gpio.LED_RED].setValue(ledRedState); 50 | } else { 51 | writeStatic(req.url == '/' ? '/index.html' : req.url, res); 52 | } 53 | }).listen(80); 54 | 55 | console.log("Web server running!"); 56 | -------------------------------------------------------------------------------- /custom_firmware/test_server/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let gSettings = { 4 | password: 'customfirmware', 5 | wifi: { 6 | ssid: 'Custom Firmware Example', 7 | password: 'customfirmware' 8 | } 9 | }; 10 | 11 | 12 | exports.getPassword = function() { 13 | return gSettings.password; 14 | } 15 | 16 | exports.handleGetSettings = function(res) { 17 | let settings = { 18 | ...gSettings, 19 | wifi: {...gSettings.wifi}}; 20 | 21 | delete settings.password; 22 | settings.wifi.password = settings.wifi.password !== ''; 23 | 24 | res.end(JSON.stringify({settings})); 25 | } 26 | 27 | exports.handleSetSettings = function(body, res) { 28 | let error = {wifi: {}}; 29 | let hasError = false; 30 | if(body.settings.password && body.settings.password.length > 0 && body.settings.password.length < 4) { 31 | error.password = 'Too short'; 32 | hasError = true; 33 | } 34 | if(body.settings.wifi.ssid && body.settings.wifi.ssid.length > 0 && body.settings.wifi.ssid.length < 4) { 35 | error.wifi.ssid = 'Too short'; 36 | hasError = true; 37 | } 38 | if(body.settings.wifi.password && body.settings.wifi.password.length > 0 && body.settings.wifi.password.length < 4) { 39 | error.wifi.password = 'Too short'; 40 | hasError = true; 41 | } 42 | if(hasError) { 43 | res.end(JSON.stringify({error})); 44 | return; 45 | } 46 | 47 | if(body.settings.password !== undefined) 48 | gSettings.password = body.settings.password || ''; 49 | if(body.settings.wifi.ssid) 50 | gSettings.wifi.ssid = body.settings.wifi.ssid; 51 | if(body.settings.wifi.password !== undefined) 52 | gSettings.wifi.password = body.settings.wifi.password || ''; 53 | 54 | console.log("New settings: ", JSON.stringify(gSettings, null, 4)); 55 | res.end(JSON.stringify({success: true})); 56 | } -------------------------------------------------------------------------------- /neonious_one/web_led_preinstalled_example/www/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | font-size: 18pt; 7 | font-family: Arial, Helvetica, sans-serif; 8 | text-align: center; 9 | } 10 | 11 | body>div { 12 | background: #eeeeff; 13 | box-shadow: 0 0 220px 125px #eeeeff; 14 | max-width: 790px; 15 | border-radius: 30px; 16 | padding: 20px; 17 | } 18 | 19 | a { 20 | text-decoration: none; 21 | } 22 | 23 | a:hover { 24 | color: #8888ff; 25 | } 26 | 27 | .welcome { 28 | font-family: Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif; 29 | letter-spacing: 5px; 30 | } 31 | 32 | .header { 33 | letter-spacing: 2px; 34 | font-size: 18pt; 35 | margin-bottom: 25px; 36 | margin-top: 40px; 37 | } 38 | 39 | .buttons { 40 | display: flex; 41 | justify-content: center; 42 | } 43 | 44 | .buttons .button { 45 | color: black; 46 | font-size: 14pt; 47 | padding: 25px; 48 | flex: 0 0 180px; 49 | border-radius: 10px; 50 | box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 51 | transition-duration: 0.4s; 52 | margin-bottom: 5px; 53 | } 54 | 55 | #err { 56 | border: 3px dashed red; 57 | color: red; 58 | background-color: white; 59 | padding: 20px; 60 | } 61 | 62 | .buttons .button:hover { 63 | box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), 0 17px 50px 0 rgba(0, 0, 0, 0.19); 64 | } 65 | 66 | .buttons .button:active { 67 | transition: 0.05s; 68 | box-shadow: 0px 0px 0px 0px black; 69 | } 70 | 71 | .button.red { 72 | margin-right: 20px; 73 | background-color: rgb(255, 107, 107); 74 | } 75 | 76 | .button.green { 77 | background-color: rgb(86, 201, 86); 78 | } 79 | 80 | #headerSSL { 81 | font-size: 13pt; 82 | } 83 | 84 | .external td { 85 | padding-right: 20px; 86 | padding-bottom: 20px; 87 | } -------------------------------------------------------------------------------- /drivers/input/dinDebounce.ts: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * A module to debounce digital input 4 | * 5 | * Written in TypeScript. Use tsc to convert it to js if needed: 6 | * 7 | * npm install -g typescript // installs typescript 8 | * tsc dinDebounce.ts // generates dinDebounce.js 9 | * 10 | **** HOW TO USE IT *** 11 | let gpio = require('gpio'); 12 | 13 | import { dinDebounce } from "./dinDebounce"; 14 | 15 | // Arguments: PIN Number, Debouncevalue in ms, pass through reference, callback function 16 | 17 | let myinput=new dinDebounce(27,gpio.INPUT,200,'Switch1',(pinValue,ref)=> { 18 | console.log('New debounced pin state for '+ref+': '+pinValue); 19 | }); 20 | 21 | */ 22 | class dinDebounce { 23 | private gpio: any; 24 | private pin: number; 25 | private pinState: number; 26 | private db: number; 27 | private cb: any; 28 | private int: any = undefined; 29 | private ref: any; 30 | 31 | constructor(pin: number, mode: number, db: number, ref:any, cb: any) { 32 | this.gpio = require('gpio'); 33 | 34 | this.pin = pin; 35 | this.db = db; 36 | this.cb = cb; 37 | this.ref = ref; 38 | 39 | this.gpio.pins[pin].setType(mode); 40 | 41 | this.gpio.pins[pin].on('rise', () => { 42 | if(this.db===0) { 43 | this.cb(this.ref,1); 44 | } else { 45 | this.debounce(1); 46 | } 47 | }); 48 | this.gpio.pins[pin].on('fall', () => { 49 | if(this.db===0) { 50 | this.cb(this.ref,0); 51 | } else { 52 | this.debounce(0); 53 | } 54 | }); 55 | } 56 | debounce(pinState: number): void { 57 | this.pinState=pinState; 58 | if(this.int===undefined) { 59 | this.int=setTimeout(()=> { 60 | this.int=undefined; 61 | this.cb(this.ref,this.pinState); 62 | },this.db); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /custom_firmware/server/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const lowsys = require('lowsys'); 5 | 6 | let gPassword = fs.readFileSync('/PASSWORD', 'utf8'); 7 | 8 | exports.getPassword = function() { 9 | return gPassword; 10 | } 11 | 12 | exports.handleGetSettings = function(res) { 13 | let wifi = lowsys.settings.wifi; 14 | let settings = { 15 | wifi: { 16 | ssid: wifi.ssid, 17 | password: wifi.password 18 | }}; 19 | settings.wifi.password = settings.wifi.password !== ''; 20 | 21 | res.end(JSON.stringify({settings})); 22 | } 23 | 24 | exports.handleSetSettings = function(body, res) { 25 | let error = {wifi: {}}; 26 | let hasError = false; 27 | if(body.settings.password && body.settings.password.length > 0 && body.settings.password.length < 4) { 28 | error.password = 'Too short'; 29 | hasError = true; 30 | } 31 | if(body.settings.wifi.ssid && body.settings.wifi.ssid.length > 0 && body.settings.wifi.ssid.length < 4) { 32 | error.wifi.ssid = 'Too short'; 33 | hasError = true; 34 | } 35 | if(body.settings.wifi.password && body.settings.wifi.password.length > 0 && body.settings.wifi.password.length < 4) { 36 | error.wifi.password = 'Too short'; 37 | hasError = true; 38 | } 39 | if(hasError) { 40 | res.end(JSON.stringify({error})); 41 | return; 42 | } 43 | 44 | if(body.settings.password !== undefined) { 45 | gPassword = body.settings.password || ''; 46 | fs.writeFile('/PASSWORD', gPassword, () => {}); 47 | } 48 | 49 | let wifi = {}; 50 | if(body.settings.wifi.ssid) 51 | wifi.ssid = body.settings.wifi.ssid; 52 | if(body.settings.wifi.password !== undefined) 53 | wifi.password = body.settings.wifi.password || ''; 54 | 55 | res.end(JSON.stringify({success: true})); 56 | if(body.settings.wifi.ssid || body.settings.wifi.password !== undefined) 57 | setTimeout(() => { lowsys.setSettings({wifi}); }, 3000); 58 | } -------------------------------------------------------------------------------- /general/can_self_test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * can_self_test.js 3 | * 4 | * Simple example program which tests whether the CAN transceiver 5 | * is wired correctly by sending a message and receiving it itself. 6 | * 7 | * When it will fail: 8 | * - CAN transceiver does not report own messages on RX pin 9 | * - No resistor between CAN high and low, so bus cannot 10 | * go back into CAN high == CAN low state 11 | */ 12 | 13 | // Pins to use. Must be ESP32 native pins 14 | const PIN_RX = 26; // use 24-26 on neonious one 15 | const PIN_TX = 25; // use 24-25 on neonious one 16 | 17 | // ID to use 18 | const MY_ID = 123; 19 | const MY_ID_LEN = 11; // 11 or 29 20 | 21 | // Setup CAN peripherial 22 | let can = require('can'); 23 | let intf = new can.CAN({ 24 | mode: can.MODE_NO_ACK, // do not wait for ACK 25 | pinRX: PIN_RX, 26 | pinTX: PIN_TX, 27 | filter: {id: MY_ID, id_len: MY_ID_LEN} 28 | }); 29 | 30 | // Handle everything which can go wrong 31 | let timer = setTimeout(() => { 32 | failed('timed out'); 33 | }, 5000); 34 | 35 | function failed(e) { 36 | if(timer) { 37 | clearTimeout(timer); 38 | timer = null; 39 | } 40 | intf.unref(); // make program exit 41 | 42 | console.log('Failed: ', e); 43 | } 44 | 45 | intf.on('error', (e) => { 46 | failed('error: ' + e); 47 | }); 48 | intf.on('rxMissed', () => { 49 | failed('rx queue full'); 50 | }); 51 | intf.on('arbLost', () => { 52 | failed('arbitration lost, should only happen with one shot messages'); 53 | }); 54 | intf.on('stateChanged', () => { 55 | if(intf.state == can.STATE_ERR_PASSIVE) 56 | failed('state changed to passive'); 57 | else if(intf.state == can.STATE_BUS_OFF) 58 | failed('state changed to bus-off. You have to call intf.recover()'); 59 | }); 60 | 61 | intf.on('message', (data, id, id_len, flags) => { 62 | if(data.toString() == 'hello!' && id == MY_ID && id_len == MY_ID_LEN) { 63 | // We have the message 64 | if(timer) { 65 | clearTimeout(timer); 66 | timer = null; 67 | } 68 | intf.unref(); // make program exit 69 | 70 | console.log('Success, sent and received messsage!'); 71 | } else 72 | failed('message received is different than sent') 73 | }); 74 | 75 | // Send a message 76 | intf.transmit(Buffer.from("hello!"), MY_ID, MY_ID_LEN, can.RECV_SELF); 77 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/buffer-util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Merges an array of buffers into a new buffer. 4 | * 5 | * @param {Buffer[]} list The array of buffers to concat 6 | * @param {Number} totalLength The total length of buffers in the list 7 | * @return {Buffer} The resulting buffer 8 | * @public 9 | */ 10 | function concat(list, totalLength) { 11 | var target = Buffer.allocUnsafe(totalLength); 12 | var offset = 0; 13 | for (var i = 0; i < list.length; i++) { 14 | var buf = list[i]; 15 | buf.copy(target, offset); 16 | offset += buf.length; 17 | } 18 | return target; 19 | } 20 | /** 21 | * Masks a buffer using the given mask. 22 | * 23 | * @param {Buffer} source The buffer to mask 24 | * @param {Buffer} mask The mask to use 25 | * @param {Buffer} output The buffer where to store the result 26 | * @param {Number} offset The offset at which to start writing 27 | * @param {Number} length The number of bytes to mask. 28 | * @public 29 | */ 30 | function _mask(source, mask, output, offset, length) { 31 | for (var i = 0; i < length; i++) { 32 | output[offset + i] = source[i] ^ mask[i & 3]; 33 | } 34 | } 35 | /** 36 | * Unmasks a buffer using the given mask. 37 | * 38 | * @param {Buffer} buffer The buffer to unmask 39 | * @param {Buffer} mask The mask to use 40 | * @public 41 | */ 42 | function _unmask(buffer, mask) { 43 | // Required until https://github.com/nodejs/node/issues/9006 is resolved. 44 | var length = buffer.length; 45 | for (var i = 0; i < length; i++) { 46 | buffer[i] ^= mask[i & 3]; 47 | } 48 | } 49 | try { 50 | var bufferUtil = require('bufferutil'); 51 | var bu_1 = bufferUtil.BufferUtil || bufferUtil; 52 | module.exports = { 53 | mask: function (source, mask, output, offset, length) { 54 | if (length < 48) 55 | _mask(source, mask, output, offset, length); 56 | else 57 | bu_1.mask(source, mask, output, offset, length); 58 | }, 59 | unmask: function (buffer, mask) { 60 | if (buffer.length < 32) 61 | _unmask(buffer, mask); 62 | else 63 | bu_1.unmask(buffer, mask); 64 | }, 65 | concat: concat 66 | }; 67 | } 68 | catch (e) /* istanbul ignore next */ { 69 | module.exports = { concat: concat, mask: _mask, unmask: _unmask }; 70 | } 71 | -------------------------------------------------------------------------------- /general/opcua_print_tree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * opcua_print_tree.js 3 | * 4 | * Simple example program which prints the tree of an OPC-UA server 5 | * It waits for the Ethernet cable to be connected before running 6 | * 7 | * Before trying this, set the URL to point to the device you are using 8 | * 9 | * Note: If you cannot connect to your server, you might have restarted 10 | * your program to often by pressing Stop. This way destroy() is 11 | * not called and the connection is not closed gracefully. 12 | * The server will need some time to timeout. 13 | */ 14 | 15 | let mod = require('opc-ua'); 16 | let lowsys = require('lowsys'); 17 | 18 | function waitForEthernet(cb) { 19 | if(lowsys.status.eth == 'CONNECTED') 20 | return cb(); 21 | 22 | console.log("Waiting for Ethernet cable to be connected..."); 23 | 24 | // To make sure low.js does not exit 25 | let interval = setInterval(() => {}, 100000); 26 | process.once('lowsysStatusChange', () => { 27 | console.log("Connected."); 28 | 29 | clearInterval(interval); 30 | this.waitForEthernet(cb); 31 | }); 32 | } 33 | 34 | function handleNode(node, prefix, callback) { 35 | console.log(prefix + node.namespace + ':' + node.node + ' / ' + node.browseName + ' / ' + node.displayName); 36 | 37 | // Required because the nodes loop back to earlier nodes => without this we get an infinite tree 38 | if(prefix.length > 12) { 39 | console.log(prefix + ' ...'); 40 | callback(); 41 | return; 42 | } 43 | 44 | node.children((err, arrayOfNodes) => { 45 | if(err) 46 | return console.error(err); 47 | 48 | function handleChild() { 49 | if(arrayOfNodes.length == 0) 50 | callback(); 51 | else 52 | handleNode(arrayOfNodes.shift(), prefix + ' ', handleChild); 53 | } 54 | handleChild(); 55 | }); 56 | } 57 | 58 | waitForEthernet(() => { 59 | let client = new mod.UAClient({ 60 | url: "opc.tcp://192.168.1.1:4840", 61 | }); 62 | client.on('connect', () => { 63 | // lookupProps adds browseName and displayName (done automatically by children(), 64 | // but not with direct access of nodes because this requires a call to the device) 65 | // at the end, the node given by lookupProbs is client.root, but has the properties 66 | client.root.lookupProps((err, node) => { 67 | if(err) 68 | return console.error(err); 69 | 70 | handleNode(node, '', () => { 71 | client.destroy(); 72 | }); 73 | }); 74 | }); 75 | client.on('error', (e) => { 76 | console.log("error with connect or idle connection things: ", e); 77 | }); 78 | }); -------------------------------------------------------------------------------- /general/chat_ws_webserver/index.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var https = require('https'); 3 | var fs = require('fs'); 4 | var tls = require('tls'); 5 | var path = require('path'); 6 | 7 | var wwwPath = path.join(__dirname, 'www'); 8 | function handleRequest(req, res) { 9 | var url = req.url == '/' ? '/index.html' : req.url; 10 | 11 | // add more as required... 12 | var contentType; 13 | if (url.substr(-4) == '.png') 14 | contentType = 'image/png'; 15 | else if (url.substr(-4) == '.css') 16 | contentType = 'text/css'; 17 | else 18 | contentType = 'text/html'; 19 | res.setHeader("Content-Type", contentType); 20 | 21 | console.log("streaming " + wwwPath + url); 22 | 23 | var stream = fs.createReadStream(wwwPath + url); 24 | stream.on('error', function (err) { 25 | res.statusCode = 404; 26 | res.end(err.message); 27 | }); 28 | stream.pipe(res); 29 | } 30 | 31 | var httpServer = http.createServer(handleRequest).listen(0, function (err) { 32 | if (!err) 33 | console.log('listening on http://localhost:' + httpServer.address().port + '/'); 34 | }); 35 | 36 | var options = { 37 | key: fs.readFileSync(path.join(__dirname, 'server.key')), 38 | cert: fs.readFileSync(path.join(__dirname, 'server.crt')) 39 | }; 40 | var httpsServer = https.createServer(options, handleRequest).listen(0, function (err) { 41 | if (!err) 42 | console.log('listening on https://localhost:' + httpsServer.address().port + '/ with self-signed certificate (warning is OK)'); 43 | }); 44 | 45 | 46 | // ***** WebSocket server for chat 47 | // remove this code to remove chat functionality 48 | 49 | var WebSocket = require('ws'); 50 | var wss = new WebSocket.Server({ noServer: true }); 51 | 52 | wss.on('connection', function connection(ws) { 53 | ws.on('message', function incoming(data) { 54 | console.log("broadcasting message: " + data); 55 | wss.clients.forEach(function each(client) { 56 | if (client !== ws && client.readyState === WebSocket.OPEN) 57 | client.send(data); 58 | }); 59 | }); 60 | }); 61 | function upgradeToWSS(req, socket, head) { 62 | if (req.url === '/Chat') { 63 | console.log("webbrowser connected to chat"); 64 | 65 | wss.handleUpgrade(req, socket, head, function done(ws) { 66 | wss.emit('connection', ws, req); 67 | }); 68 | } else 69 | socket.destroy(); 70 | } 71 | 72 | httpServer.on('upgrade', upgradeToWSS); 73 | httpsServer.on('upgrade', upgradeToWSS); 74 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Welcome to low.js! 6 | 7 | 8 | 48 | 49 | 50 | 51 |
52 |

Welcome to low.js!

53 | 54 |

This page is served by the JavaScript program you just started.

55 |

Add files to www directory to serve them, too!
(Do not forget to add mime type in 56 | handleRequest() first.)

57 | 58 |

Chat with everybody else having this website open:
(uses 59 | WebSockets)

60 |

61 |

63 |

64 |

Chat window:
(Own messages are grey.)

65 |
66 |
67 |
68 | 69 | 70 | -------------------------------------------------------------------------------- /custom_firmware/src/views/Update.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /neonious_one/cellphone_controlled_rc_car/src/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * index.js 3 | * The microcontroller application 4 | * 5 | * Cell phone controlled RC car built with JavaScript 6 | * See https://www.neonious-basics.com/?p=209 for the project details 7 | */ 8 | 9 | const http = require('http'); 10 | const fs = require('fs'); 11 | 12 | const gpio = require('gpio'); 13 | const signal = require('signal'); 14 | 15 | // The ws module must be installed. If you are not using the neonious one API but lowsync, 16 | // you must copy the module and required submodules yourself 17 | const WebSocket = require('ws'); 18 | 19 | // The pins of the motor. Connects to 20 | let pinMotorA = 20; 21 | let pinMotorB = 21; 22 | let pinMotorPWM = 25; 23 | 24 | // The pin of the steering servo 25 | let pinSteer = 17; 26 | 27 | gpio.pins[pinMotorA].setType(gpio.OUTPUT).setValue(0); 28 | gpio.pins[pinMotorB].setType(gpio.OUTPUT).setValue(0); 29 | gpio.pins[pinMotorPWM].setType(gpio.OUTPUT).setValue(0); 30 | gpio.pins[pinSteer].setType(gpio.OUTPUT).setValue(0); 31 | 32 | // Serve a static file from the www directory 33 | function writeStatic(path, res) { 34 | fs.readFile('/www/' + path, (err, data) => { 35 | if (err) { 36 | res.writeHead(200, { 'Content-Type': 'text/plain' }); 37 | res.end('fs.readFile error: ' + err); 38 | return; 39 | } 40 | 41 | let contentType = 'text/html'; 42 | if (path.substr(-4) == '.png') 43 | contentType = 'image/png'; 44 | else if (path.substr(-3) == '.js') 45 | contentType = 'text/javascript'; 46 | // add more if needed 47 | 48 | res.writeHead(200, { 'Content-Type': contentType }); 49 | res.end(data); 50 | }); 51 | } 52 | 53 | let httpServer = http.createServer(function (req, res) { 54 | writeStatic(req.url == '/' ? '/index.html' : req.url, res); 55 | }); 56 | httpServer.listen(80); 57 | 58 | let wss = new WebSocket.Server({ noServer: true }); 59 | wss.on('connection', function connection(ws) { 60 | ws.on('message', function incoming(data) { 61 | // Lets handle a message with coordinates. The message is a JSON in the format {x: ..., y: ...}. The coordinates are all between -1 and 1 62 | 63 | let vals = JSON.parse(data); 64 | if(vals.y < 0) { 65 | // Drive backwards 66 | gpio.pins[pinMotorA].setValue(0); 67 | gpio.pins[pinMotorB].setValue(1); 68 | gpio.pins[pinMotorPWM].setValue(vals.y <= -1 ? 1 : -vals.y); 69 | } else { 70 | // Drive forwards 71 | gpio.pins[pinMotorA].setValue(1); 72 | gpio.pins[pinMotorB].setValue(0); 73 | gpio.pins[pinMotorPWM].setValue(vals.y >= 1 ? 1 : vals.y); 74 | } 75 | 76 | // Send a pulse with a duration linear to the angle which we want to steer at 77 | // The formula is based on trial and error and depends on your actual servo 78 | // The pulse is sent again and again to keep the angle in place 79 | signal.send(signal.RESTART, [{index: pinSteer, setEvents: signal.EVENT_1, clearEvents: signal.EVENT_2}], [0, 500000 + (vals.x * 0.1 + 0.41) * 2000000, 10000000], () => {}); 80 | }); 81 | }); 82 | 83 | function upgradeToWSS(req, socket, head) { 84 | if (req.url === '/coords') { 85 | wss.handleUpgrade(req, socket, head, function done(ws) { 86 | wss.emit('connection', ws, req); 87 | }); 88 | } else 89 | socket.destroy(); 90 | } 91 | httpServer.on('upgrade', upgradeToWSS); -------------------------------------------------------------------------------- /neonious_one/web_led_preinstalled_example/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to neonious one! 5 | 6 | 7 | 37 | 38 | 39 | 40 |
41 |

Welcome to neonious one!

42 |

This page is served by a JavaScript example program preloaded on your neonious one.

43 |

Try out your board:

44 |
45 | Toggle Red LED 46 | Toggle Green LED 47 |
48 | 49 | 51 | 55 |

External resources:

56 | 57 | 58 | 61 | 64 | 65 |
59 | https://www.neonious.com/Documentation 60 | Be creative on what to do with this board! Get educated what you 62 | can do with this board here. 63 |
66 |
67 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /custom_firmware/src/views/Settings.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | -------------------------------------------------------------------------------- /neonious_one/cellphone_controlled_rc_car/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | Controller 17 | 18 | 19 | 20 | 21 | 22 | 135 | -------------------------------------------------------------------------------- /custom_firmware/README.txt: -------------------------------------------------------------------------------- 1 | Custom Firmware Example 2 | 3 | 4 | This example shows how to 5 | 6 | - Create a low.js project which is compiled into a custom firmware image. This firmware image can then be bulked flashed onto ESP32-WROVER devices or used to update the device over the website served by the low.js program (Over-The-Air updating) 7 | 8 | - Program a password protected single-page website application in Vue, to be served from the microcontroller. On this website, you can change settings (Wifi SSID & password, website password) and, as written above, upload a newer version of the firmware image to update the device through the website. 9 | 10 | For more information on low.js custom firmware, see the documentation at https://www.neonious.com/lowjs/documentation/custom-firmware.html 11 | 12 | 13 | ***** HOW TO COMPILE AND RUN ***** 14 | 15 | In the full version of this example, you need a key for low.js Professional (see https://www.neonious.com/Store ), as it shows how to use Over-The-Air updating. 16 | 17 | If you want to run this example without Over-The-Air updating, you can set the values for the keys "pro" and "ota_update_support" to false in lowbuild.config.json. Then you do not need the key, but you will still need a ESP32-WROVER board with 16 MB Flash. 18 | 19 | With the key: 20 | 21 | - Install lowsync (see https://www.neonious.com/lowjs/examples/getting-started.html) 22 | 23 | - Call npm install 24 | 25 | - Set the two variables PRO_KEY and FLASH_PORT in the Makefile 26 | 27 | - Call make flash 28 | 29 | If you do not have make installed, you can run the commands directly, as written in the Makefile (there are not to many commands which the Makefile calls). 30 | 31 | - Connect to the Wifi "Custom Firmware Example" with the password "customfirmware" 32 | 33 | - Login to the website with the password "customfirmware" 34 | 35 | 36 | ***** HOW TO USE THE TEST SERVER ***** 37 | 38 | The project includes a test server (implemented in the test_server directory) which runs on the PC with Node.JS and which you can start with 39 | 40 | make run-test 41 | 42 | This way, you do not need to flash an ESP32-WROVER device for every change of the website. 43 | 44 | 45 | ***** STATIC FILES / SETTINGS ***** 46 | 47 | The following settings, specified in lowbuild.config.json, are relevant to know: 48 | 49 | This project includes the user code files as static files. They are interleaved with the editable file system, however cannot be changed or deleted. 50 | 51 | This way no byte of the editable file system is used for the user code files, also static files are far faster than file system files, especially if the setting only_static_files is used, as done here. 52 | 53 | Because the factory files entry is set to null, a factory reset of the board will simply clear all editable files, keeping only the static files. 54 | 55 | The ports for neonious IDE/lowsync are closed, so they are not accessable. 56 | 57 | 58 | ***** REPOSITORY FILES / DIRECTORIES ***** 59 | 60 | The project consists of following parts: 61 | 62 | - Vue CLI based client/webbrowser project 63 | - The microcontroller JavaScript source code 64 | 65 | In detail, these parts use the following files/directories: 66 | 67 | 68 | ** Vue CLI based client: 69 | 70 | public: Static files to be served to the webbrowser 71 | 72 | src: The client code 73 | It is transpiled by Vue CLI which uses Babel, so you can use newest JavaScript. 74 | 75 | dist: The compiled output 76 | dist/ is linked to file_system/dist, so on the microcontroller it ends up in /dist/ 77 | 78 | package.json: 79 | package-lock.json: 80 | Holds dependencies and script commands for the Vue CLI part 81 | 82 | babel.config.js: Config file which is used for transpilation of the client 83 | postcss.config.js: Yet anouther config of Vue CLI 84 | 85 | 86 | ** The microcontroller JavaScript source code 87 | 88 | server: The source code 89 | It is transpiled by lowsync which uses Babel, so you can use newest JavaScript. 90 | 91 | server/ is transpiled by the Makefile to file_system/server/. The lowsync built-in transpilation is not used (disabled in lowsync.config.json), because this would also transpile the Vue project, which is already transpiled by Vue CLI. 92 | 93 | 94 | ** All together 95 | 96 | file_system: 97 | This is the file system structure of the mirocontroller. 98 | Most content is just linked from other directories. lowsync will always follow the links, 99 | not use the symbolic links themselves. 100 | 101 | lowbuild.config.json 102 | The config file for the firmware, see STATIC FILES / SETTINGS above. 103 | -------------------------------------------------------------------------------- /neonious_one/table_radar/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ----------------------------------------------------------------------------- 4 | // tableradar - /src/index.js 5 | // Written by Thomas Rogg , public domain 6 | // 7 | // Example program for neonious one 8 | // More information: http://www.neonious.com/ 9 | // 10 | // 05/25/2018 - initial version 11 | // 09/05/2018 - changes for program to work with neonious one software v1.1.0 12 | // 11/14/2018 - added note that process.gc is no longer really needed 13 | // ----------------------------------------------------------------------------- 14 | 15 | let http = require('http'); 16 | let fs = require('fs'); 17 | let gpio = require('gpio'); 18 | let signal = require('signal'); 19 | let events = require('events'); 20 | 21 | // This event emitter emits angle and distance sensor voltage 22 | // as event 'data'. HTTP server connections listen to this 23 | let radarEventEmitter = new events.EventEmitter(); 24 | 25 | /* 26 | // Remove comments to get live output into console log 27 | // Note that console log right now still is syncronous on neonious one, 28 | // so this slows down the animations. An asyncronious option would be 29 | // to use fs.writeFile 30 | radarEventEmitter.on('data', console.log); 31 | */ 32 | 33 | // Set base level of servo signal to low 34 | gpio.pins[6].setType(gpio.OUTPUT); 35 | gpio.pins[6].setValue(0); 36 | 37 | // We only do the radar if somebody conencts to our website 38 | let numStreams = 0, running = false; 39 | 40 | let last_ms = new Date().getTime(); 41 | let angle = 0, dir = 1; 42 | 43 | // radarStep 44 | // does one radar step by moving servo + getting distance sensor voltage 45 | // calls itself when both is done 46 | function radarStep() { 47 | // Explicitly call garbage collector to keep animation smooth 48 | // (not really needed in newer versions of low.js) 49 | if(process && process.gc) 50 | process.gc(); 51 | else 52 | require('neonious').gc(); // pre v1.1.0 53 | 54 | if(numStreams == 0) { 55 | // Stop animating if nobody connects via web browser 56 | running = false; 57 | return; 58 | } 59 | running = true; 60 | 61 | // Go back and forth.. left, right, left 62 | let ms = new Date().getTime(); 63 | let dur = ms - last_ms; 64 | last_ms = ms; 65 | angle += dir * dur * 0.0005; 66 | if(angle >= Math.PI) { 67 | dir = -1; 68 | angle = Math.PI; 69 | } 70 | if(angle <= 0) { 71 | dir = 1; 72 | angle = 0; 73 | } 74 | 75 | let donePart = 0; 76 | // Step 1/2: Get distance sensor voltage and emit data 77 | gpio.pins[8].getValue(gpio.ANALOG, (err, val) => { 78 | radarEventEmitter.emit('data', angle + " " + val + "\n"); 79 | if(++donePart == 2) 80 | radarStep(); 81 | }); 82 | // Step 2/2: Move servo motor 83 | // Done by sending a signal between 500 us and 2000 us (2 ms), depending on the angle 84 | // wanted. 85 | // We are working to support custom I/O protocols. But you can already use neonious one 86 | // to output arbritary short signals with up to 6 pins in nanosecond range, what we are 87 | // doing here 88 | signal.send(signal.ONESHOT, [{index: 6, setEvents: signal.EVENT_1, clearEvents: signal.EVENT_2}], [0, 500000 + angle * 2000000 / Math.PI], () => { 89 | if(++donePart == 2) 90 | radarStep(); 91 | }); 92 | } 93 | 94 | // Outputs a static file from /www directory 95 | function writeStatic(path, res) { 96 | fs.readFile('/www/' + path, (err, data) => { 97 | if (err) { 98 | res.writeHead(404); 99 | res.end('fs.readFile error: ' + err); 100 | return; 101 | } 102 | 103 | let contentType = 'text/html'; 104 | if (path.substr(-4) == '.png') 105 | contentType = 'image/png'; 106 | else if (path.substr(-3) == '.js') 107 | contentType = 'text/javascript'; 108 | 109 | res.writeHead(200, { 'Content-Type': contentType }); 110 | res.end(data); 111 | }); 112 | } 113 | 114 | // Our table radar is accessable over http! 115 | http.createServer(function (req, res) { 116 | // The data stream 117 | if (req.url == '/DataStream') { 118 | // res.write is a prototype function 119 | // this does not work with on/off directly, wrap 120 | // into closure 121 | let resWrite = function(txt) { 122 | res.write(txt); 123 | }; 124 | 125 | req.on('close', () => { 126 | radarEventEmitter.off('data', resWrite); 127 | numStreams--; 128 | }); 129 | // Do not throw fatal exceptions 130 | res.on('error', () => {}); 131 | req.on('error', () => {}); 132 | 133 | radarEventEmitter.on('data', resWrite); 134 | numStreams++; 135 | 136 | if(!running) 137 | radarStep(); 138 | } else { 139 | // Output static file 140 | writeStatic(req.url == '/' ? '/index.html' : req.url, res); 141 | } 142 | }).listen(80); 143 | -------------------------------------------------------------------------------- /custom_firmware/test_server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const crypto = require('crypto'); 7 | const urlMod = require('url'); 8 | 9 | const settings = require('./settings'); 10 | const update = require('./update'); 11 | 12 | const wwwPath = path.join(__dirname, '../dist'); 13 | 14 | let gLoginTokens = {}; 15 | 16 | const SESSION_TIMEOUT_MS = 2 * 3600 * 1000; 17 | 18 | function handleStatic(url, req, res) { 19 | var url = url == '/' ? '/index.html' : req.url; 20 | 21 | // add more as required... 22 | let contentType; 23 | let pos = url.lastIndexOf('.'); 24 | if(pos == -1) 25 | contentType = 'text/html'; 26 | else { 27 | let ending = url.substr(pos + 1); 28 | if(ending == 'html') 29 | contentType = 'text/html; charset=utf-8'; 30 | else if(ending == 'js') 31 | contentType = 'application/javascript; charset=utf-8'; 32 | else if(ending == 'map') 33 | contentType = 'application/json; charset=utf-8'; 34 | else if(ending == 'png') 35 | contentType = 'image/png'; 36 | else if(ending == 'ico') 37 | contentType = 'image/x-icon'; 38 | else if(ending == 'css') 39 | contentType = 'text/css; charset=utf-8'; 40 | else { 41 | contentType = 'text/plain'; 42 | console.error('unknown content type for ', url); 43 | } 44 | } 45 | res.setHeader("Content-Type", contentType); 46 | 47 | console.log('streaming ' + url); 48 | 49 | let stream = fs.createReadStream(wwwPath + url); 50 | stream.on('error', (err) => { 51 | let stream = fs.createReadStream(wwwPath + '/index.html'); 52 | stream.on('error', (err) => { 53 | res.statusCode = 500; 54 | res.end(); 55 | }); 56 | stream.pipe(res); 57 | }); 58 | stream.pipe(res); 59 | } 60 | 61 | function handleAPI(action, req, res, url) { 62 | console.log('api call ' + action); 63 | 64 | let now = new Date().getTime(); 65 | 66 | if(action == 'UploadFirmware') { 67 | let tim = gLoginTokens[url.query.token]; 68 | if(tim === undefined || now - tim >= SESSION_TIMEOUT_MS) { 69 | res.statusCode = 401; 70 | res.end(); 71 | return; 72 | } 73 | gLoginTokens[url.query.token] = now; 74 | 75 | res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); 76 | update.handleFirmware(req, res); 77 | return; 78 | } 79 | 80 | let body = []; 81 | let len = 0; 82 | 83 | req.on('data', (data) => { 84 | len += data.length; 85 | if(len >= 128 * 1024) { 86 | res.statusCode = 500; 87 | res.end(); 88 | } else 89 | body.push(data); 90 | }); 91 | req.on('end', () => { 92 | try { 93 | body = JSON.parse(Buffer.concat(body)); 94 | } catch(e) { 95 | res.statusCode = 500; 96 | res.end(); 97 | return; 98 | } 99 | 100 | res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); 101 | if(action == 'Login') { 102 | if(body.password != settings.getPassword()) { 103 | res.statusCode = 401; 104 | res.end(); 105 | return; 106 | } 107 | 108 | let token, tim; 109 | do { 110 | token = crypto.randomBytes(12).toString('hex'); 111 | tim = gLoginTokens[token]; 112 | } while(tim !== undefined && now - tim < SESSION_TIMEOUT_MS); 113 | 114 | gLoginTokens[token] = now; 115 | res.end(JSON.stringify({token})); 116 | return; 117 | } else if(action == 'Logout') { 118 | delete gLoginTokens[body.token]; 119 | res.end('{}'); 120 | } else { 121 | let tim = gLoginTokens[body.token]; 122 | if(tim === undefined || now - tim >= SESSION_TIMEOUT_MS) { 123 | res.statusCode = 401; 124 | res.end(); 125 | return; 126 | } 127 | gLoginTokens[body.token] = now; 128 | 129 | if(action == 'GetSettings') 130 | settings.handleGetSettings(res); 131 | else if(action == 'SetSettings') 132 | settings.handleSetSettings(body, res); 133 | else if(action == 'GetVersion') 134 | update.handleGetVersion(res); 135 | else { 136 | res.statusCode = 404; 137 | res.end(); 138 | } 139 | } 140 | }); 141 | } 142 | 143 | function handleRequest(req, res) { 144 | let url = urlMod.parse(req.url, true); 145 | if(url.pathname.substr(0, '/api/'.length) == '/api/') 146 | handleAPI(url.pathname.substr('/api/'.length), req, res, url); 147 | else 148 | handleStatic(url.pathname, req, res); 149 | } 150 | 151 | let httpServer = http.createServer(handleRequest).listen(3000, function (err) { 152 | if (!err) 153 | console.log('listening on http://localhost:' + httpServer.address().port + '/'); 154 | }); 155 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/async-limiter/coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | var addSorting = (function () { 2 | "use strict"; 3 | var cols, currentSort = { 4 | index: 0, 5 | desc: false 6 | }; 7 | // returns the summary table element 8 | function getTable() { return document.querySelector('.coverage-summary table'); } 9 | // returns the thead element of the summary table 10 | function getTableHeader() { return getTable().querySelector('thead tr'); } 11 | // returns the tbody element of the summary table 12 | function getTableBody() { return getTable().querySelector('tbody'); } 13 | // returns the th element for nth column 14 | function getNthColumn(n) { return getTableHeader().querySelectorAll('th')[n]; } 15 | // loads all columns 16 | function loadColumns() { 17 | var colNodes = getTableHeader().querySelectorAll('th'), colNode, cols = [], col, i; 18 | for (i = 0; i < colNodes.length; i += 1) { 19 | colNode = colNodes[i]; 20 | col = { 21 | key: colNode.getAttribute('data-col'), 22 | sortable: !colNode.getAttribute('data-nosort'), 23 | type: colNode.getAttribute('data-type') || 'string' 24 | }; 25 | cols.push(col); 26 | if (col.sortable) { 27 | col.defaultDescSort = col.type === 'number'; 28 | colNode.innerHTML = colNode.innerHTML + ''; 29 | } 30 | } 31 | return cols; 32 | } 33 | // attaches a data attribute to every tr element with an object 34 | // of data values keyed by column name 35 | function loadRowData(tableRow) { 36 | var tableCols = tableRow.querySelectorAll('td'), colNode, col, data = {}, i, val; 37 | for (i = 0; i < tableCols.length; i += 1) { 38 | colNode = tableCols[i]; 39 | col = cols[i]; 40 | val = colNode.getAttribute('data-value'); 41 | if (col.type === 'number') { 42 | val = Number(val); 43 | } 44 | data[col.key] = val; 45 | } 46 | return data; 47 | } 48 | // loads all row data 49 | function loadData() { 50 | var rows = getTableBody().querySelectorAll('tr'), i; 51 | for (i = 0; i < rows.length; i += 1) { 52 | rows[i].data = loadRowData(rows[i]); 53 | } 54 | } 55 | // sorts the table using the data for the ith column 56 | function sortByIndex(index, desc) { 57 | var key = cols[index].key, sorter = function (a, b) { 58 | a = a.data[key]; 59 | b = b.data[key]; 60 | return a < b ? -1 : a > b ? 1 : 0; 61 | }, finalSorter = sorter, tableBody = document.querySelector('.coverage-summary tbody'), rowNodes = tableBody.querySelectorAll('tr'), rows = [], i; 62 | if (desc) { 63 | finalSorter = function (a, b) { 64 | return -1 * sorter(a, b); 65 | }; 66 | } 67 | for (i = 0; i < rowNodes.length; i += 1) { 68 | rows.push(rowNodes[i]); 69 | tableBody.removeChild(rowNodes[i]); 70 | } 71 | rows.sort(finalSorter); 72 | for (i = 0; i < rows.length; i += 1) { 73 | tableBody.appendChild(rows[i]); 74 | } 75 | } 76 | // removes sort indicators for current column being sorted 77 | function removeSortIndicators() { 78 | var col = getNthColumn(currentSort.index), cls = col.className; 79 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 80 | col.className = cls; 81 | } 82 | // adds sort indicators for current column being sorted 83 | function addSortIndicators() { 84 | getNthColumn(currentSort.index).className += currentSort.desc ? ' sorted-desc' : ' sorted'; 85 | } 86 | // adds event listeners for all sorter widgets 87 | function enableUI() { 88 | var i, el, ithSorter = function ithSorter(i) { 89 | var col = cols[i]; 90 | return function () { 91 | var desc = col.defaultDescSort; 92 | if (currentSort.index === i) { 93 | desc = !currentSort.desc; 94 | } 95 | sortByIndex(i, desc); 96 | removeSortIndicators(); 97 | currentSort.index = i; 98 | currentSort.desc = desc; 99 | addSortIndicators(); 100 | }; 101 | }; 102 | for (i = 0; i < cols.length; i += 1) { 103 | if (cols[i].sortable) { 104 | el = getNthColumn(i).querySelector('.sorter'); 105 | if (el.addEventListener) { 106 | el.addEventListener('click', ithSorter(i)); 107 | } 108 | else { 109 | el.attachEvent('onclick', ithSorter(i)); 110 | } 111 | } 112 | } 113 | } 114 | // adds sorting functionality to the UI 115 | return function () { 116 | if (!getTable()) { 117 | return; 118 | } 119 | cols = loadColumns(); 120 | loadData(cols); 121 | addSortIndicators(); 122 | enableUI(); 123 | }; 124 | })(); 125 | window.addEventListener('load', addSorting); 126 | -------------------------------------------------------------------------------- /custom_firmware/server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const crypto = require('crypto'); 7 | const urlMod = require('url'); 8 | 9 | const settings = require('./settings'); 10 | const update = require('./update'); 11 | 12 | const wwwPath = path.join(__dirname, '../dist'); 13 | 14 | let gLoginTokens = {}; 15 | 16 | const SESSION_TIMEOUT_MS = 2 * 3600 * 1000; 17 | 18 | function handleStatic(url, req, res) { 19 | var url = url == '/' ? '/index.html' : req.url; 20 | 21 | // add more as required... 22 | let contentType; 23 | let pos = url.lastIndexOf('.'); 24 | if(pos == -1) 25 | contentType = 'text/html'; 26 | else { 27 | let ending = url.substr(pos + 1); 28 | if(ending == 'html') 29 | contentType = 'text/html; charset=utf-8'; 30 | else if(ending == 'js') 31 | contentType = 'application/javascript; charset=utf-8'; 32 | else if(ending == 'map') 33 | contentType = 'application/json; charset=utf-8'; 34 | else if(ending == 'png') 35 | contentType = 'image/png'; 36 | else if(ending == 'ico') 37 | contentType = 'image/x-icon'; 38 | else if(ending == 'css') 39 | contentType = 'text/css; charset=utf-8'; 40 | else { 41 | contentType = 'text/plain'; 42 | console.error('unknown content type for ', url); 43 | } 44 | } 45 | res.setHeader("Content-Type", contentType); 46 | 47 | console.log('streaming ' + url); 48 | 49 | let stream = fs.createReadStream(wwwPath + url); 50 | stream.on('error', (err) => { 51 | let stream = fs.createReadStream(wwwPath + '/index.html'); 52 | stream.on('error', (err) => { 53 | res.statusCode = 500; 54 | res.end(); 55 | }); 56 | stream.pipe(res); 57 | }); 58 | stream.pipe(res); 59 | } 60 | 61 | function handleAPI(action, req, res, url) { 62 | console.log('api call ' + action); 63 | 64 | let now = new Date().getTime(); 65 | 66 | if(action == 'UploadFirmware') { 67 | let tim = gLoginTokens[url.query.token]; 68 | if(tim === undefined || now - tim >= SESSION_TIMEOUT_MS) { 69 | res.statusCode = 401; 70 | res.end(); 71 | return; 72 | } 73 | gLoginTokens[url.query.token] = now; 74 | 75 | res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); 76 | update.handleFirmware(req, res); 77 | return; 78 | } 79 | 80 | let body = []; 81 | let len = 0; 82 | 83 | req.on('data', (data) => { 84 | len += data.length; 85 | if(len >= 128 * 1024) { 86 | res.statusCode = 500; 87 | res.end(); 88 | } else 89 | body.push(data); 90 | }); 91 | req.on('end', () => { 92 | try { 93 | body = JSON.parse(Buffer.concat(body)); 94 | } catch(e) { 95 | res.statusCode = 500; 96 | res.end(); 97 | return; 98 | } 99 | 100 | res.setHeader('Content-Type', 'application/javascript; charset=utf-8'); 101 | if(action == 'Login') { 102 | if(body.password != settings.getPassword()) { 103 | res.statusCode = 401; 104 | res.end(); 105 | return; 106 | } 107 | 108 | let token, tim; 109 | do { 110 | token = crypto.randomBytes(12).toString('hex'); 111 | tim = gLoginTokens[token]; 112 | } while(tim !== undefined && now - tim < SESSION_TIMEOUT_MS); 113 | 114 | gLoginTokens[token] = now; 115 | console.log("CALLING END WITH ", JSON.stringify({token})); 116 | res.end(JSON.stringify({token})); 117 | return; 118 | } else if(action == 'Logout') { 119 | delete gLoginTokens[body.token]; 120 | res.end('{}'); 121 | } else { 122 | let tim = gLoginTokens[body.token]; 123 | if(tim === undefined || now - tim >= SESSION_TIMEOUT_MS) { 124 | res.statusCode = 401; 125 | res.end(); 126 | return; 127 | } 128 | gLoginTokens[body.token] = now; 129 | 130 | if(action == 'GetSettings') 131 | settings.handleGetSettings(res); 132 | else if(action == 'SetSettings') 133 | settings.handleSetSettings(body, res); 134 | else if(action == 'GetVersion') 135 | update.handleGetVersion(res); 136 | else { 137 | res.statusCode = 404; 138 | res.end(); 139 | } 140 | } 141 | }); 142 | } 143 | 144 | function handleRequest(req, res) { 145 | let url = urlMod.parse(req.url, true); 146 | if(url.pathname.substr(0, '/api/'.length) == '/api/') 147 | handleAPI(url.pathname.substr('/api/'.length), req, res, url); 148 | else 149 | handleStatic(url.pathname, req, res); 150 | } 151 | 152 | let httpServer = http.createServer(handleRequest).listen(80, function (err) { 153 | if (!err) 154 | console.log('listening on http://localhost:' + httpServer.address().port + '/'); 155 | }); 156 | -------------------------------------------------------------------------------- /drivers/sensors/hc_sr04.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * A module to interface the HC-SR04 distance sensor module 5 | * 6 | * The HC-SR04 is an ultrasonic distance sensor module which measures distances between 7 | * 2 and ~300 cm. It is available from many vendors at a price from $ 1 up. 8 | * 9 | * TODO: make periodic measurement faster by using PWM instead of manually retriggering 10 | * 11 | * 12 | * *** HOW TO CONNECT *** 13 | * 14 | * The device is 5 V only, but as it accepts a 3.3 V signal we can still connect it 15 | * directly to the neonious one: 16 | * 17 | * GND connect to GND 18 | * VCC connect to 5 V (VIN of neonious one if powered from USB) 19 | * Trigger connect to any GPIO pin 20 | * Echo connect to 5 V tolerate pin (4 and 5 on neonious one) 21 | * 22 | * Do not connect Echo to any other pin directly as the other pins do not tolerate 5 V. 23 | * If you do not have a 5 V tolerate pin available you will have to shift the voltage. 24 | * 25 | * 26 | * *** HOW TO USE *** 27 | * 28 | * const HC_SR04 = require('./hc_sr04.js'); 29 | * 30 | * let sensor = new HC_SR04(11, // pinTrigger 31 | * 4); // pinEcho 32 | * sensor.measure((err, dist) => { 33 | * if(err) 34 | * console.log(err); 35 | * else 36 | * console.log('Distance: ' + dist + ' cm'); 37 | * }); 38 | * 39 | */ 40 | 41 | const gpio = require('gpio'); 42 | 43 | class HC_SR04 { 44 | /* 45 | * constructor 46 | * Sets up pins 47 | */ 48 | constructor(pinTrigger, pinEcho) { 49 | this._pinTrigger = pinTrigger; 50 | this._pinEcho = pinEcho; 51 | 52 | gpio.pins[pinTrigger].setType(gpio.OUTPUT); 53 | gpio.pins[pinTrigger].setValue(0); 54 | 55 | gpio.pins[pinEcho].setType(gpio.INPUT); 56 | 57 | this._riseCB = (v) => { this._rise(v); } 58 | this._fallCB = (v) => { this._fall(v); } 59 | gpio.pins[pinEcho].on('rise', this._riseCB); 60 | gpio.pins[pinEcho].on('fall', this._fallCB); 61 | } 62 | 63 | /* 64 | * close 65 | * Stops periodic measurement and resets pins 66 | */ 67 | close() { 68 | this.stop(); 69 | 70 | gpio.pins[this._pinTrigger].setType(gpio.INPUT); 71 | gpio.pins[this._pinEcho].off('rise', this._riseCB); 72 | gpio.pins[this._pinEcho].off('fall', this._fallCB); 73 | } 74 | 75 | /* 76 | * measure 77 | * Measures the distance once and calls callback with the result 78 | * 79 | * callback(err, dist) 80 | * - err an error if no distance could be measured 81 | * - dist the distance in cm if err is null 82 | */ 83 | measure(callback) { 84 | if(this._callback) 85 | throw new Error('already measuring'); 86 | 87 | this._callback = callback; 88 | this._timeout = setTimeout(() => { 89 | delete this._callback; 90 | 91 | if(this._againMS !== undefined) 92 | setTimeout(() => { 93 | this.measure(callback); 94 | }, this._againMS); 95 | callback(new Error('not connected')); 96 | }, 1000); 97 | delete this._riseStamp; 98 | 99 | gpio.pins[this._pinTrigger].setValue(1); 100 | gpio.pins[this._pinTrigger].setValue(0); 101 | } 102 | 103 | /* 104 | * start 105 | * Starts a periodic measurement, calls callback with every new result 106 | * 107 | * callback(err, dist) 108 | * - err an error if no distance could be measured (always retries) 109 | * - dist the distance in cm if err is null 110 | * waitMS time to wait between measurements (default: 200 ms) 111 | */ 112 | start(callback, waitMS) { 113 | if(this._callback) 114 | throw new Error('already measuring'); 115 | 116 | this._againMS = waitMS === undefined ? 200 : 0; 117 | this.measure(callback); 118 | } 119 | 120 | /* 121 | * stop 122 | * Stops periodic measurement, does not resets pins 123 | */ 124 | stop() { 125 | delete this._againMS; 126 | if(this._callback) { 127 | delete this._callback; 128 | clearTimeout(this._timeout); 129 | } 130 | } 131 | 132 | _rise(stamp) { 133 | if(this._callback) { 134 | this._riseStamp = stamp; 135 | 136 | clearTimeout(this._timeout); 137 | this._timeout = setTimeout(() => { 138 | let cb = this._callback; 139 | delete this._callback; 140 | 141 | if(this._againMS !== undefined) 142 | setTimeout(() => { 143 | this.measure(cb); 144 | }, this._againMS); 145 | cb(new Error('echo still high, not connected correctly?')); 146 | }, 1000); 147 | } 148 | } 149 | 150 | _fall(stamp) { 151 | if(this._riseStamp && this._callback) { 152 | let cb = this._callback; 153 | // Sound velocity 34.2 cm / ms at 20°C 154 | let val = 34.2 / 2 * (stamp - this._riseStamp); 155 | 156 | delete this._callback; 157 | clearTimeout(this._timeout); 158 | 159 | if(this._againMS !== undefined) 160 | setTimeout(() => { 161 | this.measure(cb); 162 | }, this._againMS); 163 | cb(null, val); 164 | } 165 | } 166 | } 167 | 168 | module.exports = HC_SR04; 169 | -------------------------------------------------------------------------------- /neonious_one/table_radar/www/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // ----------------------------------------------------------------------------- 4 | // tableradar - /www/index.js 5 | // Written by Thomas Rogg , public domain 6 | // 7 | // Example program for neonious one Technology Preview 8 | // More information: http://www.neonious.com/ 9 | // 10 | // This is not the main program file, but rather the JavaScript code 11 | // being sent to the user. It runs in the web browser, so we do not use 12 | // ES 6 features! 13 | // ----------------------------------------------------------------------------- 14 | 15 | var canvas, ctx; 16 | var canvasSize = []; 17 | 18 | var request; // the XMLHttpRequest 19 | var lastDataPos, lastDataTime; // for streaming 20 | 21 | var lastAngle; 22 | 23 | // This mapping from distance sensor voltage to distance in cm 24 | // was done by testing. There most probably is an easier way to figure this 25 | // out, but this works! 26 | var distance = []; 27 | distance[0.65] = 6; 28 | distance[0.53] = 8; 29 | distance[0.47] = 10; 30 | distance[0.41] = 12; 31 | distance[0.366] = 14; 32 | distance[0.327] = 16; 33 | distance[0.298] = 18; 34 | distance[0.28] = 20; 35 | distance[0.262] = 22; 36 | distance[0.252] = 24; 37 | /*distance[0.23] = 26; 38 | distance[0.21] = 28; 39 | distance[0.2] = 30; 40 | distance[0.197] = 32; 41 | distance[0.184] = 34; 42 | distance[0.178] = 36; 43 | distance[0.174] = 38; 44 | distance[0.168] = 40; 45 | distance[0.160] = 42; 46 | distance[0.15] = 44; 47 | */ 48 | 49 | var maxCM = 24; 50 | 51 | function volt2distance(volt) { 52 | var lower, upper; 53 | var lowerCM, upperCM; 54 | 55 | for(var distanceVolt in distance) { 56 | if(distanceVolt <= volt && (!lower || lower < distanceVolt)) { 57 | lower = distanceVolt; 58 | lowerCM = distance[distanceVolt]; 59 | } 60 | if(distanceVolt >= volt && (!upper || upper > distanceVolt)) { 61 | upper = distanceVolt; 62 | upperCM = distance[distanceVolt]; 63 | } 64 | } 65 | 66 | if(!lower) 67 | return upperCM / maxCM; 68 | if(!upper) 69 | return lowerCM / maxCM; 70 | 71 | var a = (volt - lower) / (upper - lower); 72 | return (upperCM * a + lowerCM * (1 - a)) / maxCM; 73 | } 74 | 75 | // Returns one line of data from the microcontroller (see /src/index.js) 76 | function getData() { 77 | var time = new Date().getTime(); 78 | 79 | // readyState 3 (did not recieve data fully) is what we need 80 | // readyState 4 means we are no longer streaming, so reconnect in this case 81 | if (!request || (request.readyState == 3 && request.status != 200) || request.readyState == 4 || time - lastDataTime > 30000) { 82 | lastDataTime = time; 83 | lastDataPos = 0; 84 | 85 | if (request) 86 | request.abort(); 87 | request = new XMLHttpRequest(); 88 | request.open("GET", "/DataStream"); 89 | request.send(); 90 | } 91 | if(request.readyState == 3) { 92 | // Get the next line from responseText 93 | var pos = request.responseText.indexOf("\n", lastDataPos + 1); 94 | if (pos != -1) { 95 | var line = request.responseText.substring(lastDataPos, pos); 96 | 97 | lastDataTime = time; 98 | lastDataPos = pos; 99 | 100 | return line; 101 | } 102 | } 103 | } 104 | 105 | // Called every 30 ms 106 | function step() { 107 | // Fade out current screen 108 | ctx.fillStyle = "rgb(0, 0, 0, 0.01)"; 109 | ctx.fillRect(0, 0, canvasSize[0], canvasSize[1]); 110 | 111 | var x = canvasSize[0] / 2; 112 | var y = canvasSize[1]; 113 | 114 | // Draw radar circles 115 | ctx.strokeStyle = "rgb(128, 255, 128)"; 116 | ctx.lineWidth = 3; 117 | 118 | for(var i = 1; i < 5; i++) { 119 | ctx.beginPath(); 120 | ctx.arc(x, y, x * i / 5, 0, 2 * Math.PI); 121 | ctx.closePath(); 122 | ctx.stroke(); 123 | } 124 | 125 | ctx.strokeStyle = "rgb(255, 255, 255)"; 126 | ctx.fillStyle = "rgb(0, 255, 0)"; 127 | 128 | while(true) { 129 | // Get a new line of data (angle + distance) from microcontroller 130 | var line = getData(); 131 | if (line) 132 | line = line.split(" "); 133 | if(!line || line.length != 2) 134 | return; // we do not have any new data? return 135 | 136 | var a = parseFloat(line[0]); 137 | var v = parseFloat(line[1]); 138 | 139 | // Convert distance sensor voltage to distance 140 | var d = volt2distance(v); 141 | 142 | // Draw line showing in the direction we are looking 143 | ctx.beginPath(); 144 | ctx.moveTo(x, y); 145 | ctx.lineTo(x + Math.cos(a) * x * 10, y - Math.sin(a) * x * 10); 146 | ctx.closePath(); 147 | ctx.stroke(); 148 | 149 | // 1.0 means we are at max distance, nothing there 150 | if(d < 1.0) { 151 | // Draw detected thing 152 | ctx.beginPath(); 153 | ctx.arc(x + Math.cos(a) * x * d, y - Math.sin(a) * x * d, x * 0.03, 0, 2 * Math.PI); 154 | ctx.closePath(); 155 | ctx.fill(); 156 | } 157 | } 158 | } 159 | 160 | function onInit() { 161 | // Website entry point 162 | canvas = document.getElementById("view"); 163 | ctx = canvas.getContext("2d"); 164 | 165 | window.onresize = onResize; 166 | onResize(); 167 | 168 | setInterval(step, 30); 169 | } 170 | 171 | function onResize() { 172 | // Our canvas should be as big as the window. No scaling! 173 | if (canvasSize[0] != canvas.offsetWidth 174 | || canvasSize[1] != canvas.offsetHeight) { 175 | canvasSize[0] = canvas.offsetWidth; 176 | canvasSize[1] = canvas.offsetHeight; 177 | 178 | canvas.width = canvasSize[0]; 179 | canvas.height = canvasSize[1]; 180 | 181 | ctx.fillStyle = "rgb(0, 0, 0, 1)"; 182 | ctx.fillRect(0, 0, canvasSize[0], canvasSize[1]); 183 | } 184 | } -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/event-target.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = function (d, b) { 4 | extendStatics = Object.setPrototypeOf || 5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 6 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 7 | return extendStatics(d, b); 8 | } 9 | return function (d, b) { 10 | extendStatics(d, b); 11 | function __() { this.constructor = d; } 12 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 13 | }; 14 | })(); 15 | /** 16 | * Class representing an event. 17 | * 18 | * @private 19 | */ 20 | var Event = /** @class */ (function () { 21 | /** 22 | * Create a new `Event`. 23 | * 24 | * @param {String} type The name of the event 25 | * @param {Object} target A reference to the target to which the event was dispatched 26 | */ 27 | function Event(type, target) { 28 | this.target = target; 29 | this.type = type; 30 | } 31 | return Event; 32 | }()); 33 | /** 34 | * Class representing a message event. 35 | * 36 | * @extends Event 37 | * @private 38 | */ 39 | var MessageEvent = /** @class */ (function (_super) { 40 | __extends(MessageEvent, _super); 41 | /** 42 | * Create a new `MessageEvent`. 43 | * 44 | * @param {(String|Buffer|ArrayBuffer|Buffer[])} data The received data 45 | * @param {WebSocket} target A reference to the target to which the event was dispatched 46 | */ 47 | function MessageEvent(data, target) { 48 | var _this = _super.call(this, 'message', target) || this; 49 | _this.data = data; 50 | return _this; 51 | } 52 | return MessageEvent; 53 | }(Event)); 54 | /** 55 | * Class representing a close event. 56 | * 57 | * @extends Event 58 | * @private 59 | */ 60 | var CloseEvent = /** @class */ (function (_super) { 61 | __extends(CloseEvent, _super); 62 | /** 63 | * Create a new `CloseEvent`. 64 | * 65 | * @param {Number} code The status code explaining why the connection is being closed 66 | * @param {String} reason A human-readable string explaining why the connection is closing 67 | * @param {WebSocket} target A reference to the target to which the event was dispatched 68 | */ 69 | function CloseEvent(code, reason, target) { 70 | var _this = _super.call(this, 'close', target) || this; 71 | _this.wasClean = target._closeFrameReceived && target._closeFrameSent; 72 | _this.reason = reason; 73 | _this.code = code; 74 | return _this; 75 | } 76 | return CloseEvent; 77 | }(Event)); 78 | /** 79 | * Class representing an open event. 80 | * 81 | * @extends Event 82 | * @private 83 | */ 84 | var OpenEvent = /** @class */ (function (_super) { 85 | __extends(OpenEvent, _super); 86 | /** 87 | * Create a new `OpenEvent`. 88 | * 89 | * @param {WebSocket} target A reference to the target to which the event was dispatched 90 | */ 91 | function OpenEvent(target) { 92 | return _super.call(this, 'open', target) || this; 93 | } 94 | return OpenEvent; 95 | }(Event)); 96 | /** 97 | * Class representing an error event. 98 | * 99 | * @extends Event 100 | * @private 101 | */ 102 | var ErrorEvent = /** @class */ (function (_super) { 103 | __extends(ErrorEvent, _super); 104 | /** 105 | * Create a new `ErrorEvent`. 106 | * 107 | * @param {Object} error The error that generated this event 108 | * @param {WebSocket} target A reference to the target to which the event was dispatched 109 | */ 110 | function ErrorEvent(error, target) { 111 | var _this = _super.call(this, 'error', target) || this; 112 | _this.message = error.message; 113 | _this.error = error; 114 | return _this; 115 | } 116 | return ErrorEvent; 117 | }(Event)); 118 | /** 119 | * This provides methods for emulating the `EventTarget` interface. It's not 120 | * meant to be used directly. 121 | * 122 | * @mixin 123 | */ 124 | var EventTarget = { 125 | /** 126 | * Register an event listener. 127 | * 128 | * @param {String} method A string representing the event type to listen for 129 | * @param {Function} listener The listener to add 130 | * @public 131 | */ 132 | addEventListener: function (method, listener) { 133 | if (typeof listener !== 'function') 134 | return; 135 | function onMessage(data) { 136 | listener.call(this, new MessageEvent(data, this)); 137 | } 138 | function onClose(code, message) { 139 | listener.call(this, new CloseEvent(code, message, this)); 140 | } 141 | function onError(error) { 142 | listener.call(this, new ErrorEvent(error, this)); 143 | } 144 | function onOpen() { 145 | listener.call(this, new OpenEvent(this)); 146 | } 147 | if (method === 'message') { 148 | onMessage._listener = listener; 149 | this.on(method, onMessage); 150 | } 151 | else if (method === 'close') { 152 | onClose._listener = listener; 153 | this.on(method, onClose); 154 | } 155 | else if (method === 'error') { 156 | onError._listener = listener; 157 | this.on(method, onError); 158 | } 159 | else if (method === 'open') { 160 | onOpen._listener = listener; 161 | this.on(method, onOpen); 162 | } 163 | else { 164 | this.on(method, listener); 165 | } 166 | }, 167 | /** 168 | * Remove an event listener. 169 | * 170 | * @param {String} method A string representing the event type to remove 171 | * @param {Function} listener The listener to remove 172 | * @public 173 | */ 174 | removeEventListener: function (method, listener) { 175 | var listeners = this.listeners(method); 176 | for (var i = 0; i < listeners.length; i++) { 177 | if (listeners[i] === listener || listeners[i]._listener === listener) { 178 | this.removeListener(method, listeners[i]); 179 | } 180 | } 181 | } 182 | }; 183 | module.exports = EventTarget; 184 | -------------------------------------------------------------------------------- /drivers/sensors/dht11_22.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * A module to interface the DHT11 or DHT22 temperature + humidity module 5 | * 6 | * 7 | * *** HOW TO CONNECT *** 8 | * 9 | * VCC (pin 1) connect to 3.3 V 10 | * Data (pin 2) connect to any GPIO pin, add 5K resistor to 3.3 V 11 | * GND (pin 4) connect to GND 12 | * 13 | * Pin 3 of DHT11/DHT22 is an unused pin. 14 | * 15 | * NOTE: Currently you must use pin 25 or pin 26 of neonious one, as the LPC822 pins 16 | * do not seem accurate enough for the DHT11. On other boards, you can use any pin. 17 | * 18 | * 19 | * *** HOW TO USE *** 20 | * 21 | * const DHT11_22 = require('./dht11.js'); 22 | * let sensor = new DHT11_22(7, true); // set to false for DHT11, true for DHT22 23 | * 24 | * sensor.measure((err, temp, humidity) => { 25 | * if(err) 26 | * console.log(err); 27 | * else { 28 | * console.log('Temperature: ' + temp + '°C'); 29 | * console.log('Humidity: ' + humidity + ' %'); 30 | * } 31 | * }); 32 | * 33 | */ 34 | 35 | const gpio = require('gpio'); 36 | 37 | class DHT11_22 { 38 | /* 39 | * constructor 40 | * Sets up pins 41 | */ 42 | constructor(pinData, isDHT22) { 43 | this._pinData = pinData; 44 | this._mult = isDHT22 ? 0.1 : 1 / 256; 45 | 46 | this._riseCB = (v) => { this._rise(v); } 47 | this._fallCB = (v) => { this._fall(v); } 48 | gpio.pins[pinData].on('rise', this._riseCB); 49 | gpio.pins[pinData].on('fall', this._fallCB); 50 | } 51 | 52 | /* 53 | * close 54 | * Stops periodic measurement and resets pins 55 | */ 56 | close() { 57 | this.stop(); 58 | 59 | gpio.pins[this._pinData].setType(gpio.INPUT); 60 | gpio.pins[this._pinData].off('rise', this._riseCB); 61 | gpio.pins[this._pinData].off('fall', this._fallCB); 62 | } 63 | 64 | /* 65 | * measure 66 | * Measures the distance once and calls callback with the result 67 | * 68 | * callback(err, dist) 69 | * - err an error if no distance could be measured 70 | * - dist the distance in cm if err is null 71 | */ 72 | measure(callback) { 73 | if(this._callback) 74 | throw new Error('already measuring'); 75 | 76 | this._callback = callback; 77 | this._timeout = setTimeout(() => { 78 | delete this._callback; 79 | 80 | if(this._againMS !== undefined) 81 | setTimeout(() => { 82 | this.measure(callback); 83 | }, this._againMS); 84 | callback(new Error('not connected')); 85 | }, 3000); 86 | delete this._riseStamp; 87 | 88 | this._n = undefined; // wait for answer first 89 | this._temp = this._hum = this._check = 0; 90 | 91 | gpio.pins[this._pinData].setValue(0); 92 | gpio.pins[this._pinData].setType(gpio.OUTPUT); 93 | setTimeout(() => { 94 | gpio.pins[this._pinData].setType(gpio.INPUT); 95 | }, 18); 96 | } 97 | 98 | /* 99 | * start 100 | * Starts a periodic measurement, calls callback with every new result 101 | * 102 | * callback(err, dist) 103 | * - err an error if no distance could be measured (always retries) 104 | * - dist the distance in cm if err is null 105 | * waitMS time to wait between measurements (default: 1000 ms) 106 | * check the datasheet for the minimum time between measurements 107 | * (1000 ms for DHT11) 108 | */ 109 | start(callback, waitMS = 1000) { 110 | if(this._callback) 111 | throw new Error('already measuring'); 112 | 113 | this._againMS = waitMS; 114 | this.measure(callback); 115 | } 116 | 117 | /* 118 | * stop 119 | * Stops periodic measurement, does not resets pins 120 | */ 121 | stop() { 122 | delete this._againMS; 123 | if(this._callback) { 124 | delete this._callback; 125 | clearTimeout(this._timeout); 126 | } 127 | } 128 | 129 | _rise(stamp) { 130 | if(this._callback) { 131 | this._riseStamp = stamp; 132 | } 133 | } 134 | 135 | _fall(stamp) { 136 | if(this._callback && this._riseStamp !== undefined) { 137 | let dur = stamp - this._riseStamp; 138 | delete this._riseStamp; 139 | 140 | if(this._n === undefined) { 141 | if(dur > 0.065 && dur < 0.1) // module answered 142 | this._n = 0; 143 | return; 144 | } 145 | 146 | this._n++; 147 | if(dur >= 0.015 && dur <= 0.047) { 148 | if(this._n <= 16) 149 | this._hum = this._hum << 1; 150 | else if(this._n <= 32) 151 | this._temp = this._temp << 1; 152 | else 153 | this._check = this._check << 1; 154 | } else if(dur <= 0.1) { 155 | if(this._n <= 16) 156 | this._hum = (this._hum << 1) | 1; 157 | else if(this._n <= 32) 158 | this._temp = (this._temp << 1) | 1; 159 | else 160 | this._check = (this._check << 1) | 1; 161 | } else { 162 | let cb = this._callback; 163 | 164 | delete this._callback; 165 | clearTimeout(this._timeout); 166 | 167 | if(this._againMS !== undefined) 168 | setTimeout(() => { 169 | this.measure(cb); 170 | }, this._againMS); 171 | cb(new Error("wrong signal")); 172 | return; 173 | } 174 | 175 | if(this._n == 40) { 176 | let cb = this._callback; 177 | 178 | delete this._callback; 179 | clearTimeout(this._timeout); 180 | 181 | if(this._againMS !== undefined) 182 | setTimeout(() => { 183 | this.measure(cb); 184 | }, this._againMS); 185 | if((this._check & 0xFF) != (((this._hum >> 8) + this._hum + (this._temp >> 8) + this._temp) & 0xFF)) 186 | cb(new Error("wrong checksum")); 187 | else { 188 | if(this._temp & 0x8000) 189 | this._temp = -(this._temp & 0x7FFF); 190 | if(this._hum & 0x8000) 191 | this._hum = -(this._hum & 0x7FFF); 192 | cb(null, this._temp * this._mult, this._hum * this._mult); 193 | } 194 | } 195 | } 196 | } 197 | } 198 | 199 | module.exports = DHT11_22; 200 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/extension.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // 3 | // Allowed token characters: 4 | // 5 | // '!', '#', '$', '%', '&', ''', '*', '+', '-', 6 | // '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~' 7 | // 8 | // tokenChars[32] === 0 // ' ' 9 | // tokenChars[33] === 1 // '!' 10 | // tokenChars[34] === 0 // '"' 11 | // ... 12 | // 13 | var tokenChars = [ 14 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 | 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 17 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 18 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 19 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 20 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 21 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0 // 112 - 127 22 | ]; 23 | /** 24 | * Adds an offer to the map of extension offers or a parameter to the map of 25 | * parameters. 26 | * 27 | * @param {Object} dest The map of extension offers or parameters 28 | * @param {String} name The extension or parameter name 29 | * @param {(Object|Boolean|String)} elem The extension parameters or the 30 | * parameter value 31 | * @private 32 | */ 33 | function push(dest, name, elem) { 34 | if (Object.prototype.hasOwnProperty.call(dest, name)) 35 | dest[name].push(elem); 36 | else 37 | dest[name] = [elem]; 38 | } 39 | /** 40 | * Parses the `Sec-WebSocket-Extensions` header into an object. 41 | * 42 | * @param {String} header The field value of the header 43 | * @return {Object} The parsed object 44 | * @public 45 | */ 46 | function parse(header) { 47 | var offers = {}; 48 | if (header === undefined || header === '') 49 | return offers; 50 | var params = {}; 51 | var mustUnescape = false; 52 | var isEscaping = false; 53 | var inQuotes = false; 54 | var extensionName; 55 | var paramName; 56 | var start = -1; 57 | var end = -1; 58 | for (var i = 0; i < header.length; i++) { 59 | var code = header.charCodeAt(i); 60 | if (extensionName === undefined) { 61 | if (end === -1 && tokenChars[code] === 1) { 62 | if (start === -1) 63 | start = i; 64 | } 65 | else if (code === 0x20 /* ' ' */ || code === 0x09 /* '\t' */) { 66 | if (end === -1 && start !== -1) 67 | end = i; 68 | } 69 | else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) { 70 | if (start === -1) { 71 | throw new SyntaxError("Unexpected character at index " + i); 72 | } 73 | if (end === -1) 74 | end = i; 75 | var name = header.slice(start, end); 76 | if (code === 0x2c) { 77 | push(offers, name, params); 78 | params = {}; 79 | } 80 | else { 81 | extensionName = name; 82 | } 83 | start = end = -1; 84 | } 85 | else { 86 | throw new SyntaxError("Unexpected character at index " + i); 87 | } 88 | } 89 | else if (paramName === undefined) { 90 | if (end === -1 && tokenChars[code] === 1) { 91 | if (start === -1) 92 | start = i; 93 | } 94 | else if (code === 0x20 || code === 0x09) { 95 | if (end === -1 && start !== -1) 96 | end = i; 97 | } 98 | else if (code === 0x3b || code === 0x2c) { 99 | if (start === -1) { 100 | throw new SyntaxError("Unexpected character at index " + i); 101 | } 102 | if (end === -1) 103 | end = i; 104 | push(params, header.slice(start, end), true); 105 | if (code === 0x2c) { 106 | push(offers, extensionName, params); 107 | params = {}; 108 | extensionName = undefined; 109 | } 110 | start = end = -1; 111 | } 112 | else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) { 113 | paramName = header.slice(start, i); 114 | start = end = -1; 115 | } 116 | else { 117 | throw new SyntaxError("Unexpected character at index " + i); 118 | } 119 | } 120 | else { 121 | // 122 | // The value of a quoted-string after unescaping must conform to the 123 | // token ABNF, so only token characters are valid. 124 | // Ref: https://tools.ietf.org/html/rfc6455#section-9.1 125 | // 126 | if (isEscaping) { 127 | if (tokenChars[code] !== 1) { 128 | throw new SyntaxError("Unexpected character at index " + i); 129 | } 130 | if (start === -1) 131 | start = i; 132 | else if (!mustUnescape) 133 | mustUnescape = true; 134 | isEscaping = false; 135 | } 136 | else if (inQuotes) { 137 | if (tokenChars[code] === 1) { 138 | if (start === -1) 139 | start = i; 140 | } 141 | else if (code === 0x22 /* '"' */ && start !== -1) { 142 | inQuotes = false; 143 | end = i; 144 | } 145 | else if (code === 0x5c /* '\' */) { 146 | isEscaping = true; 147 | } 148 | else { 149 | throw new SyntaxError("Unexpected character at index " + i); 150 | } 151 | } 152 | else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) { 153 | inQuotes = true; 154 | } 155 | else if (end === -1 && tokenChars[code] === 1) { 156 | if (start === -1) 157 | start = i; 158 | } 159 | else if (start !== -1 && (code === 0x20 || code === 0x09)) { 160 | if (end === -1) 161 | end = i; 162 | } 163 | else if (code === 0x3b || code === 0x2c) { 164 | if (start === -1) { 165 | throw new SyntaxError("Unexpected character at index " + i); 166 | } 167 | if (end === -1) 168 | end = i; 169 | var value = header.slice(start, end); 170 | if (mustUnescape) { 171 | value = value.replace(/\\/g, ''); 172 | mustUnescape = false; 173 | } 174 | push(params, paramName, value); 175 | if (code === 0x2c) { 176 | push(offers, extensionName, params); 177 | params = {}; 178 | extensionName = undefined; 179 | } 180 | paramName = undefined; 181 | start = end = -1; 182 | } 183 | else { 184 | throw new SyntaxError("Unexpected character at index " + i); 185 | } 186 | } 187 | } 188 | if (start === -1 || inQuotes) { 189 | throw new SyntaxError('Unexpected end of input'); 190 | } 191 | if (end === -1) 192 | end = i; 193 | var token = header.slice(start, end); 194 | if (extensionName === undefined) { 195 | push(offers, token, {}); 196 | } 197 | else { 198 | if (paramName === undefined) { 199 | push(params, token, true); 200 | } 201 | else if (mustUnescape) { 202 | push(params, paramName, token.replace(/\\/g, '')); 203 | } 204 | else { 205 | push(params, paramName, token); 206 | } 207 | push(offers, extensionName, params); 208 | } 209 | return offers; 210 | } 211 | /** 212 | * Builds the `Sec-WebSocket-Extensions` header field value. 213 | * 214 | * @param {Object} extensions The map of extensions and parameters to format 215 | * @return {String} A string representing the given object 216 | * @public 217 | */ 218 | function format(extensions) { 219 | return Object.keys(extensions).map(function (extension) { 220 | var configurations = extensions[extension]; 221 | if (!Array.isArray(configurations)) 222 | configurations = [configurations]; 223 | return configurations.map(function (params) { 224 | return [extension].concat(Object.keys(params).map(function (k) { 225 | var values = params[k]; 226 | if (!Array.isArray(values)) 227 | values = [values]; 228 | return values.map(function (v) { return v === true ? k : k + "=" + v; }).join('; '); 229 | })).join('; '); 230 | }).join(', '); 231 | }).join(', '); 232 | } 233 | module.exports = { format: format, parse: parse }; 234 | -------------------------------------------------------------------------------- /drivers/input/i2cRotaryEncoder.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * A module to interface the i2c Encoder V2 (https://www.tindie.com/products/Saimon/i2cencoder-v2-connect-multiple-encoder-on-i2c-bus/) 3 | * 4 | * Written in TypeScript. Use tsc to convert it to js if needed. 5 | * 6 | * Currently only the illuminated rgb encoder is supported. 7 | * 8 | * *** HOW TO USE IT *** 9 | * 10 | * Connect VCC, GND 11 | * Connect SCL, SDA (Example Neonious Pin 20,21) 12 | * Connect INT (Example Neonious pin 8) 13 | * 14 | * Daisychain multiple encoders using different addresses, same interrupt pin. 15 | * 16 | * *** HOW TO USE *** 17 | import {i2cRotaryEncoder, ic2RotaryEncoderEvent, myI2c} from "./i2cRotaryEncoder"; 18 | 19 | let gpio = require('gpio'); 20 | let I2c=new myI2c(20,21); 21 | console.log('i2c setup complete'); 22 | let r=new i2cRotaryEncoder(gpio,I2c,48,8,(re: ic2RotaryEncoderEvent)=>{ 23 | console.log(re); 24 | switch(re.event) { 25 | case r.INITIALIZED: 26 | // Example after initialization, set rgbcolor to red 27 | r.setRgb(255, 0, 0); 28 | break; 29 | case r.BTN_PUSH: 30 | console.log('Button pushed'); 31 | break; 32 | case r.BTN_RELEASE: 33 | console.log('Button released'); 34 | break; 35 | case r.ROTATED: 36 | console.log('Rotated, offset: '+re.value); 37 | break; 38 | } 39 | }); 40 | 41 | */ 42 | 43 | export interface ic2RotaryEncoderEvent { 44 | event: number, 45 | value: number 46 | } 47 | // Own i2c class with restart / recover function 48 | export class myI2c { 49 | private readonly pinSCL; 50 | private readonly pinSDA; 51 | private i2cMod: any; 52 | private con: any; 53 | 54 | constructor(pinSCL: number, pinSDA ) { 55 | this.pinSCL=pinSCL; 56 | this.pinSDA=pinSDA; 57 | this.i2cMod = require('i2c'); 58 | this.build(); 59 | } 60 | build() { 61 | this.con=new this.i2cMod.I2C({ 62 | pinSCL: this.pinSCL, 63 | pinSDA: this.pinSDA 64 | }); 65 | } 66 | restart() { 67 | this.con.destroy(); 68 | this.build(); 69 | } 70 | } 71 | export class i2cRotaryEncoder { 72 | private gpio; 73 | private i2c; 74 | private readonly pinPWR: number; 75 | private readonly pinINT: number; 76 | private readonly i2cAddr: number; 77 | private readonly GCONF=0x00; 78 | private readonly CMAX=0x0c; 79 | private readonly INTCONF=0x04; 80 | private readonly ESTATUS=0x05; 81 | private readonly CVAL=0x08; 82 | private readonly R=0x18; 83 | private initialized=false; 84 | private lastpos: number=0; 85 | private readonly pollInt: any; 86 | 87 | public INITIALIZED=255; 88 | public BTN_PUSH=1; 89 | public BTN_RELEASE=2; 90 | public ROTATED=4; 91 | public READY=8; 92 | 93 | constructor(gpio: any, i2c: any, 94 | i2cAddr: number, pinINT: number, cb) { 95 | this.gpio=gpio; 96 | this.i2c=i2c; 97 | this.pinINT=pinINT; 98 | this.i2cAddr=i2cAddr; 99 | 100 | // Interrupt PIN 101 | this.gpio.pins[this.pinINT].setType(this.gpio.INPUT_PULLUP); 102 | this.gpio.pins[this.pinINT].on('fall', ()=> { 103 | if(this.initialized===true) { 104 | this.getStatus((re: ic2RotaryEncoderEvent) => { 105 | cb(re); 106 | }); 107 | } 108 | }); 109 | // poll every 90 sec. to prevent lock on non recognised interrupt. 110 | this.pollInt=setInterval(()=>{ 111 | if(this.initialized===true) { 112 | this.getStatus((re: ic2RotaryEncoderEvent) => { 113 | }); 114 | } 115 | },90000); 116 | setTimeout(()=>{ 117 | this.setupEncoder((re: boolean)=> { 118 | this.setCmax((re: boolean)=> { 119 | this.setupInt((re: boolean)=>{ 120 | this.log('Init complete.'); 121 | this.initialized=true; 122 | cb({event: this.INITIALIZED, value: 0}); 123 | }); 124 | }); 125 | }); 126 | },2000); 127 | } 128 | log(txt: string): void { 129 | console.log('i2cRotEncV2: Addr: '+this.i2cAddr+': '+txt); 130 | } 131 | setupInt(cb) { 132 | let dataWrite = Buffer.alloc(2); 133 | dataWrite.writeUInt8(this.INTCONF, 0); 134 | dataWrite.writeUInt8(31, 1); 135 | 136 | this.i2c.con.transfer(this.i2cAddr, dataWrite, 0, (err, dataRead) => { 137 | if(err) { 138 | console.log("i2c error: ", err); 139 | setTimeout(()=>{ 140 | this.i2c.restart(); 141 | this.setupInt(cb); 142 | },1000); 143 | } else { 144 | this.log('Basic config done.'); 145 | cb(true); 146 | } 147 | }); 148 | } 149 | setCmax(cb) { 150 | let dataWrite = Buffer.alloc(5); 151 | dataWrite.writeUInt8(this.CMAX, 0); 152 | dataWrite.writeUInt8(0, 1); 153 | dataWrite.writeUInt8(0, 2); 154 | dataWrite.writeUInt8(0, 3); 155 | dataWrite.writeUInt8(100, 4); 156 | 157 | this.i2c.con.transfer(this.i2cAddr, dataWrite, 0, (err, dataRead) => { 158 | if(err) { 159 | console.log("i2c error: ", err); 160 | setTimeout(()=>{ 161 | this.i2c.restart(); 162 | this.setCmax(cb); 163 | },1000); 164 | } else { 165 | this.log('Max counter setup done.'); 166 | cb(true); 167 | } 168 | }); 169 | } 170 | setupEncoder(cb) { 171 | let dataWrite = Buffer.alloc(2); 172 | dataWrite.writeUInt8(this.GCONF, 0); 173 | dataWrite.writeUInt8(32+2, 1); 174 | 175 | this.i2c.con.transfer(this.i2cAddr, dataWrite, 0, (err, dataRead) => { 176 | if(err) { 177 | console.log("i2c error: ", err); 178 | setTimeout(()=>{ 179 | this.i2c.restart(); 180 | this.setupEncoder(cb); 181 | },1000); 182 | } else { 183 | this.log('Encoder setup done.'); 184 | cb(true); 185 | } 186 | }); 187 | } 188 | getRotaryPos(cb): void { 189 | let offset; 190 | let dataWrite = Buffer.alloc(1); 191 | dataWrite.writeUInt8(this.CVAL, 0); 192 | this.i2c.con.transfer(this.i2cAddr, dataWrite, 4, (err, dataRead) => { 193 | if(err) { 194 | console.log("i2c error: ", err); 195 | setTimeout(()=>{ 196 | this.i2c.restart(); 197 | this.getRotaryPos(cb); 198 | },1000); 199 | } else { 200 | if(dataRead.length === 4) { 201 | offset = dataRead[3]-this.lastpos; 202 | if(offset > 50) { offset = 1; } 203 | if(offset < -50) { offset = -1; } 204 | 205 | this.lastpos = dataRead[3]; 206 | cb({ event: this.ROTATED, value: offset}); 207 | } 208 | } 209 | }); 210 | } 211 | getStatus(cb): void { 212 | let dataWrite = Buffer.alloc(1); 213 | dataWrite.writeUInt8(this.ESTATUS, 0); 214 | this.i2c.con.transfer(this.i2cAddr, dataWrite, 1, (err, dataRead) => { 215 | if(err) { 216 | console.log("i2c error: ", err); 217 | setTimeout(()=>{ 218 | this.i2c.restart(); 219 | this.getStatus(cb); 220 | },1000); 221 | } else { 222 | // console.log(dataRead); 223 | if(dataRead[0] & 2) { // Push Button 224 | cb({event: this.BTN_PUSH, value: 0}); 225 | } else if(dataRead[0] & 1) { 226 | cb({event: this.BTN_RELEASE, value: 0}); 227 | } else if(dataRead[0] & 8 || dataRead[0] && 16) { 228 | this.getRotaryPos((re: ic2RotaryEncoderEvent)=>{ 229 | cb(re); 230 | }); 231 | } else { 232 | cb({ event: this.READY, value: 0 }); 233 | } 234 | } 235 | }); 236 | } 237 | setRgb(r:number,g:number,b:number) { 238 | let dataWrite = Buffer.alloc(4); 239 | dataWrite.writeUInt8(this.R, 0); 240 | dataWrite.writeUInt8(r, 1); 241 | dataWrite.writeUInt8(g, 2); 242 | dataWrite.writeUInt8(b, 3); 243 | this.i2c.con.transfer(this.i2cAddr, dataWrite, 0, (err, dataRead) => { 244 | if(err) { 245 | console.log("i2c error: ", err); 246 | setTimeout(()=>{ 247 | this.i2c.restart(); 248 | this.setRgb(r,g,b); 249 | },1000); 250 | } else { 251 | this.log('RGB set to '+r+','+g+','+b); 252 | } 253 | }); 254 | } 255 | destroy() { 256 | clearInterval(this.pollInt); 257 | } 258 | } 259 | 260 | -------------------------------------------------------------------------------- /drivers/sensors/nmea_gps.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A module to interface GPS devices which support NMEA sentences, a standard which 3 | * almost any GPS module does. Gives out all interesting information and sets system clock. 4 | * 5 | * If you are unsure which device to choose, we can recommend the u-blox NEO-6M. 6 | * 7 | * 8 | * *** HOW TO CONNECT *** 9 | * 10 | * GND connect to GND 11 | * VCC connect to 3.3 V 12 | * TX connect to any pin of the neonious one, as all support GPIO input 13 | * 14 | * 15 | * *** HOW TO USE *** 16 | * 17 | * const NMEA_GPS = require('./nmea_gps.js'); 18 | * 19 | * let sensor = new NMEA_GPS({ 20 | * setSystemTime: true, // sets system time to GPS time 21 | * pinRX: 11, // pinTX (RX at neonious one) 22 | * baud: 9600}); // baud rate (try 9600 or look into datasheet) 23 | * // also, add other options for UART module if needed 24 | * // when done, call sensor.destroy(); 25 | * 26 | * sensor.on('gps', (time, pos, speed, height, quality, numSatellites) => { 27 | * if(time !== null) 28 | * console.log('Time: ', time); // time is a Date or null 29 | * if(pos !== null) // pos.lat and pos.lon are numbers or null 30 | * console.log('Position: ', NMEA_GPS.lat2txt(pos.lat), NMEA_GPS.lon2txt(pos.lon)); 31 | * if(height !== null) // height is number or null 32 | * console.log('Height: ', height, ' m above sea level'); 33 | * if(speed !== null) // speed is number or null 34 | * console.log('Speed: ', speed, ' km/h over ground'); 35 | * 36 | * // quality is integer, see quality2txt 37 | * console.log('Quality: ', NMEA_GPS.quality2txt(quality)); 38 | * // numSatellites is integer 39 | * console.log('# Satellites: ', numSatellites); 40 | * }); 41 | * sensor.on('no-nmea', () => { 42 | * console.log('Timeout, please check connection to module') 43 | * }); 44 | * 45 | */ 46 | 47 | const uart = require('uart'); 48 | const readline = require('readline'); 49 | const lowsys = require('lowsys'); 50 | 51 | class NMEA_GPS extends uart.UART { 52 | /* 53 | * constructor 54 | */ 55 | constructor(options) { 56 | super(options); 57 | 58 | this.current = { 59 | time: null, 60 | pos: null, 61 | speed: null, 62 | height: null, 63 | quality: 0, 64 | numSatellites: 0 65 | }; 66 | this._setSystemTime = options ? !!options.setSystemTime : false; 67 | 68 | this._noNMEATimeout = setTimeout(() => { 69 | this.emit('no-nmea'); 70 | }, 10000); 71 | 72 | let iface = readline.createInterface({input: this}); 73 | iface.on('error', (e) => { this.emit('error', e); }); 74 | iface.on('line', this._handleLine.bind(this)); 75 | 76 | this.on('close', () => { 77 | clearTimeout(this._noNMEATimeout); 78 | iface.close(); 79 | }); 80 | } 81 | 82 | _handleLine(line) { 83 | if(line[0] != '$') 84 | return; 85 | 86 | // Check checksum 87 | let pos = line.indexOf('*'); 88 | if(pos == -1) 89 | return; 90 | let checkSum; 91 | for(let i = 1; i < pos; i++) 92 | checkSum ^= line.charCodeAt(i); 93 | if(checkSum != parseInt(line.substr(pos + 1), 16)) 94 | return; 95 | 96 | this._noNMEATimeout.refresh(); 97 | 98 | let current; 99 | 100 | line = line.split(','); 101 | if(line[0] == '$GPGGA') { 102 | current = Object.assign({}, this.current); 103 | 104 | if(line[2] && line[4]) { 105 | let val; 106 | 107 | val = line[2]; 108 | let lat = (val * 0.01) | 0; 109 | lat += (val - lat * 100) / 60; 110 | if(line[3] != 'N') 111 | lat = -lat; 112 | 113 | val = line[4]; 114 | let lon = (val * 0.01) | 0; 115 | lon += (val - lon * 100) / 60; 116 | if(line[5] != 'E') 117 | lon = -lon; 118 | 119 | current.pos = {lat, lon}; 120 | } else 121 | current.pos = null; 122 | current.height = line[9] !== '' ? line[9] * 1 : null; 123 | 124 | current.quality = line[6] | 0; 125 | current.numSatellites = line[7] | 0; 126 | 127 | if(this.current.pos != current.pos && (!this.current.pos || !current.pos 128 | || this.current.pos.lat != current.pos.lat || this.current.pos.lon != current.pos.lon) 129 | || this.current.height != current.height 130 | || this.current.quality != current.quality 131 | || this.current.numSatellites != current.numSatellites) { 132 | this.current = current; 133 | this.emit('gps', 134 | current.time, 135 | current.pos, 136 | current.speed, 137 | current.height, 138 | current.quality, 139 | current.numSatellites); 140 | } 141 | } 142 | if(line[0] == '$GPRMC') { 143 | current = Object.assign({}, this.current); 144 | 145 | let time = line[1]; 146 | let date = line[9]; 147 | if(time !== '' && date !== '') { 148 | let secs = parseFloat(time.substr(4)); 149 | current.time = new Date(Date.UTC( 150 | 2000 + (date.substr(4, 2) | 0), 151 | (date.substr(2, 2) | 0) - 1, 152 | date.substr(0, 2) | 0, 153 | time.substr(0, 2) | 0, 154 | time.substr(2, 2) | 0, 155 | secs | 0, 156 | (secs - (secs | 0)) * 1000 157 | )); 158 | 159 | if(this._setSystemTime) { 160 | let diff = current.time.getTime() - new Date().getTime(); 161 | // Only change if more than 10s deviation to not break 162 | // any timing loops in the user program 163 | if(!this._firstSet || diff < -10000 || diff > 10000) { 164 | lowsys.setSystemTime(current.time); 165 | this._firstSet = true; 166 | } 167 | } 168 | } else 169 | current.time = null; 170 | if(line[3] && line[5]) { 171 | let val; 172 | 173 | val = line[3]; 174 | let lat = (val * 0.01) | 0; 175 | lat += (val - lat * 100) / 60; 176 | if(line[4] != 'N') 177 | lat = -lat; 178 | 179 | val = line[5]; 180 | let lon = (val * 0.01) | 0; 181 | lon += (val - lon * 100) / 60; 182 | if(line[6] != 'E') 183 | lon = -lon; 184 | 185 | current.pos = {lat, lon}; 186 | } else 187 | current.pos = null; 188 | 189 | current.speed = line[7] !== '' ? line[7] * 1.852 : null; 190 | 191 | if(this.current.time != current.time && (!this.current.time || !current.time 192 | || this.current.time.getTime() != current.time.getTime()) 193 | || this.current.pos != current.pos && (!this.current.pos || !current.pos 194 | || this.current.pos.lat != current.pos.lat || this.current.pos.lon != current.pos.lon) 195 | || this.current.speed != current.speed) { 196 | this.current = current; 197 | this.emit('gps', 198 | current.time, 199 | current.pos, 200 | current.speed, 201 | current.height, 202 | current.quality, 203 | current.numSatellites); 204 | } 205 | } 206 | } 207 | } 208 | 209 | module.exports = NMEA_GPS; 210 | 211 | module.exports.lat2txt = function(val) { 212 | if(val === null) 213 | return '-'; 214 | 215 | let unit; 216 | if(val < 0) { 217 | unit = 'S'; 218 | val = -val; 219 | } else 220 | unit = 'N'; 221 | 222 | val = ((val * 36000) + 0.5) | 0; 223 | 224 | let part1 = (val / 36000) | 0; 225 | val -= part1 * 36000; 226 | let part2 = (val / 600) | 0; 227 | val -= part2 * 600; 228 | let part3 = (val / 10) | 0; 229 | let part4 = val - part3 * 10; 230 | 231 | txt = part1 + '°'; 232 | if(part2 || part3 || part4) { 233 | txt += part2 + "'"; 234 | if(part3 || part4) { 235 | txt += part3; 236 | if(part4) 237 | txt += '.' + part4; 238 | txt += '"'; 239 | } 240 | } 241 | 242 | return txt + unit; 243 | } 244 | 245 | module.exports.lon2txt = function(val) { 246 | if(val === null) 247 | return '-'; 248 | 249 | let unit; 250 | if(val < 0) { 251 | unit = 'W'; 252 | val = -val; 253 | } else 254 | unit = 'E'; 255 | 256 | val = ((val * 36000) + 0.5) | 0; 257 | 258 | let part1 = (val / 36000) | 0; 259 | val -= part1 * 36000; 260 | let part2 = (val / 600) | 0; 261 | val -= part2 * 600; 262 | let part3 = (val / 10) | 0; 263 | let part4 = val - part3 * 10; 264 | 265 | txt = part1 + '°'; 266 | if(part2 || part3 || part4) { 267 | txt += part2 + "'"; 268 | if(part3 || part4) { 269 | txt += part3; 270 | if(part4) 271 | txt += '.' + part4; 272 | txt += '"'; 273 | } 274 | } 275 | 276 | return txt + unit; 277 | } 278 | 279 | module.exports.quality2txt = function(quality) { 280 | switch(quality) { 281 | case 0: return 'no fix'; 282 | case 1: return 'GPS'; 283 | case 2: return 'Differential GPS'; 284 | 285 | case 3: 286 | case 4: 287 | return 'Real-Time Kinematic'; 288 | } 289 | 290 | return 'unknown'; 291 | } -------------------------------------------------------------------------------- /drivers/displays/ssd1306_sh1106.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ssd1306_sh1106.js 3 | * 4 | * This program shows how to interface an OLED display with the ssd1306 or sh1106 5 | * driver chip via SPI 6 | * 7 | * Note: SPI via LPC822 is still slow on the neonious one, as the link to the 8 | * LPC822 is not fast. Will be fixed soon. Till then, use the ESP32 pins 9 | * of the neonious one for SCLK and MOSI for faster screen refreshes! 10 | * (you must remove the line 230 (pinCS option) in this case, and connect CS 11 | * of OLED module to ground, as there are only 2 ESP32 output pins available on 12 | * neonious one). 13 | */ 14 | 15 | // **** IMPORTANT **** 16 | // In case this is not a ssd1306 but a sh1106, the left most pixel is sometimes 17 | // the pixel with the x coordinate 2 (everything shifted by 2 pixels) 18 | // Try to set this to 2, if the display has some uninitialized lines left 19 | const SSHD1306_COLUMN_OFFSET = 0; 20 | 21 | let spiMod = require('spi'); 22 | let gpio = require('gpio'); 23 | 24 | // Commands 25 | const SSD1306_SETCONTRAST = 0x81; 26 | const SSD1306_DISPLAYALLON_RESUME = 0xA4; 27 | const SSD1306_DISPLAYALLON = 0xA5; 28 | const SSD1306_NORMALDISPLAY = 0xA6; 29 | const SSD1306_INVERTDISPLAY = 0xA7; 30 | const SSD1306_DISPLAYOFF = 0xAE; 31 | const SSD1306_DISPLAYON = 0xAF; 32 | const SSD1306_SETDISPLAYOFFSET = 0xD3; 33 | const SSD1306_SETCOMPINS = 0xDA; 34 | const SSD1306_SETVCOMDETECT = 0xDB; 35 | const SSD1306_SETDISPLAYCLOCKDIV = 0xD5; 36 | const SSD1306_SETPRECHARGE = 0xD9; 37 | const SSD1306_SETMULTIPLEX = 0xA8; 38 | const SSD1306_SETLOWCOLUMN = 0x00; 39 | const SSD1306_SETHIGHCOLUMN = 0x10; 40 | const SSD1306_SETSTARTLINE = 0x40; 41 | const SSD1306_MEMORYMODE = 0x20; 42 | const SSD1306_COMSCANINC = 0xC0; 43 | const SSD1306_COMSCANDEC = 0xC8; 44 | const SSD1306_SEGREMAP = 0xA0; 45 | const SSD1306_SETPAGEADDRESS = 0xB0; 46 | const SSD1306_CHARGEPUMP = 0x8D; 47 | const SSD1306_EXTERNALVCC = 0x1; 48 | const SSD1306_INTERNALVCC = 0x2; 49 | const SSD1306_SWITCHCAPVCC = 0x2; 50 | 51 | const SSD1306_VCCSTATE = SSD1306_INTERNALVCC; 52 | 53 | class OLEDDisplay { 54 | constructor(spi, options) { 55 | this.spi = spi; 56 | this.width = options.width; 57 | this.height = options.height; 58 | this.buffer = new Buffer(this.width * this.height / 8); 59 | this.pinDC = options.pinDC; 60 | this.pinCS = options.pinCS; 61 | 62 | gpio.pins[options.pinDC].setType(gpio.OUTPUT); 63 | gpio.pins[options.pinDC].setValue(0); 64 | if (options.pinRES) { 65 | gpio.pins[options.pinRES].setType(gpio.OUTPUT); 66 | gpio.pins[options.pinRES].setValue(0); 67 | gpio.pins[options.pinRES].setValue(1); 68 | } 69 | 70 | let buf = Buffer(128); 71 | let bufPos = 0; 72 | function spi_byte(byte) { 73 | buf[bufPos++] = byte; 74 | } 75 | 76 | if (this.width == 64 && this.height == 48) { 77 | // Init sequence taken from SFE_MicroOLED.cpp 78 | spi_byte(SSD1306_DISPLAYOFF); // 0xAE 79 | spi_byte(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5 80 | spi_byte(0x80); // the suggested ratio 0x80 81 | spi_byte(SSD1306_SETMULTIPLEX); // 0xA8 82 | spi_byte(DISPLAY_HEIGHT - 1); 83 | spi_byte(SSD1306_SETDISPLAYOFFSET); // 0xD3 84 | spi_byte(0x0); // no offset 85 | spi_byte(SSD1306_SETSTARTLINE | 0x0); // line #0 86 | spi_byte(SSD1306_CHARGEPUMP); // enable charge pump 87 | spi_byte(0x14); 88 | spi_byte(SSD1306_NORMALDISPLAY); // 0xA6 89 | spi_byte(SSD1306_DISPLAYALLON_RESUME); // 0xA4 90 | spi_byte(SSD1306_SEGREMAP | 0x1); 91 | spi_byte(SSD1306_COMSCANDEC); 92 | spi_byte(SSD1306_SETCOMPINS); // 0xDA 93 | spi_byte(0x12); 94 | spi_byte(SSD1306_SETCONTRAST); // 0x81 95 | spi_byte(0x8F); 96 | spi_byte(SSD1306_SETPRECHARGE); // 0xd9 97 | spi_byte(0xF1); 98 | spi_byte(SSD1306_SETVCOMDETECT); // 0xDB 99 | spi_byte(0x40); 100 | } else if (this.width == 128 && this.height == 32) { 101 | // Init sequence taken from datasheet for UG-2832HSWEG04 (128x32 OLED module) 102 | spi_byte(SSD1306_DISPLAYOFF); // 0xAE 103 | spi_byte(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5 104 | spi_byte(0x80); // the suggested ratio 0x80 105 | spi_byte(SSD1306_SETMULTIPLEX); // 0xA8 106 | spi_byte(DISPLAY_HEIGHT - 1); 107 | spi_byte(SSD1306_SETDISPLAYOFFSET); // 0xD3 108 | spi_byte(0x0); // no offset 109 | spi_byte(SSD1306_SETSTARTLINE | 0x0); // line #0 110 | spi_byte(SSD1306_CHARGEPUMP); // 0x8D 111 | if (SSD1306_VCCSTATE == SSD1306_EXTERNALVCC) 112 | spi_byte(0x10); 113 | else 114 | spi_byte(0x14); 115 | spi_byte(SSD1306_SEGREMAP | 0x1); 116 | spi_byte(SSD1306_COMSCANDEC); 117 | spi_byte(SSD1306_SETCOMPINS); // 0xDA 118 | spi_byte(0x02); 119 | spi_byte(SSD1306_SETCONTRAST); // 0x81 120 | if (SSD1306_VCCSTATE == SSD1306_EXTERNALVCC) 121 | spi_byte(0x9F); 122 | else 123 | spi_byte(0xCF); 124 | spi_byte(SSD1306_SETPRECHARGE); // 0xd9 125 | if (SSD1306_VCCSTATE == SSD1306_EXTERNALVCC) 126 | spi_byte(0x22); 127 | else 128 | spi_byte(0xF1); 129 | spi_byte(SSD1306_SETVCOMDETECT); // 0xDB 130 | spi_byte(0x40); 131 | spi_byte(SSD1306_DISPLAYALLON_RESUME); // 0xA4 132 | spi_byte(SSD1306_NORMALDISPLAY); // 0xA6 133 | } else if (this.width == 128 && this.height == 64) { 134 | // Init sequence taken from datasheet for UG-2864HSWEG01 (128x64 OLED module) 135 | spi_byte(SSD1306_DISPLAYOFF); // 0xAE 136 | spi_byte(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5 137 | spi_byte(0x80); // the suggested ratio 0x80 138 | spi_byte(SSD1306_SETMULTIPLEX); // 0xA8 139 | spi_byte(this.height - 1); 140 | spi_byte(SSD1306_SETDISPLAYOFFSET); // 0xD3 141 | spi_byte(0x0); // no offset 142 | spi_byte(SSD1306_SETSTARTLINE | 0x0); // line #0 143 | spi_byte(SSD1306_CHARGEPUMP); // 0x8D 144 | if (SSD1306_VCCSTATE == SSD1306_EXTERNALVCC) 145 | spi_byte(0x10); 146 | else 147 | spi_byte(0x14); 148 | spi_byte(SSD1306_SEGREMAP | 0x1); 149 | spi_byte(SSD1306_COMSCANDEC); 150 | spi_byte(SSD1306_SETCOMPINS); // 0xDA 151 | spi_byte(0x12); 152 | spi_byte(SSD1306_SETCONTRAST); // 0x81 153 | if (SSD1306_VCCSTATE == SSD1306_EXTERNALVCC) 154 | spi_byte(0x9F); 155 | else 156 | spi_byte(0xCF); 157 | spi_byte(SSD1306_SETPRECHARGE); // 0xd9 158 | if (SSD1306_VCCSTATE == SSD1306_EXTERNALVCC) 159 | spi_byte(0x22); 160 | else 161 | spi_byte(0xF1); 162 | spi_byte(SSD1306_SETVCOMDETECT); // 0xDB 163 | spi_byte(0x40); 164 | spi_byte(SSD1306_DISPLAYALLON_RESUME); // 0xA4 165 | spi_byte(SSD1306_NORMALDISPLAY); // 0xA6 166 | } else 167 | throw new Error('unknown display size'); 168 | 169 | // Enable the OLED panel 170 | spi_byte(SSD1306_DISPLAYON); 171 | this.spi.transfer(this.pinCS, buf.slice(0, bufPos)); 172 | } 173 | 174 | apply(callback) { 175 | let buf = new Buffer(1); 176 | buf[0] = SSD1306_SETSTARTLINE | 0x0; // line #0 177 | this.spi.transfer(this.pinCS, buf); 178 | buf = new Buffer(3); 179 | 180 | let i = 0; 181 | let row = (y) => { 182 | if (y == (this.height >> 3)) 183 | return callback(); 184 | 185 | buf[0] = SSD1306_SETPAGEADDRESS | y; 186 | buf[1] = SSD1306_SETLOWCOLUMN | (SSHD1306_COLUMN_OFFSET & 0xf); // low col = 0 187 | buf[2] = SSD1306_SETHIGHCOLUMN | (SSHD1306_COLUMN_OFFSET >> 4); // hi col = 0 188 | this.spi.transfer(this.pinCS, buf); 189 | 190 | this.spi.flush(() => { 191 | gpio.pins[this.pinDC].setValue(1); 192 | this.spi.transfer(this.pinCS, this.buffer.slice(i, i + this.width)); 193 | i += this.width; 194 | 195 | this.spi.flush(() => { 196 | gpio.pins[this.pinDC].setValue(0); 197 | row(y + 1); 198 | }); 199 | }); 200 | } 201 | row(0); 202 | } 203 | 204 | clearAll(byte) { 205 | for (let i = 0; i < this.buffer.length; i++) 206 | this.buffer[i] = byte; 207 | } 208 | 209 | // simple pixel set functions 210 | clear(x, y) { 211 | this.buffer[x + (y >> 3) * this.width] &= ~(1 << (y & 0x07)); 212 | } 213 | 214 | set(x, y) { 215 | this.buffer[x + (y >> 3) * this.width] |= 1 << (y & 0x07); 216 | } 217 | } 218 | 219 | let spi = new spiMod.SPI({ 220 | pinSCLK: 19, 221 | pinMOSI: 23 222 | }); 223 | spi.addCS(18); 224 | 225 | let display = new OLEDDisplay(spi, { 226 | width: 128, 227 | height: 64, 228 | pinRES: 21, 229 | pinDC: 20, 230 | pinCS: 18 231 | }); 232 | 233 | let led = true; 234 | 235 | display.clearAll(0); 236 | function frame() { 237 | led = !led; 238 | gpio.pins[gpio.LED_GREEN].setValue(led); 239 | gpio.pins[gpio.LED_RED].setValue(!led); 240 | 241 | for(let i = 0; i < 10; i++) { 242 | let x = (Math.random() * 127) | 0; 243 | let y = (Math.random() * 63) | 0; 244 | display.set(x, y); 245 | display.set(x, y + 1); 246 | display.set(x + 1, y); 247 | display.set(x + 1, y + 1); 248 | 249 | x = (Math.random() * 127) | 0; 250 | y = (Math.random() * 63) | 0; 251 | display.clear(x, y); 252 | display.clear(x, y + 1); 253 | display.clear(x + 1, y); 254 | display.clear(x + 1, y + 1); 255 | } 256 | 257 | display.apply(() => { 258 | frame(); 259 | }); 260 | } 261 | frame(); -------------------------------------------------------------------------------- /drivers/industry/tmcm-3212-tmcl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * tmcm-3212-tmcl.js 3 | * 4 | * A class to interface a TMCM-3212 via CAN 5 | * 6 | * Implements all things needed for direct mode 7 | * Multiple objects can be used to interface multiple devices at the same time 8 | * 9 | * This class has several special features: 10 | * - It supports both callbacks and async/await out of the box 11 | * - Move to position and reference search call the callback when the action is done completly, 12 | * not when the command is replied to. 13 | * This is implemented by waiting till the request target position event happens. 14 | * - The motors can be accessed at the same time, even a reference search can happen on all axis 15 | * simultaneously, the callbacks will still not be mixed up. 16 | * 17 | * Example on how to use: 18 | * 19 | * let tmcm3213 = require('./tmcm-3212-tmcl.js'); 20 | * 21 | * // Pins to use. Must be ESP32 native pins 22 | * const PIN_RX = 26; // use 24-26 on neonious one 23 | * const PIN_TX = 25; // use 24-25 on neonious one 24 | * 25 | * const DEVICE_ID = 1; // in real life we can use multiple devices 26 | * const MY_ID = 2; // the device the TMCM-3212 replies to 27 | * 28 | * // Setup CAN peripherial 29 | * let can = require('can'); 30 | * let intf = new can.CAN({ 31 | * pinRX: PIN_RX, 32 | * pinTX: PIN_TX, 33 | * filter: {id: MY_ID, id_len: 11} 34 | * }); 35 | * 36 | * // Communicate with device 37 | * let device = new tmcm3213.TMCM3212(intf, DEVICE_ID, MY_ID); 38 | * 39 | * async function asyncExample() { 40 | * // Reference search mode: 65 instead of 1 => turn right, not left 41 | * // See https://www.trinamic.com/fileadmin/assets/Products/Modules_Documents/TMCM-3212_TMCL-firmware_manual_Fw1.09_Rev1.05.pdf 42 | * // for parameter details 43 | * await device.setAxisParameter(0, 193, 65); 44 | * 45 | * // Do reference search 46 | * await device.referenceSearch(0); 47 | * 48 | * // Move away from reference 49 | * await device.moveToPositionRel(0, 50000); 50 | * 51 | * // Back and forth 52 | * while(true) { 53 | * await device.moveToPositionRel(0, -20000); 54 | * await device.moveToPositionRel(0, 20000); 55 | * } 56 | * } 57 | * 58 | * asyncExample(); 59 | * 60 | * --- end of example --- 61 | */ 62 | 63 | "use strict"; 64 | 65 | class TMCM3212 { 66 | /* 67 | * constructor 68 | */ 69 | constructor(can, deviceID, replyID) { 70 | this._can = can; 71 | this._deviceID = deviceID; 72 | this._replyID = replyID; 73 | 74 | // The commands we are waiting to be answered 75 | this._commands = {}; 76 | this._commandsQueue = {}; 77 | 78 | // Handle incoming message 79 | can.on('message', (data, id, id_len, flags) => { 80 | // Is this a reply for us? 81 | if(data.length == 7 && id == this._replyID && id_len == 11) { 82 | let device = data.readUInt8(0); 83 | let status = data.readUInt8(1); 84 | let command = data.readUInt8(2); 85 | 86 | if(status == 128 && command == 138) { 87 | // Special case: reached target position 88 | let motorMask = data.readUInt8(6); 89 | 90 | for(let i = 0; i < 3; i++) { 91 | // Reinterpret it as command reply for move position 92 | let callback = this._commands[((i + 1) << 16) | (device << 8) | 4]; 93 | if((motorMask & (1 << i)) && callback) 94 | callback(null); 95 | 96 | // Same for reference search, but as reached target position 97 | // happens multiple times for reference search (seems to be implemented 98 | // as multiple move positions internally) we check whether the full reference 99 | // is done 100 | callback = this._commands[((i + 1) << 16) | (device << 8) | 13]; 101 | if((motorMask & (1 << i)) && callback) { 102 | this.transmit(13, 2, i, 0, (err, value) => { 103 | callback = this._commands[((i + 1) << 16) | (device << 8) | 13]; 104 | if(!err && value == 0 && callback) { 105 | // We are completly done! 106 | callback(null); 107 | } 108 | }); 109 | } 110 | } 111 | 112 | return; 113 | } 114 | 115 | // Call callback 116 | let callback = this._commands[(device << 8) | command]; 117 | if(!callback) 118 | return; 119 | 120 | let err; 121 | switch(status) { 122 | case 100: 123 | err = null; 124 | break; 125 | 126 | case 101: 127 | err = new Error('command loaded into EEPROM, but we currently only do direct mode'); 128 | break; 129 | 130 | case 1: 131 | err = new Error('wrong checksum'); 132 | break; 133 | 134 | case 2: 135 | err = new Error('invalid command'); 136 | break; 137 | 138 | case 3: 139 | err = new Error('wrong type'); 140 | break; 141 | 142 | case 4: 143 | err = new Error('invalid value'); 144 | break; 145 | 146 | case 5: 147 | err = new Error('configuration EEPROM locked'); 148 | break; 149 | 150 | case 6: 151 | err = new Error('configuration EEPROM locked'); 152 | break; 153 | 154 | default: 155 | err = new Error('unknown TMCL status code ' + status); 156 | break; 157 | } 158 | 159 | callback(err, err ? undefined : data.readUInt32BE(3)); 160 | } 161 | }); 162 | 163 | // Tell device that we want to get info about target position reached 164 | this.transmit(138, 1, 0, 255); 165 | } 166 | 167 | /* 168 | * transmit 169 | * Sends a TCML command for direct execution, waits asyncronly for a reply 170 | * and calls the callback with the result. If no callback is given, it returns 171 | * a promise. 172 | * No need to use directly, take a look at the specialised commands below. 173 | */ 174 | transmit(command, type, motorBank, value, callback) { 175 | if(!callback) { 176 | return new Promise((resolve, reject) => { 177 | this.transmit(command, type, motorBank, value, (err, value) => { 178 | if(err) 179 | reject(err); 180 | else 181 | resolve(value); 182 | }); 183 | }) 184 | } 185 | 186 | let index = (this._deviceID << 8) | command; 187 | if(command == 4 || (command == 13 && type == 0)) 188 | index |= (motorBank + 1) << 16; 189 | 190 | if(this._commands[index]) { 191 | if(this._commandsQueue[index]) 192 | this._commandsQueue[index].push([motorBank, value, callback]); 193 | else 194 | this._commandsQueue[index] = [[motorBank, value, callback]]; 195 | return; 196 | } 197 | 198 | let timeout; 199 | 200 | // The first thing that calls this callback wins, be it a 201 | // reply (see on('message', ...) above), an transmit error or a timeout 202 | let doneCallback = (err, value) => { 203 | if(!doneCallback) 204 | return; 205 | doneCallback = null; 206 | 207 | if(timeout) 208 | clearTimeout(timeout); 209 | delete this._commands[index]; 210 | if(this._commandsQueue[index] && this._commandsQueue[index].length) { 211 | let commandParams = this._commandsQueue[index].shift(); 212 | this.transmit(command, type, commandParams[0], commandParams[1], commandParams[2]); 213 | } 214 | 215 | if(callback) 216 | callback(err, value); 217 | else if(err) 218 | can.emit('error', err); 219 | } 220 | 221 | // Handle timeout 222 | timeout = setTimeout(() => { 223 | timeout = null; 224 | if(doneCallback) 225 | doneCallback(new Error('timeout ' + type + '/' + command + '/' + motorBank)); 226 | }, command == 4 || (command == 13 && type == 0) ? 300000 : 5000); 227 | 228 | // Transmit 229 | this._commands[index] = doneCallback; 230 | 231 | let data = Buffer.alloc(7); 232 | data.writeUInt8(command, 0); 233 | data.writeUInt8(type, 1); 234 | data.writeUInt8(motorBank, 2); 235 | data.writeUInt32BE(value, 3); 236 | 237 | this._can.transmit(data, this._deviceID, 11, (err) => { 238 | // Sent! Only call callback now if we have an error 239 | if(err && doneCallback) 240 | doneCallback(err); 241 | }); 242 | } 243 | 244 | /**************/ 245 | // Commands according to https://www.trinamic.com/fileadmin/assets/Products/Modules_Documents/TMCM-3212_TMCL-firmware_manual_Fw1.09_Rev1.05.pdf 246 | // If no callback is given, these return promises which can be used with await 247 | /**************/ 248 | 249 | rotateRight(motor, microstepsPerSecond, callback) { 250 | return this.transmit(1, 0, motor, microstepsPerSecond, callback); 251 | } 252 | rotateLeft(motor, microstepsPerSecond, callback) { 253 | return this.transmit(2, 0, motor, microstepsPerSecond, callback); 254 | } 255 | motorStop(motor, callback) { 256 | return this.transmit(3, 0, motor, 0, callback); 257 | } 258 | 259 | // Not only than the simple command, this call actually calls callback/resolves the promise when 260 | // the reference search is done completly 261 | moveToPositionAbs(motor, position, callback) { 262 | return this.transmit(4, 0, motor, position, callback); 263 | } 264 | 265 | // Not only than the simple command, this call actually calls callback/resolves the promise when 266 | // the reference search is done completly 267 | moveToPositionRel(motor, offset, callback) { 268 | return this.transmit(4, 1, motor, offset, callback); 269 | } 270 | 271 | setAxisParameter(motor, type, value, callback) { 272 | return this.transmit(5, type, motor, value, callback); 273 | } 274 | getAxisParameter(motor, type, callback) { 275 | return this.transmit(6, type, motor, 0, callback); 276 | } 277 | 278 | // bank 0 is always used, as other banks are not needed for direct mode 279 | setGlobalParameter(type, value, callback) { 280 | return this.transmit(9, type, 0, value, callback); 281 | } 282 | 283 | // bank 0 is always used, as other banks are not needed for direct mode 284 | getGlobalParameter(type, callback) { 285 | return this.transmit(10, type, 0, 0, callback); 286 | } 287 | 288 | // other than the simple command, this call actually calls callback/resolves the promise when 289 | // the reference search is done completly 290 | referenceSearch(motor, callback) { 291 | return this.transmit(13, 0, motor, 0, callback); 292 | } 293 | 294 | // bank must be 2 (digital outputs) 295 | // If port is null, sets all at once 296 | setGPIOLevel(port, bank, level, callback) { 297 | return this.transmit(14, port === null ? 255 : port, bank, level, callback); 298 | } 299 | 300 | // bank 0: digital inputs 301 | // bank 1: analog inputs (value between 0 and 2^16-1/65535) 302 | // bank 2: digital outputs 303 | // If port is null on digital, returns a bit vector of all 304 | // To query the state of the ENABLE input, use port=10, bank=0 305 | getGPIOLevel(port, bank, callback) { 306 | return this.transmit(15, port === null ? 255 : port, bank, 0, callback); 307 | } 308 | } 309 | 310 | exports.TMCM3212 = TMCM3212; -------------------------------------------------------------------------------- /drivers/industry/X20BC008U.js: -------------------------------------------------------------------------------- 1 | /* 2 | * X20BC008U.js 3 | * 4 | * A class to interface a B&R X20BC008U bus controller via OPC-UA and read/write all GPIOs 5 | * of attached X20DI* and X20DO* devices. 6 | * 7 | * This class takes care of all error handling. It waits for the Ethernet cable to be 8 | * connected before trying to connect to the bus controller (otherwise the connection 9 | * might fail at auto-start when the microcontroller boots). Also, all errors are handled 10 | * and result into a transparent reconnect which is not noticed outside of the class. 11 | * 12 | * All GPIOs of a device are read and written at once for most efficiency. 13 | * GPIO reads are pushed to the microcontroller via subscriptions, so you can listen 14 | * for them without using CPU. 15 | * 16 | * Note: If you cannot connect to your server, you might have restarted 17 | * your program to often by pressing Stop. This way destroy() is 18 | * not called and the connection is not closed gracefully. 19 | * The server will need some time to timeout. 20 | * 21 | * Example on how to use: 22 | * 23 | 24 | let br = new X20BC008U("opc.tcp://192.168.1.1:4840"); 25 | 26 | // Called when inputs of a device change 27 | br.on('input', (deviceIndex, vals) => { 28 | console.log("Device ", deviceIndex, ":", vals); 29 | for(let pinIndex = 0; pinIndex < 6; pinIndex++) { 30 | // Use only one of both: 31 | // Option 1: read directly from given bit field 32 | console.log(" pin ", pinIndex, ": ", (vals & (1 << pinIndex)) ? 1 : 0); 33 | // Option 2: call br.getLevel (less unreadable arithmetic) 34 | console.log(" pin ", pinIndex, ": ", br.getLevel(deviceIndex, pinIndex)); 35 | } 36 | }); 37 | 38 | // Let the 6 outputs of the device flash again and again 39 | const OUTPUT_DEVICE = 4; 40 | 41 | let vals = 0; 42 | setInterval(() => { 43 | br.setLevels(OUTPUT_DEVICE, vals % 64); 44 | vals++; 45 | }, 100); 46 | 47 | * --- end of example --- 48 | */ 49 | 50 | let events = require('events'); 51 | let opcua = require('opc-ua'); 52 | let lowsys = require('lowsys'); 53 | 54 | class X20BC008U extends events.EventEmitter { 55 | /* 56 | * API CALLS 57 | * 58 | * In addition, you can use obj.on('input', (deviceIndex, vals) => {...}) 59 | * to get changes in input pins pushed to your code. vals is a bit field 60 | * of all input values of the device, just as it is returned by obj.getLevels 61 | */ 62 | 63 | constructor(url) { 64 | this.mURL = url; 65 | this.initConnection(); 66 | } 67 | 68 | getLevel(deviceIndex, pinIndex) { 69 | return (this.mVals[deviceIndex] & (1 << pinIndex)) ? 1 : 0; 70 | } 71 | 72 | getLevels(deviceIndex) { 73 | return this.mVals[deviceIndex]; 74 | } 75 | 76 | setLevel(deviceIndex, pinIndex, val) { 77 | if (val) 78 | this.mVals[deviceIndex] = this.mVals[deviceIndex] | (1 << pinIndex); 79 | else 80 | this.mVals[deviceIndex] = this.mVals[deviceIndex] & ~(1 << pinIndex); 81 | this.writeDevice(deviceIndex); 82 | } 83 | 84 | setLevels(deviceIndex, vals) { 85 | this.mVals[deviceIndex] = vals; 86 | this.writeDevice(deviceIndex); 87 | } 88 | 89 | /* 90 | * EVERYTHING BELOW HERE IS INTERNAL 91 | */ 92 | 93 | initConnection() { 94 | this.mInputNodes = {}; 95 | this.mOutputNodes = {}; 96 | this.mVals = []; 97 | this.mWriteDeviceStatus = []; 98 | 99 | if (this.mClient) { 100 | this.mClient.destroy(); 101 | this.mClient = null; 102 | 103 | setTimeout(() => { 104 | this.initConnection(); 105 | }, 1000); 106 | return; 107 | } 108 | 109 | this.waitForEthernet(() => { 110 | let client = this.mClient = new opcua.UAClient({ 111 | url: this.mURL, 112 | timeout: 5000 113 | }); 114 | 115 | client.on('connect', () => { 116 | if (client != this.mClient) return; 117 | 118 | client.createSubscription((err, subscription) => { 119 | if (client != this.mClient) return; 120 | if (err) { 121 | console.error(err); 122 | this.initConnection(); 123 | return; 124 | } 125 | 126 | this.mSubscription = subscription; 127 | 128 | this.listDevices(client, (err, devices) => { 129 | if (client != this.mClient) return; 130 | if (err) { 131 | console.error(err); 132 | this.initConnection(); 133 | return; 134 | } 135 | 136 | for (let i = 0; i < devices.length; i++) { 137 | this.mVals.push(0); 138 | if (devices[i].name.substr(0, 5) == "X20DO") 139 | this.initOutputDevice(i, devices[i].node); 140 | if (devices[i].name.substr(0, 5) == "X20DI") 141 | this.initInputDevice(i, devices[i].node); 142 | } 143 | }); 144 | }); 145 | }); 146 | 147 | client.on('dataChanged', (node, val) => { 148 | if (typeof val != 'number') 149 | return; 150 | 151 | let inputIndex = this.mInputNodes[node.node]; 152 | if (inputIndex !== undefined) { 153 | this.mVals[inputIndex] = val; 154 | this.emit('input', inputIndex, val); 155 | } 156 | }); 157 | 158 | client.on('error', (err) => { 159 | console.error("error: ", err); 160 | console.error("reconnecting"); 161 | 162 | this.initConnection(); 163 | }); 164 | }); 165 | } 166 | 167 | waitForEthernet(cb) { 168 | if (lowsys.status.eth == 'CONNECTED') 169 | return cb(); 170 | 171 | console.log("Waiting for Ethernet cable to be connected..."); 172 | 173 | // To make sure low.js does not exit 174 | let interval = setInterval(() => {}, 100000); 175 | process.once('lowsysStatusChange', () => { 176 | console.log("Connected."); 177 | 178 | clearInterval(interval); 179 | this.waitForEthernet(cb); 180 | }); 181 | } 182 | 183 | // Returns an array with all devices connected to the X20BC008U, in the format: 184 | // [{name: 'X20...', node: }, ...] 185 | // The array is sorted exactly in the order the devices are connected 186 | listDevices(client, callback) { 187 | let client = this.mClient; 188 | 189 | client.objects.subNode('2:DeviceSet/2:X20BC008U/2:X2X/2:SubDevices', (err, node) => { 190 | if (client != this.mClient) return; 191 | if (err) return callback(err); 192 | 193 | node.children((err, nodes) => { 194 | if (client != this.mClient) return; 195 | if (err) return callback(err); 196 | 197 | var devices = []; 198 | for (var i = 0; i < nodes.length; i++) { 199 | var node = nodes[i]; 200 | if (node.browseName.substr(0, 2) != 'ST') 201 | continue; 202 | 203 | var name = node.displayName; 204 | var pos = name.indexOf(' | '); 205 | if (pos >= 0) 206 | name = name.substr(pos + 3); 207 | 208 | devices[parseInt(node.browseName.substr(2)) - 1] = { 209 | name: name, 210 | node: node 211 | }; 212 | } 213 | callback(null, devices); 214 | }); 215 | }); 216 | } 217 | 218 | initInputDevice(index, node) { 219 | let client = this.mClient; 220 | 221 | // Set to packed format 222 | node.subNode('2:ParameterSet/2:DigitalInputsPacked', (err, subNode) => { 223 | if (client != this.mClient) return; 224 | if (err) { 225 | console.error(err); 226 | this.initConnection(); 227 | return; 228 | } 229 | 230 | subNode.write(1, opcua.TYPE_BYTE, (err) => { 231 | if (client != this.mClient) return; 232 | if (err) { 233 | console.error(err); 234 | this.initConnection(); 235 | return; 236 | } 237 | 238 | node.subNode('2:MethodSet/2:ApplyChanges', (err, subNode) => { 239 | if (client != this.mClient) return; 240 | if (err) { 241 | console.error(err); 242 | this.initConnection(); 243 | return; 244 | } 245 | 246 | subNode.call((err) => { 247 | if (client != this.mClient) return; 248 | if (err) { 249 | console.error(err); 250 | this.initConnection(); 251 | return; 252 | } 253 | 254 | // Get packed variable 255 | node.subNode('2:ParameterSet/2:DigitalInput', (err, subNode) => { 256 | if (client != this.mClient) return; 257 | if (err) { 258 | console.error(err); 259 | this.initConnection(); 260 | return; 261 | } 262 | 263 | // Subscribe to it 264 | this.mSubscription.add(subNode, (err) => { 265 | if (client != this.mClient) return; 266 | if (err) { 267 | console.error(err); 268 | this.initConnection(); 269 | return; 270 | } 271 | 272 | this.mInputNodes[subNode.node] = index; 273 | }); 274 | }); 275 | }); 276 | }); 277 | }); 278 | }); 279 | } 280 | 281 | initOutputDevice(index, node) { 282 | let client = this.mClient; 283 | 284 | // Set to packed format 285 | node.subNode('2:ParameterSet/2:DigitalOutputsPacked', (err, subNode) => { 286 | if (client != this.mClient) return; 287 | if (err) { 288 | console.error(err); 289 | this.initConnection(); 290 | return; 291 | } 292 | 293 | subNode.write(1, opcua.TYPE_BYTE, (err) => { 294 | if (client != this.mClient) return; 295 | if (err) { 296 | console.error(err); 297 | this.initConnection(); 298 | return; 299 | } 300 | 301 | node.subNode('2:MethodSet/2:ApplyChanges', (err, subNode) => { 302 | if (client != this.mClient) return; 303 | if (err) { 304 | console.error(err); 305 | this.initConnection(); 306 | return; 307 | } 308 | 309 | subNode.call((err) => { 310 | if (client != this.mClient) return; 311 | if (err) { 312 | console.error(err); 313 | this.initConnection(); 314 | return; 315 | } 316 | 317 | // Get packed variable 318 | node.subNode('2:ParameterSet/2:DigitalOutput', (err, subNode) => { 319 | this.mOutputNodes[index] = subNode; 320 | this.mWriteDeviceStatus[index] = 0; 321 | 322 | this.writeDevice(index); 323 | }); 324 | }); 325 | }); 326 | }); 327 | }); 328 | } 329 | 330 | writeDevice(deviceIndex) { 331 | if (!this.mOutputNodes[deviceIndex] || !this.mClient) 332 | return; 333 | 334 | if (this.mWriteDeviceStatus[deviceIndex]) { 335 | this.mWriteDeviceStatus[deviceIndex] = 2; 336 | return; 337 | } 338 | this.mWriteDeviceStatus[deviceIndex] = 1; 339 | 340 | let client = this.mClient; 341 | this.mOutputNodes[deviceIndex].write(this.mVals[deviceIndex], opcua.TYPE_BYTE, (err) => { 342 | if (client != this.mClient) return; 343 | if (err) { 344 | console.error(err); 345 | this.initConnection(); 346 | return; 347 | } 348 | 349 | let redo = this.mWriteDeviceStatus[deviceIndex] == 2; 350 | this.mWriteDeviceStatus[deviceIndex] = 0; 351 | 352 | if (redo) 353 | this.writeDevice(deviceIndex); 354 | }); 355 | } 356 | }; 357 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/sender.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var crypto = require('crypto'); 3 | var PerMessageDeflate = require('./permessage-deflate'); 4 | var bufferUtil = require('./buffer-util'); 5 | var validation = require('./validation'); 6 | var constants = require('./constants'); 7 | /** 8 | * HyBi Sender implementation. 9 | */ 10 | var Sender = /** @class */ (function () { 11 | /** 12 | * Creates a Sender instance. 13 | * 14 | * @param {net.Socket} socket The connection socket 15 | * @param {Object} extensions An object containing the negotiated extensions 16 | */ 17 | function Sender(socket, extensions) { 18 | this._extensions = extensions || {}; 19 | this._socket = socket; 20 | this._firstFragment = true; 21 | this._compress = false; 22 | this._bufferedBytes = 0; 23 | this._deflating = false; 24 | this._queue = []; 25 | } 26 | /** 27 | * Frames a piece of data according to the HyBi WebSocket protocol. 28 | * 29 | * @param {Buffer} data The data to frame 30 | * @param {Object} options Options object 31 | * @param {Number} options.opcode The opcode 32 | * @param {Boolean} options.readOnly Specifies whether `data` can be modified 33 | * @param {Boolean} options.fin Specifies whether or not to set the FIN bit 34 | * @param {Boolean} options.mask Specifies whether or not to mask `data` 35 | * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit 36 | * @return {Buffer[]} The framed data as a list of `Buffer` instances 37 | * @public 38 | */ 39 | Sender.frame = function (data, options) { 40 | var merge = data.length < 1024 || (options.mask && options.readOnly); 41 | var offset = options.mask ? 6 : 2; 42 | var payloadLength = data.length; 43 | if (data.length >= 65536) { 44 | offset += 8; 45 | payloadLength = 127; 46 | } 47 | else if (data.length > 125) { 48 | offset += 2; 49 | payloadLength = 126; 50 | } 51 | var target = Buffer.allocUnsafe(merge ? data.length + offset : offset); 52 | target[0] = options.fin ? options.opcode | 0x80 : options.opcode; 53 | if (options.rsv1) 54 | target[0] |= 0x40; 55 | if (payloadLength === 126) { 56 | target.writeUInt16BE(data.length, 2); 57 | } 58 | else if (payloadLength === 127) { 59 | target.writeUInt32BE(0, 2); 60 | target.writeUInt32BE(data.length, 6); 61 | } 62 | if (!options.mask) { 63 | target[1] = payloadLength; 64 | if (merge) { 65 | data.copy(target, offset); 66 | return [target]; 67 | } 68 | return [target, data]; 69 | } 70 | var mask = crypto.randomBytes(4); 71 | target[1] = payloadLength | 0x80; 72 | target[offset - 4] = mask[0]; 73 | target[offset - 3] = mask[1]; 74 | target[offset - 2] = mask[2]; 75 | target[offset - 1] = mask[3]; 76 | if (merge) { 77 | bufferUtil.mask(data, mask, target, offset, data.length); 78 | return [target]; 79 | } 80 | bufferUtil.mask(data, mask, data, 0, data.length); 81 | return [target, data]; 82 | }; 83 | /** 84 | * Sends a close message to the other peer. 85 | * 86 | * @param {(Number|undefined)} code The status code component of the body 87 | * @param {String} data The message component of the body 88 | * @param {Boolean} mask Specifies whether or not to mask the message 89 | * @param {Function} cb Callback 90 | * @public 91 | */ 92 | Sender.prototype.close = function (code, data, mask, cb) { 93 | var buf; 94 | if (code === undefined) { 95 | buf = constants.EMPTY_BUFFER; 96 | } 97 | else if (typeof code !== 'number' || !validation.isValidStatusCode(code)) { 98 | throw new TypeError('First argument must be a valid error code number'); 99 | } 100 | else if (data === undefined || data === '') { 101 | buf = Buffer.allocUnsafe(2); 102 | buf.writeUInt16BE(code, 0); 103 | } 104 | else { 105 | buf = Buffer.allocUnsafe(2 + Buffer.byteLength(data)); 106 | buf.writeUInt16BE(code, 0); 107 | buf.write(data, 2); 108 | } 109 | if (this._deflating) { 110 | this.enqueue([this.doClose, buf, mask, cb]); 111 | } 112 | else { 113 | this.doClose(buf, mask, cb); 114 | } 115 | }; 116 | /** 117 | * Frames and sends a close message. 118 | * 119 | * @param {Buffer} data The message to send 120 | * @param {Boolean} mask Specifies whether or not to mask `data` 121 | * @param {Function} cb Callback 122 | * @private 123 | */ 124 | Sender.prototype.doClose = function (data, mask, cb) { 125 | this.sendFrame(Sender.frame(data, { 126 | fin: true, 127 | rsv1: false, 128 | opcode: 0x08, 129 | mask: mask, 130 | readOnly: false 131 | }), cb); 132 | }; 133 | /** 134 | * Sends a ping message to the other peer. 135 | * 136 | * @param {*} data The message to send 137 | * @param {Boolean} mask Specifies whether or not to mask `data` 138 | * @param {Function} cb Callback 139 | * @public 140 | */ 141 | Sender.prototype.ping = function (data, mask, cb) { 142 | var readOnly = true; 143 | if (!Buffer.isBuffer(data)) { 144 | if (data instanceof ArrayBuffer) { 145 | data = Buffer.from(data); 146 | } 147 | else if (ArrayBuffer.isView(data)) { 148 | data = viewToBuffer(data); 149 | } 150 | else { 151 | data = Buffer.from(data); 152 | readOnly = false; 153 | } 154 | } 155 | if (this._deflating) { 156 | this.enqueue([this.doPing, data, mask, readOnly, cb]); 157 | } 158 | else { 159 | this.doPing(data, mask, readOnly, cb); 160 | } 161 | }; 162 | /** 163 | * Frames and sends a ping message. 164 | * 165 | * @param {*} data The message to send 166 | * @param {Boolean} mask Specifies whether or not to mask `data` 167 | * @param {Boolean} readOnly Specifies whether `data` can be modified 168 | * @param {Function} cb Callback 169 | * @private 170 | */ 171 | Sender.prototype.doPing = function (data, mask, readOnly, cb) { 172 | this.sendFrame(Sender.frame(data, { 173 | fin: true, 174 | rsv1: false, 175 | opcode: 0x09, 176 | mask: mask, 177 | readOnly: readOnly 178 | }), cb); 179 | }; 180 | /** 181 | * Sends a pong message to the other peer. 182 | * 183 | * @param {*} data The message to send 184 | * @param {Boolean} mask Specifies whether or not to mask `data` 185 | * @param {Function} cb Callback 186 | * @public 187 | */ 188 | Sender.prototype.pong = function (data, mask, cb) { 189 | var readOnly = true; 190 | if (!Buffer.isBuffer(data)) { 191 | if (data instanceof ArrayBuffer) { 192 | data = Buffer.from(data); 193 | } 194 | else if (ArrayBuffer.isView(data)) { 195 | data = viewToBuffer(data); 196 | } 197 | else { 198 | data = Buffer.from(data); 199 | readOnly = false; 200 | } 201 | } 202 | if (this._deflating) { 203 | this.enqueue([this.doPong, data, mask, readOnly, cb]); 204 | } 205 | else { 206 | this.doPong(data, mask, readOnly, cb); 207 | } 208 | }; 209 | /** 210 | * Frames and sends a pong message. 211 | * 212 | * @param {*} data The message to send 213 | * @param {Boolean} mask Specifies whether or not to mask `data` 214 | * @param {Boolean} readOnly Specifies whether `data` can be modified 215 | * @param {Function} cb Callback 216 | * @private 217 | */ 218 | Sender.prototype.doPong = function (data, mask, readOnly, cb) { 219 | this.sendFrame(Sender.frame(data, { 220 | fin: true, 221 | rsv1: false, 222 | opcode: 0x0a, 223 | mask: mask, 224 | readOnly: readOnly 225 | }), cb); 226 | }; 227 | /** 228 | * Sends a data message to the other peer. 229 | * 230 | * @param {*} data The message to send 231 | * @param {Object} options Options object 232 | * @param {Boolean} options.compress Specifies whether or not to compress `data` 233 | * @param {Boolean} options.binary Specifies whether `data` is binary or text 234 | * @param {Boolean} options.fin Specifies whether the fragment is the last one 235 | * @param {Boolean} options.mask Specifies whether or not to mask `data` 236 | * @param {Function} cb Callback 237 | * @public 238 | */ 239 | Sender.prototype.send = function (data, options, cb) { 240 | var opcode = options.binary ? 2 : 1; 241 | var rsv1 = options.compress; 242 | var readOnly = true; 243 | if (!Buffer.isBuffer(data)) { 244 | if (data instanceof ArrayBuffer) { 245 | data = Buffer.from(data); 246 | } 247 | else if (ArrayBuffer.isView(data)) { 248 | data = viewToBuffer(data); 249 | } 250 | else { 251 | data = Buffer.from(data); 252 | readOnly = false; 253 | } 254 | } 255 | var perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; 256 | if (this._firstFragment) { 257 | this._firstFragment = false; 258 | if (rsv1 && perMessageDeflate) { 259 | rsv1 = data.length >= perMessageDeflate._threshold; 260 | } 261 | this._compress = rsv1; 262 | } 263 | else { 264 | rsv1 = false; 265 | opcode = 0; 266 | } 267 | if (options.fin) 268 | this._firstFragment = true; 269 | if (perMessageDeflate) { 270 | var opts = { 271 | fin: options.fin, 272 | rsv1: rsv1, 273 | opcode: opcode, 274 | mask: options.mask, 275 | readOnly: readOnly 276 | }; 277 | if (this._deflating) { 278 | this.enqueue([this.dispatch, data, this._compress, opts, cb]); 279 | } 280 | else { 281 | this.dispatch(data, this._compress, opts, cb); 282 | } 283 | } 284 | else { 285 | this.sendFrame(Sender.frame(data, { 286 | fin: options.fin, 287 | rsv1: false, 288 | opcode: opcode, 289 | mask: options.mask, 290 | readOnly: readOnly 291 | }), cb); 292 | } 293 | }; 294 | /** 295 | * Dispatches a data message. 296 | * 297 | * @param {Buffer} data The message to send 298 | * @param {Boolean} compress Specifies whether or not to compress `data` 299 | * @param {Object} options Options object 300 | * @param {Number} options.opcode The opcode 301 | * @param {Boolean} options.readOnly Specifies whether `data` can be modified 302 | * @param {Boolean} options.fin Specifies whether or not to set the FIN bit 303 | * @param {Boolean} options.mask Specifies whether or not to mask `data` 304 | * @param {Boolean} options.rsv1 Specifies whether or not to set the RSV1 bit 305 | * @param {Function} cb Callback 306 | * @private 307 | */ 308 | Sender.prototype.dispatch = function (data, compress, options, cb) { 309 | var _this = this; 310 | if (!compress) { 311 | this.sendFrame(Sender.frame(data, options), cb); 312 | return; 313 | } 314 | var perMessageDeflate = this._extensions[PerMessageDeflate.extensionName]; 315 | this._deflating = true; 316 | perMessageDeflate.compress(data, options.fin, function (_, buf) { 317 | options.readOnly = false; 318 | _this.sendFrame(Sender.frame(buf, options), cb); 319 | _this._deflating = false; 320 | _this.dequeue(); 321 | }); 322 | }; 323 | /** 324 | * Executes queued send operations. 325 | * 326 | * @private 327 | */ 328 | Sender.prototype.dequeue = function () { 329 | while (!this._deflating && this._queue.length) { 330 | var params = this._queue.shift(); 331 | this._bufferedBytes -= params[1].length; 332 | params[0].apply(this, params.slice(1)); 333 | } 334 | }; 335 | /** 336 | * Enqueues a send operation. 337 | * 338 | * @param {Array} params Send operation parameters. 339 | * @private 340 | */ 341 | Sender.prototype.enqueue = function (params) { 342 | this._bufferedBytes += params[1].length; 343 | this._queue.push(params); 344 | }; 345 | /** 346 | * Sends a frame. 347 | * 348 | * @param {Buffer[]} list The frame to send 349 | * @param {Function} cb Callback 350 | * @private 351 | */ 352 | Sender.prototype.sendFrame = function (list, cb) { 353 | if (list.length === 2) { 354 | this._socket.write(list[0]); 355 | this._socket.write(list[1], cb); 356 | } 357 | else { 358 | this._socket.write(list[0], cb); 359 | } 360 | }; 361 | return Sender; 362 | }()); 363 | module.exports = Sender; 364 | /** 365 | * Converts an `ArrayBuffer` view into a buffer. 366 | * 367 | * @param {(DataView|TypedArray)} view The view to convert 368 | * @return {Buffer} Converted view 369 | * @private 370 | */ 371 | function viewToBuffer(view) { 372 | var buf = Buffer.from(view.buffer); 373 | if (view.byteLength !== view.buffer.byteLength) { 374 | return buf.slice(view.byteOffset, view.byteOffset + view.byteLength); 375 | } 376 | return buf; 377 | } 378 | -------------------------------------------------------------------------------- /general/chat_ws_webserver/node_modules/ws/lib/websocket-server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var __extends = (this && this.__extends) || (function () { 3 | var extendStatics = function (d, b) { 4 | extendStatics = Object.setPrototypeOf || 5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 6 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 7 | return extendStatics(d, b); 8 | } 9 | return function (d, b) { 10 | extendStatics(d, b); 11 | function __() { this.constructor = d; } 12 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 13 | }; 14 | })(); 15 | var EventEmitter = require('events'); 16 | var crypto = require('crypto'); 17 | var http = require('http'); 18 | var url = require('url'); 19 | var PerMessageDeflate = require('./permessage-deflate'); 20 | var extension = require('./extension'); 21 | var constants = require('./constants'); 22 | var WebSocket = require('./websocket'); 23 | /** 24 | * Class representing a WebSocket server. 25 | * 26 | * @extends EventEmitter 27 | */ 28 | var WebSocketServer = /** @class */ (function (_super) { 29 | __extends(WebSocketServer, _super); 30 | /** 31 | * Create a `WebSocketServer` instance. 32 | * 33 | * @param {Object} options Configuration options 34 | * @param {String} options.host The hostname where to bind the server 35 | * @param {Number} options.port The port where to bind the server 36 | * @param {http.Server} options.server A pre-created HTTP/S server to use 37 | * @param {Function} options.verifyClient An hook to reject connections 38 | * @param {Function} options.handleProtocols An hook to handle protocols 39 | * @param {String} options.path Accept only connections matching this path 40 | * @param {Boolean} options.noServer Enable no server mode 41 | * @param {Boolean} options.clientTracking Specifies whether or not to track clients 42 | * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable permessage-deflate 43 | * @param {Number} options.maxPayload The maximum allowed message size 44 | * @param {Function} callback A listener for the `listening` event 45 | */ 46 | function WebSocketServer(options, callback) { 47 | var _this = _super.call(this) || this; 48 | options = Object.assign({ 49 | maxPayload: 100 * 1024 * 1024, 50 | perMessageDeflate: false, 51 | handleProtocols: null, 52 | clientTracking: true, 53 | verifyClient: null, 54 | noServer: false, 55 | backlog: null, 56 | server: null, 57 | host: null, 58 | path: null, 59 | port: null 60 | }, options); 61 | if (options.port == null && !options.server && !options.noServer) { 62 | throw new TypeError('One of the "port", "server", or "noServer" options must be specified'); 63 | } 64 | if (options.port != null) { 65 | _this._server = http.createServer(function (req, res) { 66 | var body = http.STATUS_CODES[426]; 67 | res.writeHead(426, { 68 | 'Content-Length': body.length, 69 | 'Content-Type': 'text/plain' 70 | }); 71 | res.end(body); 72 | }); 73 | _this._server.listen(options.port, options.host, options.backlog, callback); 74 | } 75 | else if (options.server) { 76 | _this._server = options.server; 77 | } 78 | if (_this._server) { 79 | _this._removeListeners = addListeners(_this._server, { 80 | listening: _this.emit.bind(_this, 'listening'), 81 | error: _this.emit.bind(_this, 'error'), 82 | upgrade: function (req, socket, head) { 83 | _this.handleUpgrade(req, socket, head, function (ws) { 84 | _this.emit('connection', ws, req); 85 | }); 86 | } 87 | }); 88 | } 89 | if (options.perMessageDeflate === true) 90 | options.perMessageDeflate = {}; 91 | if (options.clientTracking) 92 | _this.clients = new Set(); 93 | _this.options = options; 94 | return _this; 95 | } 96 | /** 97 | * Returns the bound address, the address family name, and port of the server 98 | * as reported by the operating system if listening on an IP socket. 99 | * If the server is listening on a pipe or UNIX domain socket, the name is 100 | * returned as a string. 101 | * 102 | * @return {(Object|String|null)} The address of the server 103 | * @public 104 | */ 105 | WebSocketServer.prototype.address = function () { 106 | if (this.options.noServer) { 107 | throw new Error('The server is operating in "noServer" mode'); 108 | } 109 | if (!this._server) 110 | return null; 111 | return this._server.address(); 112 | }; 113 | /** 114 | * Close the server. 115 | * 116 | * @param {Function} cb Callback 117 | * @public 118 | */ 119 | WebSocketServer.prototype.close = function (cb) { 120 | var _this = this; 121 | if (cb) 122 | this.once('close', cb); 123 | // 124 | // Terminate all associated clients. 125 | // 126 | if (this.clients) { 127 | for (var _i = 0, _a = this.clients; _i < _a.length; _i++) { 128 | var client = _a[_i]; 129 | client.terminate(); 130 | } 131 | } 132 | var server = this._server; 133 | if (server) { 134 | this._removeListeners(); 135 | this._removeListeners = this._server = null; 136 | // 137 | // Close the http server if it was internally created. 138 | // 139 | if (this.options.port != null) { 140 | server.close(function () { return _this.emit('close'); }); 141 | return; 142 | } 143 | } 144 | process.nextTick(emitClose, this); 145 | }; 146 | /** 147 | * See if a given request should be handled by this server instance. 148 | * 149 | * @param {http.IncomingMessage} req Request object to inspect 150 | * @return {Boolean} `true` if the request is valid, else `false` 151 | * @public 152 | */ 153 | WebSocketServer.prototype.shouldHandle = function (req) { 154 | if (this.options.path && url.parse(req.url).pathname !== this.options.path) { 155 | return false; 156 | } 157 | return true; 158 | }; 159 | /** 160 | * Handle a HTTP Upgrade request. 161 | * 162 | * @param {http.IncomingMessage} req The request object 163 | * @param {net.Socket} socket The network socket between the server and client 164 | * @param {Buffer} head The first packet of the upgraded stream 165 | * @param {Function} cb Callback 166 | * @public 167 | */ 168 | WebSocketServer.prototype.handleUpgrade = function (req, socket, head, cb) { 169 | var _this = this; 170 | socket.on('error', socketOnError); 171 | var version = +req.headers['sec-websocket-version']; 172 | var extensions = {}; 173 | if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || 174 | !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) || 175 | !this.shouldHandle(req)) { 176 | return abortHandshake(socket, 400); 177 | } 178 | if (this.options.perMessageDeflate) { 179 | var perMessageDeflate = new PerMessageDeflate(this.options.perMessageDeflate, true, this.options.maxPayload); 180 | try { 181 | var offers = extension.parse(req.headers['sec-websocket-extensions']); 182 | if (offers[PerMessageDeflate.extensionName]) { 183 | perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]); 184 | extensions[PerMessageDeflate.extensionName] = perMessageDeflate; 185 | } 186 | } 187 | catch (err) { 188 | return abortHandshake(socket, 400); 189 | } 190 | } 191 | // 192 | // Optionally call external client verification handler. 193 | // 194 | if (this.options.verifyClient) { 195 | var info = { 196 | origin: req.headers["" + (version === 8 ? 'sec-websocket-origin' : 'origin')], 197 | secure: !!(req.connection.authorized || req.connection.encrypted), 198 | req: req 199 | }; 200 | if (this.options.verifyClient.length === 2) { 201 | this.options.verifyClient(info, function (verified, code, message, headers) { 202 | if (!verified) { 203 | return abortHandshake(socket, code || 401, message, headers); 204 | } 205 | _this.completeUpgrade(extensions, req, socket, head, cb); 206 | }); 207 | return; 208 | } 209 | if (!this.options.verifyClient(info)) 210 | return abortHandshake(socket, 401); 211 | } 212 | this.completeUpgrade(extensions, req, socket, head, cb); 213 | }; 214 | /** 215 | * Upgrade the connection to WebSocket. 216 | * 217 | * @param {Object} extensions The accepted extensions 218 | * @param {http.IncomingMessage} req The request object 219 | * @param {net.Socket} socket The network socket between the server and client 220 | * @param {Buffer} head The first packet of the upgraded stream 221 | * @param {Function} cb Callback 222 | * @private 223 | */ 224 | WebSocketServer.prototype.completeUpgrade = function (extensions, req, socket, head, cb) { 225 | var _this = this; 226 | var _a; 227 | // 228 | // Destroy the socket if the client has already sent a FIN packet. 229 | // 230 | if (!socket.readable || !socket.writable) 231 | return socket.destroy(); 232 | var key = crypto.createHash('sha1') 233 | .update(req.headers['sec-websocket-key'] + constants.GUID, 'binary') 234 | .digest('base64'); 235 | var headers = [ 236 | 'HTTP/1.1 101 Switching Protocols', 237 | 'Upgrade: websocket', 238 | 'Connection: Upgrade', 239 | "Sec-WebSocket-Accept: " + key 240 | ]; 241 | var ws = new WebSocket(null); 242 | var protocol = req.headers['sec-websocket-protocol']; 243 | if (protocol) { 244 | protocol = protocol.trim().split(/ *, */); 245 | // 246 | // Optionally call external protocol selection handler. 247 | // 248 | if (this.options.handleProtocols) { 249 | protocol = this.options.handleProtocols(protocol, req); 250 | } 251 | else { 252 | protocol = protocol[0]; 253 | } 254 | if (protocol) { 255 | headers.push("Sec-WebSocket-Protocol: " + protocol); 256 | ws.protocol = protocol; 257 | } 258 | } 259 | if (extensions[PerMessageDeflate.extensionName]) { 260 | var params = extensions[PerMessageDeflate.extensionName].params; 261 | var value = extension.format((_a = {}, 262 | _a[PerMessageDeflate.extensionName] = [params], 263 | _a)); 264 | headers.push("Sec-WebSocket-Extensions: " + value); 265 | ws._extensions = extensions; 266 | } 267 | // 268 | // Allow external modification/inspection of handshake headers. 269 | // 270 | this.emit('headers', headers, req); 271 | socket.write(headers.concat('\r\n').join('\r\n')); 272 | socket.removeListener('error', socketOnError); 273 | ws.setSocket(socket, head, this.options.maxPayload); 274 | if (this.clients) { 275 | this.clients.add(ws); 276 | ws.on('close', function () { return _this.clients.delete(ws); }); 277 | } 278 | cb(ws); 279 | }; 280 | return WebSocketServer; 281 | }(EventEmitter)); 282 | module.exports = WebSocketServer; 283 | /** 284 | * Add event listeners on an `EventEmitter` using a map of 285 | * pairs. 286 | * 287 | * @param {EventEmitter} server The event emitter 288 | * @param {Object.} map The listeners to add 289 | * @return {Function} A function that will remove the added listeners when called 290 | * @private 291 | */ 292 | function addListeners(server, map) { 293 | for (var _i = 0, _a = Object.keys(map); _i < _a.length; _i++) { 294 | var event = _a[_i]; 295 | server.on(event, map[event]); 296 | } 297 | return function removeListeners() { 298 | for (var _i = 0, _a = Object.keys(map); _i < _a.length; _i++) { 299 | var event = _a[_i]; 300 | server.removeListener(event, map[event]); 301 | } 302 | }; 303 | } 304 | /** 305 | * Emit a `'close'` event on an `EventEmitter`. 306 | * 307 | * @param {EventEmitter} server The event emitter 308 | * @private 309 | */ 310 | function emitClose(server) { 311 | server.emit('close'); 312 | } 313 | /** 314 | * Handle premature socket errors. 315 | * 316 | * @private 317 | */ 318 | function socketOnError() { 319 | this.destroy(); 320 | } 321 | /** 322 | * Close the connection when preconditions are not fulfilled. 323 | * 324 | * @param {net.Socket} socket The socket of the upgrade request 325 | * @param {Number} code The HTTP response status code 326 | * @param {String} [message] The HTTP response body 327 | * @param {Object} [headers] Additional HTTP response headers 328 | * @private 329 | */ 330 | function abortHandshake(socket, code, message, headers) { 331 | if (socket.writable) { 332 | message = message || http.STATUS_CODES[code]; 333 | headers = Object.assign({ 334 | 'Connection': 'close', 335 | 'Content-type': 'text/html', 336 | 'Content-Length': Buffer.byteLength(message) 337 | }, headers); 338 | socket.write("HTTP/1.1 " + code + " " + http.STATUS_CODES[code] + "\r\n" + 339 | Object.keys(headers).map(function (h) { return h + ": " + headers[h]; }).join('\r\n') + 340 | '\r\n\r\n' + 341 | message); 342 | } 343 | socket.removeListener('error', socketOnError); 344 | socket.destroy(); 345 | } 346 | --------------------------------------------------------------------------------