[update] Electron15 のデフォルトに従い、nodeIntegration: false, contextIsolation: true で動作するようコードを修正、remote をやめて ipc を使用するよう修正。

This commit is contained in:
ocogeclub 2021-10-01 20:39:50 +09:00
parent 5caa639a26
commit c6957ef715
17 changed files with 3589 additions and 2926 deletions

View File

@ -597,6 +597,19 @@
<field name="continuous">once</field>
<field name="interim">TRUE</field>
</block>
<label text="顔認識" web-line="4.0" web-line-width="200"></label>
<block type="ugj_faceapi">
<field name="with_landmark">FALSE</field>
</block>
<block type="ugj_face_init"></block>
<block type="ugj_face_display"></block>
<block type="ugj_face_detect">
<field name="x" id="TF-ziC[]OAJ9]r}YjUQg" variabletype=""></field>
<field name="y" id="]G:_%S*1v4!9_+yx532d" variabletype=""></field>
<field name="w" id="wP$LdeXDCiWzrI!/9R)G" variabletype=""></field>
<field name="h" id="8+E.-dP-Omt}v2~DCC]M" variabletype="">高さ</field>
</block>
<block type="ugj_face_drawrect"></block>
<label text="画像認識" web-line="4.0" web-line-width="200"></label>
<block type="ugj_library_load">
<value name="lib">
@ -966,9 +979,9 @@
<script src="./google-blockly/blocks_compressed.js"></script>
<script src="./google-blockly/msg/js/ja.js"></script>
<script src="./lib/custom-dialog.js"></script>
<script src="./ugj_const.js"></script>
<script src="./ugj_blocks.js"></script>
<script src="./ugj_script.js"></script>
<script src="./index_elutil.js"></script>
<script src="./index.js"></script>
<script src="./lib/beautify.min.js"></script>
<script src="./lib/prettify.js"></script>
<script src="./.shared/skyway_key.js"></script>

726
index.js
View File

@ -1,147 +1,619 @@
const { app, BrowserWindow, Menu, session } = require('electron');
require('@electron/remote/main').initialize();
'use strict';
/** Force disable security warning */
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
let elutil = elUtil_new();
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
const testfunc = () => {
elutil.openURL('http://ocoge.club');
}
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
/** Icon */
icon: "./icon.png",
width: 1280,
height: 940,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true
//============ User Customize Start ===============
// カスタムブロックカラー定義
Blockly.HSV_SATURATION = 0.55;
Blockly.HSV_VALUE = 0.75;
var gpio_color = '0';
var multimedia_color = '240';
var network_color = '340';
var special_color = '20';
var snippets_color = '90';
// テーマ
var theme = Blockly.Theme.defineTheme('ocoge', {
'base': Blockly.Themes.Classic,
'startHats': true,
'componentStyles': {
'toolboxBackgroundColour': 'aliceblue',
'flyoutBackgroundColour': 'lavender',
'toolboxForegroundColour': 'white',
'flyoutForegroundColour': 'steelblue'
},
'blockStyles': {
'gpio_blocks': {
"colourPrimary": gpio_color
},
'multimedia_blocks': {
"colourPrimary": multimedia_color
},
'network_blocks': {
"colourPrimary": network_color
},
'special_blocks': {
"colourPrimary": special_color
},
'snippets_blocks': {
"colourPrimary": snippets_color
}
})
/** Maximize Window when launch */
win.maximize();
},
'categoryStyles': {
"gpio_category": {
"colour": gpio_color
},
"multimedia_category": {
"colour": multimedia_color
},
"network_category": {
"colour": network_color
},
"special_category": {
"colour": special_color
},
"snippets_category": {
"colour": snippets_color
}
},
});
// and load the index.html of the app.
win.loadFile('index.html')
// Customize messages
Blockly.Msg["CONTROLS_IF_MSG_THEN"] = "ならば";
Blockly.Msg["CONTROLS_REPEAT_INPUT_DO"] = "";
Blockly.Msg["MATH_CHANGE_TITLE"] = "変数 %1 を %2 増やす";
Blockly.Msg["VARIABLES_SET"] = "変数 %1 を %2 にする";
// Blockly.Msg["TEXT_PRINT_TITLE"] = "ダイアログに %1 を表示";
Blockly.Msg["LOGIC_BOOLEAN_FALSE"] = "偽";
Blockly.Msg["LOGIC_BOOLEAN_TOOLTIP"] = "真 または 偽 を返します。";
Blockly.Msg["LOGIC_BOOLEAN_TRUE"] = "真";
// ローカライズ対応の準備
Blockly.Msg["UGJ_CONTROL_FOR_TITLE"] = "%1 %2 を %3 から %4 まで %5 ずつ %6 %7 %8";
Blockly.Msg["UGJ_CONTROL_FOR_INDEX"] = "番号";
Blockly.Msg["UGJ_CONTROL_FOR_INCREASE"] = "増やして";
Blockly.Msg["UGJ_CONTROL_FOR_DECREASE"] = "減らして";
Blockly.Msg["UGJ_CONTROL_FOR_TOOLTIP"] = "インデックス番号を決められた数ずつ増やし(減らし)ながら、ステートメントを実行します。";
Blockly.Msg["UGJ_FOREACH_TITLE"] = "リスト %1 の各 %2 について %3 %4";
Blockly.Msg["UGJ_FOREACH_ITEM"] = "項目";
Blockly.Msg["UGJ_FOREACH_TOOLTIP"] = "リストの各項目について、その項目を変数「項目」としてステートメントを実行します。";
// Open the DevTools.
// win.webContents.openDevTools()
Blockly.Msg["GPIOCHIP_OPEN_TITLE"] = "GPIO を使えるようにする";
Blockly.Msg["GPIOCHIP_OPEN_TOOLTIP"] = "GPIOを初期化して接続します。";
Blockly.Msg["GPIOCHIP_CLOSE_TITLE"] = "GPIO の後片付けをする";
Blockly.Msg["GPIOCHIP_CLOSE_TOOLTIP"] = "GPIOとの接続を終了します。";
Blockly.Msg["GPIOCHIP_CLOSE_TITLE"] = "GPIO の後片付けをする";
Blockly.Msg["GPIO_CLAIM_INPUT_TITLE"] = "GPIO %1 を入力モードにして %2";
Blockly.Msg["GPIO_CLAIM_INPUT_TOOLTIP"] = "GPIO端子を入力モードにして、プルアップ・プルダウン・無しを設定します。";
Blockly.Msg["GPIO_CLAIM_OUTPUT_TITLE"] = "GPIO %1 を出力モードにする";
Blockly.Msg["GPIO_CLAIM_OUTPUT_TOOLTIP"] = "GPIO端子のモードを出力に設定します。";
Blockly.Msg["GPIO_READ_TITLE"] = "GPIO %1 の値";
Blockly.Msg["GPIO_READ_TOOLTIP"] = "GPIO端子の値をデジタル値0または1で読み取ります。";
Blockly.Msg["GPIO_WRITE_TITLE"] = "GPIO %1 の値を %2 にする";
Blockly.Msg["GPIO_WRITE_TOOLTIP"] = "GPIO端子の値をデジタル値0または1で出力します。";
Blockly.Msg["TX_SERVO_TITLE"] = "GPIO %1 のサーボモータの回転を %2 にする";
Blockly.Msg["TX_SERVO_TOOLTIP"] = "サーボモータの回転をパルス幅(10002000μsec)までの数値で指定します。";
Blockly.Msg["TX_PWM_TITLE"] = "PWM : GPIO %1 に、パルス周波数 %2 Hz, デューティー比 %3 %%で出力";
Blockly.Msg["TX_PWM_TOOLTIP"] = "パルス周波数をセットして、GPIO端子がPWM出力できるようにします。レンジは100固定です。";
Blockly.Msg["I2C_OPEN_TITLE"] = "アドレス %1 の I2C デバイスを開く";
Blockly.Msg["I2C_OPEN_TOOLTIP"] = "I2C接続されたデバイスとの通信を開始します。一度にオープンできるI2Cデバイスはひとつだけです。";
Blockly.Msg["SERIAL_OPEN_TITLE"] = "シリアルポート %1 を速度 %2 bpsで開く";
Blockly.Msg["SERIAL_OPEN_TOOLTIP"] = "シリアルデバイスとの接続を開きます。";
Blockly.Msg["SERIAL_CLOSE_TITLE"] = "シリアルポートを閉じる";
Blockly.Msg["SERIAL_CLOSE_TOOLTIP"] = "シリアルデバイスとの接続を閉じます。";
Blockly.Msg["SERIAL_WRITE_TITLE"] = "シリアルポートに %1 を送信する";
Blockly.Msg["SERIAL_WRITE_TOOLTIP"] = "シリアル接続されたデバイスにデータを送信します。シリアルポートは開かれていなくてはいけません。";
Blockly.Msg["SERIAL_READ_TITLE"] = "シリアルポートから %1 文字読み込む";
Blockly.Msg["SERIAL_READ_TOOLTIP"] = "オープン済みシリアルポートから、指定のバイト数だけデータを読み込みます。";
Blockly.Msg["I2C_CLOSE_TITLE"] = "I2C デバイスを閉じる";
Blockly.Msg["I2C_CLOSE_TOOLTIP"] = "I2C接続されたデバイスと通信を切断します。";
Blockly.Msg["I2C_WRITE_BYTE_TITLE"] = "デバイスに1バイトデータ %1 を送信";
Blockly.Msg["I2C_WRITE_BYTE_TOOLTIP"] = "i2cデバイスにバイトデータを送信します。0-0xFFの範囲の数字で入力してください。";
Blockly.Msg["I2C_READ_BYTE_TITLE"] = "I2C デバイスから 1 バイト受け取る";
Blockly.Msg["I2C_READ_BYTE_TOOLTIP"] = "オープン済み I2C デバイスからデータを 1 バイト受け取ります。";
Blockly.Msg["I2C_WRITE_BYTE_DATA_TITLE"] = "レジスタ %1 に %2 を書き込む";
Blockly.Msg["I2C_WRITE_BYTE_DATA_TOOLTIP"] = "デバイスの指定されたレジスタに1バイトを書き込みます。";
Blockly.Msg["I2C_READ_BYTE_DATA_TITLE"] = "レジスタ %1 の値";
Blockly.Msg["I2C_READ_BYTE_DATA_TOOLTIP"] = "デバイスの指定されたレジスタから1バイトを読み込みます。";
Blockly.Msg["I2C_READ_DEVICE_TITLE"] = "i2cデバイスから %1 バイト受け取る";
Blockly.Msg["I2C_READ_DEVICE_TOOLTIP"] = "デバイスから指定したバイト数のデータを受け取ります。データが指定の長さより短いこともあります。";
Blockly.Msg["I2C_WRITE_DEVICE_TITLE"] = "i2c デバイスに %1 を送信";
Blockly.Msg["I2C_WRITE_DEVICE_TOOLTIP"] = "i2c デバイスにデータを送信します。";
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
Blockly.Msg["UGJ_BME280_TITLE"] = "BME280アドレス %1 )の計測値: %2 %3 %4";
Blockly.Msg["UGJ_BME280_TOOLTIP"] = "環境センサーBME280で、気温摂氏、湿度、気圧hPaを計測し、それぞれを変数に代入します。";
Blockly.Msg["UGJ_BME280_READ_TEMP"] = "気温";
Blockly.Msg["UGJ_BME280_READ_HUM"] = "湿度";
Blockly.Msg["UGJ_BME280_READ_PRES"] = "気圧";
/** For SkyWay */
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders['Origin'] = 'electron://localhost';
callback({
cancel: false,
requestHeaders: details.requestHeaders
Blockly.Msg["UGJ_CODECHAR_TITLE"] = "コード %1 の文字";
Blockly.Msg["UGJ_CODECHAR_TOOLTIP"] = "文字コードを文字に変換します。";
Blockly.Msg["UGJ_CHARCODE_TITLE"] = "%1 の文字コード";
Blockly.Msg["UGJ_CHARCODE_TOOLTIP"] = "入力テキストの1文字目の文字コードを返します。";
Blockly.Msg["UGJ_HEXTODEC_TITLE"] = "0x %1";
Blockly.Msg["UGJ_HEXTODEC_TOOLTIP"] = "16進数を10進数に変換します。";
Blockly.Msg["UGJ_DECTOHEX_TITLE"] = "%1 を16進数に変換";
Blockly.Msg["UGJ_DECTOHEX_TOOLTIP"] = "10進数を16進数に変換します。";
Blockly.Msg["UGJ_CANVAS_INIT_TITLE"] = "キャンバスを表示";
Blockly.Msg["UGJ_CANVAS_INIT_TOOLTIP"] = "キャンバスを表示し、使用できるようにします。";
Blockly.Msg["UGJ_FACEAPI_TITLE"] = "TensorFlowによる顔検出 %1 ランドマークを検出 %2 %3";
Blockly.Msg["UGJ_FACEAPI_TOOLTIP"] = "TensorFlow とFaceAPI をロードし、顔検出をできるようにします。";
Blockly.Msg["UGJ_SLEEP_TITLE"] = "%1 秒待つ";
Blockly.Msg["UGJ_SLEEP_TOOLTIP"] = "指定した秒数だけ処理を中断します。";
// Customize Toolbox
class CustomCategory extends Blockly.ToolboxCategory {
/** Constructor for a custom category. @override */
constructor(categoryDef, toolbox, opt_parent) {
super(categoryDef, toolbox, opt_parent);
}
/** @override */
addColourBorder_(colour) {
this.rowDiv_.style.backgroundColor = colour;
}
/** @override */
setSelected(isSelected) {
// We do not store the label span on the category, so use getElementsByClassName.
var labelDom = this.rowDiv_.getElementsByClassName('blocklyTreeLabel')[0];
if (isSelected) {
// Change the background color of the div to white.
this.rowDiv_.style.backgroundColor = 'white';
// Set the colour of the text to the colour of the category.
labelDom.style.color = this.colour_;
this.iconDom_.style.color = this.colour_;
} else {
// Set the background back to the original colour.
this.rowDiv_.style.backgroundColor = this.colour_;
// Set the text back to white.
labelDom.style.color = 'white';
this.iconDom_.style.color = 'white';
}
// This is used for accessibility purposes.
Blockly.utils.aria.setState(/** @type {!Element} */(this.htmlDiv_),
Blockly.utils.aria.State.SELECTED, isSelected);
}
}
Blockly.registry.register(
Blockly.registry.Type.TOOLBOX_ITEM,
Blockly.ToolboxCategory.registrationName,
CustomCategory, true);
//============ User Customize End ===============
//背景canvasとマスコットの準備
const ugj_canvasBgImg = (imgSrc, x, y) => { //x,y == -1: center or middle
let el = document.getElementById('canvas_bg');
let ctx = el.getContext('2d');
ctx.fillStyle = 'rgb(255,255,255)';
ctx.fillRect(0, 0, 480, 360);
let img = new Image();
img.src = imgSrc;
img.onload = () => {
if (x < 0) { //センタリング
let w = img.width;
if (w >= 480) x = 0;
else x = Math.floor((480 - w) / 2);
}
if (y < 0) { //縦中寄せ
let h = img.height;
if (h >= 360) y = 0;
else y = Math.floor((360 - h) / 2);
}
ctx.drawImage(img, x, y);
}
};
// HTML部品のインスタンス - 画面上の必要な部品はすべてここで取得しておく
let ugjel_displayArea = document.getElementById('display_area'); // ディスプレイ部
let ugjel_blackboard = document.getElementById('blackboard'); // 黒板
let ugjel_inputForm = document.getElementById('inputForm'); // 入力フォーム
let ugjel_inputBox = document.getElementById('inputBox'); // 入力フィールド
// その他のプロパティ
let ugj_inputEvLstnrID = 0; // 入力フォームの動的イベントリスナの最新のID
let ugj_sounds = (names => { // サウンドファイルのいろいろの配列の初期化
let sounds = [];
names.forEach(value => {
let filepath = `./sounds/${value}.wav`;
sounds[value] = { 'file': filepath, 'audio': new Audio(filepath) };
});
return sounds;
})(['meow', 'bounce', 'type_chime', 'type_dink', 'type_tap', 'type_space', 'type_return']); // サウンドファイルのベース名のリスト
// メソッド
// マスコット選択
const ugj_selectMascot = async () => {
let fname = await elutil.selectMascotFile();
if (fname) {
ugj_canvasBgImg(fname, -1, -1);
elutil.setMascotFilePath(fname);
}
}
// サウンド再生 - 連続再生のため、再生開始後すぐにオーディオ要素を再生成する
const ugj_soundPlay = soundName => {
ugj_sounds[soundName]['audio'].play();
ugj_sounds[soundName]['audio'] = new Audio(ugj_sounds[soundName]['file']);
};
// OK,Cancel 2択のダイアログを表示
const ugj_confirm = (title, message, callback) => {
CustomDialog.show(title, message, {
showOkay: true,
onOkay: () => callback(true),
showCancel: true,
onCancel: () => callback(false)
});
};
const ugj_htmlEntities = str =>// HTMLエンティティのエスケープ
String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');//.replace(/&/g, '&amp;').replace(/ /g, '&nbsp;');
// 新規ワークスペース
const ugj_newWorkspace = () => {
ugj_confirm('新規ワークスペース', '保存していない内容はすべて破棄されます。よろしいですか?', okey => {
if (okey) {
workspace.clear();
elutil.newFile();
}
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
// app.on('ready', createWindow)
app.on('ready', () => {
createWindow();
/** Custom Menu */
let template = [
{
label: 'Menu',
submenu: [
{
label: 'Toggle Menu Bar',
click: () => {
win.setMenuBarVisibility(!win.menuBarVisible);
},
accelerator: "CommandOrControl+Shift+M"
},
{
label: 'Reload',
click: () => {
win.reload();
},
accelerator: "F5"
},
{
label: 'Mascot',
click: (item, focusedWindow) => {
if (focusedWindow)
focusedWindow.webContents.executeJavaScript('ugj_selectMascot()');
},
accelerator: "CommandOrControl+M"
},
{
label: 'Toggle Developer Tools',
click: () => {
win.webContents.toggleDevTools()
},
accelerator: "F12"
},
{
label: 'About',
click: () => {
var os = require('os');
var detail = 'Version: ' + process.env.npm_package_version + '\n'
+ 'Node.js: ' + process.versions.node + '\n'
+ 'Chrome: ' + process.versions.chrome + '\n'
+ 'Electron: ' + process.versions.electron + '\n'
+ 'V8: ' + process.versions.v8 + '\n'
+ 'OS: ' + os.type + ' ' + os.arch + ' ' + os.version + ' ' + os.release;
var options = {
type: 'info',
buttons: ['OK'],
title: 'OCoGe',
message: 'OCoGe - Oiwa Code Generator',
detail: detail
};
require('electron').dialog.showMessageBox(win, options);
},
accelerator: "CommandOrControl+I"
},
{
label: 'Quit',
click: () => {
app.quit();
},
accelerator: "CommandOrControl+Q"
// ワークスペースをファイルに保存・読込
const ugj_saveWorkspaceToFile = async () => {
let xml = Blockly.Xml.workspaceToDom(workspace);
let xml_text = Blockly.Xml.domToText(xml);
if (await elutil.saveWsFile(xml_text) === false) {
alert('保存できませんでした。');
}
]
}
const ugj_loadWorkspaceFromFile = async () => {
let xml_text = await elutil.loadWsFile();
if (xml_text.length > 0) {
let xml = Blockly.Xml.textToDom(xml_text);
Blockly.Xml.domToWorkspace(xml, workspace);
}
]
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
win.setMenuBarVisibility(false);
})
}
// ワークスペースを別名で保存
const ugj_saveWorkspaceAs = () => {
elutil.newFile();
ugj_saveWorkspaceToFile();
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
// ワークスペースをローカルストレージに保存・読込
const ugj_saveWorkspace = () => {
// Workspace
let xml = Blockly.Xml.workspaceToDom(workspace);
let xml_text = Blockly.Xml.domToText(xml);
localStorage.setItem("ocoge.xml", xml_text);
}
const ugj_loadWorkspace = () => {
// Workspace
let xml_text = localStorage.getItem("ocoge.xml");
if (xml_text !== null) {
if (xml_text.length != 0) {
let xml = Blockly.Xml.textToDom(xml_text);
Blockly.Xml.domToWorkspace(xml, workspace);
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
}
})
}
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
// // Python コードフォーマッタ YAPF をコール
// const ugj_pyBeautify = (code) => {
// let formatted;
// formatted = window.ocogeapi.child_process.spawnSync('python3', ['-m', 'yapf'], { input: code }).stdout.toString();
// return formatted;
// }
// Python コードフォーマッタ Black をコール
const ugj_pyBeautify = (code) => {
let formatted;
formatted = window.ocogeapi.child_process.spawnSync('python3', ['-m', 'black', '-'], { input: code }).stdout.toString();
return formatted;
}
// ワークスペースからコードを生成して必要であれば整形処理をする
const ugj_createCode = (args) => {
const addAsync = args.async || false;
const beautify = args.beautify || false;
const prettify = args.prettify || false;
const ext = args.ext || 'js';
let code;
if (ext == 'py') { // Python コード出力
try {
code = Blockly.Python.workspaceToCode(workspace);
} catch (e) { // Pythonコードを持たないブロックがある場合
window.alert('Python 非対応のブロックが使用されています。\n' + e.message);
}
code = Blockly.Python.workspaceToCode(workspace);
}
else { // Javascript コード出力
code = Blockly.JavaScript.workspaceToCode(workspace);
}
if (ext == 'py') { //Python
// コードを綺麗に
if (beautify) code = ugj_pyBeautify(code);
} else { // JavaScript
// await使用のため、必要に応じてコード全体をasync付き即時関数でラップ
if (addAsync) {
code = [
'(async () => {',
code,
'})();'
].join('\n');
}
// コードを綺麗に
if (beautify) code = js_beautify(code, { indent_size: 2 });
}
// シンタックスハイライト(HTML化): 先に HTML エンティティをエスケープ
if (prettify) code = PR.prettyPrintOne(ugj_htmlEntities(code), ext, true);
return code;
}
// ブロックスクリプト実行
const ugj_runCode = async () => {
document.activeElement.blur(); //実行ボタンからフォーカスを外す:エンターキー押下が悪さをするため
let code = ugj_createCode({ 'async': true });
await eval(code).catch(e => { alert(e); });
}
// コードをダイアログで表示・保存
// エレメントのオブジェクトとかコールバックとか Python対応とか
// 色々この中で完結させてみる
const ugj_showCode = () => {
var ext = 'js';
const dialog = document.getElementById('codeDlg');
const content = document.getElementById('dlgContent');
const btn_close = document.getElementById('dlgClose');
const btn_export = document.getElementById('dlgExport');
const chkbox_cli = document.getElementById('dlgCli');
const dialog_title = document.getElementById('dlgTitle');
const showCode = () =>
content.innerHTML = ugj_createCode({ 'beautify': true, 'ext': ext, 'prettify': true });
showCode();
dialog.showModal();
const close_cb = () => {
dialog.close();
btn_close.removeEventListener('click', close_cb);
btn_export.removeEventListener('click', export_cb);
}
const export_cb = () => {
code = ugj_createCode({ 'ext': ext, 'async': true, 'beautify': true });
// blackboardWrite()とwindow.alert()、fukidashi()をconsole.log()に書き換え、
// document... と ugj_... と elutil... をコメントアウト(ブラウザ関連部分の追放という意味では不完全なので注意)
// あと正規表現もいい加減
if (chkbox_cli.checked && ext == 'js')
code = code.replace(/const appendDiv[^#]*\/\/#/gm, 'const blackboardWrite = text => console.log(text);').replace('window.alert', 'console.log').replace(/ugj_fukidashi(.*), \d+(\);)/gm, 'console.log$1$2').replace(/(^(?=.*document.)[^;]*;)/gm, '/* $1 */').replace(/(^(?=.*ugj_)[^;]*;)/gm, '/* $1 */').replace(/(^(?=.*elutil.)[^;]*;)/gm, '/* $1 */');
if (elutil.saveFile(code, ext) === false) {
alert('保存できませんでした。');
}
close_cb();
}
btn_close.addEventListener('click', close_cb);
btn_export.addEventListener('click', export_cb);
document.addEventListener('keypress', (ev) => {
if (ev.key == 'p' && ext == 'js') {
ext = 'py';
showCode();
}
});
document.addEventListener('keyup', (ev) => {
if (ev.key == 'p') {
ext = 'js';
showCode();
}
});
}
// フキダシ
let ugj_fdTimeoutID = null;
let ugj_fdRecentBox = null;
const ugj_fukidashi = (text, sec) => {
// Canvas Context
const context = document.getElementById('canvas').getContext('2d');
// 吹き出しを消去する関数
const clearFd = (x, y, w, h) => context.clearRect(x, y, w, h);
// 前回の思い出を忘れる
if (ugj_fdRecentBox !== null) {
clearFd(ugj_fdRecentBox.x, ugj_fdRecentBox.y, ugj_fdRecentBox.w, ugj_fdRecentBox.h);
clearTimeout(ugj_fdTimeoutID);
ugj_fdRecentBox = null;
}
// 基本設定
let rtopX = 170; // フキダシ右上 X座標
let rtopY = 40; // フキダシ右上 Y座標
let boxWidth = 140;
let padding = 5;
let radius = 5;// 円弧の半径
// 吹き出しの背景色
context.fillStyle = "#b7e6ff";
// テキスト設定
let limitedWidth = boxWidth - (padding * 2);
let size = 14;
context.font = size + "px ''";
// テキスト調整 行に分解
let lineTextList = text.split("\n");
let newLineTextList = [];
lineTextList.forEach((lineText) => {
if (context.measureText(lineText).width > limitedWidth) {
characterList = lineText.split("");// 1文字ずつ分割
let preLineText = "";
lineText = "";
characterList.forEach((character) => {
lineText += character;
if (context.measureText(lineText).width > limitedWidth) {
newLineTextList.push(preLineText);
lineText = character;
}
preLineText = lineText;
});
}
newLineTextList.push(lineText);
});
let lineLength = newLineTextList.length;
// 角丸
let width = boxWidth;// 枠の幅
let height = (size * lineLength) + (padding * 3); // 枠の高さ
let toRadianCoefficient = Math.PI / 180; // 角度からラジアンへの変換係数
// 角丸原点(左上座標)
let boxOrigin = {
"x": rtopX - width,
"y": rtopY,
}
// 円弧から円弧までの直線は自動で引かれます、角度は回り方によって変わります。
// arc(中心x, 中心y, 半径, 開始角度, 終了角度, 反時計回り)
context.beginPath();
context.arc(boxOrigin.x + radius, boxOrigin.y + radius, radius, 180 * toRadianCoefficient, 270 * toRadianCoefficient, false);// 左上
context.arc(boxOrigin.x + width - radius, boxOrigin.y + radius, radius, 270 * toRadianCoefficient, 0, false);// 右上
context.arc(boxOrigin.x + width - radius, boxOrigin.y + height - radius, radius, 0, 90 * toRadianCoefficient, false);// 右下
context.arc(boxOrigin.x + radius, boxOrigin.y + height - radius, radius, 90 * toRadianCoefficient, 180 * toRadianCoefficient, false);// 左下
context.closePath();
context.fill();
// 矢印(ヒゲ)
let arrow = {
"x": rtopX - width / 2 + 40,
"y": rtopY + height + 10,
"width": 10,
"height": 10,
}
context.beginPath();
context.moveTo(arrow.x, arrow.y);
context.lineTo(arrow.x, arrow.y - arrow.height);
context.lineTo(arrow.x - arrow.width, arrow.y - arrow.height);
context.fill();
// テキスト描画
context.fillStyle = "#000000";
newLineTextList.forEach((lineText, index) => {
context.fillText(lineText, boxOrigin.x + padding, boxOrigin.y + padding + (size * (index + 1)));
});
// 描画した吹き出しの位置情報を保存
ugj_fdRecentBox = { x: boxOrigin.x, y: boxOrigin.y, w: width, h: height + arrow.height };
// 指定時間後に消去0以下で自動消去なし
if (sec > 0) {
ugj_fdTimeoutID = setTimeout(() => {
clearFd(boxOrigin.x, boxOrigin.y, width, height + arrow.height);
}, sec * 1000);
}
// return [boxOrigin.x, boxOrigin.y, width, height+arrow.height];
// https://qiita.com/horikeso/items/95595f379a8dfa63c34a
}
// コードジェネレータモードJavascript/Python切り替え
var isPyMode;
const ugj_pyMode = (checked) => isPyMode = checked;
//=====================================
//======= Blockly GUI codes ===========
// Use in a block or block definition:
// Resizable workspace injection script
var blocklyArea = document.getElementById('blocklyArea');
var blocklyDiv = document.getElementById('blocklyDiv');
// var workspace = Blockly.inject(blocklyDiv,
// {toolbox: document.getElementById('toolbox')});
var workspace = Blockly.inject(blocklyDiv,
{
toolbox: document.getElementById('toolbox'),
theme: theme,
renderer: 'thrasos',
scrollbars: true,
grid: {
spacing: 20,
length: 1,
colour: '#888',//888
snap: true
},
zoom: { startScale: 1.0, controls: true },
trashcan: true,
media: './google-blockly/media/'
});
var onresize = function (e) {
// Compute the absolute coordinates and dimensions of blocklyArea.
var element = blocklyArea;
var x = 0;
var y = 0;
do {
x += element.offsetLeft;
y += element.offsetTop;
element = element.offsetParent;
} while (element);
// Position blocklyDiv over blocklyArea.
blocklyDiv.style.left = x + 'px';
blocklyDiv.style.top = y + 'px';
blocklyDiv.style.width = blocklyArea.offsetWidth + 'px';
blocklyDiv.style.height = blocklyArea.offsetHeight + 'px';
Blockly.svgResize(workspace);
};
window.addEventListener('resize', onresize, false);
onresize();
Blockly.svgResize(workspace);
//=====================================
//=====================================
// ワークスペースの未保存の変更のフラグ
const ugj_wsUpdateCB = event => {
if (event.type != Blockly.Events.UI) {
elutil.setWsChanged(true);
}
}
// ウィンドウロード・アンロード時
window.onload = () => {
var menu = document.getElementById('conmenu'); //独自コンテキストメニュー
var area = document.getElementById('dlgContent'); //対象エリア
var body = document.body; //bodyエリア
body.oncontextmenu = () => false;
//右クリック時に独自コンテキストメニューを表示する
area.addEventListener('contextmenu', function (e) {
menu.style.left = e.pageX + 'px';
menu.style.top = e.pageY + 'px';
menu.classList.add('on');
});
//左クリック時に独自コンテキストメニューを非表示にする
body.addEventListener('click', function () {
if (menu.classList.contains('on')) {
menu.classList.remove('on');
}
});
// ワークスペースといくつかの環境のオートリストア
ugj_loadWorkspace();
elutil.loadPrefsFromLS();
setTimeout(() => { // 環境設定のロードが終わってからイベントリスナを作成
workspace.addChangeListener(ugj_wsUpdateCB);
}, 100);
// 背景canvas
ugj_canvasBgImg(elutil.getMascotFilePath(), -1, -1);
}
window.onbeforeunload = () => {
ugj_saveWorkspace();
elutil.savePrefsToLS();
elutil.killAllChildren();
}

315
index_elutil.js Normal file
View File

@ -0,0 +1,315 @@
/** Electron / Node.js に由来する機能に関する諸々 */
/** ブラウザ動作時にもちょびっと対応 */
'use strict'
// ファイル定数
const ugj_const = {
doc_root: '/home/pi/Documents/ocoge_docs/',
app_name: 'ocoge',
mascot_path: './img/',
mascot_defname: 'tamachee.png',
library_path: './lib/',
executable_path: './bin/',
localStorage_fname: 'ocoge.json',
error_ja_all: 'エラーが発生しました。\n『おこげ倶楽部』までお問い合わせください。'
}
/** クラス elUtil ****************************************************************** */
// Electron 動作用
class elUtil {
constructor() {
this.path = window.ocogeapi.path
this.fs = window.ocogeapi.fs
this.ipcRenderer = window.ocogeapi.electron_ipcRenderer
this.shell = window.ocogeapi.electron_shell
this.saveFilepath = null;
this.wsChanged = false;
this.mascotFilePath = this.path.join(ugj_const.mascot_path, ugj_const.mascot_defname);
this.children = [];
}
// 0で数値の桁合わせ : NUM=値 LEN=桁数
zeroPadding(NUM, LEN) {
return (Array(LEN).join('0') + NUM).slice(-LEN);
}
// 現在の日付時刻から workspace フォルダ内のユニークなファイルパスを作成
getUniqueFilepath() {
let today = new Date();
let filename = today.getFullYear() + '-' + this.zeroPadding((today.getMonth() + 1), 2) + '-' + this.zeroPadding(today.getDate(), 2) + '-' + this.zeroPadding(today.getHours(), 2) + '-' + this.zeroPadding(today.getMinutes(), 2) + '-' + this.zeroPadding(today.getSeconds(), 2);
let filepath = this.path.join(ugj_const.doc_root, filename);
return filepath;
}
// リンクを外部ブラウザで開く
openURL(url) {
this.shell.openExternal(url);
}
// saveFilepath を更新
// ウィンドウタイトルバーテキストを変更
setSaveFilepath(filepath) {
this.saveFilepath = filepath;
this.setWsChanged(false);
}
// ワークスペースが変更された・保存された
// ウィンドウタイトルバーテキストを変更
setWsChanged(changed) {
let title;
this.wsChanged = changed;
if (this.saveFilepath) title = this.saveFilepath + ' - ' + ugj_const.app_name;
else title = ugj_const.app_name;
if (changed) title = '*' + title;
this.ipcRenderer.send('set_title', title);
}
// 保存ファイルプロパティを更新
newFile() { this.setSaveFilepath(null); }
// ワークスペースファイル読み込みの一連の動作のラッパ
async loadWsFile() {
let filepath = await this.openFile('xml', ugj_const.doc_root);
if (filepath.length > 0) {
if (this.saveFilepath === null) {
this.setSaveFilepath(filepath);
} //読み込みに失敗してもsaveFilepathが更新されてしまうのはちょっと具合が悪いかも
return this.readFromFile(filepath);
} else {
return '';
}
}
// その他ファイル読み込みの一連の動作のラッパ
async loadFile(ext) {
let filepath = await this.openFile(ext, ugj_const.doc_root);
if (filepath.length > 0) {
return this.readFromFile(filepath);
} else {
return '';
}
}
async selectMascotFile() {
return await this.openFile('png', ugj_const.mascot_path);
}
// オープンファイルダイアログ
async openFile(ext, dpath) {
let title = 'Select a file';
let filter;
if (ext == 'xml') {
filter = { name: 'XML - Extensible Markup Language', extensions: ['xml'] };
} else if (ext == 'js') {
filter = { name: 'JS - JavaScript', extensions: ['js'] };
} else if (ext == 'png') {
filter = { name: 'PNG - Portable Network Graphics', extensions: ['png'] };
} else {
filter = { name: 'text file', extensions: ['txt'] };
}
let filepaths = await this.ipcRenderer.invoke('open_dialog', title, dpath, filter);
if (filepaths == undefined) {
return '';
} else {
return filepaths[0];
}
}
// ファイルからデータを読み込み
readFromFile(filepath) {
let data = '';
try {
data = this.fs.readFileSync(filepath, 'utf-8');
}
catch (err) {
console.log(err);
}
return data;
}
// テキストファイル読み込み: 外部スクリプト動的読み込みに使用
readTextFile(filepath) {
return this.readFromFile(filepath);
}
// ワークスペースファイル保存の一連の動作のラッパ
async saveWsFile(data) {
if (this.saveFilepath === null) {
let filepath = await this.selectSaveFile('xml');
if (filepath === undefined) { //キャンセル
return undefined;
} else {
this.setSaveFilepath(filepath);
} //これも保存が成功したら変更するようにすべきかしら
} else this.setWsChanged(false);
return this.writeToFile(this.saveFilepath, data);
}
// その他ファイル保存の一連の動作のラッパ
async saveFile(data, ext) {
let filepath = await this.selectSaveFile(ext);
if (filepath === undefined) { //キャンセル
return undefined;
}
return this.writeToFile(filepath, data);
}
// ファイル保存ダイアログ
async selectSaveFile(ext) {
let title = '保存先を決定してください';
let filter, filter_name;
let defName;
if (ext == 'xml') {
filter = { name: 'xml file', extensions: ['xml'] };
defName = this.getUniqueFilepath() + '.xml';
} else if (ext == 'js' || ext == 'py') {
if (ext == 'js') filter_name = 'javascript file';
else filter_name = 'python file'
filter = { name: filter_name, extensions: [ext] };
// ワークスペース保存名がある場合、それをベースにファイル名の候補を決める
if (this.saveFilepath === null) {
defName = this.getUniqueFilepath() + '.' + ext;
} else {
let dirname = this.path.dirname(this.saveFilepath);
let basename = this.path.basename(this.saveFilepath, '.xml');
defName = path.join(dirname, basename) + '.' + ext;
}
} else {
filter = { name: 'text file', extensions: ['txt'] };
}
let filename = await this.ipcRenderer.invoke('save_dialog', title, defName, filter);
return filename;
}
// ファイル書き込み
writeToFile(filepath, data) {
try {
this.fs.writeFileSync(filepath, data);
return true;
}
catch (err) {
return false;
}
}
// 子プロセス関連
// 新しい子プロセスを作成し、配列に保存
addChild(child) {
this.children.push(child);
}
// 全ての子プロセスを殺し、配列をクリア
killAllChildren() {
this.children.forEach(function (child) {
child.kill();
});
this.children = [];
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージに保存
savePrefsToLS() {
let wc = '0';
if (this.wsChanged) wc = '1';
let o = { 'saveFilepath': this.saveFilepath, 'wsChanged': wc, 'mascotFilePath': this.mascotFilePath };
let s = JSON.stringify(o);
localStorage.setItem(this.localStorage_fname, s);
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージからロード
loadPrefsFromLS() {
let s = localStorage.getItem(this.localStorage_fname);
if (s !== null) {
let o = JSON.parse(s);
this.setSaveFilepath(o.saveFilepath);
if (o.wsChanged == '0') this.setWsChanged(false);
else this.setWsChanged(true);
if (o.mascotFilePath) this.setMascotFilePath(o.mascotFilePath);
}
}
// マスコット画像パスをプロパティにセット
setMascotFilePath(fpath) {
this.mascotFilePath = fpath;
}
getMascotFilePath() {
return this.mascotFilePath;
}
// ファイル名にアプリケーションのドキュメントルートまでのパスをつけて返す
getDocPath(filename) {
return this.path.join(this.appDocRoot, filename);
}
}
// ブラウザ動作用
class brUtil {
// マスコット
getMascotFilePath() { return `./img/${ugj_const.mascot_defname}`; }
//ワークスペースのダウンロード
saveWsFile(xml_text) {
let blob = new Blob([xml_text], { "type": "text/xml" });
const downLoadLink = document.createElement("a");
document.body.appendChild(downLoadLink);
downLoadLink.download = 'workspace.xml';
downLoadLink.href = URL.createObjectURL(blob);
downLoadLink.click();
downLoadLink.parentElement.removeChild(downLoadLink);
return true;
}
//ワークスペースのインポート
loadWsFile() {
const fileInputEl = document.createElement('input');
document.body.appendChild(fileInputEl);
fileInputEl.type = 'file';
fileInputEl.addEventListener('change', ev => {
let reader = new FileReader();
reader.readAsText(ev.target.files[0]);
reader.addEventListener('load', () => {
let xml = Blockly.Xml.textToDom(reader.result);
Blockly.Xml.domToWorkspace(xml, workspace);
});
});
fileInputEl.click();
fileInputEl.parentElement.removeChild(fileInputEl);
return '';
}
// index.jsから万一呼ばれても何もしない関数
openURL() { ; }
selectMascotFile() { ; }
setMascotFilePath(fname) { ; }
saveFile() { ; }
savePrefsToLS() { ; }
loadPrefsFromLS() { ; }
newFile() { ; }
setWsChanged() { ; }
killAllChildren() { ; }
}
// Electron 動作 / ブラウザ動作自動判別
const is_el = (typeof window.ocogeapi !== 'undefined')
// utilクラスのインスタンスを返す
const elUtil_new = () => {
if (is_el) return new elUtil
else return new brUtil
}
// "require" for "BLOCK"s
// ブラウザ動作時にはすべてアラートを表示
const require = module_name => {
if (is_el) {
switch (module_name) {
case '@ocogeclub/lgpio':
return window.ocogeapi.lgpio;
case '@ocogeclub/bme280':
return window.ocogeapi.bme280;
case 'fs':
return window.ocogeapi.fs;
case 'path':
return window.ocogeapi.path;
default:
throw new Error(`Unknown module "${module_name}" required.\nStopped.`);
}
} else {
let block;
switch (module_name) {
case '@ocogeclub/lgpio':
block = 'GPIO';
break;
case '@ocogeclub/bme280':
block = 'BME280';
break;
case 'fs':
block = 'ファイル';
break;
default:
throw new Error(ugj_const.error_ja_all);
}
throw `ブロック「${block}」は、Web体験版ではご利用になれません。\n詳しくは https://ocoge.club/ をご覧ください。`;
}
}

View File

@ -0,0 +1,39 @@
[
{
"weights":
[
{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008194216092427571,"min":-0.9423348506291708}},
{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006839508168837603,"min":-0.8412595047670252}},
{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009194007106855804,"min":-1.2779669878529567}},
{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0036026100317637128,"min":-0.3170296827952067}},
{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.000740380117706224,"min":-0.06367269012273527}},
{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},
{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":1,"min":0}},
{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0037702228508743585,"min":-0.6220867703942692}},
{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0033707996209462483,"min":-0.421349952618281}},
{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014611541991140328,"min":-1.8556658328748217}},
{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002832523046755323,"min":-0.30307996600281956}},
{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006593170586754294,"min":-0.6329443763284123}},
{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.012215249211180444,"min":-1.6001976466646382}},
{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002384825547536214,"min":-0.3028728445370992}},
{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005859645441466687,"min":-0.7617539073906693}},
{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013121426806730382,"min":-1.7845140457153321}},
{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032247188044529336,"min":-0.46435950784122243}},
{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002659512618008782,"min":-0.32977956463308894}},
{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015499923743453681,"min":-1.9839902391620712}},
{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0032450980999890497,"min":-0.522460794098237}},
{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005911862382701799,"min":-0.792189559282041}},
{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021025861478319356,"min":-2.2077154552235325}},
{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00349616945958605,"min":-0.46149436866535865}},
{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008104994250278847,"min":-1.013124281284856}},
{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.029337059282789044,"min":-3.5791212325002633}},
{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0038808938334969913,"min":-0.4230174278511721}},
{"name":"fc/weights","shape":[128,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.014016061670639936,"min":-1.8921683255363912}},
{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029505149698724935,"min":0.088760145008564}}
],
"paths":
[
"face_landmark_68_tiny_model.bin"
]
}
]

Binary file not shown.

View File

@ -1 +1,30 @@
[{"weights":[{"name":"conv0/filters","shape":[3,3,3,16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009007044399485869,"min":-1.2069439495311063}},{"name":"conv0/bias","shape":[16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005263455241334205,"min":-0.9211046672334858}},{"name":"conv1/depthwise_filter","shape":[3,3,16,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004001977630690033,"min":-0.5042491814669441}},{"name":"conv1/pointwise_filter","shape":[1,1,16,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013836609615999109,"min":-1.411334180831909}},{"name":"conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0015159862590771096,"min":-0.30926119685173037}},{"name":"conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002666276225856706,"min":-0.317286870876948}},{"name":"conv2/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015265831292844286,"min":-1.6792414422128714}},{"name":"conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0020280554598453,"min":-0.37113414915168985}},{"name":"conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006100742489683862,"min":-0.8907084034938438}},{"name":"conv3/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016276211832083907,"min":-2.0508026908425725}},{"name":"conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394414279975143,"min":-0.7637432129944072}},{"name":"conv4/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006716050119961009,"min":-0.8059260143953211}},{"name":"conv4/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021875603993733724,"min":-2.8875797271728514}},{"name":"conv4/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0041141652009066415,"min":-0.8187188749804216}},{"name":"conv5/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008423839597141042,"min":-0.9013508368940915}},{"name":"conv5/pointwise_filter","shape":[1,1,256,512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.030007277283014035,"min":-3.8709387695088107}},{"name":"conv5/bias","shape":[512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008402082966823203,"min":-1.4871686851277068}},{"name":"conv8/filters","shape":[1,1,512,25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.028336129469030042,"min":-4.675461362389957}},{"name":"conv8/bias","shape":[25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002268134028303857,"min":-0.41053225912299807}}],"paths":["tiny_face_detector_model-shard1"]}]
[
{
"weights":
[
{"name":"conv0/filters","shape":[3,3,3,16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009007044399485869,"min":-1.2069439495311063}},
{"name":"conv0/bias","shape":[16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005263455241334205,"min":-0.9211046672334858}},
{"name":"conv1/depthwise_filter","shape":[3,3,16,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004001977630690033,"min":-0.5042491814669441}},
{"name":"conv1/pointwise_filter","shape":[1,1,16,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013836609615999109,"min":-1.411334180831909}},
{"name":"conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0015159862590771096,"min":-0.30926119685173037}},
{"name":"conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002666276225856706,"min":-0.317286870876948}},
{"name":"conv2/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015265831292844286,"min":-1.6792414422128714}},
{"name":"conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0020280554598453,"min":-0.37113414915168985}},
{"name":"conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006100742489683862,"min":-0.8907084034938438}},
{"name":"conv3/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016276211832083907,"min":-2.0508026908425725}},
{"name":"conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394414279975143,"min":-0.7637432129944072}},
{"name":"conv4/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006716050119961009,"min":-0.8059260143953211}},
{"name":"conv4/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021875603993733724,"min":-2.8875797271728514}},
{"name":"conv4/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0041141652009066415,"min":-0.8187188749804216}},
{"name":"conv5/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008423839597141042,"min":-0.9013508368940915}},
{"name":"conv5/pointwise_filter","shape":[1,1,256,512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.030007277283014035,"min":-3.8709387695088107}},
{"name":"conv5/bias","shape":[512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008402082966823203,"min":-1.4871686851277068}},
{"name":"conv8/filters","shape":[1,1,512,25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.028336129469030042,"min":-4.675461362389957}},
{"name":"conv8/bias","shape":[25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002268134028303857,"min":-0.41053225912299807}}
],
"paths":
[
"tiny_face_detector_model.bin"
]
}
]

View File

@ -1,60 +1,60 @@
'use strict';
// const { strictEqual } = require('assert');
this.pi = null;
class BME280 {
this.i2cBusNo = null;
this.i2cAddress = null;
this.i2cHand = null;
constructor(options) {
this.I2C_ADDRESS_B = 0x76;
this.I2C_ADDRESS_A = 0x77;
this.CHIP_ID = 0x58;
this.REGISTER_DIG_T1 = 0x88;
this.REGISTER_DIG_T2 = 0x8A;
this.REGISTER_DIG_T3 = 0x8C;
this.REGISTER_DIG_P1 = 0x8E;
this.REGISTER_DIG_P2 = 0x90;
this.REGISTER_DIG_P3 = 0x92;
this.REGISTER_DIG_P4 = 0x94;
this.REGISTER_DIG_P5 = 0x96;
this.REGISTER_DIG_P6 = 0x98;
this.REGISTER_DIG_P7 = 0x9A;
this.REGISTER_DIG_P8 = 0x9C;
this.REGISTER_DIG_P9 = 0x9E;
this.REGISTER_DIG_H1 = 0xA1;
this.REGISTER_DIG_H2 = 0xE1;
this.REGISTER_DIG_H3 = 0xE3;
this.REGISTER_DIG_H4 = 0xE4;
this.REGISTER_DIG_H5 = 0xE5;
this.REGISTER_DIG_H6 = 0xE7;
this.REGISTER_CHIPID = 0xD0;
this.REGISTER_RESET = 0xE0;
this.REGISTER_CONTROL_HUM = 0xF2;
this.REGISTER_CONTROL = 0xF4;
this.REGISTER_PRESSURE_DATA = 0xF7;
this.REGISTER_TEMP_DATA = 0xFA;
this.REGISTER_HUMIDITY_DATA = 0xFD;
exports.init = (options) => {
this.pi = require('@ocogeclub/lgpio');
this.i2cBusNo = (options && options.hasOwnProperty('i2cBusNo')) ? options.i2cBusNo : 1;
this.i2cAddress = (options && options.hasOwnProperty('i2cAddress')) ? options.i2cAddress : BME280.BME280_DEFAULT_I2C_ADDRESS();
this.i2cAddress = (options && options.hasOwnProperty('i2cAddress')) ? options.i2cAddress : this.BME280_DEFAULT_I2C_ADDRESS();
this.i2cHand = this.pi.i2c_open(this.i2cBusNo, this.i2cAddress);
this.I2C_ADDRESS_B = 0x76;
this.I2C_ADDRESS_A = 0x77;
this.CHIP_ID = 0x58;
this.REGISTER_DIG_T1 = 0x88;
this.REGISTER_DIG_T2 = 0x8A;
this.REGISTER_DIG_T3 = 0x8C;
this.REGISTER_DIG_P1 = 0x8E;
this.REGISTER_DIG_P2 = 0x90;
this.REGISTER_DIG_P3 = 0x92;
this.REGISTER_DIG_P4 = 0x94;
this.REGISTER_DIG_P5 = 0x96;
this.REGISTER_DIG_P6 = 0x98;
this.REGISTER_DIG_P7 = 0x9A;
this.REGISTER_DIG_P8 = 0x9C;
this.REGISTER_DIG_P9 = 0x9E;
this.REGISTER_DIG_H1 = 0xA1;
this.REGISTER_DIG_H2 = 0xE1;
this.REGISTER_DIG_H3 = 0xE3;
this.REGISTER_DIG_H4 = 0xE4;
this.REGISTER_DIG_H5 = 0xE5;
this.REGISTER_DIG_H6 = 0xE7;
this.REGISTER_CHIPID = 0xD0;
this.REGISTER_RESET = 0xE0;
this.REGISTER_CONTROL_HUM = 0xF2;
this.REGISTER_CONTROL = 0xF4;
this.REGISTER_PRESSURE_DATA = 0xF7;
this.REGISTER_TEMP_DATA = 0xFA;
this.REGISTER_HUMIDITY_DATA = 0xFD;
}
init() {
let r;
r = this.pi.i2c_write_byte_data(this.i2cHand, this.REGISTER_CHIPID, 0);
if (r < 0) return r;
let chipId = this.pi.i2c_read_byte_data(this.i2cHand, this.REGISTER_CHIPID);
if (chipId !== BME280.CHIP_ID_BME280() &&
chipId !== BME280.CHIP_ID1_BMP280() &&
chipId !== BME280.CHIP_ID2_BMP280() &&
chipId !== BME280.CHIP_ID3_BMP280()) {
if (chipId !== this.CHIP_ID_BME280() &&
chipId !== this.CHIP_ID1_BMP280() &&
chipId !== this.CHIP_ID2_BMP280() &&
chipId !== this.CHIP_ID3_BMP280()) {
return `Unexpected BMx280 chip ID: 0x${chipId.toString(16).toUpperCase()}`;
}
// console.log(`Found BMx280 chip ID 0x${chipId.toString(16).toUpperCase()} on bus i2c-${this.i2cBusNo}, address 0x${this.i2cAddress.toString(16).toUpperCase()}`);
@ -73,31 +73,31 @@ class BME280 {
return 0;
});
}
}
// reset()
//
// Perform a power-on reset procedure. You will need to call init() following a reset()
//
reset() {
// reset()
//
// Perform a power-on reset procedure. You will need to call init() following a reset()
//
exports.reset = () => {
const POWER_ON_RESET_CMD = 0xB6;
let r = this.pi.i2c_write_byte_data(this.i2cHand, this.REGISTER_RESET, POWER_ON_RESET_CMD);
if (r < 0) return `cannot power-on reset: ${r}`;
else return 0;
}
}
// cancel()
//
// Cancels the sensor and releases resources.
//
cancel() {
// cancel()
//
// Cancels the sensor and releases resources.
//
exports.cancel = () => {
if (this.i2cHand >= 0) {
this.pi.i2c_close(this.i2cHand);
this.i2cHand = 0;
}
}
}
readSensorData() {
exports.readSensorData = () => {
if (!this.cal) {
return 'You must first call bme280.init()';
}
@ -108,7 +108,7 @@ class BME280 {
if (!buffer) return `couldn't grab data`;
// Temperature (temperature first since we need t_fine for pressure and humidity)
//
let adc_T = BME280.uint20(buffer[3], buffer[4], buffer[5]);
let adc_T = this.uint20(buffer[3], buffer[4], buffer[5]);
let tvar1 = ((((adc_T >> 3) - (this.cal.dig_T1 << 1))) * this.cal.dig_T2) >> 11;
let tvar2 = (((((adc_T >> 4) - this.cal.dig_T1) * ((adc_T >> 4) - this.cal.dig_T1)) >> 12) * this.cal.dig_T3) >> 14;
let t_fine = tvar1 + tvar2;
@ -117,7 +117,7 @@ class BME280 {
// Pressure
//
let adc_P = BME280.uint20(buffer[0], buffer[1], buffer[2]);
let adc_P = this.uint20(buffer[0], buffer[1], buffer[2]);
let pvar1 = t_fine / 2 - 64000;
let pvar2 = pvar1 * pvar1 * this.cal.dig_P6 / 32768;
pvar2 = pvar2 + pvar1 * this.cal.dig_P5 * 2;
@ -139,7 +139,7 @@ class BME280 {
// Humidity (available on the BME280, will be zero on the BMP280 since it has no humidity sensor)
//
let adc_H = BME280.uint16(buffer[6], buffer[7]);
let adc_H = this.uint16(buffer[6], buffer[7]);
let h = t_fine - 76800;
h = (adc_H - (this.cal.dig_H4 * 64 + this.cal.dig_H5 / 16384 * h)) *
@ -153,9 +153,9 @@ class BME280 {
humidity: humidity,
pressure_hPa: pressure_hPa
};
}
}
loadCalibration(callback) {
exports.loadCalibration = (callback) => {
let buffer = this.pi.i2c_read_i2c_block_data(this.i2cHand, this.REGISTER_DIG_T1, 24);
// for (let i = 0; i < 24; i++) console.log(parseInt(buffer[i], 16));
if (buffer) {
@ -168,19 +168,19 @@ class BME280 {
let h6 = this.pi.i2c_read_byte_data(this.i2cHand, this.REGISTER_DIG_H6);
this.cal = {
dig_T1: BME280.uint16(buffer[1], buffer[0]),
dig_T2: BME280.int16(buffer[3], buffer[2]),
dig_T3: BME280.int16(buffer[5], buffer[4]),
dig_T1: this.uint16(buffer[1], buffer[0]),
dig_T2: this.int16(buffer[3], buffer[2]),
dig_T3: this.int16(buffer[5], buffer[4]),
dig_P1: BME280.uint16(buffer[7], buffer[6]),
dig_P2: BME280.int16(buffer[9], buffer[8]),
dig_P3: BME280.int16(buffer[11], buffer[10]),
dig_P4: BME280.int16(buffer[13], buffer[12]),
dig_P5: BME280.int16(buffer[15], buffer[14]),
dig_P6: BME280.int16(buffer[17], buffer[16]),
dig_P7: BME280.int16(buffer[19], buffer[18]),
dig_P8: BME280.int16(buffer[21], buffer[20]),
dig_P9: BME280.int16(buffer[23], buffer[22]),
dig_P1: this.uint16(buffer[7], buffer[6]),
dig_P2: this.int16(buffer[9], buffer[8]),
dig_P3: this.int16(buffer[11], buffer[10]),
dig_P4: this.int16(buffer[13], buffer[12]),
dig_P5: this.int16(buffer[15], buffer[14]),
dig_P6: this.int16(buffer[17], buffer[16]),
dig_P7: this.int16(buffer[19], buffer[18]),
dig_P8: this.int16(buffer[21], buffer[20]),
dig_P9: this.int16(buffer[23], buffer[22]),
dig_H1: h1,
dig_H2: h2,
@ -193,77 +193,74 @@ class BME280 {
// console.log('BME280 cal = ' + JSON.stringify(this.cal, null, 2));
callback();
}
}
}
static BME280_DEFAULT_I2C_ADDRESS() {
exports.BME280_DEFAULT_I2C_ADDRESS = () => {
return 0x77;
}
}
static CHIP_ID1_BMP280() {
exports.CHIP_ID1_BMP280 = () => {
return 0x56;
}
}
static CHIP_ID2_BMP280() {
exports.CHIP_ID2_BMP280 = () => {
return 0x57;
}
}
static CHIP_ID3_BMP280() {
exports.CHIP_ID3_BMP280 = () => {
return 0x58;
}
}
static CHIP_ID_BME280() {
exports.CHIP_ID_BME280 = () => {
return 0x60;
}
}
static int16(msb, lsb) {
let val = BME280.uint16(msb, lsb);
exports.int16 = (msb, lsb) => {
let val = this.uint16(msb, lsb);
return val > 32767 ? (val - 65536) : val;
}
}
static uint16(msb, lsb) {
exports.uint16 = (msb, lsb) => {
return msb << 8 | lsb;
}
}
static uint20(msb, lsb, xlsb) {
exports.uint20 = (msb, lsb, xlsb) => {
return ((msb << 8 | lsb) << 8 | xlsb) >> 4;
}
}
static convertCelciusToFahrenheit(c) {
exports.convertCelciusToFahrenheit = (c) => {
return c * 9 / 5 + 32;
}
}
static convertHectopascalToInchesOfMercury(hPa) {
exports.convertHectopascalToInchesOfMercury = (hPa) => {
return hPa * 0.02952998751;
}
}
static convertMetersToFeet(m) {
exports.convertMetersToFeet = (m) => {
return m * 3.28084;
}
}
static calculateHeatIndexCelcius(temperature_C, humidity) {
exports.calculateHeatIndexCelcius = (temperature_C, humidity) => {
return -8.784695 + 1.61139411 * temperature_C + 2.33854900 * humidity +
-0.14611605 * temperature_C * humidity + -0.01230809 * Math.pow(temperature_C, 2) +
-0.01642482 * Math.pow(humidity, 2) + 0.00221173 * Math.pow(temperature_C, 2) * humidity +
0.00072546 * temperature_C * Math.pow(humidity, 2) +
-0.00000358 * Math.pow(temperature_C, 2) * Math.pow(humidity, 2);
}
}
static calculateDewPointCelcius(temperature_C, humidity) {
exports.calculateDewPointCelcius = (temperature_C, humidity) => {
return 243.04 * (Math.log(humidity / 100.0) + ((17.625 * temperature_C) / (243.04 + temperature_C))) /
(17.625 - Math.log(humidity / 100.0) - ((17.625 * temperature_C) / (243.04 + temperature_C)));
}
}
static calculateAltitudeMeters(pressure_hPa, seaLevelPressure_hPa) {
exports.calculateAltitudeMeters = (pressure_hPa, seaLevelPressure_hPa) => {
if (!seaLevelPressure_hPa) {
seaLevelPressure_hPa = 1013.25;
}
return (1.0 - Math.pow(pressure_hPa / seaLevelPressure_hPa, (1 / 5.2553))) * 145366.45 * 0.3048;
}
}
module.exports = BME280;
/*
* This code was forked from skylarstein's bme280-sensor: https://github.com/skylarstein/bme280-sensor

178
main.js Normal file
View File

@ -0,0 +1,178 @@
const { app, BrowserWindow, Menu, session, ipcMain, dialog } = require('electron')
const path = require('path')
/** Force disable security warning */
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
/** Icon */
icon: "./icon.png",
width: 1280,
height: 940,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
// nodeIntegration: true,
// contextIsolation: false
}
})
/** Maximize Window at startup */
win.maximize()
// and load the index.html of the app.
win.loadFile('index.html')
// Open the DevTools.
win.webContents.openDevTools()
// Emitted when the window is closed.
// win.on('closed', () => {
// // Dereference the window object, usually you would store windows
// // in an array if your app supports multi windows, this is the time
// // when you should delete the corresponding element.
// win = null
// })
/** For SkyWay */
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
details.requestHeaders['Origin'] = 'electron://localhost'
callback({
cancel: false,
requestHeaders: details.requestHeaders
})
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
// app.on('ready', createWindow)
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
/** Custom Menu */
let template = [
{
label: 'Menu',
submenu: [
{
label: 'Toggle Menu Bar',
click: () => {
win.setMenuBarVisibility(!win.menuBarVisible)
},
accelerator: "CommandOrControl+Shift+M"
},
{
label: 'Reload',
click: () => {
win.reload()
},
accelerator: "F5"
},
{
label: 'Mascot',
click: (item, focusedWindow) => {
if (focusedWindow)
focusedWindow.webContents.executeJavaScript('ugj_selectMascot()')
},
accelerator: "CommandOrControl+M"
},
{
label: 'Toggle Developer Tools',
click: () => {
win.webContents.toggleDevTools()
},
accelerator: "F12"
},
{
label: 'About',
click: () => {
var os = require('os')
var detail = 'Version: ' + process.env.npm_package_version + '\n'
+ 'Node.js: ' + process.versions.node + '\n'
+ 'Chrome: ' + process.versions.chrome + '\n'
+ 'Electron: ' + process.versions.electron + '\n'
+ 'V8: ' + process.versions.v8 + '\n'
+ 'OS: ' + os.type + ' ' + os.arch + ' ' + os.version + ' ' + os.release
var options = {
type: 'info',
buttons: ['OK'],
title: 'OCoGe',
message: 'OCoGe - Oiwa Code Generator',
detail: detail
}
require('electron').dialog.showMessageBox(win, options)
},
accelerator: "CommandOrControl+I"
},
{
label: 'Quit',
click: () => {
app.quit()
},
accelerator: "CommandOrControl+Q"
}
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
win.setMenuBarVisibility(false)
})
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
// app.on('activate', () => {
// // On macOS it's common to re-create a window in the app when the
// // dock icon is clicked and there are no other windows open.
// if (win === null) {
// createWindow()
// }
// })
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
// IPC
ipcMain.on('set_title', (ev, title) => {
win.setTitle(title)
})
ipcMain.handle('open_dialog', (ev, title, dpath, filter) => {
let filepaths = dialog.showOpenDialogSync(win, {
properties: ['openFile'],
title: title,
defaultPath: dpath,
filters: [
filter
]
})
return filepaths
})
ipcMain.handle('save_dialog', (ev, title, defName, filter) => {
let filename = dialog.showSaveDialogSync(win, {
title: title,
defaultPath: defName,
filters: [filter]
})
console.log(filename)
return filename
})

2252
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "ocoge",
"version": "0.1.3",
"description": "\"大岩産 Code Generator\" は、Google Blockly ライブラリを使用した、Raspberry Pi 上で動作するブロックプログラム開発・実行環境です。",
"main": "index.js",
"main": "main.js",
"scripts": {
"start": "electron .",
"rebuild": "electron-rebuild"
@ -19,14 +19,15 @@
},
"homepage": "https://github.com/ocogeclub/ocoge#readme",
"devDependencies": {
"electron": "^14.0.0",
"electron-rebuild": "^3.2.3"
"electron": "15.0.0",
"electron-rebuild": "3.2.3"
},
"dependencies": {
"@electron/remote": "^1.2.1",
"@ocogeclub/bme280": "file:local_modules/@ocogeclub/bme280",
"@ocogeclub/lgpio": "file:local_modules/@ocogeclub/lgpio",
"axios": "^0.21.1",
"nodemailer": "^6.6.0"
"nodemailer": "^6.6.0",
"@tensorflow/tfjs-node": "^3.9.0",
"@vladmandic/face-api": "^1.5.3"
}
}

19
preload.js Normal file
View File

@ -0,0 +1,19 @@
const electron = require("electron")
const path = require('path')
electron.contextBridge.exposeInMainWorld(
"ocogeapi",
{
// Electron固有index.htmlから直接 <script src で呼ばれたJSファイルでのみ利用可
electron_ipcRenderer: electron.ipcRenderer,
electron_shell: electron.shell,
// どこからでも呼び出し可
fs: require('fs'),
path: path,
child_process: require('child_process'),
lgpio: require('@ocogeclub/lgpio'),
bme280: require('@ocogeclub/bme280'),
// グローバル変数
gtest: 'qwerty' // テスト用
}
);

View File

@ -1039,15 +1039,14 @@ Blockly.JavaScript['ugj_bme280'] = function (block) {
var variable_hum = Blockly.JavaScript.nameDB_.getName(block.getFieldValue('hum'), Blockly.Variables.NAME_TYPE);
var variable_pres = Blockly.JavaScript.nameDB_.getName(block.getFieldValue('pres'), Blockly.Variables.NAME_TYPE);
Blockly.JavaScript.provideFunction_(
'require_bme280', [`const BME280 = require('@ocogeclub/bme280');`]
'require_bme280', [`const bme280 = require('@ocogeclub/bme280');`]
);
var code = [
`const options = {`,
` i2cBusNo: 1,`,
` i2cAddress: ${value_address}`,
`};`,
`const bme280 = new BME280(options);`,
`bme280.init();`,
`bme280.init(options);`,
`let thp = bme280.readSensorData();`,
`${variable_temp} = Math.round(thp.temperature_C * 10) / 10;`,
`${variable_hum} = Math.round(thp.humidity * 10) / 10;`,
@ -1184,42 +1183,65 @@ Blockly.JavaScript['ugj_tfpredict_predict'] = function (block) {
return code;
};
/******************** */
/** Face Detection ** */
/******************** */
Blockly.Blocks['ugj_face_library'] = {
var with_landmark;
var ugjFaceapiDefinition = {
"type": "ugj_faceapi",
"message0": "%{BKY_UGJ_FACEAPI_TITLE}",
"args0": [
{
"type": "field_checkbox",
"name": "with_landmark",
"checked": false
},
{
"type": "input_dummy"
},
{
"type": "input_statement",
"name": "do"
}
],
"inputsInline": true,
"tooltip": "%{BKY_UGJ_FACEAPI_TOOLTIP}",
"helpUrl": "",
"style": "multimedia_blocks"
};
Blockly.Blocks['ugj_faceapi'] = {
init: function () {
this.appendDummyInput()
.appendField("face-api.jsによる顔認識");
this.setInputsInline(true);
this.setOutput(true, "Library");
// this.setOutputShape(Blockly.OUTPUT_SHAPE_ROUND);
this.setStyle('multimedia_blocks')
this.setTooltip("face-api.jsをロードし、顔認識ができるようにします。");
this.setHelpUrl("");
this.jsonInit(ugjFaceapiDefinition);
}
};
Blockly.JavaScript['ugj_face_library'] = function (block) {
var code = `'${ugj_const.library_path}face-api.js'`;
return [code, Blockly.JavaScript.ORDER_NONE];
Blockly.JavaScript['ugj_faceapi'] = function (block) {
with_landmark = block.getFieldValue('with_landmark') == 'TRUE';
var statements_do = Blockly.JavaScript.statementToCode(block, 'do');
var code = [
`require('@tensorflow/tfjs-node');`,
`const faceapi = require('@vladmandic/face-api/dist/face-api.node.js');`,
statements_do,
''
].join('\n');
return code;
};
Blockly.Blocks['ugj_face_init'] = {
init: function () {
this.appendDummyInput()
.appendField("顔認識のビデオを開始");
.appendField("顔検出のビデオを開始");
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setStyle('multimedia_blocks')
this.setTooltip("顔認識のためのビデオストリームを開始します。");
this.setTooltip("顔検出のためのビデオストリームを開始します。");
this.setHelpUrl("");
}
};
Blockly.JavaScript['ugj_face_init'] = function (block) {
var code = [
"const videoEl = document.getElementById('subdisplay');",
"const stream = await navigator.mediaDevices.getUserMedia({ video: {} });",
`const displaySize = { width: videoEl.width, height: videoEl.height };`,
"const stream = await navigator.mediaDevices.getUserMedia({ audio: false, video: displaySize });",
"videoEl.srcObject = stream;",
""
].join('\n');
@ -1273,55 +1295,71 @@ Blockly.JavaScript['ugj_face_detect'] = function (block) {
var variable_w = Blockly.JavaScript.nameDB_.getName(block.getFieldValue('w'), Blockly.Variables.NAME_TYPE);
var variable_h = Blockly.JavaScript.nameDB_.getName(block.getFieldValue('h'), Blockly.Variables.NAME_TYPE);
var statements_do = Blockly.JavaScript.statementToCode(block, 'do');
var code = [
`await faceapi.loadTinyFaceDetectorModel('${ugj_const.library_path}models/');`,
"const options = new faceapi.TinyFaceDetectorOptions({ inputSize: 128, scoreThreshold : 0.3 });",
"videoEl.onplay = onPlay;",
"async function onPlay() {",
" let result = await faceapi.detectSingleFace(videoEl, options);",
" if (result) {",
var code_model = `await faceapi.nets.tinyFaceDetector.load('${ugj_const.library_path}models/');`;
if (with_landmark) { code_model += `\nawait faceapi.nets.faceLandmark68TinyNet.load('${ugj_const.library_path}models/');`; }
var code_detect_face = " let result = await faceapi.detectSingleFace(videoEl, options)"
if (with_landmark) { code_detect_face += `.withFaceLandmarks(true);` }
else { code_detect_face += `;` }
var code_rect;
if (with_landmark) {
code_rect = [
` ${variable_x} = Math.round(result.detection.box.x);`,
` ${variable_y} = Math.round(result.detection.box.y);`,
` ${variable_w} = Math.round(result.detection.box.width);`,
` ${variable_h} = Math.round(result.detection.box.height);`,
].join('\n');
} else {
code_rect = [
` ${variable_x} = Math.round(result.box.x);`,
` ${variable_y} = Math.round(result.box.y);`,
` ${variable_w} = Math.round(result.box.width);`,
` ${variable_h} = Math.round(result.box.height);`,
].join('\n');
}
var code = [
code_model,
"const options = new faceapi.TinyFaceDetectorOptions({ inputSize: 128, scoreThreshold : 0.3 });",
`const onPlay = async () => {`,
// ` const detectInterval = setInterval(async () => {`,
code_detect_face,
" if (result) {",
code_rect,
statements_do,
" }",
// ` }, 500);`,
" setTimeout(() => onPlay())",
"}",
"videoEl.onplay = onPlay;",
""
].join('\n');
return code;
};
//
Blockly.Blocks['ugj_face_drawrect'] = {
init: function () {
this.appendValueInput("color")
.setCheck("Colour")
.appendField("顔にボックスを");
this.appendDummyInput()
.appendField("色で描画");
.appendField("検出結果を描画");
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setStyle('multimedia_blocks')
this.setTooltip("顔の位置に四角形を表示します。");
this.setTooltip("ビデオに検出結果を四角や点で表示します。「ビデオを表示」ブロックが必要です。");
this.setHelpUrl("");
}
};
Blockly.JavaScript['ugj_face_drawrect'] = function (block) {
var value_color = Blockly.JavaScript.valueToCode(block, 'color', Blockly.JavaScript.ORDER_ATOMIC);
var code_draw = ` faceapi.draw.drawDetections(overlay, resizedDetections);`;
if (with_landmark) { code_draw += `\n faceapi.draw.drawFaceLandmarks(overlay, resizedDetections);`; }
var code = [
" const { width, height } = videoEl instanceof HTMLVideoElement",
" ? faceapi.getMediaDimensions(videoEl)",
" : videoEl;",
" overlay.width = width;",
" overlay.height = height;",
" const resizedDetections = [result].map(res => res.forSize(width, height));",
` faceapi.drawDetection(overlay, resizedDetections.map(det => det.box), { withScore: false, lineWidth: 4, boxColor: ${value_color} });`,
` faceapi.matchDimensions(overlay, displaySize);`,
` const resizedDetections = faceapi.resizeResults(result, displaySize);`,
code_draw,
""
].join('\n');
return code;
};
/**************************** */
/** Say while some seconds ** */
/**************************** */

View File

@ -1,7 +0,0 @@
const ugj_const = {
doc_root: '/home/pi/Documents/ocoge_docs/',
app_name: 'ocoge',
mascot_path: './img/',
library_path: './lib/',
executable_path: './bin/'
};

View File

@ -1,240 +0,0 @@
"use strict";
/** Node.js または Electron 固有の機能を利用した関数のモジュール ************* */
// Require
const fs = require('fs');
const path = require("path");
// const ugj_const = require('./ugj_const');
const mainWin = require('@electron/remote').getCurrentWindow();
const dialog = require('@electron/remote').dialog;
const shell = require('electron').shell;
const clipboard = require('electron').clipboard;
var saveFilepath = null;
var wsChanged = false;
var mascotFilePath = ugj_const.mascot_path + 'tamachee.png';
// 0で数値の桁合わせ
// NUM=値 LEN=桁数
const zeroPadding = (NUM, LEN) => (Array(LEN).join('0') + NUM).slice(-LEN);
// 現在の日付時刻から workspace フォルダ内のユニークなファイルパスを作成
const getUniqueFilepath = () => {
let today = new Date();
let filename = today.getFullYear() + '-' + zeroPadding((today.getMonth() + 1), 2) + '-' + zeroPadding(today.getDate(), 2) + '-' + zeroPadding(today.getHours(), 2) + '-' + zeroPadding(today.getMinutes(), 2) + '-' + zeroPadding(today.getSeconds(), 2);
let filepath = path.join(ugj_const.doc_root, filename);
return filepath;
}
// クリップボードにコピー
exports.copyText = text => clipboard.writeText(text);
// リンクを外部ブラウザで開く
exports.openURL = (url) => {
shell.openExternal(url);
}
// タイトルバーにファイル名を表示
// const setTitle = () => {
// if (saveFilepath) let title = ugj_const.app_name;
// else let title = saveFilepath + ' - ' + ugj_const.app_name;
// mainWin.setTitle(title);
// }
// saveFilepath を更新
// ウィンドウタイトルバーテキストを変更
const setSaveFilepath = filepath => {
let title;
saveFilepath = filepath;
// if (filepath) title = filepath + ' - ' + ugj_const.app_name;
// else title = ugj_const.app_name;
// mainWin.setTitle(title);
this.setWsChanged(false);
}
// ワークスペースが変更された・保存された
// ウィンドウタイトルバーテキストを変更
exports.setWsChanged = changed => {
let title;
wsChanged = changed;
if (saveFilepath) title = saveFilepath + ' - ' + ugj_const.app_name;
else title = ugj_const.app_name;
if (changed) title = '*' + title;
mainWin.setTitle(title);
}
// 保存ファイルプロパティを更新
exports.newFile = () => setSaveFilepath(null);
// ワークスペースファイル読み込みの一連の動作のラッパ
exports.loadWsFile = () => {
let filepath = openFile('xml', ugj_const.doc_root);
if (filepath.length > 0) {
if (saveFilepath === null) {
setSaveFilepath(filepath);
} //読み込みに失敗してもsaveFilepathが更新されてしまうのはちょっと具合が悪いかも
return readFromFile(filepath);
} else {
return '';
}
}
// その他ファイル読み込みの一連の動作のラッパ
exports.loadFile = ext => {
let filepath = openFile(ext, ugj_const.doc_root);
if (filepath.length > 0) {
return readFromFile(filepath);
} else {
return '';
}
}
exports.selectMascotFile = () => {
return openFile('png', ugj_const.mascot_path);
}
// オープンファイルダイアログ
const openFile = (ext, dpath) => {
let filter;
if (ext == 'xml') {
filter = { name: 'XML - Extensible Markup Language', extensions: ['xml'] };
} else if (ext == 'js') {
filter = { name: 'JS - JavaScript', extensions: ['js'] };
} else if (ext == 'png') {
filter = { name: 'PNG - Portable Network Graphics', extensions: ['png'] };
} else {
filter = { name: 'text file', extensions: ['txt'] };
}
let filepaths = dialog.showOpenDialogSync(mainWin, {
properties: ['openFile'],
title: 'Select a file',
defaultPath: dpath,
filters: [
filter
]
});
if (filepaths == undefined) {
return '';
} else {
return filepaths[0];
}
}
// ファイルからデータを読み込み
const readFromFile = filepath => {
let data = '';
try {
data = fs.readFileSync(filepath, 'utf-8');
}
catch (err) {
console.log(err);
}
return data;
}
// テキストファイル読み込み: 外部スクリプト動的読み込みに使用
exports.readTextFile = filepath => {
return readFromFile(filepath);
}
// ワークスペースファイル保存の一連の動作のラッパ
exports.saveWsFile = data => {
if (saveFilepath === null) {
let filepath = selectSaveFile('xml');
if (filepath === undefined) { //キャンセル
return undefined;
} else {
setSaveFilepath(filepath);
} //これも保存が成功したら変更するようにすべきかしら
} else this.setWsChanged(false);
return writeToFile(saveFilepath, data);
}
// その他ファイル保存の一連の動作のラッパ
exports.saveFile = (data, ext) => {
let filepath = selectSaveFile(ext);
if (filepath === undefined) { //キャンセル
return undefined;
}
return writeToFile(filepath, data);
}
// ファイル保存ダイアログ
const selectSaveFile = ext => {
let filter, filter_name;
let defName;
if (ext == 'xml') {
filter = { name: 'xml file', extensions: ['xml'] };
defName = getUniqueFilepath() + '.xml';
} else if (ext == 'js' || ext == 'py') {
if (ext == 'js') filter_name = 'javascript file';
else filter_name = 'python file'
filter = { name: filter_name, extensions: [ext] };
// ワークスペース保存名がある場合、それをベースにファイル名の候補を決める
if (saveFilepath === null) {
defName = getUniqueFilepath() + '.' + ext;
} else {
let dirname = path.dirname(saveFilepath);
let basename = path.basename(saveFilepath, '.xml');
defName = path.join(dirname, basename) + '.' + ext;
}
} else {
filter = { name: 'text file', extensions: ['txt'] };
}
let filename = dialog.showSaveDialogSync(mainWin, {
title: '保存先を決定してください',
defaultPath: defName,
filters: [filter]
});
return filename;
}
// ファイル書き込み
const writeToFile = (filepath, data) => {
try {
fs.writeFileSync(filepath, data);
return true;
}
catch (err) {
return false;
}
}
// 子プロセス関連
let children = [];
// 新しい子プロセスを作成し、配列に保存
exports.addChild = (child) => {
children.push(child);
}
// 全ての子プロセスを殺し、配列をクリア
exports.killAllChildren = () => {
children.forEach(function (child) {
child.kill();
});
children = [];
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージに保存
exports.savePrefsToLS = () => {
let wc = '0';
if (wsChanged) wc = '1';
let o = { 'saveFilepath': saveFilepath, 'wsChanged': wc, 'mascotFilePath': mascotFilePath };
let s = JSON.stringify(o);
localStorage.setItem("ocoge.json", s);
}
// 設定(保存ファイルパスと未保存フラグ)をローカルストレージからロード
exports.loadPrefsFromLS = () => {
let s = localStorage.getItem("ocoge.json");
if (s !== null) {
let o = JSON.parse(s);
setSaveFilepath(o.saveFilepath);
if (o.wsChanged == '0') this.setWsChanged(false);
else this.setWsChanged(true);
if (o.mascotFilePath) this.setMascotFilePath(o.mascotFilePath);
}
}
// マスコット画像パスをプロパティにセット
exports.setMascotFilePath = fpath => mascotFilePath = fpath;
exports.getMascotFilePath = () => mascotFilePath;
// ファイル名にアプリケーションのドキュメントルートまでのパスをつけて返す
exports.getDocPath = filename => {
return path.join(appDocRoot, filename);
}

View File

@ -135,6 +135,9 @@ Blockly.Msg["UGJ_DECTOHEX_TOOLTIP"] = "10進数を16進数に変換します。"
Blockly.Msg["UGJ_CANVAS_INIT_TITLE"] = "キャンバスを表示";
Blockly.Msg["UGJ_CANVAS_INIT_TOOLTIP"] = "キャンバスを表示し、使用できるようにします。";
Blockly.Msg["UGJ_FACEAPI_TITLE"] = "TensorFlowによる顔検出 %1 ランドマークを検出 %2 %3";
Blockly.Msg["UGJ_FACEAPI_TOOLTIP"] = "TensorFlow とFaceAPI をロードし、顔検出をできるようにします。";
Blockly.Msg["UGJ_SLEEP_TITLE"] = "%1 秒待つ";
Blockly.Msg["UGJ_SLEEP_TOOLTIP"] = "指定した秒数だけ処理を中断します。";

2058
yarn.lock Normal file

File diff suppressed because it is too large Load Diff