Raspberry pi 開機自動開啟 terminal 並執行 python 程式

最近我有個需求是當我的樹莓派重開機後,要在自動登入 LXDE GUI session 後自動執行終端機 terminal 並執行特定程式,但發現其實並不簡單。
由於受限於動作在登入 GUI session 後,crontab/etc/rc.local 都不適用我的情境,必須用 lxsession 才行,經過了多次的登入登出總算成功達到想要的目的,在此作為紀錄。

在底下假設自動登入使用者為 pi,且 Python 程式放於 /home/pi/Desktop/myScript/main.py,可以編輯 /home/pi/.config/lxsession/LXDE/autostart 文件,於最底下加上 lxterminal --command="/bin/bash -c 'cd /home/pi/Desktop/myScript ; python main.py'",會發現這行直接於 terminal 執行是沒問題的,但是在 autostart 會錯,網路上也有人在問一樣的問題

後來輾轉發現有人將 command 包在 script 中執行後好像可以,我自己試了也發現這樣就行了,/home/pi/autostart.sh

1
2
3
4
#!/bin/bash

cd /home/pi/Desktop/myScript
python main.py

記得給 autostart.sh 執行權限:chmod +x /home/pi/autostart.sh
並於 /home/pi/.config/lxsession/LXDE/autostart 中新增 lxterminal --command="/home/pi/autostart.sh",接著即可重開機/登入登出測試運作情況。

參考資料:

  1. bash - open a terminal on boot and auto-run a looping python script - Raspberry Pi Stack Exchange
  2. How to open a new terminal and run commands in it - Raspberry Pi Forums
  3. HowTo auto-run LXTerminal from LXDE desktop at startup - Raspberry Pi Forums
  4. How to autostart a program on Raspbian? - Raspberry Pi Forums
  5. terminal - How can I open a lxterminal with a script running in it? - Super User

於 iOS 裝置上開發 Web App 筆記

修改案例為一個已營運多年的 Discuz 論壇,聽到使用者的聲音,希望能於手機上用 App 瀏覽論壇,基於考量,開發一個 Native App 及上架是不太可能的,而論壇本身已有一版手機版網站,因此決定以 Web App 的方式來實驗看看,不過坑比想像中的大。

Apple 有一篇基本上講的蠻清楚的文章:Configuring Web Applications
不過實際測試會發現問題蠻多的,以下一一列出自己碰到的狀況:

  1. App 圖示
    文件中明確的說出有幾個放法:

    • 要讓整個網站都有 icon,可以將 PNG 格式的圖片放置於根目錄,命名為:apple-touch-icon.png
    • 要在特定頁面加上自訂的 icon 可以於 <head> 中加入如:

      1
      <link rel="apple-touch-icon" href="/custom_icon.png">
    • 要在不同解析度的裝置上能讀取不同尺寸的圖示,可以於 <head> 中加入如:

      1
      2
      3
      4
      <link rel="apple-touch-icon" href="touch-icon-iphone.png">
      <link rel="apple-touch-icon" sizes="152x152" href="touch-icon-ipad.png">
      <link rel="apple-touch-icon" sizes="180x180" href="touch-icon-iphone-retina.png">
      <link rel="apple-touch-icon" sizes="167x167" href="touch-icon-ipad-retina.png">

      圖示大小可參考於 iOS Human Interface Guidelines 的 Graphics 一章,實際上這章已經變成 Icons and Images 內的一部分了。

    在這個區塊文件還特別註明了 iOS 7 以前 Safari 的特殊狀況,因此要特別注意,雖然這點對於 App icon 沒什麼影響,卻讓我對文件下一區塊 Specifying a Launch Screen Image 沒有任何特別註明任何狀況而浪費了很多時間,這區塊理應給個提醒,不過稍後再提。

    App icon 會於首次要加入主畫面時去伺服器要相關檔案,之後基本上便會使用這個 icon 當作 app 的圖示;而問題是若 icon 需要更新時該如何更新?於 iOS 10.3.2 在同一天內測試發現除非移除 Web App 再重新加入主畫面,否則不會更新 App icon,而隨後試了 stackoverflow 這篇的方法發現可行,雖然需要重開機才會更新,但起碼是個應急的方法:

    1
    2
    3
    <link rel="apple-touch-icon" sizes="152x152" href="/static/webapp/icons/icon-152x152.png?m=<?php echo filemtime('apple-touch-icon.png'); ?>">
    <link rel="apple-touch-icon" sizes="180x180" href="/static/webapp/icons/icon-180x180.png?m=<?php echo filemtime('apple-touch-icon.png'); ?>">
    <link rel="apple-touch-icon" sizes="167x167" href="/static/webapp/icons/icon-167x167.pngm=<?php echo filemtime('apple-touch-icon.png'); ?>">
  2. App 標題
    這個沒遇到什麼狀況,文件提到預設抓 <title>,若需預設於 <head> 加入並修改 content 內容:

    1
    <meta name="apple-mobile-web-app-title" content="AppTitle">
  3. 隱藏 Safari UI
    將 Web App 視為獨立模式 App (standalone mode),於 <head> 中加入:

    1
    <meta name="apple-mobile-web-app-capable" content="yes">

    為獨立 App 時預設所有超連結將會導回至 Safari App 中,因此須注意在 standalone mode 時將站內相關超連結改為用 AJAX 處理或更改 window.location 位置。
    可以參考作法:Stay Standalone: Prevent links in standalone web apps opening Mobile Safari

  4. 改變 Status Bar 外觀
    只有為獨立 App 時有效,不過只有三種選擇:預設(淺灰色)、黑色、透明。
    以下為黑色範例:

    1
    <meta name="apple-mobile-web-app-status-bar-style" content="black">

    可參考 stackoverflow 這篇Supported Meta Tags

  5. 加上 Launch Screen Image / Splash Screens
    於 App 開啟時秀出的畫面,如一般 native App 開啟時的效果一樣,如這篇開頭圖片效果,不過在此需特別提一下 iOS 8 9 10 實測不支援此效果,由於官方文件沒有特別提到,讓我一直以為自己哪邊寫錯了;另外要使用此功能也必須為獨立 App 才行。

    1
    <link rel="apple-touch-startup-image" href="/launch.png">

    這個也支援不同解析度的圖片:可參考這篇,不過網路上已經有實用的轉換工具 About splash-screens - Appscope 可供轉換成各種尺寸的圖片及對應的程式碼。
    想要先測試手機上支不支援,也可以先上這篇最底下的範例 PWA Splash Screens Demo 來檢查手機支援的狀況如何。

除這些基本設定外,遇到 web App 切到背景後再切回前景,其行為跟 native App 不一樣,web App 只要到了背景 native App 被 terminated 一樣,什麼狀態都要重來,因此預設情況下開啟 web App → 到了頁面 A → 切換至背景(例如收到推播去看個訊息等等) → 回到 web App,此時會回到預設的首頁,而非最後瀏覽的頁面 A;為了解決這個問題,可以利用 sessionStorage 紀錄是否為本次首次開啟 web App,以及 localStorage 紀錄最後去的連結頁面。這篇結合了上面所提的超連結問題及 web App 前景背景切換問題,並有一步一步的解說及範例,相當詳細。

最後,Appetize.io 提供了各種不同機型及 OS 版本的線上模擬功能對於測試 Web App 來說算是蠻方便的工具。
相較之下 Android 在這塊的設定上相當方便,也沒遇到什麼大問題,甚至幫 iOS 寫了部分教學範例呢……

參考資料:

  1. Configuring Web Applications
  2. html - Is it possible to force iphone/ipod to update apple-touch-icon once webapp is added to home screen? - Stack Overflow
  3. Stay Standalone: Prevent links in standalone web apps opening Mobile Safari
  4. html - apple-mobile-web-app-status-bar-style in ios 10 - Stack Overflow
  5. Adding Custom iOS Splash Screens To Your Progressive Web App
  6. Progressive Web App Splash Screens – Dave Hudson – Medium
  7. Full Screen Web Apps on iOS - Andy Mercer

利用批次檔複製 git 兩筆 commit 的差異檔案

上週不曉得為什麼躺著就掉進坑中,開始自願協助改善某個論壇功能,一加入才發現長期以來沒有使用版本控制,且更新聽說是直接將檔案拉到正式網站上的 FTP 測試。聽起來有點毛骨悚然,因此先幫忙建了一個 git,先別想 CI/CD 了,需要的成本有點太高了,考量到種種狀況,於是決定先寫個 script 來協助上傳到 FTP 的功能。

diffcopy.bat

1
2
3
4
5
6
7
8
9
10
11
@echo off

SETLOCAL ENABLEDELAYEDEXPANSION
for /f %%a in ('git diff --name-only %1 %2') do (
set "filepath=%%a"
set "filepath=!filepath:/=\!"
set "destFilepath=diffExport\!filepath!"
echo !destFilepath!
xcopy !filepath! !destFilepath!* /Y /I /Q
)
ENDLOCAL

使用方法:

1
diffcopy ae40e29 05b7a26

執行結果

如此一來即可取得 commit ae40e29 到 05b7a26 之間的差異檔案,並複製到 diffExport 資料夾,原本想將資料夾如 Stack Overflow 這篇一樣設為第三個參數,後來想想簡單就好,不過記得於每次下指令前須先手動將 diffExport 清空才行。

雖然有找到 GitPython 看似蠻方便的 library,但想想還是寫個 batch 好了。

參考資料:

  1. git - Copy all files changed in last commit - Stack Overflow

ASP.NET Core 2 使用 MySQL 作為資料庫

本篇建立在完成使用 Visual Studio Code 將模型新增至 ASP.NET Core 中的 Razor 頁面應用程式教學之後,已建立 Sqlite 作為資料庫,並完成本篇教學,預計將資料庫轉為 MySQL。

先將 MySQL NuGet 套件安裝到目前專案中,可參考 Database Providers

1
dotnet add package MySql.Data.EntityFrameworkCore

appsettings.json 中,將 MovieContext 資料取代為連線設定:

1
2
3
4
5
6
7
8
9
10
11
12
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"ConnectionStrings": {
// "MovieContext": "Data Source=MvcMovie.db",
"MovieContext": "server=192.168.99.100;database=mydatabase;user=myuser;password=mypassword"
}
}

Startup.cs 中,將 options.UseSqlite(Configuration.GetConnectionString("MovieContext")) 取代為 UseMySQL

1
2
services.AddDbContext<MovieContext>(options => 
options.UseMySQL(Configuration.GetConnectionString("MovieContext")));

接著於 CLI 中執行指令(若還沒 migrate 過,請先執行 dotnet ef migrations add InitialCreate):

1
dotnet ef database update

應會遇到 Table 'mydatabase.__EFMigrationsHistory' doesn't exist 的錯誤訊息,此為已知問題,可藉由新增這張資料表來解決此問題:

1
2
3
4
5
6
CREATE TABLE `__EFMigrationsHistory` 
(
`MigrationId` nvarchar(150) NOT NULL,
`ProductVersion` nvarchar(32) NOT NULL,
PRIMARY KEY (`MigrationId`)
);

新增後再重新執行 dotnet ef database update,成功後此時於資料庫中應可看到 Movie 資料表,此時即大功告成。

後記:若選擇 Pomelo.EntityFrameworkCore.MySql 應該就不會有資料表不存在的問題,而 ConnectionStrings 應改為以下格式:Server=localhost;database=mydatabase;uid=myuser;pwd=mypassword;

參考資料:

  1. Database Providers - EF Core | Microsoft Docs
  2. MySQL :: MySQL Connector/Net Developer Guide :: 8.3 Entity Framework Core Support
  3. EF Core `update-database` on MySql fails with `__EFMigrationsHistory’ doesn’t exist` - Stack Overflow

ASP.NET Core 2 發行時封裝整個靜態檔案資料夾

在某些狀況,可能會想把 wwwroot 包起來,以防止遇到不預期的修改,雖然以下方式也只是簡易的防君子不防小人的方式。
首先以 ASP.NET Core 使用者入門的範例為例,建立一個新專案並直接發行它:

1
dotnet new razor -o aspnetcoreapp

將此專案發行到 out 資料夾中:

1
dotnet publish -c release -o out

完成後進到 out 資料夾中,可以看到以下檔案:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ls -l
total 408
-rw-r--r-- 1 North 1049089 178 Mar 28 14:14 appsettings.Development.json
-rw-r--r-- 1 North 1049089 113 Mar 28 14:14 appsettings.json
-rwxr-xr-x 1 North 1049089 68096 Mar 28 14:37 aspnetcoreapp.PrecompiledViews.dll*
-rw-r--r-- 1 North 1049089 42496 Mar 28 14:37 aspnetcoreapp.PrecompiledViews.pdb
-rw-r--r-- 1 North 1049089 280120 Mar 28 14:37 aspnetcoreapp.deps.json
-rwxr-xr-x 1 North 1049089 7168 Mar 28 14:36 aspnetcoreapp.dll*
-rw-r--r-- 1 North 1049089 1668 Mar 28 14:36 aspnetcoreapp.pdb
-rw-r--r-- 1 North 1049089 221 Mar 28 14:37 aspnetcoreapp.runtimeconfig.json
-rw-r--r-- 1 North 1049089 628 Mar 28 14:14 bundleconfig.json
-rw-r--r-- 1 North 1049089 387 Mar 28 14:37 web.config
drwxr-xr-x 1 North 1049089 0 Mar 28 14:37 wwwroot/

可以執行,驗證結果是否正確:

1
dotnet aspnetcoreapp.dll

接著就開始改 code 了,首先於 aspnetcoreapp.csproj 的 <Project> 內加上這些內容:

1
2
3
4
5
6
<ItemGroup>
<Content Remove=".\wwwroot\**\*" />
<EmbeddedResource Include=".\wwwroot\**\*">
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
</EmbeddedResource>
</ItemGroup>

把 out 資料夾清空後再重新發行後,可以看到 wwwroot 資料夾不見了,且aspnetcoreapp.dll 的檔案大小大了不少:

1
2
3
4
5
6
7
8
9
10
11
12
$ ls -l
total 2420
-rw-r--r-- 1 North 1049089 178 Mar 28 14:14 appsettings.Development.json
-rw-r--r-- 1 North 1049089 113 Mar 28 14:14 appsettings.json
-rwxr-xr-x 1 North 1049089 68096 Mar 28 14:59 aspnetcoreapp.PrecompiledViews.dll*
-rw-r--r-- 1 North 1049089 42496 Mar 28 14:59 aspnetcoreapp.PrecompiledViews.pdb
-rw-r--r-- 1 North 1049089 280120 Mar 28 14:59 aspnetcoreapp.deps.json
-rwxr-xr-x 1 North 1049089 2068480 Mar 28 14:59 aspnetcoreapp.dll*
-rw-r--r-- 1 North 1049089 1672 Mar 28 14:59 aspnetcoreapp.pdb
-rw-r--r-- 1 North 1049089 221 Mar 28 14:59 aspnetcoreapp.runtimeconfig.json
-rw-r--r-- 1 North 1049089 628 Mar 28 14:14 bundleconfig.json
-rw-r--r-- 1 North 1049089 387 Mar 28 14:59 web.config

此時若執行 dotnet aspnetcoreapp.dll 會發現所有靜態檔案都沒被載入,因為 UseStaticFiles() 預設只載入 wwwroot 資料夾內的內容,因此要再做些修改。
開啟 Startup.cs,於開頭加上:

1
2
using System.Reflection;
using Microsoft.Extensions.FileProviders;

並於 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 內加上,加的位置端看於對 Middleware 想做什麼操作,在這裡不多加描述:

1
2
3
4
var embeddedProvider = new EmbeddedFileProvider(Assembly.GetEntryAssembly());
app.UseStaticFiles(new StaticFileOptions {
FileProvider = embeddedProvider
});

接著再執行 dotnet aspnetcoreapp.dll,理應可得到正確的結果。以上範例存放於此 aspnetcoreappEmbedStaticFiles

不過若用 Hex Editor 或直接用文字編輯器打開 aspnetcoreapp.dll 後可以看到內部還是可以看到 css, js 等內容,且可以被修改(若字元長度沒變的話),因此只是一個簡易的防範而已。

參考資料:

  1. Visual Studio publish profiles for ASP.NET Core app deployment | Microsoft Docs
  2. Common MSBuild Project Items
  3. How can I have an entire folder be an embedded resource in a Visual Studio project? - Stack Overflow
  4. Include Files using Wildcard into a folder in Visual Studio - Stack Overflow