2024年2月17日 星期六

 首先到 mbed online IDE

https://ide.mbed.com/compiler/

第一次使用經過註冊程序就會看到IDE畫面



接著開始新增專案 mbed沒有提供STM32F4 DISC1版本

但是 Seeed Arch Max 剛好是使用 STMD32F407 這顆 MCU

所以選取 Seeed Arch Max 當作專案 platform

專案 template 選取 Blinky LED Hello World

程式碼修改如下:

#include "mbed.h"

#define BLINKING_RATE     500

int main()
{
    // Initialise PD12 as an output
    DigitalOut gLED(PD_12);
    
    while (true) {
        gLED = !gLED;
        ThisThread::sleep_for(BLINKING_RATE);
    }
}

按下介面上的 Compile 按鈕

mbed online IDE 會在完成後跳出下載 binary 檔案

將這個 .bin 檔存入 STM32F4 DISC1 的資料夾

最後就會看到 PD12 開始閃爍囉!

Reference

https://www.hackster.io/PSoC_Rocks/easy-programming-stm32f407-discovery-board-with-mbed-8ead8e

在AWS EC2上安裝WordPress

環境: Ubuntu 18.04 Server

第一次登入EC2後

  1. sudo apt update
  2. sudo apt install apache2
  3. sudo add-apt-repository ppa:ondrej/php
  4. sudo apt update
  5. sudo apt install php7.4
  6. sudo apt install mysql-server
  7. sudo apt install php7.4-mysql

以上完成wordpress需要的軟體。

在使用mysql server前先進行初始化。

  1. mysql_secure_installation
  2. 設定root密碼及刪除不必要的使用者帳號
  3. sudo mysql
mysql> USE mysql;
mysql> UPDATE user SET plugin='mysql_native_password' WHERE User='root';

將原本root的登入方式(auth_socket)改為mysql_native_password

mysql> CREATE DATABASE new_db;
mysql> CREATE USER 'new_user'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL PRIVILEGES ON new_db.* TO 'new_user'@'localhost';

建立wordpress的資料庫(new_db)並設定資料庫權限給新建的使用者帳戶(new_user),之後wordpress安裝時需要輸入此使用者帳戶及密碼。

接下來到wordpress.org下載最新版本(目前為5.5.1)的wordpress

% cd /var/www/html
% sudo unzip wordpress-5.5.1.zip
% sudo chown -R www-data. wordpress/

最後打開AWS EC2的security group的port 80

連入網站即可開始進行wordpress的最後設定。

Linux Read-only file system

 常用的解決方法

sudo mount -o remount,rw /

在respberry pi如果出現PARTUUID=xxx Error
可以將指令改成
sudo mount -o remount,rw /dev/mmcblk0p2 /

Ubuntu install RTL8188 Wifi Usb Adapter Driver

 Download driver from:

https://github.com/ulli-kroll/rtl8188fu

After ‘make’:

sudo modprobe cfg80211
sudo insmod rtl8188fu.ko

Connect to AP:
nmcli r wifi on
nmcli d wifi list
nmcli d wifi connect “ssid” password “pw”

gradle exception

 我在下指令 gradlew signingReport 時, 出現了這個錯誤

error occurred during initialization of vm could not reserve enough space for 1572864kb object heap

原來我安裝到了 32bit 版本的 JDK

所以 allocate 過大的 heap size 會出現問題

解決的方法當然是改成安裝 64bit JDK 或者如果是用 Android Studio 的

可以在 gradle.properties 檔修改成

org.gradle.jvmargs=-Xmx512M

就可以了

PS. 如果下指令 java -d32 -version 會正常出現版本說明的話 那就是安裝到 32bit JDK

ZYNQ add SPI interface and petalinux device tree

 ZYNQ add SPI interface

  1. Open “Open Block Diagram”
  2. Add “AXI QUAD SPI” block
  3. Run “Run Conection Automation”
  4. Generate Bitstream
  5. Open “Open Implementation Design”
  6. Switch to “I/O Planning” and select “I/O Ports”

  1. Pin assign for SPI interface


  1. Generate Bitstream and export hardware

Customize SPI device tree for petalinux

After update HDF and execute command: petalinux-config –get-hw-description=<HDF Directory>

In components/plnx_workspace/device-tree/device-tree/pl.dtsi, will produce SPI device node automatically as below:

Modify project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi as below

Enable SPI device driver

$ petalinux-config -c kernel

Go to Device Drivers -> SPI Support -> User mode SPI device driver support and enable it.

Use spidev_test.c to verify the SPI interface.

$ ls /dev/spi*

$ gcc spidev_test.c -o spidev_test

$ ./spidev_test -D /dev/spidev1.0

FFmpeg/SDL2 on Windows 專案設定

自工作以來,好像每隔一陣子就有機會用到ffmpeg來做視訊處理

像是 android app, embedded linux 或是windows上的視訊播放程式

所以記錄一下這次 Windows 平台搭配 VS 2019 的專案建置流程

  1. 下載 ffmpeg/SDL2 windows build
    ffmpeg 選擇 win64 shared
    SDL2 選擇 win32-x64
  2. 建立VS 2019 CMake專案

3. 複製ffmpeg zip裡的 include/lib 資料夾到<project>目錄下

5. 複製ffmpeg zip裡 include/bin 的 *.dll 到 <project>/out/build/x64-Debug

6. 複製sdl2 zip裡的 include 資料夾到 <project>/include/ 下更名為 SDL2

7. 複製sdl2 zip裡的 lib/SDL2.lib 到 <project>/lib

8. 複製sdl2 zip裡的 lib/SDL2.dll 到 <project>/out/build/x64-Debug

最後配置 CMakeLists.txt 如下

cmake_minimum_required (VERSION 3.8)

project ("ffmpeg-test")
include_directories("include")
link_directories("lib")
add_executable (ffmpeg-test "ffmpeg-test.cpp" "ffmpeg-test.h")
target_link_libraries(
	ffmpeg-test
	avcodec
	avdevice
	avfilter
	avformat
	avutil
	postproc
	swresample
	swscale
	SDL2
)

接下來我們就可以用一個簡單的 Hello World 驗証專案設定是否正常

#define SDL_MAIN_HANDLED // Important!
#include "ffmpeg-test.h"

using namespace std;

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/avstring.h>
#include <libavutil/time.h>
#include <libavutil/opt.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_thread.h>
}

int main()
{
	const char* version = av_version_info();
	cout << "FFmpeg version: " << version << endl;

	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
		cout << "SDL could not initialize! SDL_Error: " << SDL_GetError() << endl;
	}
	else {
		cout << "SDL Initialize OK!" << endl;
	}
	return 0; 

Raspberry Pi + Webcam 居家遠端監看

 



預計達成的目標是使用者透過手機或電腦上的瀏覽器連線到網站就可以遙控遠端的Webcam 拍攝一張照片回傳到網頁上觀看。完整原始碼請到 Github 下載。

現在 open source 社群非常發達,不管是軟體還是硬體上,開發的平台或工具越來越多樣,像這樣的系統架構只要靠著各站點約一百行左右的程式碼就可以完成,真的是非常方便。這次實驗將會用到的技術包括:

Python, Node.js, HTML and Javascript.

首先,要能在 Raspberry Pi 3 上擷取一張照片,使用的 webcam 當然要支援 v4l2 驅動及 UVC 相容,這樣我們就可以很方便的用 opencv-python 模組來抓影像。

# snapshot.py
import cv2


def capture():
	cap = cv2.VideoCapture(0)
	ret, frame = cap.read()
	cv2.imwrite("snap.jpg", frame)
	cap.release()


capture()

接下來,我們需要一些網際網路及 TCP/IP 的觀念。

我們的 Raspberry Pi 在家中使用 WiFi 上網,屬於 Private Network,身在Public Network 的網站是無法直接透過 IP 位址找到 Raspberry Pi。所以我們要在Raspberry Pi 和網站中間保持一條 TCP 連線,當使用者按下拍照鈕後,網站可以透過這條連線通知 Raspberry Pi 進行拍照及上傳。

我採用 Node.js 開發 camera client 程式,當然也可以用 python 來實作,不過用Node.js 的話,程式會更加簡短。

// camera_client.js
const net = require('net');
const exec = require('child_process').exec;
const request = require('request');
const fs = require('fs');

const PORT = 18080; // web server tcp port
const HOST = '<web server ip>';

var client = new net.Socket();
client.connect(POST, HOST, function() {
	console.log('Connected');
});

client.on('data', function(msg) {
  if (msg == "snapshot") {
    console.log('Snaphsot');
  }
});

client.on('close', function() {
	console.log('Connection closed');
	client = null;
});

當 camera_client.js 收到 ‘snapshot’ 訊息後,就要執行 snapshot.py 來拍照並存成 snap.jpg 檔。

exec('python3 snapshot.py', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log('Snaphsot...done');
});

存檔完成後,我們可以用 request 模組,以 HTTP 協定的方式上傳 snap.jpg 檔。

var req = request.post(HOST + '/fileupload', function (err, resp, body) {
  if (err) {
    console.log('Error');
  }
});
var form = req.form();
form.append('filetoupload', fs.createReadStream('snap.jpg'), {
    name: 'snap.jpg'
});

以上在 Raspberry Pi 上的開發就完成了。

網站端的開發我一樣採用 Node.js,它可以很快速的建立一個簡單的 web server 提供使用者一個簡易的網頁,同時也可以接收 camera_client.js 的 TCP 連線。

首先我們建立一個 index.html 網頁提供使用者檢視照片及一個拍照按鈕。

<!DOCTYPE html>
<html>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript">
  ...
  function snapshot() {		 
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        setTimeout(reloadSnapshot, 1000);
      }
    };
    xhttp.open("GET", "snapshot", true);
    xhttp.send();
  }
</script>
<body>
  <img id="snap-img" src="snap.jpg" style="max-width:100%;height:auto;"/><br/>
  <input id="snap-btn" type="button" value="Snapshot" onclick="snapshot()" />
</body>
</html>

這個網頁會向 web server 下載 snap.jpg 然後當使用者下載拍照鈕後,會用 AJAX發出 GET request 到 web server。接下來我們實作 camera_server.js 來處理及回應這兩個動作。

// camera_server.js
const net = require('net');
const http = require('http');
const fs = require('fs');
const formidable = require('formidable');

const HOST = '0.0.0.0';

const requestListener = function (req, res) {
  if (req.url == '/') {
    var html = fs.readFileSync('index.html');
    res.setHeader("Content-Type", "text/html");
    res.writeHead(200);
    res.end(html);
  }
  else if (req.url.startsWith('/snap.jpg')) {
    if (!fs.existsSync('snap.jpg')) {
      res.setHeader("Content-Type", "image/jpeg");
      res.writeHead(400);
      res.end();
      return;
    }
    var img = fs.readFileSync('snap.jpg');
    res.setHeader("Content-Type", "image/jpeg");
    res.writeHead(200);
    res.end(img);
  }
  ...
}

const web = http.createServer(requestListener);
web.listen(8080, HOST, () => {
    console.log('Web Server is running');
});

Node.js 強大的 http 模組讓我們一秒變身簡易 web server 我們可以建立 Request Listener 來處理所以網頁的 request。以 req.url 來判斷要回應 index.html 或 snap.jpg。

前面提到過 camera_client.js 要跟 camera_server.js 保持 TCP 連線,所以現在在 camera_server.js 加入一個 socket server 並開啟 TCP_PORT 等待 camera_client.js 連入。

var cameraClient = null;
const TCP_PORT = 8080;
var server = net.createServer();    
server.on('connection', handleConnection);
server.listen(TCP_PORT, HOST, function() {    
	console.log('TCP Server is running');  
});

function handleConnection(conn) {
  cameraClient = conn;
  var remoteAddress = conn.remoteAddress + ':' + conn.remotePort;  
  console.log('new client connection from %s', remoteAddress);
  conn.on('data', onReceiveData);  
  conn.once('close', onConnClose);  
  conn.on('error', onConnError);

  function onReceiveData(msg) {
  };
  function onConnClose() {  
    console.log('connection from %s closed', remoteAddress);
    cameraClient = null;
  };
  function onConnError(err) {
    console.log('connection from %s error', remoteAddress);
    cameraClient = null;
  };
}

接下來我們在 camera_server.js 的 Request Listener 收到 snapshot request 後,我們就可以送出 ‘snapshot’ 訊息到 camera_client.js。然後我用 formidable 模組處理 camera_client.js 上傳檔案的動作。

...	
else if (req.url == '/snapshot') {
  // send 'snapshot' message to camera_client.js
  cameraClient.write('snapshot');
  if (fs.existsSync('snap.jpg'))
    fs.unlinkSync('snap.jpg');
  res.setHeader("Content-Type", "text/html");
  res.writeHead(200);
  res.end();
}
else if (req.url == '/fileupload') {
  var form = new formidable.IncomingForm();
  form.parse(req, function (err, fields, files) {
    var oldpath = files.filetoupload.path;
    var newpath = './' + files.filetoupload.name;
    fs.rename(oldpath, newpath, function (err) {
      if (err) throw err;
        res.write('File uploaded');
      res.end();
    });
  });
}
...

最後比較麻煩的是,前端網頁在送出 snapshot GET request 後要如何知道新的影像己經上傳完成?我們可以在 index.html 中送出 ‘snapshot’ 訊息後設定一個 timer 固定一小段時間就去詢問 camera_server.js 上的 snap.jpg 是否更新了。

...
function reloadSnapshot() {
  var xhttp = new XMLHttpRequest();
  xhttp.open("GET", "reload", false);
  xhttp.send();
  if (xhttp.status != 200) {
    setTimeout(reloadSnapshot, 1000);
    return;
  }
  document.getElementById('snap-img').src = "snap.jpg?random="+new 
    Date().getTime();
}
...

我們用 setTimeout() 每秒送出 reload 訊息去詢問 camera_server.js 是否有更新影像,若有的話,這裡有個小技巧,用 snap.jpg?random=… 的方式強迫瀏覽器更新影像。

最後在 camera_server.js 的 Request Listener加入 reload 訊息處理。如果 snap.jpg 檔案不存在則回應 404。

...
else if (req.url == '/reload') {
  if (!fs.existsSync('snap.jpg')) {
    res.setHeader("Content-Type", "text/html");
    res.writeHead(404);
    res.end();
    return;
  }
  res.setHeader("Content-Type", "text/html");
  res.writeHead(200);
  res.end();
}
...

到這裡,我們就用一百行左右的程式碼完成了簡易的遠端監看系統。在 Raspberry Pi 跟 web server 上分別啟動程式。

Web-Server# node camera-server.js
RPi# node camera-client.js

用瀏覽器連線到 http://<host_ip>:<web_port>/ 就可以開始使用囉。

完整原始碼請到 Github 下載。

Auto renew SSL certification

 Let’s Encrypt 組織免費提供瀏覽器認可的SSL憑證

不過這項服務每3個月需要重複執行申請手續

若想要快速的完成申請, 我們可以安裝 certbot 這項工具來幫助我們.

sudo apt install certbot python3-certbot-apache

sudo certbot --apache

在經過一些安裝步驟後, 最後只要下指令

sudo certbot renew

cerbot 就可以自動幫我們完成SSL憑證申請手續並自動放到apache web server下的目錄.

certbot 還有自動更新功能, 可以用以下指令檢查自動更新功能是否正常

sudo systemctl status certbot.timer

不同的作業系統在 certbot 官方 也都有提供完整的安裝指引.

自製螢幕錄影程式

網路上雖然免費的螢幕錄影程式很多

但是大多不是免安裝的綠色軟體

而且有的要配合網站使用,有的要申請一個帳號才能下載

於是乾脆自己做了一個簡單的螢幕錄影程式

雖然只能全螢幕錄影,不過對我來說可以錄影就好

剪輯或是轉檔有其它的軟體可以處理。

下載點


程式開發幕後

我常說看似簡單的程式,真的要做到推出給大眾使用

其實還是需要一番功夫的

螢幕錄影程式短短不到一百行程式就完成

但是在程式打包封裝上,除了常用的 pyinstaller

我推薦搭配使用 auto-py-to-exe

它可以說是 pyinstaller 的 GUI 版本

pyinstaller 在下 –add-data 參數時,因為要加上路徑的關係,整個指令變得很長很複雜

有了 auto-py-to-exe 就變得簡單多了

在加入程式圖示(.ico檔)時,需要把 .ico 加入圖示及加入附加檔案


若是想打包成 –onefile 就需要在程式裡另外處理了

原因請參考 stackoverflow

首先加入

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)
原先載入圖示的程式碼
window.wm_iconbitmap('logo.ico')

改為

window.wm_iconbitmap(resource_path('logo.ico'))

就可以了!

2024年2月5日 星期一

ESP32-CAM + Arduino IDE 操作方式

1. 使用 UART TO USB Dongle

2. 接線

5V     <------> 5V

GND <-----> GND (與5V同一側的GND)

TX    <-----> U0R

RX    <------> U0T

ESP32CAM上的IO0接上自己的GND 

3. Arduino IDE

新增額外的開發板管理員網址

https://raw.githubusercontent.com/espressif/arduino-esp32/master/package.json

新增開發板管理員


開發板選擇 AI Thinker ESP32-CAM

最後開啟 "範例" -> "ESP32" -> "Camera" -> "CameraWebServer"
修改 CAMERA_MODEL define 及 ssid, password 變數
燒錄程式後 斷開IO0 按下RESET鍵就可以在序列監視器上看到 ESP32-CAM 的 IP




Python Tkinter First Example

import tkinter as tk def on_closing():     root.destroy() class MainWindow(tk.Tk):     def __init__(self, *args, **kwargs):         tk.Tk.__...