This commit is contained in:
2022-06-02 17:55:14 +08:00
commit f86dc56286
598 changed files with 87559 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
# Family
## Wife
- 帳戶
- 中國信託新竹分行
- SWIFTCODECTCBTWTP299
- 台幣帳戶299511057829
- 外幣帳戶0000554131046751
## Ray
- IDO100875958
## Denny
- IDO100968172
## Me
- Logitech
- Empolyee ID239344
- ZOOM Direct number03-6122437
- ZOOM Extension No.862437
- [Qualcomm ID](https://myaccount.qualcomm.com/login)
- Account: [jteng2@logitech.com](mailto:jteng2@logitech.com)
- Password: Vn571jim@
- Software tool
- Office Professional Plus 2019 Key: `X88NP-YKCMM-DKVWJ-QYPWF-7H8J6`
- VS2013 key: `YDJWM-3WVQ7-JXMKW-DTHV3-2KXPG`
- Crypto coin
- ERC20: `0x9Ce80345355Ad8C17991620E13d8423900CEDcd0`
- ERC20: `0x9Ce80345355Ad8C17991620E13d8423900CEDcd0`
- 國泰證券
- 銀行交割帳號:`(013)699510287987`
- 證券交易帳號:`8888-3839353`

View File

@@ -0,0 +1 @@
- [產品spec](https://docs.google.com/spreadsheets/d/1iie7TIg7EFnC16Vic9Q7_iVELgtV2AphOkNuQ236M78/edit#gid=1851180669)

View File

@@ -0,0 +1,34 @@
北京核芯達科技有限公司
註冊資金5000萬元
CEO李慎威
李慎威在集成电路领域具有近27年资深行业经验曾先后担任PALM、联发科MediaTek智能手机产品部副总经理、高通Qualcomm中国区芯片产品线研发负责人、紫光展锐展讯通信副总裁、Imagination高级副总裁等职务。
由半導體產業資深老兵李慎威與北汽產投、Imagination、翠微股份聯合發起
特別是在定位技術上核芯達偏愛SLAM技術。
核芯達開發的基於智慧駕艙和麵向L2-L4多級別環境感知方案預計將分別於2021和2022年實現成功流片
![[Pasted image 20210728112504.png]]
北汽產投
SLAM——自動駕駛導航裡的熱門技術
SLAM 是同步定位與地圖構建 (Simultaneous Localization And Mapping) 的縮寫,最早是由 Hugh Durrant-Whyte 和 John J.Leonard 在1988年提出的。SLAM與其說是一個演算法不如說它是一個概念更為貼切它被定義為解決“機器人從未知環境的未知地點出發在運動過程中通過重複觀測到的地圖特徵比如牆角柱子等定位自身位置和姿態再根據自身位置增量式的構建地圖從而達到同時定位和地圖構建的目”的問題方法的統稱。
SLAM技術的核心步驟大體上而言SLAM包含了感知、定位、建圖這三個過程。
感知——機器人能夠通過感測器獲取周圍的環境資訊。
定位——通過感測器獲取的當前和歷史資訊,推測出自身的位置和姿態。
建圖——根據自身的位姿以及感測器獲取的資訊,描繪出自身所處環境的樣貌。
雙目立體視覺,強光下變現更優異
![[Pasted image 20210728112028.png]]
TOF簡單來講通過光的飛行時間來計算距離。
結構光:通過紅外鐳射器,將具有一定結構特徵的光線投射到被拍攝物體上,再由專門的紅外攝像頭進行採集反射的結構光圖案,根據三角測量原理進行深度資訊的計算。
雙目立體視覺原理類似於萊卡相機對近距離和遠距離有一些視差的補償。核芯達對這個技術很是看好原因就在於不容易受到強光干擾成本較低也沒有什麼專利困擾。李慎威解釋人對亮到全黑環境會有一個適應過程人在這方面的過程非常慢如果從暗到亮這個適應是比較快的。對於TOF和結構光則會有一些短板近期特斯拉與一輛側躺的白色卡車相撞事件就是一個很好的例子。機器視覺並沒有計算出來前方有物體存在。

View File

@@ -0,0 +1,190 @@
## docker-compose.yml
```yaml
version: '3'
services:
nginx_reverseproxy_l4:
container_name: nginx
restart: always
image: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./data:/etc/nginx
```
## 準備
目錄架構如下:
```
.
├── data
│   ├── mime.types
│   └── nginx.conf
└── docker-compose.yaml
```
### nginx.conf
其中`nginx.conf`的內容如下:
```
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
stream {
map $ssl_preread_server_name $backend_name {
tjn.awin.one trojan;
storj.awin.one swag;
blog.awin.one swag;
gitea.awin.one swag;
}
# trojan
upstream trojan {
server 192.168.1.31:443;
}
# swag
upstream swag {
server 192.168.1.20:44320;
}
# 监听 443 并开启 ssl_preread
server {
listen 80 reuseport;
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
}
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
```
### mime.types
其中`mime.types`的內容如下:
```
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}
```

View File

@@ -0,0 +1,247 @@
[SWAG](https://docs.linuxserver.io/general/swag)可以幫我們反向代理並且有https。是由linuxserver.io包裝的Nginx webserver和reverse proxy的container。
#### 建立docker-compose
1. 建立folder
```bash
mkdir -p ~/dockers/linuxserverswag ; cd ~/dockers/linuxserverswag
```
2. 建立`docker-compose.yaml`
```
vim docker-compose.yaml
```
填入內容如下:
```yaml
version: "2.1"
services:
swag:
image: ghcr.io/linuxserver/swag
container_name: swag
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Taipei
- URL=awin.one
- SUBDOMAINS=wildcard
- VALIDATION=dns
- CERTPROVIDER= #optional
- DNSPLUGIN=cloudflare #optional
- PROPAGATION= #optional
- DUCKDNSTOKEN= #optional
- EMAIL=awinhuang@gmail.com
- ONLY_SUBDOMAINS=false #optional
- EXTRA_DOMAINS= #optional
- STAGING=false #optional
- MAXMINDDB_LICENSE_KEY= #optional
volumes:
- ./config:/config
ports:
- 44320:443
- 8020:80 #optional
restart: unless-stopped
```
3. 先跑一次:
```bash
sudo docker-compose up
```
會發現有錯誤,這是正常的,錯誤訊息像這樣:
```
swag | Unsafe permissions on credentials configuration file: /config/dns-conf/cloudflare.ini
swag | Cleaning up challenges
swag | Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address and Global key?)
swag | ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.
```
按`ctrl + c`退出。這時候`config`目錄也會有swag所mapping出來的相關檔案。修改`config/dns-conf/cloudflare.ini`
```bash
vim config/dns-conf/cloudflare.ini
```
把`config/dns-conf/cloudflare.ini`改為:
```
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
# Replace with your values
With global api key:
dns_cloudflare_email = awinhuang@gmail.com
dns_cloudflare_api_key = <YOUR_API_KEY_FROM_CLOUDFLARE>
# With token (comment out both lines above and uncomment below):
#dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
```
4. 再跑一次:
```bash
sudo docker-compose up
```
這一次就可以順利取得認證了,訊息像這樣:
```
swag | IMPORTANT NOTES:
swag | - Congratulations! Your certificate and chain have been saved at:
swag | /etc/letsencrypt/live/awin.one/fullchain.pem
swag | Your key file has been saved at:
swag | /etc/letsencrypt/live/awin.one/privkey.pem
swag | Your certificate will expire on 2021-04-26. To obtain a new or
swag | tweaked version of this certificate in the future, simply run
swag | certbot again. To non-interactively renew *all* of your
swag | certificates, run "certbot renew"
swag | - If you like Certbot, please consider supporting our work by:
swag |
swag | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
swag | Donating to EFF: https://eff.org/donate-le
swag |
swag | New certificate generated; starting nginx
swag | Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,
swag | and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key.
swag | [cont-init.d] 50-config: exited 0.
swag | [cont-init.d] 60-renew: executing...
swag | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
swag | [cont-init.d] 60-renew: exited 0.
swag | [cont-init.d] 70-templates: executing...
swag | [cont-init.d] 70-templates: exited 0.
swag | [cont-init.d] 99-custom-files: executing...
swag | [custom-init] no custom files found exiting...
swag | [cont-init.d] 99-custom-files: exited 0.
swag | [cont-init.d] done.
swag | [services.d] starting services
swag | [services.d] done.
swag | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
swag | Server ready
```
最後一行的`swag | Server ready`表示server已經跑起來了。先按下`ctrl + c`退出再來設定reverse proxy。
5. 修正`config/dns-conf/cloudflare.ini`的安全性問題
```
cd ~/dockers/linuxserverswag ; chmod 600 config/dns-conf/cloudflare.ini
```
#### Setup reverse proxy
1. 建立folder
```
cd ~/dockers/linuxserverswag ; mkdir -p config/nginx/sites-available config/nginx/sites-enabled
```
2. 建立以下檔案:
- `config/nginx/sites-available/common.conf`,內容:
```
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
```
- `config/nginx/sites-available/common_location.conf`,內容:
```
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
```
- `config/nginx/sites-available/storj.conf`,內容:
```
upstream storj {
server 192.168.1.11:14002;
}
server {
listen 443 ssl;
server_name storj.awin.one;
include /config/nginx/sites-available/common.conf;
include /config/nginx/ssl.conf;
location / {
proxy_pass http://192.168.1.11:14002/;
include /config/nginx/sites-available/common_location.conf;
}
}
```
- `config/nginx/sites-available/blog.conf`,內容:
```
upstream blog {
server 192.168.1.30:80;
}
server {
listen 443 ssl;
server_name blog.awin.one;
include /config/nginx/sites-available/common.conf;
include /config/nginx/ssl.conf;
location / {
proxy_pass http://192.168.1.30:80/;
include /config/nginx/sites-available/common_location.conf;
}
}
```
- `config/nginx/sites-available/gitea.conf`,內容:
```
upstream gitea {
server 192.168.1.32:3000; ## 網址
}
server {
listen 443 ssl;
server_name gitea.awin.one; ## 網域
include /config/nginx/sites-available/common.conf;
include /config/nginx/ssl.conf;
location / {
proxy_pass http://192.168.1.32:3000/; ## 網址
include /config/nginx/sites-available/common_location.conf;
}
}
```
3. 在`config/nginx/sites-enabled`裡面建立要enable的config
```
cd config/nginx/sites-enabled ; ln -s ../sites-available/storj.conf . ; cd -
```
4. 修改`config/nginx/nginx.conf`
```
vim config/nginx/nginx.conf
```
找到`include /config/nginx/site-confs/*;`這一行把它comment掉在下面新增一行
```
include /config/nginx/sites-enabled/*.conf;
```
5. 啟動swag
```
cd ~/dockers/linuxserverswag ; sudo docker-compose up -d
```
#### Restart
```bash
cd ~/dockers/linuxserverswag ; sudo docker-compose restart
```
#### Update certification
1. 進到dockr的swag bash裡面`sudo docker exec -it swag /bin/bash`
2. 輸入 `certbot renew`
3. ![[Pasted image 20210422205534.png]]
#### Trouble shooting
- 如果遇到類似下面的錯誤:
```
ERROR: for swag Cannot start service swag: driver failed programming external connectivity on endpoint swag (7c527d046631e0957de0b831ca25bed296de76e2eb96378964cb0110d7fb017d): Bind for 0.0.0.0:443 failed: port is already allocated.
```
表示有其他程式佔住了80 port可能是其他docker container或是service必須先關閉它們。[^1]
#### 參考來源
1. [linuxserver/docker-swag: Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.](https://github.com/linuxserver/docker-swag)
2. [How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt](https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/)
3. [SWAG setup - LinuxServer.io](https://docs.linuxserver.io/general/swag#understanding-the-proxy-conf-structure)
4. [NGINX Docs | NGINX Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
5. [Nginx 設定反向代理 Reverse Proxy](https://www.opencli.com/linux/nginx-config-reverse-proxy)
6. [用 Nginx 伺服器建立反向代理](https://noob.tw/nginx-reverse-proxy/)
7. [俄羅斯不愧是戰鬥民族nginx - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10188498)
[^1]: [啟動docker時出現「Cannot start service :driver failed programming external connectivity on endpoint xxx, Bind for 0.0.0.0:80 failed: port is already allocated 」](https://mitsuiwei.com/docker-cannot-start-service/)

View File

@@ -0,0 +1,14 @@
## 準備
### 安裝Maria DB
## 1. 下載trojan安裝檔
```
wget https://raw.githubusercontent.com/Jrohy/trojan/master/install.sh
```
## 參考
- [Trojan史上最全面的脚本Trojan所有脚本集合 - 简书](https://www.jianshu.com/p/bb14fc75292d)
- [VPS 初体验(三)在 VPS 上快速搭建 trojan 服务 - Kiku 的个人博客](https://kiku.vip/2021/10/16/%E5%9C%A8%20VPS%20%E5%BF%AB%E9%80%9F%E6%90%AD%E5%BB%BA%20trojan%20%E6%9C%8D%E5%8A%A1/)
- [mku228/trojan: 科学上网/翻墙梯子/自由上网/trojan 搭建教程 免费机场、VPN工具 小白科学上网一键搭建VPN梯子最新2022教程](https://github.com/mku228/trojan)
- [Jrohy/trojan: trojan多用户管理部署程序, 支持web页面管理](https://github.com/Jrohy/trojan)

View File

@@ -0,0 +1,48 @@
## docker-compose.yml
```yaml
version: "3"
networks:
gitea:
external: false
services:
server:
image: gitea/gitea:latest
container_name: gitea
environment:
- USER_UID=1000
- USER_GID=1000
- DB_TYPE=mysql
- DB_HOST=db:3306
- DB_NAME=gitea
- DB_USER=gitea
- DB_PASSWD=gitea
- TZ=Asia/Taipei
restart: always
networks:
- gitea
volumes:
- ./datas/gitea:/data
ports:
- "3000:80"
- "322:22"
depends_on:
- db
db:
image: mysql:8
restart: always
environment:
- MYSQL_ROOT_PASSWORD=gitea
- MYSQL_USER=gitea
- MYSQL_PASSWORD=gitea
- MYSQL_DATABASE=gitea
networks:
- gitea
volumes:
- ./datas/db:/var/lib/mysql
```
## 文件
- [Gitea Docs: Config Cheat Sheet](https://docs.gitea.io/zh-tw/config-cheat-sheet/)

View File

@@ -0,0 +1,2 @@
[二次大戰德軍虎式戰車考證 - Mobile01](https://www.mobile01.com/topicdetail.php?f=637&t=5384277)
[戰車之王:米歇爾.魏特曼 @ "Loyalty Is My Honor忠誠即是我的榮耀"WAFFEN-SS 武裝親衛隊簡介 :: 痞客邦 ::](https://mdoohan74.pixnet.net/blog/post/8820801-%E6%88%B0%E8%BB%8A%E4%B9%8B%E7%8E%8B%3A%E7%B1%B3%E6%AD%87%E7%88%BE.%E9%AD%8F%E7%89%B9%E6%9B%BC)

View File

@@ -0,0 +1,6 @@
# 升級零件
1. [差速齒9579R](https://youtu.be/t2HpcLTBsTc)
2. [前保桿6835](https://www.youtube.com/watch?v=aWxCktCLTI8)
1. 需要螺絲
1. m3x30
2. 平頭m4x20

View File

@@ -0,0 +1,51 @@
### 德國四號
![[德國四號_舊化做例_001.jpg|160]]
### 德國Tiger I
![[Tiger I 做例_001.jpg]]
![[Tiger I 做例_002.jpg]]
![[Tiger I 做例_003.jpg]]
![[Tiger I 做例_004.jpg]]
![[Tiger I 做例_005.jpg]]
![[Tiger I 做例_006.jpg]]
參考:
- [Panzerkampfwagen VI Tiger Sd.Kfz.181, Tiger I - Tanks Encyclopedia](https://tanks-encyclopedia.com/ww2/germany/panzer-vi_tiger.php)
- [Tiger I Information Center - Paint Schemes](http://www.alanhamby.com/paints.shtml)
### 德國虎王
![[德國虎王_作例_001.jpg]]
![[德國虎王_作例_002.jpg]]
![[德國虎王_作例_003.jpg]]
![[德國虎王_作例_004.jpg]]
參考:
- [Panzerkampfwagen Tiger Ausf.B (Sd.Kfz.182) Tiger II](https://tanks-encyclopedia.com/ww2/germany/panzer-vi_konigstiger.php)
### Selbstfahrlafette IV
- [大麥克斯](https://zh.wikipedia.org/wiki/%E5%A4%A7%E9%BA%A5%E5%85%8B%E6%96%AF)
![[Dicker Max_001.jpg]]
![[Dicker Max_002.jpg]]
![[Dicker Max_003.jpg]]
![[Dicker Max_004.jpg]]
![[Dicker Max_005.jpg]]
![[Dicker Max_006.jpg]]
![[Dicker Max_007.jpg]]
![[Dicker Max_008.jpg]]
![[Dicker Max_009.jpg]]
![[Dicker Max_010.jpg]]
![[Dicker Max_011.jpg]]
![[Dicker Max_012.jpg]]
![[Dicker Max_013.jpg]]
![[Dicker Max_014.jpg]]
![[Dicker Max_015.jpg]]
![[Dicker Max_016.jpg]]
![[Dicker Max_017.jpg]]
![[Dicker Max_018.jpg]]
![[Dicker Max_019.jpg]]
![[Dicker Max_020.jpg]]
![[Dicker Max_021.jpg]]
![[Dicker Max_022.jpg]]
![[Dicker Max_023.jpg]]
![[Dicker Max_024.jpg]]
![[Dicker Max_025.jpg]]

View File

@@ -0,0 +1,10 @@
Title:
Date: {{date:YYYY-MM-DD}} {{time:HH:mm}}
Modified: {{date:YYYY-MM-DD}} {{time:HH:mm}}
Category:
Tags:
Slug:
Authors: Awin Huang
Summary:
This is the content of post.

View File

@@ -0,0 +1 @@
{{date:YYYY-MM-DD}}

View File

@@ -0,0 +1,7 @@
---
tags:
aliases:
date: {{date}}
time: {{time:HH:mm:ss}}
description:
---

View File

@@ -0,0 +1,6 @@
---
date: {{date}}
tags:
aliases:
-
---

View File

@@ -0,0 +1,5 @@
| header | header | header |
|:------:|:------ | ------:|
| 1-2 | 1-1 | 1-3 |
| 2-2 | 2-1 | 2-3 |
| 3-2 | 3-1 | 3-3 |

View File

@@ -0,0 +1,8 @@
- Week{{date:ww}}: {{date:YYYY-MM-DD(ddd)}}
- 星期一:
- 星期二:
- 星期三:
- 星期四:
- 星期五:
- 星期六:
- 星期日:

View File

@@ -0,0 +1,47 @@
時間:{{time:HH:mm:ss}}
### TAG
### All TODOs
```tasks
not done
path does not include 2021
path does not include 2022/01
path does not include 2022/02
path does not include 2022/03
path does not include 2022/04
path does not include 004 - Archives歸檔
path does not include 001. Kong
group by folder
group by filename
group by heading
```
---
### Doing TODOs
```tasks
not done
has start date
happens before tomorrow
path does not include 2021
path does not include 2022/01
path does not include 2022/02
path does not include 2022/03
path does not include 2022/04
path does not include 004 - Archives歸檔
path does not include 001. Kong
group by folder
group by filename
group by heading
```
---
### 新增TODO
#### 私事
#### 公事
### 今日回顧

View File

@@ -0,0 +1,11 @@
書名:
日期:{{date:YYYY-MM-DD(ddd)}}
Link
# 筆記
# 心得
# TODO

View File

@@ -0,0 +1 @@
- [如何透過 adb command line 指令啟動 Android App](https://kkboxsqa.wordpress.com/2014/08/20/%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-adb-command-line-%E6%8C%87%E4%BB%A4%E5%95%9F%E5%8B%95-android-app/)

View File

@@ -0,0 +1,164 @@
## Build AOSP
### Build compile environment
1. Install Ubuntu 18.04
2. Install packages: `sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig`
- https://source.android.com/setup/build/initializing
3. Install Repo
```
mkdir ~/bin
PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo ;\
chmod a+x ~/bin/repo ;\
gpg --recv-key 8BB9AD793E8E6153AF0F9A4416530D5E920F5C65 ;\
curl https://storage.googleapis.com/git-repo-downloads/repo.asc | gpg --verify - ~/bin/repo
```
- https://source.android.com/setup/develop#installing-repo
4. Download AOSP source
1. Create folder for AOSP
```
mkdir -p ~/codes/aosp ; cd ~/codes/aosp
```
2. Setup git
```
git config --global user.name AwinHuang ;\
git config --global user.email awinhuang@gmail.com
```
3. Download source code
```
repo init -u https://android.googlesource.com/platform/manifest ;\
repo sync -j8
```
- 如果要切換某一個特定版本可以使用`-b`,例如:`repo init -u https://android.googlesource.com/platform/manifest -b android-10.0.0_r47`。
- 要知道版本tag可以查看https://source.android.com/setup/start/build-numbers#source-code-tags-and-builds
5. Build code
```
source build/envsetup.sh ;\
lunch aosp_arm-eng ;\
make clobber ;\
make -j16
```
- `make clobber`用來刪除build資料夾
### Reference
- [GitHub - henrymorgen/android-knowledge-system: Android应用开发最强原创知识体系](https://github.com/henrymorgen/android-knowledge-system)
- [Android AOSP基础AOSP源码和内核源码下载 | BATcoder - 刘望舒](http://liuwangshu.cn/framework/aosp/2-download-aosp.html)
- [Android AOSP基础Android系统源码的整编和单编 | BATcoder - 刘望舒](http://liuwangshu.cn/framework/aosp/3-compiling-aosp.html)
## Build kernel
1. Download the code
```
mkdir -p ~/codes/kernel ;\
cd ~/codes/kernel ;\
repo init -u https://android.googlesource.com/kernel/manifest ;\
repo sync -j16
```
2. Compile
```
build/build.sh
```
- 如果遇到`Command 'java' not found, but can be installed with:`
- 依序安裝
- `sudo apt install default-jre`
- `sudo apt install openjdk-11-jre-headless`
- `sudo apt install openjdk-8-jre-headless`
- 執行 `sudo update-alternatives --config java`
- 選擇 `/usr/lib/jvm/java-11-openjdk-amd64/bin/java`
- 再次compile
- `source build/envsetup.sh`
- `mm idegen`
3. 產生android.iml和android.ipr
在source code跟目錄下執行`development/tools/idegen/idegen.sh`
### Reference
- [Android kernel源码下载与编译](https://blog.csdn.net/u010164190/article/details/106561022)
## Android App programming
- R的全名`<PACKAGE_NAME> + .R`例如package name是`com.awin.testapp`,那全名是`com.awin.testapp.R`。
- AndroidX = Android eXtension
- Layout
- layout_margin: 物件與其他物件的距離
- layout_gravity: 物件在容器內的位置(靠左、靠右、置中...
- textApperance: 字型大小
- Extensions
- Android 4.1 沒有自動加入的extension
- 打開build.gradle在`plugins`區塊中加入:
```
id 'kotlin-kapt'
id 'kotlin-android-extensions'
```
- 使用ViewModel & LiveData
- 確認有 `kotlin-kapt` 這個plugin。
![[Pasted image 20210330102148.png]]
- [Android jetpack所有library](https://developer.android.com/jetpack/androidx/explorer)
- [Android jetpack - Lifecycle](https://developer.android.com/jetpack/androidx/releases/lifecycle)
- 從`Declaring dependencies`這區塊複製必要的module
![[Pasted image 20210330110411.png]]
```
def lifecycle_version = "2.3.1"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Annotation processor
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
```
- Create a ViewModel
```kotlin
viewModel = ViewModelProvider(this).get(GuessViewModel::class.java)
```
- Observe a live data in ViewModel
```kotlin
viewModel.counter.observe(this, Observer {
counter.setText(it.toString())
})
```
counter這個變數是包含在ViewModel裡面的live data我們的資料則是放在counter裡面的「value」。
所以如果要取用我們的data則是`viewModel.counter.value`。
- 使用LiveData
- `val counter = MutableLiveData<Int>()`
### Use ViewBinding
ViewBinding is used to replace Kotlin Synthetics.
1. 在`app/build.gradle`中加入:
```
plugins {
...
id 'kotlin-parcelize' <-- Add this
}
android {
...
buildFeatures { <-- Add this
viewBinding true
}
...
}
```
2. 在你的activity裡面
1. `import <APP_DOMAIN_NAME>.databinding.<ACTIVITY_NAME>Binding`
假如APP_DOMAIN_NAME是`com.example.testmultisectioncyclerview`ACTIVITY_NAME是`ActivityMain`,那就是:
`import com.example.testmultisectioncyclerview.databinding.ActivityMainBinding`
2. 用`lateinit`宣告一個變數變數名稱是activity的名字加上binding例如`ActivityMain`就是:
`private lateinit var activityBinding: ActivityMainBinding`
3. 在`onCreate()`中,就可以用`activityBinding`來取得view與其他元件了
```
activityBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(activityBinding.root) <-- root就是view
```
## ADB usage
- [如何透過 adb command line 指令啟動 Android App](https://kkboxsqa.wordpress.com/2014/08/20/%E5%A6%82%E4%BD%95%E9%80%8F%E9%81%8E-adb-command-line-%E6%8C%87%E4%BB%A4%E5%95%9F%E5%8B%95-android-app/)
# MISC
## 教學文
- [Android Template 小技巧 及 寫程式常見的問題](https://www.eeaseries.com/2021/01/android-template.html?m=1)
- [Jetpack Compose 基础知识](https://developers.google.com/codelabs/jetpack-compose-basics?hl=zh-cn#0)
- [一文带你了解适配Android 11分区存储](https://zhuanlan.zhihu.com/p/354632087)

View File

@@ -0,0 +1,39 @@
- 變數宣告的方式變了
- Old: `int a = 3;`
- New: `int a {3};`
- `if`裡面可以宣告變數
```cpp
if (auto a {3}; a > b) {
// Do something
}
```
- `unique_ptr`: 無法複製的指標
- 傳統方法:
```cpp
unique_ptr<uint8_t[]> buffer = new uint8_t[256];
```
- 新方法:
```cpp
auto buffer = std::make_unique<uint8_t[]>(256);
```
- `share_ptr`: 可以複製,但要避免循環參考問題
- 透過refernce傳遞array參數
- 考慮一個帶有長度的陣列要傳到function裡面但是又希望在function面可以指定陣列長度
```cpp
double value[] { 1.0, 2.0, 3.0 }; // Error!
double value[] { 1.0, 2.0, 3.0, 4.0, 5.0 }; // Pass!
double average(const double (&array)[5]) {
...
}
```
- 用 `std::string_view` 代替 `const std::string&`。
## Multi-Thread
### 使用`std::async`
- [C++ 使用 Async 非同步函數開發平行化計算程式教學](https://blog.gtwang.org/programming/cpp-11-async-function-parallel-computing-tutorial/)
- [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)

View File

@@ -0,0 +1,39 @@
- 變數宣告的方式變了
- Old: `int a = 3;`
- New: `int a {3};`
- `if`裡面可以宣告變數
```cpp
if (auto a {3}; a > b) {
// Do something
}
```
- `unique_ptr`: 無法複製的指標
- 傳統方法:
```cpp
unique_ptr<uint8_t[]> buffer = new uint8_t[256];
```
- 新方法:
```cpp
auto buffer = std::make_unique<uint8_t[]>(256);
```
- `share_ptr`: 可以複製,但要避免循環參考問題
- 透過refernce傳遞array參數
- 考慮一個帶有長度的陣列要傳到function裡面但是又希望在function面可以指定陣列長度
```cpp
double value[] { 1.0, 2.0, 3.0 }; // Error!
double value[] { 1.0, 2.0, 3.0, 4.0, 5.0 }; // Pass!
double average(const double (&array)[5]) {
...
}
```
- 用 `std::string_view` 代替 `const std::string&`。
## Multi-Thread
### 使用`std::async`
- [C++ 使用 Async 非同步函數開發平行化計算程式教學](https://blog.gtwang.org/programming/cpp-11-async-function-parallel-computing-tutorial/)
- [std::atomic](https://en.cppreference.com/w/cpp/atomic/atomic)

View File

@@ -0,0 +1,23 @@
> Class template類別樣板不是類別而是建立類別的方法。
定義類別樣板
```cpp
template <template parameter list>
class ClassName
{
// Template class definition
};
```
`typename`來指定會變動的變數型態,例:
```cpp
template <typename T1, typename T2>
class MyTemplateClass
{
public:
T1 length;
T2 weight;
};
```

View File

@@ -0,0 +1,2 @@
- 等號左邊的值
- 「有固定address」的變數

View File

@@ -0,0 +1,59 @@
move operator可以讓[[rvalue]]被參考從而進一部的消除複製的成本。例如以下的function會回傳一個很大的陣列
```cpp
vector<int> generateBigArray() {
const int size = 1000000;
vector<int> array;
for (int i = 0; i < size; i++) {
array[i] = i;
}
return array;
}
```
當我們呼叫這個function並把結果回傳到一個變數的時候每一次這個大陣列都會被複製一次
```cpp
vector<int> atemp = generateBigArray(); // 複製1000000個int
```
如果使用[[rvalue]] reference就可以避開這些複製
```cpp
vector<int>&& atemp = generateBigArray(); // 已經建立好的array會直接「移動」到atemp省下了複製的步驟
```
## move contructor
move contructor跟copy constructor很類似只是參數由`&`改成了`&&`
例:
```cpp
template <typename T>
inline Array<T>::Array(const Array&& moved) :
size{moved.size},
elements{moved.elements}
{
moved.elements = nullptr;
}
```
## move assignment operator
```cpp
template <typename T>
Array<T>& Array<T>::operator=(const Array&& rhs)
{
if (this != &rhs) {
delete [] elements;
elements = rhs.elements;
size = rhs.size;
rhs.elements = nullptr;
}
return *this;
}
```
## 明確的「移動」
如果有一個現存的「大東西」,可以使用`std::move`來把它「移」到別的地方,進而避開了複製的行為。例:
```cpp
std::vector<std::string> my_dictionary(10000000);
std::vector<std::string> dictionary2 = std::move(my_dictionary);
```
`std::move`之後my_dictionary的size會變成0。

View File

@@ -0,0 +1,9 @@
- 等號右邊的值
- 臨時的值,例如運算的結果
# rvalue參考
可以用兩個`&`來參考rvalue。例如
```
int c{5};
int&& rtepm {c + 3};
```

View File

@@ -0,0 +1,133 @@
---
tags:
aliases:
date: 2022-05-26
time: 15:12:04
description:
---
## 策略模式Strategy
策略模式可以提供不一樣的演算法,但是又不用更改程式。
以常見的鴨子為例有一個基礎類別Duck如何衍生出會飛得鴨子跟不會飛的鴨子抑或是會叫的跟不會叫的
第一部是將會變動的部份分離出來,讓鴨子類別不需要去在乎飛跟叫的問題。
再來是把飛跟叫的部份包裝成另一個class並以之為基礎類別來實做出「實際的類別」。
以一般C++的override方法會用的方式大致是這樣
- !!!col
- 1
### 基礎類別
```cpp
class duck {
duck() {}
void fly() {}
void makeSound() {}
void run() {}
}
```
- 1
### 衍生類別,遙控鴨子,會飛不會叫
```cpp
class duckRC : public duck {
duckRC() {}
void fly() { printf("I can fly"); }
void makeSound() { printf("I cannot make sound!"); }
}
```
- 1
### 衍生類別,木頭鴨子,不會飛不會叫
```cpp
class duckWood : public duck {
duckWood() {}
void fly() { printf("I cannot fly"); }
void makeSound() { printf("I cannot make sound!"); }
}
```
但是這樣的話如果基礎類別會更改的話,那麼子類別也全部都會受影響,例如,現在希望所有的鴨子都要有一個「跑」的功能,所以我們就在基礎類別裡加一個`run()`
```cpp
class duck {
void fly() {}
void makeSound() {}
void run() { printf("I can run"); }
}
```
結果現在木頭鴨子也能跑了,這不符合我們的設計,所以我們必須回頭更改木頭鴨子的程式,改成
```cpp
class duckWood : public duck {
void fly() { printf("I cannot fly"); }
void makeSound() { printf("I cannot make sound!"); }
void run() { printf("I cannot run!"); } // <- 要改!
}
```
如果我們類別很多那麼就很可能有沒改到的漏網之魚bug就發生了。
### 封裝變動的部份
比較好的方法一旦類別寫完之後,我們就不要動它了,日後新增的功能也不可以影響到他。我們把「會變動」的部份分離出來,變成各自的類別。
為了簡化我們討論「飛」這個功能就好「飛」只分成「會飛」跟「不會飛」2種類別
- !!!col
- 1
### 基礎類別IFly
```cpp
class IFly {
void doFly() = 0;
}
```
- 1
### 衍生類別CanFly
```cpp
class CanFly {
void doFly() { printf("I can fly"); }
}
```
- 1
### 衍生類別CannotFly
```cpp
class CannotFly {
void doFly() { printf("I cannot fly"); }
}
```
回到鴨子的基礎類別這邊,基礎類別`duck`本來是直接擁有`fly()`這個member function現在我們把他改成他擁有的是`IFly`
```cpp
class duck {
duck() {}
fly() { fly->doFly() };
IFly* fly = nullptr;
...
}
```
重寫遙控鴨子這個衍生類別:
```cpp
class duckRC : public duck {
duckRC() {
this->fly = new CanFly();
}
...
}
```
重寫木頭鴨子,不會飛,所以:
```cpp
class duckWood : public duck {
duckWood() {
this->fly = new CannotFly();
}
}
```
現在,不管是遙控鴨子或是木頭鴨子,在被呼叫`fly()`的時候都是根據它fly的「實際類別」來動作遙控鴨子的`fly()`呼叫的是`CanFly`的`doFly()`,木頭鴨子的`fly()`呼叫的是`CannotFly`的`doFly()`它們的動作完全取決於他們初始化的方式而他們初始化的方式則取決於你的產品定義要是今天你需要不同的飛行方式增加新的fly類別即可不會影響到舊有的。要是鴨子基礎類別增加了新的行為如run那麼就幫鴨子基礎類別的`run()`加個什麼都不做的預設動作就好,反正舊有的鴨子衍生類別本來就對這個新行為沒反應。要是`CanFly`的定義改變了,那麼你就是改變了使用`CanFly`的所有類別,這是你的定義明確的改變了,不是有程式被「額外」的改變了。
- !!!col
- 1
### 原本直接繼承的方式
![[Pasted image 20220526182952.png]]
- 2
### 封裝變動的部份
![[Pasted image 20220526183019.png]]
這樣做的另一個好處是fly的初始化是動態的只要再多一個`set()` function就可以動態的切換實作也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。

View File

@@ -0,0 +1,301 @@
## Initialization
```cpp
av_register_all();
avformat_network_init();
```
## Command line
利用FFMpeg來轉檔
```
ffmpeg -i <SOURCE.mp4> -t 10 -s <W>x<H> -pix_fmt <OUTPUT_FORMAT> <OUTPUT_FILENAME>
```
- `-i`:來源檔名
- `-t`:影片長度(秒)
- `-s`影片大小長x寬
- `-pix_fmt`:輸出的影像格式
example:
```
ffmpeg -i test.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv
```
## Open file
使用`avformat_open_input()`來打開檔案。
```cpp
AVFormatContext* pFormatContext = NULL;
char* path = "E:\\download\\Incredible Patagonia_1920x1080.mp4";
AVDictionary* pDict = NULL;
int result = avformat_open_input(&pFormatContext, path, NULL, &pDict);
```
若是開啟失敗`result`會是一個非0的值。可以用`av_strerror()`來印出詳細的錯誤訊息。例:
```cpp
char errorMsg[1024] = { 0 };
av_strerror(result, errorMsg, sizeof(errorMsg) - 1);
printf("Open file %s error, code = %d, message = %s\n", path, result, errorMsg);
```
### Get file information
`avformat_find_stream_info()`來把檔案的video/audio相關訊息放到`AVFormatContext`裡面。例:
```cpp
result = avformat_find_stream_info(pFormatContext, NULL);
```
也可以用
```cpp
av_dump_format(pFormatContext, 0, path, 0);
```
來直接印出相關訊息不過這方法是Ffmpeg內定的如果要印出自己想要的內容就必須將`AVFormatContext`裡面的`AVStream`一一撈出來:
```cpp
for (int i = 0; i < pFormatContext->nb_streams; i++) {
AVStream* avStream = pFormatContext->streams[i];
if (avStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
printf("Stream[%d] is a audio stream.\n", i);
printf(" Sample rate: %d\n", avStream->codecpar->sample_rate);
printf(" Format : %d\n", avStream->codecpar->format);
printf(" Channels : %d\n", avStream->codecpar->channels);
printf(" FPS : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
printf(" Frame Size : %d\n", avStream->codecpar->frame_size);
printf(" Codec ID : %d\n", avStream->codecpar->codec_id);
} else if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
printf("Stream[%d] is a video stream.\n", i);
printf(" Width : %d\n", avStream->codecpar->width);
printf(" Height: %d\n", avStream->codecpar->height);
printf(" FPS : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
printf(" Codec ID: %d\n", avStream->codecpar->codec_id);
}
}
```
這個for-loop可以找出檔案裡面的video stream index與audio stream index。
另一個找出video stream index與audio stream index的方法
```cpp
int videoStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audioStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
printf("Found best video stream index: %d\n", videoStream);
printf("Found best audio stream index: %d\n", audioStream);
```
## Demux
### 重要結構
#### AVPackt
##### 重要的member
- `AVBufferRef *buf`buffer的reference count基本上不會用到。
- `int64_t pts`:顯示順序
- `int64_t dts`decode順序
- `uint8_t *data`實際存data的buffer
- `int size`上面那個buffer的大小
##### 操作function
- `AVPacket* av_packet_alloc()`產生一個兂的AVPacket
- `AVPacket* av_packet_clone(const AVPacket*)`複製一個AVPacket
- `int av_packet_ref(AVPacket*, const AVPacket*)`增加reference count
- `av_packet_unref(AVPacket*)`減少reference count
- `void av_packet_free(AVPacket**)`釋放AVPacket
- `void av_init_packet(AVPacket*)`初始化AVPacket
- `int av_packet_from_data(AVPacket*, uint8_t*, int size)`從自己的data
- `av_seek_frame`
- `AVFormatContext*`
- `int stream_index`要seek的stream
- `int64_t timestamp`timestamp
- `int flag`seek的方法
- `#define AVSEEK_FLAG_BACKWARD 1`:往後找
- `#define AVSEEK_FLAG_BYTE 2`以byte為單位跳到timestamp相對應的byte
- `#define AVSEEK_FLAG_ANY 4`跳到任意frame不一定要key frame
- `#define AVSEEK_FLAG_FRAME 8`依照frame number來找
## Decode
### 重要結構
#### AVCodecContext
##### 重要的member
- `int thread_count`:執行續的數量
- `AVRational time_base`
##### 操作function
- `avcodec_register_all()`註冊所有的decoder。
- `AVCodec* avcodec_find_decoder(enum AVCodecID)`用codec ID來找到相對應的codec instance
- `AVCodec* avcodec_find_decoder_by_name(const char* name)`用codec的名子來找到相對應的codec instance
- `AVCodecContext* avcodec_alloc_context3(const AVCodec*)`用codec來建立`AVCodecContext`codec只是用來解碼解碼之後的相關資料要放在`AVCodecContext`裡面。
- `void avcodec_free_context(AVCodecContext**)`:釋放`AVCodecContext`
- `int avcodec_open2(AVCodecContext*, const AVCodec*, AVDictionary*)`
- 開始解碼
- `AVDictionary`的選項請看`libavcodec/options_table.h`
#### AVFrame
##### 重要的member
- `uint8_t* data[AV_NUM_DATA_POINTERS]`存放這個frame的資料
- `int linesize[AV_NUM_DATA_POINTERS]`存放指向各個plane的pointer
- Plane mode
- For video
- 0Y
- 1U
- 2V
- 3NULL
- For audio
- 0Left channel
- 1Right channel
- 2NULL
- Non plane mode
- For video
- 0RGB RGB RGB
- 1NULL
- `int width`For video, frame width
- `int height`For video, frame height
- `int nb_samples`For audio
- `int64_t pts`這個frame的pts
- `int64_t pkt_dts`Packet的dts
- `int sample_rate`For audiosampling rate
- `uint64_t channel_layout`For audio
- `int channels`For audio
- `int format`For video it's AVPixelFormat, for audio it's AVSampleFormat
##### 操作function
- `AVFrame* av_frame_alloc()`
- `void av_frame_free(AVFrame*)`
- `AVFrame* av_frame_clone(const AVFrame*)`
- `int av_frame_ref(AVFrame* dst, const AVFrame* src)`
- `void av_frame_unref(AVFrame*)`
- `int avcodec_send_packet(AVCodecContext*, const AVPacket*)`
- `int avcodec_receive_frame(AVCodecContext*, AVFrame*)`
## Frame scaling
##### 操作function
- `sws_getContext`
- `sws_getCachedContext`
```
SwsContext* sws_getCachedContext(
SwsContext*,
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags,
SwsFilter srcFilter,
SwsFilter dstFilter,
const double* param)
```
- `sws_scale`
```
int sws_scale(
SwsContext*,
const uint8_t* const srcSlice[], // 來源buffer的pointer, 如果是plane mode要丟srcSlice[3]
const int srcStride[], // linesize
int srcSliceY,
int srcSliceH, // image height
uint8_t* const dst[], // 目標buffer的pointer, 如果是plane mode要丟dst[3]
const int dstStride[]) // linesize
```
- `void sws_freeContext(SwsContext*)`
## Audio resampling
- `SwrContext* swr_alloc()`
- `swr_alloc_set_opts`
```cpp
SwrContext* swr_alloc_set_opts(
SwrContext*,
int64_t out_ch_layout, // Output
AVSampleFormat outSampleFormat, // Output
int outSampleRate, // Output
int64_t in_ch_layout, // Input
AVSampleFormat inSampleFormat, // Input
int inSampleRate, // Input
int log_offset, // 一般丟0
void* log_ctx) // 一般丟0
```
- `int swr_init(SwrContext*)`
- `void swr_free(SwrContext**)`
- `swr_convert`
```cpp
int swt_convert(
SwrContext*,
uint8_t** outData,
int outCount, // Sample count for one channel
uint8_t** inData,
int inCount) // Sample count for one channel
```
## Audio 播放
### 會用到的class
#### QAudioFormat
- `setSampleRate`
- `setSampleSize`
- `setChannelCount`
- `setCodec("audio/pcm")`
- `setByteOrder(QAudioFormat::LittleEndian)`
- `setSampleType(QAudioFormat::UnSignedInt)`
#### QAudioOutput
- Constuctor參數就是一個`QAudioFormat`
- `QIODevice* start()`
- `suspend()`
- `resume()`
- `bufferSize()`
- `byteFree()`
- `periodSize()`
#### QIODevice
- `qint64 write(const char* data, qint64 len)`
#### QBuffer
```cpp
QBuffer buffer;
if (buffer.open(QIODevice::ReadWrite)) {
qInfo() << "Buffer Opened!";
QByteArray data("Hello World");
for (int i = 0; i < 5; i++) {
buffer.write(data);
buffer.write("\r\n");
}
//File and device access you may need to flush the data to the device
//buffer.flush()
//Move to the first position
buffer.seek(0);
qInfo() << buffer.readAll(); // retuen a QVariant
qInfo() << "closing the buffer";
//ALWAYS close your device!
buffer.close();
}
```
### Example
```cpp
QAudioFormat audioFormat;
audioFormat.setSampleRate(44100);
audioFormat.setSampleSize(16);
audioFormat.setChannelCount(2);
audioFormat.setCodec("audio/pcm");
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
audioFormat.setSampleType(QAudioFormat::UnSignedInt);
QAudioOutput* audioOutput = new QAudioOutput(audioFormat);
QIODevice* ioDevice = audioOutput->start();
int size = audioOutput->periodSize();
char* buffer = new char[size];
FILE *pFile = fopen("E:\\download\\out.pcm", "rb");
while (!feof(pFile)) {
if (audioOutput->bytesFree() < size) {
QThread::msleep(1);
}
qint64 len = fread(buffer, 1, size, pFile);
if (len <= 0) {
break;
}
ioDevice->write(buffer, len);
}
fclose(pFile);
delete buffer;
buffer = NULL;
```
### 使用FFmpeg decode audio
- [音视频开发之旅60) -调试分析FFmpeg (解封装部分的)常用结构体](https://zhuanlan.zhihu.com/p/441055685)
## 參考資料
- [mpv.io](https://mpv.io/)
- [《FFmpeg基础知识》](https://opensourcelibs.com/lib/zhangfangtaozft-ffmpeg)
- [ffmpeg - 专题 - 简书](https://www.jianshu.com/c/21a6fafc8ee3)

View File

@@ -0,0 +1,206 @@
## 執行
### Linux
```bash
export FLASK_APP=RobotRunAutoServer.py
export FLASK_DEBUG=1
flask run --reload
```
### Windows
```
set FLASK_APP=RobotRunAutoServer.py
set FLASK_DEBUG=1
flask run --reload
```
## 路由
```python
@app.route('/')
def index():
return 'Index Page'
@app.route('/query/<SOMETHING>', methods=['GET', 'POST'])
def query(SOMETHING):
return 'Hello, {}!'.format(SOMETHING)
```
## 靜態檔案
```
url_for('static', filename='style.css')
```
## 模板
```python
from flask import render_template
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
```
依照 Flask 的慣例,它會在 templates 資料夾內去找模板檔,而 templates 資料夾的位置會根據這支 web app 是模組或是套件而有所不同。
如果是模組:
```
/application.py
/templates
/hello.html
```
如果是套件:
```
/application
/__init__.py
/templates
/hello.html
```
## 存取 request 資料
客戶端傳來的資料都放在 `request` 這個全域變數內web app 利用 `request` 提供的資訊與客戶端互動。
客戶端的 HTTP 方法存放在 `request``method` 屬性內,而 form 就存放在 `request``form` 屬性內。示例:
```Python
from flask import request
from flask import render_template
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'], request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username / password'
# the code below is executed if the request method was GET or the credentials were invalid
return render_template('login.html', error=error)
```
如果 `form` 屬性不存在,伺服器端會引發 `KeyError` 錯誤,客戶端則會收到 HTTP 400 錯誤。
如果是要取用 `URL` 參數,則使用 `args` 屬性內的 `get()` 方法:
```Python
searchword = request.args.get('key', '')
```
如果是要取用客戶端上傳的檔案,先確定在前端 HTML 表單的設定正確的屬性 `enctype="multipart/form-data"`,瀏覽器才會正確的把檔案上傳。調用 `request``files` 屬性就可以調用到檔案:
```Python
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
```
`files` 本身是個 dictionary所以必須呼叫它裡面的 key 才會接到真正的檔案,這個 `file` 物件的行為就像標準的 Python `file` 物件,但它有個 `save()` 方法讓我們把檔案存到自己想要的路徑。
如果想要沿用客戶端上傳的檔名,則調用 `filename` 屬性,但由於檔名的不可預知,可能會有安全風險,最好用 Werkzeug 的 `secure_filename()` 方法過濾掉:
```Python
from flask import request
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename))
```
如果是要讀取 cookie 就調用 `request` 物件的 `cookies` 屬性,如果是要設置 cookie 就用 `response` 物件的 `set_cookie()` 方法。
`request``cookies` 也是一個 dictionary裡面放了所有可以調用的 cookie。
但是如果想使用 session 的話Flask 有提供更完善的 session 機制可以利用,不要手工用 cookie 來管理 session。
讀取 cookie 的範例:
```Python
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a KeyError if the cookie is missing.
```
存入 cookie 的範例:
```Python
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
```
## 重導頁面與錯誤頁面
要重導使用 `redirect()` 方法,要中斷並報錯用 `abort()` 方法:
```Python
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
```
如果不想使用 Flask 預設的陽春錯誤頁,則利用 `errorhandler()` 修飾子來做客製:
```Python
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
```
注意到 `return` 那行最後面的 `404`,雖然上面的修飾子已經是 404`return` 後面還是要加 `404` Flask 才認得這是 404 錯誤頁。
## Session
Session 用來紀錄、辨識用戶的活動,實現方式是加密過的 cookie。
得先設置密鑰才能使用 session
```Python
from flask import Flask, session, redirect, url_for, escape, request
from werkzeug.utils import secure_filename
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
if 'username' in session:
return f"Logged in as {escape(session['username'])}"
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
```
前面模板的章節有說過,模板引擎會幫我們把表單的 HTML 過濾掉,而在這裡沒有使用模板引擎,所以手動調用了 `escape()` 方法來濾掉 HTML 碼。
最後附註一點,瀏覽器可能會限制單一 cookie 容量,如果發現某個值應該要有卻調用不出來的話,想想看是不是超過 cookie 的容量上限了。
## Log 紀錄
Flask app 物件有使用 Python 內建的 `logger` 模組,可以簡單調用:
```Python
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
```

View File

@@ -0,0 +1,108 @@
## Install
```
sudo apt update && sudo apt install apache2
```
## 測試Apache
```
sudo service apache2 status
```
## 設置虛擬主機Virtual Hosts
假設要建立2個網站*test1.ui-code.com*與*test2.ui-code.com*
### 建立目錄並設置權限Permissions
```
sudo mkdir -p /var/www/test1.ui-code.com/public_html
sudo mkdir -p /var/www/test2.ui-code.com/public_html
sudo chmod -R 755 /var/www
```
### 建立測試頁面
#### 建立test1.ui-code.com的測試頁面
```
sudo nano /var/www/test1.ui-code.com/public_html/index.html
```
填入以下內容:
```html
<html>
<head>
<title>Welcome to test1.ui-code.com</title>
</head>
<body>
<h1>Welcome to test1.ui-code.com</h2>
</body>
</html>
```
#### 建立test2.ui-code.com的測試頁面
```
sudo nano /var/www/test2.ui-code.com/public_html/index.html
```
填入以下內容:
```html
<html>
<head>
<title>Welcome to test2.ui-code.com</title>
</head>
<body>
<h1>Welcome to test2.ui-code.com</h2>
</body>
</html>
```
### 建立虛擬主機文件Virtual Host Files
虛擬主機文件位於 /etc/apache2/sites-available/ 中,其用於告訴 Apache 網頁伺服器如何響應Respond 各種網域請求Request
讓我們為test1.ui-code.com 網域創建一個新的虛擬主機文件。
```
sudo nano /etc/apache2/sites-available/test1.ui-code.com.conf
```
將以下內容貼上:
```
<VirtualHost *:80>
ServerAdmin webmaster@test1.ui-code.com
ServerName test1.ui-code.com
ServerAlias www.test1.ui-code.com
DocumentRoot /var/www/test1.ui-code.com/public_html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
再來為test2.ui-code.com 網域創建一個新的虛擬主機文件。
```
sudo nano /etc/apache2/sites-available/test2.ui-code.com.conf
```
將以下內容貼上:
```
<VirtualHost *:80>
ServerAdmin webmaster@test2.ui-code.com
ServerName test2.ui-code.com
ServerAlias www.test2.ui-code.com
DocumentRoot /var/www/test2.ui-code.com/public_html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
```
### 啟用新的虛擬主機文件Virtual Host Files
現在我們有兩個虛擬主機文件,我們需要使用 a2ensite 工具來啟用它們。
```
sudo a2ensite test1.ui-code.com
sudo a2ensite test2.ui-code.com
```
測試配置語法是否有錯誤。
```
apachectl configtest
```
如果「Syntax OK」重啟 Apache。
```
sudo systemctl reload apache2
```
## 參考
- [[教學][Ubuntu 架站] 在 Ubuntu 20.04 安裝 Apache 網頁伺服器,並架設多個網站(多網域) | 優程式](https://ui-code.com/archives/271)

View File

@@ -0,0 +1,411 @@
## Reverse Proxy(Layer4)
[[02. PARA/03. Resources資源/HTTP Server/Nginx#Reverse Proxy]]所用的方法雖然可以反向代理多個網站但是對於像是Trojan這種TLS不行被中斷的服務來說會導致handshake失敗所以需要用Nginx的stream來做Layer 4的轉發。
### docker-compose.yaml
需要先把`nginx.conf``mime.types`給copy到data目錄下。
依序執行下面2個命令
```shell
sudo docker run --rm -it nginx cat /etc/nginx/nginx.conf > nginx.conf
sudo docker run --rm -it nginx cat /etc/nginx/mime.types > mime.types
```
然後:
```shell
mkdir data ;\
mv nginx.conf mime.types data
```
建立`docker-compose.yaml`
```yaml
version: '3'
services:
nginx_reverseproxy_l4:
container_name: nginx
restart: always
image: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./data:/etc/nginx
```
修改`data/nginx.conf`
```nginx
events {
worker_connections 1024; <-- event 不用動
}
stream {
map $ssl_preread_server_name $backend_name {
tjn.awin.one trojan;
storj.awin.one swag;
}
# trojan
upstream trojan {
server 192.168.1.31:443;
}
# swag
upstream swag {
server 192.168.1.20:44320;
}
# 监听 443 并开启 ssl_preread
server {
listen 80 reuseport;
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
}
}
http {
... <-- http 不用動
}
```
2022/05/17更新
```nginx
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
stream {
map $ssl_preread_server_name $backend_name {
tjn.awin.one trojan;
storj.awin.one swag;
blog.awin.one swag;
gitea.awin.one swag;
}
# trojan
upstream trojan {
server 192.168.1.31:443;
}
# swag
upstream swag {
server 192.168.1.20:44320;
}
# 监听 443 并开启 ssl_preread
server {
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
}
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
## 新增這一段沒有這一段會造成http無法連線所以拿憑證會失敗
server {
listen 80;
server_name tjn.awin.one;
location / {
proxy_pass http://192.168.1.31:80;
# 把 IP、Protocol 等 header 都一起送給反向代理的 server
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}
}
}
```
重點來源:
1. [Trojan 共用 443 端口方案 - 程小白](https://www.chengxiaobai.cn/record/trojan-shared-443-port-scheme.html)
2. [NaiveProxy + Trojan + 多HTTPS站点共存复用443端口 | 心底的河流](https://lhy.life/20200815-naiveproxy+trojan/)
3. `$ssl_preread_server_name`的官方說明:[Module ngx_stream_ssl_preread_module](http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html)
## Reverse Proxy
> **重要**確定docker與docker-compose已經安裝好。
> 參考[[RaspberryPi#Docker]]與[[RaspberryPi#docker-compose]]
### Use SWAG docker
[swag](https://github.com/linuxserver/docker-swag)(之前叫做letsencrypt)是linuxserver.io包裝的Nginx webserver和reverse proxy的container。
#### Setup HTTPS
1. 建立folder
```bash
mkdir -p ~/dockers/linuxserverswag ; cd ~/dockers/linuxserverswag
```
2. 建立`docker-compose.yaml`
```
vim docker-compose.yaml
```
填入內容如下:
```yaml
version: "2.1"
services:
swag:
image: ghcr.io/linuxserver/swag
container_name: swag
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Taipei
- URL=awin.one
- SUBDOMAINS=wildcard
- VALIDATION=dns
- CERTPROVIDER= #optional
- DNSPLUGIN=cloudflare #optional
- PROPAGATION= #optional
- DUCKDNSTOKEN= #optional
- EMAIL=awinhuang@gmail.com
- ONLY_SUBDOMAINS=false #optional
- EXTRA_DOMAINS= #optional
- STAGING=false #optional
- MAXMINDDB_LICENSE_KEY= #optional
volumes:
- ./config:/config
ports:
- 44320:443
- 8020:80 #optional
restart: unless-stopped
```
3. 先跑一次:
```bash
sudo docker-compose up
```
會發現有錯誤,這是正常的,錯誤訊息像這樣:
```
swag | Unsafe permissions on credentials configuration file: /config/dns-conf/cloudflare.ini
swag | Cleaning up challenges
swag | Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address and Global key?)
swag | ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.
```
按`ctrl + c`退出。這時候`config`目錄也會有swag所mapping出來的相關檔案。修改`config/dns-conf/cloudflare.ini`
```bash
vim config/dns-conf/cloudflare.ini
```
把`config/dns-conf/cloudflare.ini`改為:
```
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
# Replace with your values
With global api key:
dns_cloudflare_email = awinhuang@gmail.com
dns_cloudflare_api_key = <YOUR_API_KEY_FROM_CLOUDFLARE>
# With token (comment out both lines above and uncomment below):
#dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
```
4. 再跑一次:
```bash
sudo docker-compose up
```
這一次就可以順利取得認證了,訊息像這樣:
```
swag | IMPORTANT NOTES:
swag | - Congratulations! Your certificate and chain have been saved at:
swag | /etc/letsencrypt/live/awin.one/fullchain.pem
swag | Your key file has been saved at:
swag | /etc/letsencrypt/live/awin.one/privkey.pem
swag | Your certificate will expire on 2021-04-26. To obtain a new or
swag | tweaked version of this certificate in the future, simply run
swag | certbot again. To non-interactively renew *all* of your
swag | certificates, run "certbot renew"
swag | - If you like Certbot, please consider supporting our work by:
swag |
swag | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
swag | Donating to EFF: https://eff.org/donate-le
swag |
swag | New certificate generated; starting nginx
swag | Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,
swag | and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key.
swag | [cont-init.d] 50-config: exited 0.
swag | [cont-init.d] 60-renew: executing...
swag | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
swag | [cont-init.d] 60-renew: exited 0.
swag | [cont-init.d] 70-templates: executing...
swag | [cont-init.d] 70-templates: exited 0.
swag | [cont-init.d] 99-custom-files: executing...
swag | [custom-init] no custom files found exiting...
swag | [cont-init.d] 99-custom-files: exited 0.
swag | [cont-init.d] done.
swag | [services.d] starting services
swag | [services.d] done.
swag | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
swag | Server ready
```
最後一行的`swag | Server ready`表示server已經跑起來了。先按下`ctrl + c`退出再來設定reverse proxy。
5. 修正`config/dns-conf/cloudflare.ini`的安全性問題
```
cd ~/dockers/linuxserverswag ; chmod 600 config/dns-conf/cloudflare.ini
```
#### Setup reverse proxy
1. 建立folder
```
cd ~/dockers/linuxserverswag ; mkdir -p config/nginx/sites-available config/nginx/sites-enabled
```
2. 建立以下檔案:
- `config/nginx/sites-available/common.conf`,內容:
```
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
```
- `config/nginx/sites-available/common_location.conf`,內容:
```
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
```
- `config/nginx/sites-available/storj.conf`,內容:
```
upstream storj {
server 192.168.1.11:14002;
}
server {
listen 443 ssl;
server_name storj.awin.one;
include /config/nginx/sites-available/common.conf;
include /config/nginx/ssl.conf;
location / {
proxy_pass http://192.168.1.11:14002/;
include /config/nginx/sites-available/common_location.conf;
}
}
```
3. 在`config/nginx/sites-enabled`裡面建立要enable的config
```
cd config/nginx/sites-enabled ; ln -s ../sites-available/storj.conf . ; cd -
```
4. 修改`config/nginx/nginx.conf`
```
vim config/nginx/nginx.conf
```
找到`include /config/nginx/site-confs/*;`這一行把它comment掉在下面新增一行
```
include /config/nginx/sites-enabled/*.conf;
```
5. 啟動swag
```
cd ~/dockers/linuxserverswag ; sudo docker-compose up -d
```
#### Restart
```bash
cd ~/dockers/linuxserverswag ; sudo docker-compose restart
```
#### Update certification
1. 進到dockr的swag bash裡面`sudo docker exec -it swag /bin/bash`
2. 輸入 `certbot renew`
3. ![[Pasted image 20210422205534.png]]
#### Trouble shooting
- 如果遇到類似下面的錯誤:
```
ERROR: for swag Cannot start service swag: driver failed programming external connectivity on endpoint swag (7c527d046631e0957de0b831ca25bed296de76e2eb96378964cb0110d7fb017d): Bind for 0.0.0.0:443 failed: port is already allocated.
```
表示有其他程式佔住了80 port可能是其他docker container或是service必須先關閉它們。[^1]
#### 參考來源
1. [linuxserver/docker-swag: Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.](https://github.com/linuxserver/docker-swag)
2. [How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt](https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/)
3. [SWAG setup - LinuxServer.io](https://docs.linuxserver.io/general/swag#understanding-the-proxy-conf-structure)
-----
- 參考
- [NGINX Docs | NGINX Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
- [Nginx 設定反向代理 Reverse Proxy](https://www.opencli.com/linux/nginx-config-reverse-proxy)
- [用 Nginx 伺服器建立反向代理](https://noob.tw/nginx-reverse-proxy/)
- [俄羅斯不愧是戰鬥民族nginx - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10188498)
[^1]: [啟動docker時出現「Cannot start service :driver failed programming external connectivity on endpoint xxx, Bind for 0.0.0.0:80 failed: port is already allocated 」](https://mitsuiwei.com/docker-cannot-start-service/)
# 新增網站
## 新網站自帶SSL
進到Rasperberry Pi192.168.1.20)之後,切換到`~/dockers/nginx_reverseproxy_l4`,並編輯`data/nginx.conf`在裡面加入新網站的網域與要轉址的ip。
例如要加入一個trojan VPNtrojan VPN本身就有SSL加密所以不用再經過swag`data/nginx.conf`的改變如下:
![[Pasted image 20220506145247.png]]
## 新http網站
如果新的網站只是一般的http網站那便把它掛到swag後面這樣就可以經由https來訪問假如要加入一個blog網站但因為我們有2層的reverse proxy第一層是layer 4第2層是swag所以如果是自帶https的要掛到layer 4之後沒有https要先由layer 4轉到swag再轉到實際的server上。
步驟如下:
1. 進到Rasperberry Pi192.168.1.20
2. 設定nginx_reverseproxy_l4這邊我們需要把「沒有https」的網站由nginx_reverseproxy_l4導引到linuxserverswag
```
cd ~/dockers/nginx_reverseproxy_l4
vim data/nginx.conf
```
加入下圖紅框的設定:
![[Pasted image 20220516152230.png]]
3. 切換到`~/dockers/linuxserverswag/config/nginx/sites-available`
4. 新增一個confing檔例如叫做`blog.conf`,內容如下:
```
upstream blog {
server 192.168.1.30:80; ## 網址
}
server {
listen 443 ssl;
server_name blog.awin.one; ## 網域
include /config/nginx/sites-available/common.conf;
include /config/nginx/ssl.conf;
location / {
proxy_pass http://192.168.1.30:80/; ## 網址
include /config/nginx/sites-available/common_location.conf;
}
}
```
改變3個有註解的地方就可以了。
5. 剛剛的config檔新增到`sites-enabled`裡面
1. `cd ~/dockers/linuxserverswag/config/nginx/sites-enabled`
2. `ln -s ../sites-available/blog.conf .`
6. 重新啟動swag`cd ~/dockers/linuxserverswag ; sudo docker-compose restart`

View File

@@ -0,0 +1,2 @@
- Source code: https://github.com/ipfs/ipfs
- Document: https://docs.ipfs.io/

View File

@@ -0,0 +1 @@
- [The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/?fbclid=IwAR0iZMkTCkhHzGbL41qqAmdZADiGtZe7Cw556NMuXpwv4cfD0yEer0lcCiU)

View File

@@ -0,0 +1,14 @@
`timedatectl`可以看現在的時間、時區等等。也可以改變時區。
![[Pasted image 20220516125616.png]]
`timedatectl list-timezones`列出所有時區我們已經知道我們的時區是Taipei所以可以用grep直接把它抓出來
```bash
timedatectl list-timezones | grep -i taipei
```
結果如下:
![[Pasted image 20220516125742.png]]
設定時區:
```
timedatectl set-timezone Asia/Taipei
```

View File

@@ -0,0 +1,13 @@
## Hyper-V
### Set screen resolution
1. `sudo vim /etc/default/grub`
2. Find `GRUB_CMDLINE_LINUX_DEFAULT`
3. Add `video=hyperv_fb:1600x9000`, e.g. `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=hyperv_fb:1600x900"`
4. `sudo update-grub `
5. `sudo reboot`
## VirtualBox
### Settting
#### 操作分享資料夾
需要把目前的使用者加到`vboxsf` group才能夠操作分享資料夾。
`sudo adduser $USER vboxsf`

View File

@@ -0,0 +1,57 @@
`scp` 指令的語法跟一般的 `cp` 類似,只不過 `scp` 可以在不同的 Linux 主機之間複製檔案,其語法為:
```
scp [帳號@來源主機]:來源檔案 [帳號@目的主機]:目的檔案
```
# 保留檔案時間與權限
若要讓檔案在複製之後,還可保留原本的修改時間、存取時間與權限,可以加上 `-p` 參數:
```
scp -p /path/file1 myuser@192.168.0.1:/path/file2
```
# 資料壓縮
若要將資料壓縮之後再傳送,減少網路頻寬的使用量,可以加上 `-C` 參數:
```
scp -C /path/file1 myuser@192.168.0.1:/path/file2
```
# 限制傳輸速度
若要限制網路的使用頻寬,可以用 `-l` 指定可用的網路頻寬上限值(單位為 Kbit/s
```
# 限制傳輸速度為 400 Kbit/s
scp -l 400 /path/file1 myuser@192.168.0.1:/path/file2
```
這樣就會限制 `scp` 只能使用 `400` Kbit/s也就是 `400 / 8 = 50` KB/s。
# 自訂連接埠
一般 SSH 伺服器的連接埠號為 22如果遇到使用非標準埠號的伺服器可以用 `-P` 來指定埠號。若遠端的 SSH 伺服器使用 `2222` 這個連接埠,我們就可以這樣複製檔案:
```
# 使用 2222 連接埠
scp -P 2222 /path/file1 myuser@192.168.0.1:/path/file2
```
# IPv4 與 IPv6
`-4``-6` 兩個參數分別可以讓 `scp` 使用 IPv4 與 IPv6 來傳輸資料:
```
# 使用 IPv4
scp -4 /path/file1 myuser@192.168.0.1:/path/file2
# 使用 IPv6
scp -6 /path/file1 myuser@192.168.0.1:/path/file2
```
# 更快的方法使用SSH+TAR
```
ssh 使用者@主機 "cd 目標目錄 ;tar -zcvf - 目標" | cat > 目標.tar.gz
```
例:
```
ssh 192.168.0.22 "cd /var ;tar -zcvf - log" | cat > 22_log.tar.gz
```
# 參考
- [SSH + TAR 取代 SCP @ Vexed's Blog :: 隨意窩 Xuite日誌](https://blog.xuite.net/vexed/tech/586811949)
- [Linux中互传文件ssh+tar 与Scp 比较 - 简书](https://www.jianshu.com/p/856a2dc883e0)
- [轉貼--ssh tar 命令把遠端檔拉回來或推過去 --- 山城風雲的點滴](http://jimsung168.blogspot.com/2014/01/ssh-tar.html)

View File

@@ -0,0 +1,29 @@
https://www.mkdocs.org/
## Commands
`mkdocs new [dir-name]` - Create a new project.
`mkdocs serve` - Start the live-reloading docs server. Goto `http://127.0.0.1:8000` to view WEB pages.
`mkdocs build` - Build the documentation site.
`mkdocs build --clean` - Clean the output folder.
`mkdocs -h` - Print help message and exit.
## Theme
#### Change theme to [mkdocs-material](https://squidfunk.github.io/mkdocs-material/getting-started/)
1. Install by pip
```
pip install mkdocs-material
```
2. Modify `mkdocs.yml`
```
site_name: RobotRunDoc
nav:
- Home: index.md
- 'User Guide':
- 'Installation': 'Installation/installation.md'
- 'APIs': 'APIs/Kong.md'
- About: about.md
theme:
name: material <-- HERE
```

View File

@@ -0,0 +1,134 @@
## Install MySQL
- 下載MySQL https://downloads.mysql.com/archives/installer/
- 安裝
- 安裝之後執行MSQL Installer - Community
- ![[Pasted image 20210423152623.png]]
- 選擇MySQL Server的Reconfigure
- ![[Pasted image 20210423152747.png]]
- 依序設定port、帳號密碼。
## Install MariaDB
1. `sudo apt install mariadb-server`
2. Configuring MariaDB: `sudo mysql_secure_installation`
這步驟會問幾個問題,大概是:
1. 輸入目前root的密碼
2. 是否要切換為unix_socket -> No
3. 要變更root的密碼嗎-> No
3. Testing MariaDB: `sudo systemctl status mariadb`
4. Show version: `sudo mysqladmin version`
## Syntax
### Basic
login as root: `mysql -u root -p`
### User management
#### Create user
```sql
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'test123test!';
```
#### Remove user
```sql
DROP USER 'testuser'@'localhost';
```
#### Grant user's permission
Below commands grant user's permission to *CREATE* and *DROP*.
```sql
GRANT CREATE ON *.* TO 'testuser'@'localhost';
GRANT DROP ON tutorial_database.* TO 'testuser'@'localhost';
FLUSH PRIVILEGES;
```
#### Remove user's permission
```sql
REVOKE <permission> ON database.table FROM 'user'@'localhost';
```
上面命令中的`<permission>`必須替換成下面的其中一個:
- ALL - Allow complete access to a specific database. If a database is not specified, then allow complete access to the entirety of MySQL.
- CREATE - Allow a user to create databases and tables.
- DELETE - Allow a user to delete rows from a table.
- DROP - Allow a user to drop databases and tables.
- EXECUTE - Allow a user to execute stored routines.
- GRANT OPTION - Allow a user to grant or remove another user's privileges.
- INSERT - Allow a user to insert rows from a table.
- SELECT - Allow a user to select data from a database.
- SHOW DATABASES- Allow a user to view a list of all databases.
- UPDATE - Allow a user to update rows in a table.
例如:`REVOKE CREATE ON *.* FROM 'testuser'@'localhost';`
最後,不要忘記`FLUSH PRIVILEGES;`
#### Show user's permission
```sql
SHOW GRANTS FOR 'testuser'@'localhost';
```
#### Change user's password
```sql
ALTER USER 'user-name'@'localhost' IDENTIFIED BY 'NEW_USER_PASSWORD';
```
#### View all users
```sql
SELECT User,Host FROM mysql.user;
```
### Database
#### Show all databases
```sql
SHOW DATABASES;
```
### Create table
The rule:
```sql
CREATE TABLE table_name (column_name column_type);
```
Example:
```sql
CREATE TABLE tutorials_tbl(
tutorial_id INT NOT NULL AUTO_INCREMENT,
tutorial_title VARCHAR(100) NOT NULL,
tutorial_author VARCHAR(40) NOT NULL,
submission_date DATE,
PRIMARY KEY ( tutorial_id )
);
```
```sql
use autostation;
CREATE TABLE station_state(
id CHAR(255) NOT NULL,
ip CHAR(255) NOT NULL,
dhcp_ip CHAR(255) NOT NULL,
name CHAR(255) NOT NULL,
setupfilemd5 CHAR(255) NOT NULL,
setupfileversion CHAR(255) NOT NULL,
status CHAR(255) NOT NULL,
update_time DATETIME NOT NULL,
PRIMARY KEY ( id )
);
```
### Insert
The rule:
```
INSERT INTO <table_name> ( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN );
```
Example:
```
INSERT INTO client_state ( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN );
```
## 參考
- [Create a MySQL User on Linux via Command Line | Liquid Web](https://www.liquidweb.com/kb/create-a-mysql-user-on-linux-via-command-line/)
- [Grant Permissions to a MySQL User on Linux via Command Line | Liquid Web](https://www.liquidweb.com/kb/grant-permissions-to-a-mysql-user-on-linux-via-command-line/)
- [Remove Permissions for a MySQL User on Linux via Command Line - Liquid Web](https://www.liquidweb.com/kb/remove-permissions-for-a-mysql-user-on-linux-via-command-line/)
- [Remove a MySQL User on Linux via Command Line - Liquid Web](https://www.liquidweb.com/kb/remove-a-mysql-user-on-linux-via-command-line/)

View File

@@ -0,0 +1,2 @@
DNS服務公司用途如下
- [量身打造、100% 客製化抗廣告的 NextDNS - jkgtw's blog](https://www.jkg.tw/p3382/)

View File

@@ -0,0 +1,292 @@
## Reverse Proxy(Layer4)
[[02. PARA/03. Resources資源/Nginx#Reverse Proxy]]所用的方法雖然可以反向代理多個網站但是對於像是Trojan這種TLS不行被中斷的服務來說會導致handshake失敗所以需要用Nginx的stream來做Layer 4的轉發。
### docker-compose.yaml
需要先把`nginx.conf``mime.types`給copy到data目錄下。
依序執行下面2個命令
```shell
sudo docker run --rm -it nginx cat /etc/nginx/nginx.conf > nginx.conf
sudo docker run --rm -it nginx cat /etc/nginx/mime.types > mime.types
```
然後:
```shell
mkdir data ;\
mv nginx.conf mime.types data
```
建立`docker-compose.yaml`
```yaml
version: '3'
services:
nginx_reverseproxy_l4:
container_name: nginx
restart: always
image: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./data:/etc/nginx
```
修改`data/nginx.conf`
```nginx
events {
worker_connections 1024; <-- event 不用動
}
stream {
map $ssl_preread_server_name $backend_name {
tjn.awin.one trojan;
storj.awin.one swag;
}
# trojan
upstream trojan {
server 192.168.1.31:443;
}
# swag
upstream swag {
server 192.168.1.20:44320;
}
# 监听 443 并开启 ssl_preread
server {
listen 80 reuseport;
listen 443 reuseport;
listen [::]:443 reuseport;
proxy_pass $backend_name;
ssl_preread on;
}
}
http {
... <-- http 不用動
}
```
重點來源:
1. [Trojan 共用 443 端口方案 - 程小白](https://www.chengxiaobai.cn/record/trojan-shared-443-port-scheme.html)
2. [NaiveProxy + Trojan + 多HTTPS站点共存复用443端口 | 心底的河流](https://lhy.life/20200815-naiveproxy+trojan/)
3. `$ssl_preread_server_name`的官方說明:[Module ngx_stream_ssl_preread_module](http://nginx.org/en/docs/stream/ngx_stream_ssl_preread_module.html)
## Reverse Proxy
> **重要**確定docker與docker-compose已經安裝好。
> 參考[[RaspberryPi#Docker]]與[[RaspberryPi#docker-compose]]
### Use SWAG docker
[swag](https://github.com/linuxserver/docker-swag)(之前叫做letsencrypt)是linuxserver.io包裝的Nginx webserver和reverse proxy的container。
#### Setup HTTPS
1. 建立folder
```bash
mkdir -p ~/dockers/linuxserverswag ; cd ~/dockers/linuxserverswag
```
2. 建立`docker-compose.yaml`
```
vim docker-compose.yaml
```
填入內容如下:
```yaml
version: "2.1"
services:
swag:
image: ghcr.io/linuxserver/swag
container_name: swag
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Taipei
- URL=awin.one
- SUBDOMAINS=wildcard
- VALIDATION=dns
- CERTPROVIDER= #optional
- DNSPLUGIN=cloudflare #optional
- PROPAGATION= #optional
- DUCKDNSTOKEN= #optional
- EMAIL=awinhuang@gmail.com
- ONLY_SUBDOMAINS=false #optional
- EXTRA_DOMAINS= #optional
- STAGING=false #optional
- MAXMINDDB_LICENSE_KEY= #optional
volumes:
- ./config:/config
ports:
- 44320:443
- 8020:80 #optional
restart: unless-stopped
```
3. 先跑一次:
```bash
sudo docker-compose up
```
會發現有錯誤,這是正常的,錯誤訊息像這樣:
```
swag | Unsafe permissions on credentials configuration file: /config/dns-conf/cloudflare.ini
swag | Cleaning up challenges
swag | Error determining zone_id: 9103 Unknown X-Auth-Key or X-Auth-Email. Please confirm that you have supplied valid Cloudflare API credentials. (Did you enter the correct email address and Global key?)
swag | ERROR: Cert does not exist! Please see the validation error above. Make sure you entered correct credentials into the /config/dns-conf/cloudflare.ini file.
```
按`ctrl + c`退出。這時候`config`目錄也會有swag所mapping出來的相關檔案。修改`config/dns-conf/cloudflare.ini`
```bash
vim config/dns-conf/cloudflare.ini
```
把`config/dns-conf/cloudflare.ini`改為:
```
# Instructions: https://github.com/certbot/certbot/blob/master/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py#L20
# Replace with your values
With global api key:
dns_cloudflare_email = awinhuang@gmail.com
dns_cloudflare_api_key = <YOUR_API_KEY_FROM_CLOUDFLARE>
# With token (comment out both lines above and uncomment below):
#dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567
```
4. 再跑一次:
```bash
sudo docker-compose up
```
這一次就可以順利取得認證了,訊息像這樣:
```
swag | IMPORTANT NOTES:
swag | - Congratulations! Your certificate and chain have been saved at:
swag | /etc/letsencrypt/live/awin.one/fullchain.pem
swag | Your key file has been saved at:
swag | /etc/letsencrypt/live/awin.one/privkey.pem
swag | Your certificate will expire on 2021-04-26. To obtain a new or
swag | tweaked version of this certificate in the future, simply run
swag | certbot again. To non-interactively renew *all* of your
swag | certificates, run "certbot renew"
swag | - If you like Certbot, please consider supporting our work by:
swag |
swag | Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
swag | Donating to EFF: https://eff.org/donate-le
swag |
swag | New certificate generated; starting nginx
swag | Starting 2019/12/30, GeoIP2 databases require personal license key to download. Please retrieve a free license key from MaxMind,
swag | and add a new env variable "MAXMINDDB_LICENSE_KEY", set to your license key.
swag | [cont-init.d] 50-config: exited 0.
swag | [cont-init.d] 60-renew: executing...
swag | The cert does not expire within the next day. Letting the cron script handle the renewal attempts overnight (2:08am).
swag | [cont-init.d] 60-renew: exited 0.
swag | [cont-init.d] 70-templates: executing...
swag | [cont-init.d] 70-templates: exited 0.
swag | [cont-init.d] 99-custom-files: executing...
swag | [custom-init] no custom files found exiting...
swag | [cont-init.d] 99-custom-files: exited 0.
swag | [cont-init.d] done.
swag | [services.d] starting services
swag | [services.d] done.
swag | nginx: [alert] detected a LuaJIT version which is not OpenResty's; many optimizations will be disabled and performance will be compromised (see https://github.com/openresty/luajit2 for OpenResty's LuaJIT or, even better, consider using the OpenResty releases from https://openresty.org/en/download.html)
swag | Server ready
```
最後一行的`swag | Server ready`表示server已經跑起來了。先按下`ctrl + c`退出再來設定reverse proxy。
5. 修正`config/dns-conf/cloudflare.ini`的安全性問題
```
cd ~/dockers/linuxserverswag ; chmod 600 config/dns-conf/cloudflare.ini
```
#### Setup reverse proxy
1. 建立folder
```
cd ~/dockers/linuxserverswag ; mkdir -p config/nginx/sites-available config/nginx/sites-enabled
```
2. 建立以下檔案:
- `config/nginx/sites-available/common.conf`,內容:
```
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
```
- `config/nginx/sites-available/common_location.conf`,內容:
```
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
```
- `config/nginx/sites-available/storj.conf`,內容:
```
upstream storj {
server 192.168.1.11:14002;
}
server {
listen 443 ssl;
server_name storj.awin.one;
include /config/nginx/sites-available/common.conf;
include /config/nginx/ssl.conf;
location / {
proxy_pass http://192.168.1.11:14002/;
include /config/nginx/sites-available/common_location.conf;
}
}
```
3. 在`config/nginx/sites-enabled`裡面建立要enable的config
```
cd config/nginx/sites-enabled ; ln -s ../sites-available/storj.conf . ; cd -
```
4. 修改`config/nginx/nginx.conf`
```
vim config/nginx/nginx.conf
```
找到`include /config/nginx/site-confs/*;`這一行把它comment掉在下面新增一行
```
include /config/nginx/sites-enabled/*.conf;
```
5. 啟動swag
```
cd ~/dockers/linuxserverswag ; sudo docker-compose up -d
```
#### Restart
```bash
cd ~/dockers/linuxserverswag ; sudo docker-compose restart
```
#### Update certification
1. 進到dockr的swag bash裡面`sudo docker exec -it swag /bin/bash`
2. 輸入 `certbot renew`
3. ![[Pasted image 20210422205534.png]]
#### Trouble shooting
- 如果遇到類似下面的錯誤:
```
ERROR: for swag Cannot start service swag: driver failed programming external connectivity on endpoint swag (7c527d046631e0957de0b831ca25bed296de76e2eb96378964cb0110d7fb017d): Bind for 0.0.0.0:443 failed: port is already allocated.
```
表示有其他程式佔住了80 port可能是其他docker container或是service必須先關閉它們。[^1]
#### 參考來源
1. [linuxserver/docker-swag: Nginx webserver and reverse proxy with php support and a built-in Certbot (Let's Encrypt) client. It also contains fail2ban for intrusion prevention.](https://github.com/linuxserver/docker-swag)
2. [How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt](https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/)
3. [SWAG setup - LinuxServer.io](https://docs.linuxserver.io/general/swag#understanding-the-proxy-conf-structure)
-----
- 參考
- [NGINX Docs | NGINX Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)
- [Nginx 設定反向代理 Reverse Proxy](https://www.opencli.com/linux/nginx-config-reverse-proxy)
- [用 Nginx 伺服器建立反向代理](https://noob.tw/nginx-reverse-proxy/)
- [俄羅斯不愧是戰鬥民族nginx - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10188498)
[^1]: [啟動docker時出現「Cannot start service :driver failed programming external connectivity on endpoint xxx, Bind for 0.0.0.0:80 failed: port is already allocated 」](https://mitsuiwei.com/docker-cannot-start-service/)

View File

@@ -0,0 +1,63 @@
## 使用
### 操作
- `Ctrl-Enter`: 可以增加TODO的框框或是用來更改框框的狀態
### 自訂快速鍵
- `Alt+Insert`: 插入樣板
- `Ctrl + Shift + <-`: 摺疊所有標題 ^e897a3
- `Ctrl + Shift + ->`: 展開所有標題 ^5587cd
### 搜尋
`file:` 開頭來指定指**只搜尋檔名**
`content:` 開頭來指定**只指搜尋內容**
`tag:` 開頭來搜尋標籤,例: `tag:#mydata`
`path:` 來搜尋特定路徑下的檔案,路徑必須要用`""`包圍,例:`path:"path/to/foder" search_name`
## 設定
目前所使用的theme是`Obsdn-dark-rmx`
CSS路徑`<vault>/obsidian.css`
### CSS
#### 讓中文字型使用**微軟正黑體**
```
.CodeMirror pre.CodeMirror-line {
font-size: 14px;
font-family: Fira Code, Microsoft JhengHei, Source Code Pro, monospace;
}
```
不用照抄,重點是在`font-family`裡面,加入`Microsoft JhengHei`,愈前面優先順序愈大。
#### 顯示**Bullet Point Relationship Lines**
```
/*=== Bullet Point Relationship Lines ===*/
.cm-hmd-list-indent, ul ul {
position: relative;
}
.cm-hmd-list-indent::before, ul ul::before {
content:'';
border-left: 1px solid rgba(0, 122, 255, 0.55);
position: absolute;
}
.cm-hmd-list-indent::before {
left: 0;
top: -5px;
bottom: -4px;
}
ul ul::before {
left: -11px;
top: 0;
bottom: 0;
```
`obsidian.css`最後端加入上面的CSS就可以顯示**Bullet Point Relationship Lines**,如:
![[Pasted image 20201206094618.png]]
來源:
- https://www.youtube.com/watch?v=ea9hkXem1f4
- https://gist.github.com/emisjerry/36d1d115ae22cd0902fc528875f86414
## Plugin
### Dataview
- [Source code](https://github.com/blacksmithgu/obsidian-dataview)
- [介紹:[Obs26] Dataview外掛: 製作索引筆記利器;動態查詢筆記資料 - YouTube](https://www.youtube.com/watch?v=dkWA7Qd0CLA&list=PLWg9zacwOnwfcpVm5pAKgOHms7PntsgJS&index=18)

View File

@@ -0,0 +1,389 @@
## Build code
### 前置作業
1. 要有NVIDIA GPU
2. 安裝NVIDIA GPU driver
3. 安裝[CUDA](https://developer.nvidia.com/cuda-downloads)
4. 安裝[cuDNN](https://developer.nvidia.com/rdp/cudnn-download)
5. 安裝[CMake](https://cmake.org/download/)
### CMake && Visual Studio
1. 下載OpenCV[opencv](https://github.com/opencv/opencv)
2. 解壓縮後放到`D:\temp\build_opencv\opencv-4.5.3\source`
3. 建立`D:\temp\build_opencv\opencv-4.5.3\build`
4. 下載OpenCV contrib[opencv_contrib](https://github.com/opencv/opencv_contrib)
5. 解壓縮後放到`D:\temp\build_opencv\opencv_contrib`
6. 打開cmake-gui
7. 如圖設定
![[Pasted image 20210818115317.png]]
8.`Add Entry`先加入以下define
![[Pasted image 20210818115507.png]]
如果是檔名type就選FILEPATH如果是目錄type就選PATH
- PYTHON3_EXECUTABLE=C:/python39/python.exe
- PYTHON3_INCLUDE_DIR=C:/python39/include
- PYTHON3_LIBRARY=C:/python39/libs/python39.lib
- PYTHON3_NUMPY_INCLUDE_DIRS=C:/python39/Lib/site-packages/numpy/core/include
- PYTHON3_PACKAGES_PATH=C:/python39/Lib/site-packages
如果要build win32 + Python 3.6.3 x86的話改為以下設定
- PYTHON3_EXECUTABLE=C:/Python363/python.exe
- PYTHON3_INCLUDE_DIR=C:/Python363/include
- PYTHON3_LIBRARY=C:/Python363/libs/python36.lib
- PYTHON3_NUMPY_INCLUDE_DIRS=C:/Python363/Lib/site-packages/numpy/core/include
- PYTHON3_PACKAGES_PATH=C:/Python363/Lib/site-packages
9. 按Configure會跳出一個視窗platform選x64
![[Pasted image 20210818115809.png]]
10. 這些要打勾
- BUILD_opencv_world
- BUILD_opencv_python3
- OPENCV_DNN_CUDA
- OPENCV_PYTHON3_VERSION
- OPENCV_FORCE_PYTHON_LIBS
- OPENCV_ENABLE_NONFREE
- ENABLE_FAST_MATH
- WITH_CUDA
- WITH_OPENMP
- OPENCV_EXTRA_MODULES_PATH=D:/temp/build_opencv/opencv_contrib/modules
11. 再按一次Configure必須沒有錯誤的跑完像是
```
General configuration for OpenCV 4.5.3 =====================================
Version control: unknown
Extra modules:
Location (extra): D:/temp/build_opencv/opencv_contrib/modules
Version control (extra): 4.5.3-6-g907efb96
Platform:
Timestamp: 2021-08-18T03:40:06Z
Host: Windows 10.0.19043 AMD64
CMake: 3.21.1
CMake generator: Visual Studio 16 2019
CMake build tool: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/MSBuild.exe
MSVC: 1929
Configuration: Debug Release
CPU/HW features:
Baseline: SSE SSE2 SSE3
requested: SSE3
Dispatched code generation: SSE4_1 SSE4_2 FP16 AVX AVX2 AVX512_SKX
requested: SSE4_1 SSE4_2 AVX FP16 AVX2 AVX512_SKX
SSE4_1 (17 files): + SSSE3 SSE4_1
SSE4_2 (2 files): + SSSE3 SSE4_1 POPCNT SSE4_2
FP16 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
AVX (5 files): + SSSE3 SSE4_1 POPCNT SSE4_2 AVX
AVX2 (31 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2
AVX512_SKX (7 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2 AVX_512F AVX512_COMMON AVX512_SKX
C/C++:
Built as dynamic libs?: YES
C++ standard: 11
C++ Compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe (ver 19.29.30040.0)
C++ flags (Release): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /MP /MD /O2 /Ob2 /DNDEBUG
C++ flags (Debug): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /MP /MDd /Zi /Ob0 /Od /RTC1
C Compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe
C flags (Release): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /MP /MD /O2 /Ob2 /DNDEBUG
C flags (Debug): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /MP /MDd /Zi /Ob0 /Od /RTC1
Linker flags (Release): /machine:x64 /INCREMENTAL:NO
Linker flags (Debug): /machine:x64 /debug /INCREMENTAL
ccache: NO
Precompiled headers: NO
Extra dependencies: cudart_static.lib nppc.lib nppial.lib nppicc.lib nppidei.lib nppif.lib nppig.lib nppim.lib nppist.lib nppisu.lib nppitc.lib npps.lib cublas.lib cudnn.lib cufft.lib -LIBPATH:C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.4/lib/x64
3rdparty dependencies:
OpenCV modules:
To be built: aruco barcode bgsegm bioinspired calib3d ccalib core cudaarithm cudabgsegm cudacodec cudafeatures2d cudafilters cudaimgproc cudalegacy cudaobjdetect cudaoptflow cudastereo cudawarping cudev datasets dnn dnn_objdetect dnn_superres dpm face features2d flann fuzzy gapi hfs highgui img_hash imgcodecs imgproc intensity_transform line_descriptor mcc ml objdetect optflow phase_unwrapping photo plot python3 quality rapid reg rgbd saliency shape stereo stitching structured_light superres surface_matching text tracking ts video videoio videostab wechat_qrcode world xfeatures2d ximgproc xobjdetect xphoto
Disabled: -
Disabled by dependency: -
Unavailable: alphamat cvv freetype hdf java julia matlab ovis python2 python2 sfm viz
Applications: tests perf_tests apps
Documentation: NO
Non-free algorithms: NO
Windows RT support: NO
GUI:
Win32 UI: YES
VTK support: NO
Media I/O:
ZLib: build (ver 1.2.11)
JPEG: build-libjpeg-turbo (ver 2.1.0-62)
WEBP: build (ver encoder: 0x020f)
PNG: build (ver 1.6.37)
TIFF: build (ver 42 - 4.2.0)
JPEG 2000: build (ver 2.4.0)
OpenEXR: build (ver 2.3.0)
HDR: YES
SUNRASTER: YES
PXM: YES
PFM: YES
Video I/O:
DC1394: NO
FFMPEG: YES (prebuilt binaries)
avcodec: YES (58.134.100)
avformat: YES (58.76.100)
avutil: YES (56.70.100)
swscale: YES (5.9.100)
avresample: YES (4.0.0)
GStreamer: NO
DirectShow: YES
Media Foundation: YES
DXVA: YES
Parallel framework: Concurrency
Trace: YES (with Intel ITT)
Other third-party libraries:
Intel IPP: 2020.0.0 Gold [2020.0.0]
at: D:/temp/build_opencv/opencv-4.5.3/build/3rdparty/ippicv/ippicv_win/icv
Intel IPP IW: sources (2020.0.0)
at: D:/temp/build_opencv/opencv-4.5.3/build/3rdparty/ippicv/ippicv_win/iw
Lapack: NO
Eigen: NO
Custom HAL: NO
Protobuf: build (3.5.1)
NVIDIA CUDA: YES (ver 11.4, CUFFT CUBLAS)
NVIDIA GPU arch: 35 37 50 52 60 61 70 75 80 86
NVIDIA PTX archs:
cuDNN: YES (ver 8.2.2)
OpenCL: YES (NVD3D11)
Include path: D:/temp/build_opencv/opencv-4.5.3/source/3rdparty/include/opencl/1.2
Link libraries: Dynamic load
Python 3:
Interpreter: C:/python39/python.exe (ver 3.9.6)
Libraries: C:/python39/libs/python39.lib (ver 3.9.6)
numpy: C:/python39/Lib/site-packages/numpy/core/include (ver 1.19.5)
install path: C:/python39/Lib/site-packages/cv2/python-3.9
Python (for build): C:/python39/python.exe
Java:
ant: NO
JNI: NO
Java wrappers: NO
Java tests: NO
Install to: D:/temp/build_opencv/opencv-4.5.3/build/install
-----------------------------------------------------------------
Configuring done
```
12. 按下Generate按鈕就會生sln檔案。
### CMake by command
用command line的話就可以將多個平台一次編起來不用一直改GUI。
寫了一個`build.bat`可以編譯不同版本與平台:
```bat
rem build.bat
echo on
rem build.bat
rem win32 ARCH=%1, win32/x64
rem 15 VS_CODE=%2, 15/16
rem 2017 VS_VERSION=%3, 2017/2019
rem DEBUG BUILD_TYPE=%4, DEBUG/RELEASE
rem "C:/Python363/python.exe" PYTHON3_EXE=%5
rem "C:/Python363/include" PYTHON3_INCLUDE=%6
rem "C:/Python363/libs/python36.lib" PYTHON3_LIB=%7
rem "C:/Python363/Lib/site-packages/numpy/core/include" PYTHON3_NP_INCLUDE=%8
rem "C:/Python363/Lib/site-packages" PYTHON3_PACKAGES=%9
rem 3.6.3 PY_VERSION=%10
rem 1 CLEAN_BUILD=%11, 1: Delete build folder, 0: Do nothing
set DO_CONFIG=1
set DO_BUILD=1
set ARCH=%1
set VS_CODE=%2
set VS_VERSION=%3
set BUILD_TYPE=%4
set PYTHON3_EXE=%5
set PYTHON3_INCLUDE=%6
set PYTHON3_LIB=%7
set PYTHON3_NP_INCLUDE=%8
set PYTHON3_PACKAGES=%9
set GENERATOR="Visual Studio %VS_CODE% %VS_VERSION%"
shift
set PY_VERSION=%9
set CV_VERSION=4.5.3
set CV_SOURCE="opencv-%CV_VERSION%\source"
set CV_BUILD=build\vs%VS_VERSION%.%ARCH%.%BUILD_TYPE%
set CV_EXTRA_MODULES="opencv_contrib\modules"
shift
set CLEAN_BUILD=%9
if %CLEAN_BUILD% == 1 (
rmdir /s /q %CV_BUILD%
)
if %DO_CONFIG% == 1 (
mkdir %CV_BUILD%
"C:\Program Files\CMake\bin\cmake.exe" ^
-B%CV_BUILD% ^
-H%CV_SOURCE% ^
-G%GENERATOR% ^
-A%ARCH% ^
-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^
-DOPENCV_EXTRA_MODULES_PATH=%CV_EXTRA_MODULES% ^
-DCMAKE_INSTALL_PREFIX=%CV_BUILD% ^
-DINSTALL_PYTHON_EXAMPLES=OFF ^
-DINSTALL_C_EXAMPLES=OFF ^
-DBUILD_opencv_python3=ON ^
-DPYTHON3_EXECUTABLE=%PYTHON3_EXE% ^
-DPYTHON3_INCLUDE_DIR=%PYTHON3_INCLUDE% ^
-DPYTHON3_LIBRARY=%PYTHON3_LIB% ^
-DPYTHON3_NUMPY_INCLUDE_DIRS=%PYTHON3_NP_INCLUDE% ^
-DPYTHON3_PACKAGES_PATH=%PYTHON3_PACKAGES% ^
-DOPENCV_PYTHON3_VERSION=%PY_VERSION% ^
-DOPENCV_FORCE_PYTHON_LIBS=ON ^
-DBUILD_opencv_world=ON ^
-DOPENCV_ENABLE_NONFREE=ON ^
-DENABLE_FAST_MATH=ON ^
-DWITH_OPENMP=ON ^
-DWITH_OPENGL=ON
)
if %DO_BUILD% == 1 (
"C:\Program Files\CMake\bin\cmake.exe" --build %CV_BUILD% --target INSTALL --config %BUILD_TYPE%
)
```
這樣以後就可以用參數的方法來設定譬如要使用vs2019來編譯x64 release的話就可以這樣下
```bat
rem build_vs2019.x64.RELEASE.bat
echo off
rem ----------------------------------------------------------------------------------------------------
rem
rem x64, RELEASE, 2019, CLEAN
rem
set ARCH=x64
set VS_CODE=16
set VS_VERSION=2019
set BUILD_TYPE=RELEASE
set PYTHON3_EXE="C:/Python39/python.exe"
set PYTHON3_INCLUDE="C:/Python39/include"
set PYTHON3_LIB="C:/Python39/libs/python39.lib"
set PYTHON3_NP_INCLUDE="C:/Python39/Lib/site-packages/numpy/core/include"
set PYTHON3_PACKAGES="C:/Python39/Lib/site-packages"
set PY_VERSION=3.9
set CLEAN_BUILD=1
echo "============================================================"
echo "Build %ARCH%.%VS_VERSION%(%VS_CODE%).%BUILD_TYPE%, Python=%PY_VERSION%, CLEAN_BUILD=%CLEAN_BUILD%"
echo ""
call build.bat ^
%ARCH% %VS_CODE% %VS_VERSION% %BUILD_TYPE% %PYTHON3_EXE% ^
%PYTHON3_INCLUDE% %PYTHON3_LIB% %PYTHON3_NP_INCLUDE% %PYTHON3_PACKAGES% ^
%PY_VERSION% %CLEAN_BUILD%
rem ----------------------------------------------------------------------------------------------------
```
### 參考
- [Build OpenCV GPU Version On Windows 10](https://medium.com/chung-yi/build-opencv-gpu-version-on-windows-10-c37a33437525)
- [Cannot install openCV 3.1.0 with python3. CMAKE not including or linking python correctly](https://stackoverflow.com/questions/42638342/cannot-install-opencv-3-1-0-with-python3-cmake-not-including-or-linking-python)
- [在Windows上编译带CUDA(GPU)的OpenCV](https://shaogui.life/2021/03/08/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91%E5%B8%A6CUDA(GPU)%E7%9A%84opencv/)
- [How to use OpenCV DNN Module with Nvidia GPU on Windows](https://learnopencv.com/how-to-use-opencv-dnn-module-with-nvidia-gpu-on-windows/)
- [Accelerate OpenCV 4.5.0 on Windows build with CUDA and python bindings - James Bowley](https://jamesbowley.co.uk/accelerate-opencv-4-5-0-on-windows-build-with-cuda-and-python-bindings/#python_bindings)
## 影像處理
- sift
```python
def adjust_gamma(image, gamma=1.0):
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255
for i in np.arange(0, 256)]).astype("uint8")
return cv2.LUT(image, table)
def sift_compare(path_a, path_b):
'''
Use SIFT features to measure image similarity
@args:
{str} path_a: the path to an image file
{str} path_b: the path to an image file
@returns:
TODO
'''
# initialize the sift feature detector
orb = cv2.ORB_create()
# get the images
img_a = cv2.imread(path_a)
img_b = cv2.imread(path_b)
img_a = adjust_gamma(img_a, 0.1)
img_b = adjust_gamma(img_b, 0.1)
# find the keypoints and descriptors with SIFT
kp_a, desc_a = orb.detectAndCompute(img_a, None)
kp_b, desc_b = orb.detectAndCompute(img_b, None)
# print(f'len kp_a = {len(kp_a)}, len desc_a = {len(desc_a)}')
# print(f'type kp_b = {type(kp_b)}, type desc_b = {type(desc_b)}')
# print(f'len kp_b = {len(kp_b)}, len desc_b = {len(desc_b) if desc_b else 0}')
if desc_a is None or desc_b is None:
# rr.LOG('Error: desc_a = {}, desc_b = {}'.format(type(desc_a), type(desc_b)))
# rr.LOG('Error: Return score 0.')
return 0
# Initialize the bruteforce matcher
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# match.distance is a float between {0:100} - lower means more similar
matches = bf.match(desc_a, desc_b)
similar_regions = [i for i in matches if i.distance < 70]
# print('Length of similar_regions = {}'.format(len(similar_regions)))
# print('Length of matches = {}'.format(len(matches)))
sorted_matches = sorted(matches, key = lambda x:x.distance)
good_matches = sorted_matches[:int(len(sorted_matches) * 0.5)]
similar_regions2 = [i for i in good_matches if i.distance < 70]
# print('Length of good_matches = {}'.format(len(good_matches)))
if len(matches) == 0:
return 0
return len(similar_regions) / len(matches)
```
### Color tempertature
code
```
img1 = cv2.imread(file)
B, G, R = cv2.split(img1)
avgB = cv2.mean(B)[0]
avgG = cv2.mean(G)[0]
avgR = cv2.mean(R)[0]
# X = avgR * -0.14282 + avgG * 1.54924 + avgB * -0.95641
# Y = avgR * -0.32466 + avgG * 1.57837 + avgB * -0.73191
# Z = avgR * -0.68202 + avgG * 0.77073 + avgB * 0.56332
X = 2.789 * avgR + 1.7517 * avgG + 1.1302 * avgB
Y = 1 * avgR + 4.5907 * avgG + 0.0601 * avgB
Z = 0 * avgR + 0.0565 * avgG + 5.5943 * avgB
x = X / (X + Y + Z)
y = Y / (X + Y + Z)
n = (x - 0.3320) / (0.1858 - y)
# n = (0.23881 * avgR + 0.25499 * avgG + -0.58291 * avgB) / (0.11109 * avgR + -0.85406 * avgG + 0.52289 * avgB)
CCT = 449 * pow(n, 3) + 3525 * pow(n, 2) + 6823.3 * n + 5520.33
# CCT = 437 * pow(n, 3) + 3601 * pow(n, 2) + 6831 * n + 5517
# print(f'x = {x}')
# print(f'y = {y}')
# print(f'n = {n}')
print(f'{file}: CCT = {CCT}')
```
- [How to Calculate the Color Temperature / Tint of the Colors in an Image?](https://dsp.stackexchange.com/questions/8949/how-to-calculate-the-color-temperature-tint-of-the-colors-in-an-image)
- [Calculating Color Temperature and Illuminance using the TAOS TCS3414CS Digital Color Sensor_](https://ams.com/documents/20143/80162/TCS34xx_AN000517_1-00.pdf)
- [Java RGB转色温(CCT)](https://www.codeleading.com/article/16404945886/)
- [Calculate color temperature (CCT) from CIE 1931 xy coordinates](https://www.waveformlighting.com/tech/calculate-color-temperature-cct-from-cie-1931-xy-coordinates)
- [OpenCV: Color conversions](https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html)
- [sRGB色彩空間 - 維基百科,自由的百科全書](https://zh.wikipedia.org/wiki/SRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4)
- [Color temperature - Wikipedia](https://en.wikipedia.org/wiki/Color_temperature#Approximation)
- [What color is a blackbody? - some pixel rgb values](http://www.vendian.org/mncharity/dir3/blackbody/)

View File

@@ -0,0 +1,2 @@
- [opengl-tutorial](http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-1-opening-a-window/)
- [Learn OpenGL, extensive tutorial resource for learning Modern OpenGL](https://learnopengl.com/)

View File

@@ -0,0 +1,26 @@
## Create a site
Use `pelican-quickstart` to create a new site.
## Plugin
```bash
git clone --recursive https://github.com/getpelican/pelican-plugins.git
```
## Theme
先把所有佈景主題都clone下來
```bash
git clone --recursive https://github.com/getpelican/pelican-themes.git
```
`pelicanconf.py`裡面的`THEME`指向theme的目錄就可以換佈景主題了。例如要用[[blue-penguin](https://github.com/jody-frankowski/blue-penguin)]這一個主題。把`pelicanconf.py`裡面加入`THEME = 'pelican-themes/blue-penguin'`就可以了。
## 預覽
```
make html
make serve
```
參考:
- [koko's Note Python - 安裝 Pelican Theme 來改變你的靜態網站主題](https://note.koko.guru/install-pelican-theme.html)
- [nest theme](https://github.com/molivier/nest)
- [Flex theme](https://github.com/alexandrevicenzi/Flex/wiki/Custom-Settings)

View File

@@ -0,0 +1,244 @@
# 安裝
## 下載ISO
- [Get the free Proxmox VE ISO installer](https://www.proxmox.com/en/downloads/category/iso-images-pve)
## 準備USB disk
- 用[Rufus](https://rufus.ie/)的話
1. 在遇到詢問是否要下載 Grub 時,請選擇「否」
2. 必須使用DD mode來建立開機碟。參考[Prepare Installation Media - Proxmox VE](https://pve.proxmox.com/wiki/Prepare_Installation_Media#_instructions_for_windows)
![[Pasted image 20210128212917.png]]
# 設定
## 關閉「闔上螢幕後休眠」
打開`/etc/systemd/logind.conf`
```
nano /etc/systemd/logind.conf
```
找到下面兩行把值改成ignore
```
HandleLidSwitch=ignore
HandleLidSwitchDocked=ignore
```
然後重開機:
```
systemctl restart systemd-logind.service
```
圖示:
![[Pasted image 20210129194144.png]]
## 增加硬碟
先用`lsblk`列出所有硬碟,這裡假設`sda`是我們的開機磁碟,我要要新增`sdb`
```
root@pve:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 931.5G 0 disk <-- 目前在用的
├─sda1 8:1 0 1007K 0 part
├─sda2 8:2 0 512M 0 part
└─sda3 8:3 0 931G 0 part
sdb 8:16 0 111.8G 0 disk <-- 要新增的
├─sdb1 8:17 0 100M 0 part
├─sdb2 8:18 0 16M 0 part
├─sdb3 8:19 0 111.1G 0 part
└─sdb4 8:20 0 563M 0 part
```
然後安裝`parted`,我們要用它來分割硬碟:
```
apt install parted
```
開始分割:
```
parted /dev/sdb mklabel gpt
```
建立primary partition格式為`ext4`
```
parted -a opt /dev/sdb mkpart primary ext4 0% 100%
```
再來將分割好的硬碟格式化為`ext4`label命名為`data2`
```
mkfs.ext4 -L data2 /dev/sdb1
```
再用`lsblk`看一次會發現sdb已經重新分割成1個partition了
```
root@pve:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 931.5G 0 disk
├─sda1 8:1 0 1007K 0 part
├─sda2 8:2 0 512M 0 part
└─sda3 8:3 0 931G 0 part
sdb 8:16 0 111.8G 0 disk
└─sdb1 8:17 0 111.8G 0 part
```
`lsblk -fs`可以看到每一個硬碟的檔案系統格式:
```
root@pve:~# lsblk -fs
NAME FSTYPE LABEL UUID FSAVAIL FSUSE% MOUNTPOINT
sda1 zfs_member rpool 11775123664036754029
└─sda zfs_member rpool 11775123664036754029
sda2 vfat rpool 32D0-3449
└─sda zfs_member rpool 11775123664036754029
sda3 zfs_member rpool 11775123664036754029
└─sda zfs_member rpool 11775123664036754029
sdb1 ext4 data2 bc6d2c41-a3ca-4b0f-a5de-51ee28ae9cec <-- 剛剛分割的
└─sdb
```
接下來,將新硬碟掛載到檔案目錄上,先建立一個新目錄來掛載新硬碟:
```shell
mkdir -p /mnt/data
```
接下來編輯`/etc/fstab`,將新硬碟寫進來,這樣開機之後才會自動把它掛載起來:
```
nano /etc/fstab
```
加入這一行(注意:**data2**要改成你自己的label
```
LABEL=data2 /mnt/data ext4 defaults 0 2
```
剛新硬碟掛起來:
```
mount -a
```
`df`就可以看到新硬碟了:
```
root@pve:~# df
Filesystem 1K-blocks Used Available Use% Mounted on
udev 16288892 0 16288892 0% /dev
tmpfs 3262688 9324 3253364 1% /run
rpool/ROOT/pve-1 942667136 1267584 941399552 1% /
tmpfs 16313440 43680 16269760 1% /dev/shm
tmpfs 5120 0 5120 0% /run/lock
tmpfs 16313440 0 16313440 0% /sys/fs/cgroup
rpool 941399680 128 941399552 1% /rpool
rpool/data 941399680 128 941399552 1% /rpool/data
rpool/ROOT 941399680 128 941399552 1% /rpool/ROOT
/dev/fuse 30720 16 30704 1% /etc/pve
tmpfs 3262688 0 3262688 0% /run/user/0
/dev/sdb1 114854020 61464 108915208 1% /mnt/data <-- 新硬碟在這裡
```
參考:
- [How to add storage to Proxmox](https://nubcakes.net/index.php/2019/03/05/how-to-add-storage-to-proxmox/)
## 增加iSCSI磁碟
### 增加需要CHAP認證的iSCSI磁碟
1. 先確認找的到iSCSI磁碟
```
iscsiadm -m discovery -t st -p 192.168.1.11:3260
```
如果有找到的話會回傳一串IQN值像是
```
root@pve:~# iscsiadm -m discovery -t st -p 192.168.1.11:3260
192.168.1.11:3260,1 iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3
[fe80::211:32ff:fe20:eadd]:3260,1 iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3
```
2. 有IQN就可以用下列的命令連線與掛載
```
iscsiadm -m node -T iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3 --op update --name node.session.auth.username --value=名字
iscsiadm -m node -T iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3 --op update --name node.session.auth.password --value=密碼
iscsiadm -m node -T iqn.2000-01.com.synology:DiskStation.Target-1.3e589efea3 -l #連線
iscsiadm -m node -o update -n node.startup -v automatic #設定開機自動掛載
```
## 增加NFS磁碟
1. 先在Synology上開一個NFS disk設定如下
![[Pasted image 20220506091522.png]]
2. 再到Proxmox的 Datacenter->Storage->Add 來增加一個 *NFS*,設定如下
![[Pasted image 20220506091624.png]]
### 更改NFS mount為soft
1. 編輯`/etc/pve/storage.cfg`
2. 做如下修改
![[Pasted image 20220506095531.png]]
### 參考
- [[經驗分享]Proxmox VE 採用 NFS 連接儲存的重點事項](http://blog.jason.tools/2019/02/pve-nfs-mount.html)
## 設定VM備份目錄
如果將VM或LXC備份到某個目錄先建立要備份的目錄
```shell
mkdir -p /mnt/data/backup/
```
再來用WEB UI操作如下
![[Pasted image 20210129202041.png]]
![[Pasted image 20210129202047.png]]
最後再到 Datacenter->Backups建立一個scheule來備份就可以了
![[Pasted image 20210129202231.png]]
## 將資料備份到NAS
1. 先在NAS開一個share folder跟一個帳號。
![[Pasted image 20210202190402.png]]
![[Pasted image 20210202190537.png]]
2. Proxmox到裡將剛剛新開的folder給掛載起來。
![[Pasted image 20210202190640.png]]
會跳出一個視窗,如下圖來填,記得**content**那一欄有4個要選。
![[Pasted image 20210202190709.png]]
3. Proxmox到 Datacenter->Backup 新增一個排程。
![[Pasted image 20210202190903.png]]
一樣會跳出一個視窗,依需求來填,要注意的是**Storage**必須是前一步驟的**ID****Selection Mode**可以選擇**All**。
![[Pasted image 20210202191150.png]]
參考:
- [HASS + Proxmox: Automatic Backups to Synology NAS](https://kleypot.com/automatic-offline-backups/)
## 更新
### 加入更新來源
編輯`/etc/apt/sources.list`,加入:
```
deb http://ftp.debian.org/debian bullseye main contrib
deb http://ftp.debian.org/debian bullseye-updates main contrib
# PVE pve-no-subscription repository provided by proxmox.com,
# NOT recommended for production use
deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription
# security updates
deb http://security.debian.org/debian-security bullseye-security main contrib
```
### 取消訂閱服務
編輯`/etc/apt/sources.list.d/pve-enterprise.list`,把下面這行注釋掉:
```
deb https://enterprise.proxmox.com/debian/pve buster pve-enterprise
```
也就是變成:
```
#deb https://enterprise.proxmox.com/debian/pve buster pve-enterprise
```
使用`apt update`來更新套件。
使用`apt dist-upgrade`來升級系統版本。
## 重灌後要做的事情
1. 建立ZFS pool。
2. 確認S.M.A.R.T. 是否啟用,預設是啟用的。
`smartctl -a /dev/<SDA_N>`
1. 打開IOMMU
2. 打開vm aware
3. 增加NFS共享磁碟
4. 排程備份
5. 上傳安裝Windows需要的驅動ISO
1. [Windows VirtIO Drivers](https://pve.proxmox.com/wiki/Windows_VirtIO_Drivers)
6. 把常用的VM轉為template
7. 安裝[Cockpit-Linux Server](https://pvecli.xuan2host.com/cockpit/), 讓您的PVE有更棒的圖形管理介面。
## 參考
- [套件功能的更新Proxmox update](https://wiki.freedomstu.com/books/proxmox-ve-%E8%99%9B%E6%93%AC%E7%B3%BB%E7%B5%B1%E8%A8%98%E9%8C%84/page/%E5%A5%97%E4%BB%B6%E5%8A%9F%E8%83%BD%E7%9A%84%E6%9B%B4%E6%96%B0%EF%BC%88proxmox-update%EF%BC%89)
- [裝完PVE後的11件必作清單 (中文翻譯)](https://www.youtube.com/watch?v=pY4Lm2Hoqik)
- [Before I do anything on Proxmox, I do this first...](https://www.youtube.com/watch?v=GoZaMgEgrHw&t=0s)
# Trouble shooting
- *Emergency mode*,表示開機失敗,請檢查`/etc/fstab`是不是有無法掛載的disk。
## 參考
- [[Fix] Getting out of emergency mode : Proxmox](https://www.reddit.com/r/Proxmox/comments/hai75k/fix_getting_out_of_emergency_mode/)

View File

@@ -0,0 +1,56 @@
一個範例:
```python
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--update_tool", default=WIN_FW_UPDATER_PATH, help="The path of win_fw_updater.exe.")
parser.add_argument("-f", "--firmware", required=True, help="The path of ITB file.")
parser.add_argument("-w", "--waittime_update_firmware", default=600, type=int, help="Wait time for update the firmware.")
parser.add_argument("-g", "--ignore_check_file_path", action='store_true', help="Skip check the existence of file.")
args = parser.parse_args()
```
#### 要求user一定要設定的參數
使用`required=True`
例如:
```python
parser.add_argument("-f", "--firmware", required=True, help="The path of ITB file.")
```
如果使用者沒有下`-f`(或者`--firmware=XXX`)就會報錯,如下:
```bash
FwUpdateCheck.py: error: the following arguments are required: -f/--firmware
```
#### 有設定才會產生的參數
使用`action='store_true'``action='store_false'`
例如:
```python
parser.add_argument("-g", "--ignore_check_file_path", action='store_true', help="Skip check the existence of file.")
```
當使用者沒有設置`-g`時,`args.ignore_check_file_path``False`,當設置時,`args.ignore_check_file_path``True`
#### 使用預設值
例如:
```python
parser.add_argument("-u", "--update_tool", default="C:\\tool.exe", help="The path of win_fw_updater.exe.")
```
`default=<Something>`來設定參數的預設值,上面的例子中,`args.update_tool`的預設值為`C:\tool.exe`
另外可以用`type=<Object type>`來指定預設值的型別。例如:
```python
parser.add_argument("-n", "--number", default=50, type=int, help="Assign a number")
```
上例中,`args.number`的預設值是50型別是`int`,所以可以直接運算,不需要再經過`int(args.number)`這樣的轉換。
#### 限制使用者的選擇
Example:
```python
parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
```
使用`choices=<list>`來限定輸入的選項,上例中,使用者只能輸入'rock'、'paper'、'scissors'這三個字串中的其中一個,否則會報錯:
```bash
error: argument move: invalid choice: 'fire' (choose from 'rock', 'paper', 'scissors')
```
-----
- https://docs.python.org/zh-tw/3/library/argparse.html

View File

@@ -0,0 +1,48 @@
## 在decorator內取得function的default argument與class member
```python
import sys
import inspect
from functools import wraps
def exampleDecorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Decorator: call by {func.__name__}")
def get_default_args(func):
signature = inspect.signature(func)
return {
k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty
}
## Get default
defaultKwargs = get_default_args(func)
defaultKwargs.update(kwargs)
print(f"Decorator: args = {args}, kwargs = {kwargs}, defaultKwargs = {defaultKwargs}")
objectInstance = args[0]
if hasattr(objectInstance, 'defaultArg1'):
print(f'objectInstance has defaultArg1, a.defaultArg1({type(objectInstance.defaultArg1)}) = {objectInstance.defaultArg1}')
if objectInstance.defaultArg1:
## Do something here
print("Decorator: some message...")
else:
print('objectInstance does not have defaultArg1')
return func(*args, **kwargs)
return wrapper
class ExampleClass():
def __init__(self, defaultArg1=True, defaultArg2="SomeString"):
self.defaultArg1 = defaultArg1
self.defaultArg2 = defaultArg2
print(f'self.defaultArg1 = {self.defaultArg1}, self.defaultArg2 = {self.defaultArg2}')
@exampleDecorator
def run(self, arg1=1, arg2=2):
print(f"ExampleClass.run(), arg1 = {arg1}, arg2 = {arg2}")
example = ExampleClass()
example.run()
```

View File

@@ -0,0 +1,126 @@
### 準備
```
import logging
```
### logging level
| level | level number | funtion |
|:---------|:-------------|:---------------------|
| NOTSET | 0 | |
| DEBUG | 10 | `logging.debug()` |
| INFO | 20 | `logging.info()` |
| WARNING | 30 | `logging.warning()` |
| ERROR | 40 | `logging.error()` |
| CRITICAL | 50 | `logging.critical()` |
```
import logging
LOG_FORMAT = '%(asctime)s %(levelname)s: %(message)s'
LOG_FILENAME = 'C:\\RobotRun\\Output\\RobotRunDocUpdater.log'
logging.basicConfig(level=logging.INFO, filename=LOG_FILENAME, filemode='a', format=LOG_FORMAT)
logging.info('logging start')
```
### Print Exception
`logging` 模組也提供可以紀錄完整的堆疊追蹤 (stack traces),若在 `logging.error()` 加上 `exc_info` 參數,並將該參數設為 `True`,就可以紀錄 Exception如下
```python
import logging
try:
x = 5 / 0
except:
logging.error("Catch an exception.", exc_info=True)
```
也可以使用`logging.exception("Catch an exception.")`,效果跟`logging.error("Catch an exception.", exc_info=True)`一樣。
### 自訂 logging 輸出格式
預設的訊息輸出格式只有 `levelname``name``message`,下面是其他相關的資訊:
| 格式化字串 | 說明 |
|:------------------|:---------------------------------------------------------------------|
| `%(asctime)s` | 日期時間, 格式為 `YYYY-MM-DD HH:mm:SS,ms`例如2018-12-13 17:20:30,567 |
| `%(filename)s` | 模組檔名 |
| `%(funcName)s` | 函數名稱 |
| `%(levelname)s` | 日誌的等級名稱 |
| `%(levelno)s` | 日誌的等級數值 |
| `%(lineno)d` | 呼叫日誌函數所在的行數 |
| `%(message)s` | 訊息 |
| `%(module)s` | 模組名稱 |
| `%(name)s` | logger 的名稱 |
| `%(pathname)s` | 檔案的完整路徑 (如果可用) |
| `%(process)d` | process ID (如果可用) |
| `%(thread)d` | 執行緒 ID (如果可用) |
| `%(threradName)s` | 執行緒名稱 |
例:
```python
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
logging.debug('debug message') --> 2018-12-13 17:40:34,604 DEBUG: debug message
```
### 儲存log
只要在 `logging.basicConfig()` 內的 `filename` 參數設定要儲存的日誌檔名,就可以將 logging 儲存:
```python
import logging
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
logging.basicConfig(level=logging.DEBUG, filename='myLog.log', filemode='w', format=FORMAT)
logging.debug('debug message')
```
預設 `filemode` 參數是設為 `a`,代表 append (附加) 的意思每次執行程式時Logging 會將新的訊息加在舊的訊息後面,不會覆蓋舊的訊息。若要改成新訊息覆蓋就訊息,那可以將 `filemode` 參數設為 `w`,代表 write 的意思。
### 儲存log也輸出到console
`logging`有4個主要module
- Logger暴露了應用程式程式碼能直接使用的介面。
- Handler記錄器產生的日誌記錄傳送至合適的目的地。
- Filter提供了更好的粒度控制它可以決定輸出哪些日誌記錄。
- Formatter指明瞭最終輸出中日誌記錄的佈局。
#### Handler
其中`Handlers`有以下幾類:
1. `logging.StreamHandler` -> 控制檯輸出
使用這個Handler可以向類似與`sys.stdout`或者`sys.stderr`的任何檔案物件(file object)輸出資訊。
它的建構函式是: `StreamHandler([strm])` 其中`strm`引數是一個檔案物件。預設是`sys.stderr`
2. `logging.FileHandler` -> 檔案輸出
和StreamHandler類似用於向一個檔案輸出日誌資訊。不過`FileHandler`會幫你開啟這個檔案。
它的建構函式是:`FileHandler(filename[,mode])` filename是檔名必須指定一個檔名。 `mode`是檔案的開啟方式。預設是`'a'`,即新增到檔案末端。
3. `logging.handlers.RotatingFileHandler` -> 按照大小自動分割日誌檔案,一旦達到指定的大小重新生成檔案
這個Handler類似於上面的`FileHandler`但是它可以管理檔案大小。當檔案達到一定大小之後它會自動將當前日誌檔案改名然後建立一個新的同名日誌檔案繼續輸出。比如日誌檔案是chat.log。當chat.log達到指定的大小之後`RotatingFileHandler`自動把 檔案改名為chat.log.1。不過如果chat.log.1已經存在會先把chat.log.1重新命名為chat.log.2。
最後重新建立 chat.log繼續輸出日誌資訊。它的建構函式是`RotatingFileHandler(filename[, mode[, maxBytes[, backupCount]]])`,其中`filename``mode`兩個引數和FileHandler一樣。`maxBytes`用於指定日誌檔案的最大檔案大小。如果maxBytes為0意味著日誌檔案可以無限大這時上面描述的重新命名過程就不會發生。 `backupCount`用於指定保留的備份檔案的個數。比如如果指定為2當上面描述的重新命名過程發生時原有的chat.log.2並不會被更名,而是被刪除。
4. `logging.handlers.TimedRotatingFileHandler` -> 按照時間自動分割日誌檔案
這個Handler和`RotatingFileHandler`類似,不過,它沒有通過判斷檔案大小來決定何時重新建立日誌檔案,而是間隔一定時間就自動建立新的日誌檔案。重新命名的過程與`RotatingFileHandler`類似,不過新的檔案不是附加數字,而是當前時間。它的建構函式是:`TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])`,其中`filename`引數和`backupCount`引數和`RotatingFileHandler`具有相同的意義。`interval`是時間間隔。 `when`引數是一個字串。表示時間間隔的單位,不區分大小寫。它有以下取值: S 秒 M 分 H 小時 D 天 W 每星期(`interval==0`時代表星期一) midnight 每天凌晨。
#### Formatters
Formatters預設的時間格式為`%Y-%m-%d %H:%M:%S`
#### Example
新增2個handler一個輸出到螢幕上一個寫到檔案裡。寫到檔案裡的那個handler必須是`logging.handlers.RotatingFileHandler`超過1MB時會自動分割。
```python
import logging
import logging.handlers
logger = logging.getLogger(filename) # filename就是你要存log的檔名
shell_print = logging.StreamHandler() # 往螢幕上輸出
shell_print.setFormatter(format_str) # 設定螢幕上顯示的格式
file_print = logging.handlers.RotatingFileHandler(
filename=filename,
mode='a',
maxBytes=1024*1024,
backupCount=backCount,
encoding='utf-8')
file_print.setFormatter(format_str) # 設定檔案裡寫入的格式
logger.addHandler(sh) # 把物件加到logger裡
logger.addHandler(th)
```
-----
參考:
- [Python - 日誌 (logging) 模組](https://titangene.github.io/article/python-logging.html)
- [`logging` — Logging facility for Python](https://docs.python.org/3/library/logging.html#module-logging "logging: Flexible event logging system for applications.")

View File

@@ -0,0 +1,61 @@
### 將camera包裝成class
```python
class CameraCv(object):
def __init__(self, videoSource=0):
self.videoSource = videoSource
self.camera = None
self.cameraWidth = 0
self.cameraHeight = 0
self.cameraPreviewThreadHandle = None
self.cameraPreviewThreadStopEvent = threading.Event()
self.lastframeRGB = None
self.latestFrame = None
def start(self):
print("Open Camera")
self.camera = cv2.VideoCapture(self.videoSource, cv2.CAP_DSHOW)
if not self.camera.isOpened():
raise ValueError("Unable to open video source {}".format(self.videoSource))
# Get video source width and height
self.cameraWidth = self.camera.get(cv2.CAP_PROP_FRAME_WIDTH)
self.cameraHeight = self.camera.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.cameraPreviewThreadStopEvent.clear()
self.cameraPreviewThreadHandle = threading.Thread(target=self.collectFrame, daemon=True, args=())
self.cameraPreviewThreadHandle.start()
def stop(self):
print("Close Camera")
self.cameraPreviewThreadStopEvent.set()
if self.camera.isOpened():
self.camera.release()
cv2.destroyAllWindows()
def collectFrame(self):
while True:
ret, frame = self.camera.read()
if ret:
# Return a boolean success flag and the current frame converted to BGR
self.lastframeRGB = frame
self.latestFrame = ImageTk.PhotoImage(image=Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
if self.cameraPreviewThreadStopEvent.is_set():
break
time.sleep(0.016)
def draw(self, container):
if self.latestFrame is not None:
container.imgtk = self.latestFrame
container.configure(image=self.latestFrame)
def read(self):
return self.camera.read()
def getLastFrameRgb(self):
return self.lastframeRGB
def saveFrame(self, filepath):
cv2.imwrite(filepath, self.getLastFrameRgb())
```

View File

@@ -0,0 +1,49 @@
### subprocess.Popen
```python
import subprocess
process = subprocess.Popen(['echo', 'More output'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
stdout, stderr
```
Input arguments is a list.
Notice `communicate()` will **block** until process was finished.
And the output string `stdout` and `stderr` is of type `byte`. You can convert the output to `string` by:
```python
new_string = stdout.decode('utf-8')
```
or use `universal_newlines=True` in `subprocess.Popen()`. Example:
```python
process = subprocess.Popen(['ping', '-c 4', 'python.org'],
stdout=subprocess.PIPE,
universal_newlines=True)
```
The `.poll()` will return the exit code of process. If process is still running. `.poll()` will return `None`. Example:
```python
process = subprocess.Popen(['ping', '-c 4', 'python.org'], stdout=subprocess.PIPE, universal_newlines=True)
while True:
output = process.stdout.readline()
print(output.strip())
# Do something else
return_code = process.poll()
if return_code is not None:
print('RETURN CODE', return_code)
# Process has finished, read rest of the output
for output in process.stdout.readlines():
print(output.strip())
break
```
-----
參考:
- [docs.python.org: `subprocess.Popen`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
### subprocess.run
`subprocess.run()``subprocess.Popen()`是一樣的行為,差別是`subprocess.run()`會在process執行完畢之後才return也就是說流程會被block住。
`subprocess.run()`會回傳一個型別是`subprocess.CompletedProcess`的object.
-----
參考:
- [docs.python.org: _class_ `subprocess.CompletedProcess`](https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess)

View File

@@ -0,0 +1,2 @@
- [Python 多執行緒 threading 模組平行化程式設計教學 - G. T. Wang](https://blog.gtwang.org/programming/python-threading-multithreaded-programming-tutorial/)
- [Python — 多線程. 介紹 | by Jease | Jease隨筆 | Medium](https://medium.com/jeasee%E9%9A%A8%E7%AD%86/python-%E5%A4%9A%E7%B7%9A%E7%A8%8B-eb36272e604b)

View File

@@ -0,0 +1,96 @@
### 把matplotlib包裝成獨立視窗
```python
class Plot2D(Frame):
def __init__(self, parent, dataCollector, **kwargs):
Frame.__init__(self, parent.mainWindow, **kwargs)
self.parent = parent
self.mainWindows = Toplevel(parent.mainWindow)
self.mainWindows.title("AF State")
self.figure = plt.Figure(figsize=(9,5), dpi=100)
self.figure.suptitle('AF value plot', fontsize=16)
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.figure, master=self.mainWindows)
self.canvas.get_tk_widget().pack(fill='both')
self.axline = None
self.dataCollector = dataCollector
self.dataCollector.start()
def close(self):
print("Plot2D close")
self.mainWindows.destroy()
self.dataCollector.stop()
self.dataCollector = None
def draw(self):
if self.dataCollector:
datax, datay = self.dataCollector.getPlotData()
self.ax.clear()
self.ax.set_xlabel('Last {} datas'.format(self.dataCollector.getDataLength()))
self.axline, = self.ax.plot(datax, datay)
self.canvas.draw()
def getWindow(self):
return self.mainWindows
def getLastData(self):
return self.dataCollector.getLastData()
```
其中這一行:
```python
self.mainWindows = Toplevel(parent.mainWindow)
```
是用來開一個新的視窗,其中的`parent.mainWindow`就是用`tk.TK()`所產生出來的root。
因為需要一直更新資料,所以需要的一個`DataCollector`來提供資料,`DataCollector`會提供畫圖需要的list
```python
datax, datay = self.dataCollector.getPlotData()
```
`DataCollector`的定義如下:
```python
class AfStateCollector(threading.Thread):
def __init__(self, dataLength=100, pollingInterval=0.033):
threading.Thread.__init__(self)
self.dataLength = dataLength
self.pollingInterval = pollingInterval
self.stopEvent = threading.Event()
self.data = []
self.xdata = []
def run(self):
while True:
if self.stopEvent.is_set():
break
afValue = self.readAf()
self.data.append(afValue)
self.xdata.append(len(self.xdata))
if len(self.data) > self.dataLength:
self.data = self.data[-self.dataLength:]
self.xdata = list(range(self.dataLength))
# print(f'afValue = {afValue}')
time.sleep(self.pollingInterval)
print("AfStateCollector stopped.")
def readAf(self):
ReadTestXUreg_cmd = "lvreg testxu read 10"
ReadTestXUreg_cmd_process = subprocess.Popen(ReadTestXUreg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
outstring, err = ReadTestXUreg_cmd_process.communicate()
outstring = outstring.strip().decode('utf-8')
outstring = int(outstring, 16)
outstring_H = (outstring & 0xFF00) / 256
outstring_L = outstring & 0xFF
outAFStat = int(outstring_L * 256 + outstring_H)
return outAFStat
```
- [Python GUI之tkinter視窗視窗教程大集合看這篇就夠了 - IT閱讀](https://www.itread01.com/content/1547705544.html)
- [【Python】改善 VideoCapture 的影像延遲 | 夏恩的程式筆記 - 點部落](https://dotblogs.com.tw/shaynling/2017/12/28/091936)
- [Displaying a video feed with OpenCV and Tkinter - PyImageSearch](https://www.pyimagesearch.com/2016/05/30/displaying-a-video-feed-with-opencv-and-tkinter/)

View File

@@ -0,0 +1,96 @@
## 單元測試
### [pytest](https://docs.pytest.org/en/7.1.x/)
Pytest 不僅可以幫助我們運行測試還可以幫助我們配置如何運行它們、運行哪些文件等等……Pytest 有一個配置文件 `pytest.ini`,您可以在其中描述它的配置,例如哪個版本應該是 Pytest 或者哪些是測試文件,例如下列。
```ini
# pytet.ini
[pytest]
minversion = 6.0
addopts = -ra -q — cov=src — cov-report=html
python_files = test_*.py
```
### [tox](https://tox.wiki/en/latest/)
Tox 是一個通用的virtualenv管理和測試命令行工具。
使用不同的 Python 版本和解釋器檢查您的包是否正確安裝
在每個環境中運行您的測試,配置您選擇的測試工具
作為持續集成服務器的前端,大大減少樣板文件並合併 CI 和基於 shell 的測試。
Tox 也有它的配置文件。
```ini
[tox]
isolated_build = True
envlist = py{38}
[testenv]
usedevelop = true
deps = -r src/requirements_dev.txt
```
## 程式檢查工具
用來檢查程式是否符合coding style、PEP8之類的規範
### [pylint](https://github.com/PyCQA/pylint)
Pylint config: create `.pylintrc` file
```
[MESSAGES CONTROL]
disable=
missing-docstring,
too-few-public-methods[REPORTS]
output-format=colorized
files-output=no
reports=no
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
```
### [flake8](https://github.com/pycqa/flake8)
Flake8 config: create `.flake8` file
```
[flake8]
ignore = E203, E266, E501, W503, F403, F401, E402
max-line-length = 120
max-complexity = 18
select = B,C,E,F,W,T4,B9
exclude =
.git,
tests
```
### [mypy](http://www.mypy-lang.org/)
## Git hook
### pre-commit
Pre-commit 是一個創建 git hook的framework以確保您的代碼編寫與您定義的代碼樣式相對應。
它會掃描您的原始碼並運行您將在預提交配置文件中定義的所有檢查器:`.pre-commit-config.yaml`
```
repos:
- repo: 'https://gitlab.com/pycqa/flake8'
rev: 3.8.2
hooks:
- id: flake8
name: Style Guide Enforcement (flake8)
args:
- '--max-line-length=120'
- repo: 'https://github.com/pre-commit/mirrors-mypy'
rev: v0.720
hooks:
- id: mypy
name: Optional Static Typing for Python (mypy)
```
## 漏洞檢查
### [SonarQube](https://www.sonarqube.org/)
有很多用於漏洞掃描的工具,但我們將看看[Sonarqube](https://www.sonarqube.org/)。Sonarqube 是用於代碼質量和安全掃描的開源強大工具,是該行業的領先工具之一。
更多在[官方文檔](https://docs.sonarqube.org/latest/)中。
您可以使用 Docker 映像設置本地 Sonarqube 服務器並定義`sonar-project.properties`
```
# must be unique in a given SonarQube instance
sonar.projectKey=python_app_blueprint
# --- optional properties ---
# defaults to project key
#sonar.projectName=My project
# defaults to 'not provided'
#sonar.projectVersion=1.0
# Path is relative to the sonar-project.properties file. Defaults to .
#sonar.sources=.
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
```

View File

@@ -0,0 +1,21 @@
![[Pasted image 20220519094358.png]]
1. Button必須是QToolButton.
2. 建立menu.
3. 建立action.
4. 把action加到menu裡面
5. 把menu設定給button
6. code example:
```cpp
QMenu* saveFrameMenu = new QMenu;
saveRawAction = new QAction(QIcon(QPixmap(":/image/resources/button-raw.png")), "SaveRaw", this);
saveJpgAction = new QAction(QIcon(QPixmap(":/image/resources/button-jpg.png")), "SaveJpg", this);
saveBmpAction = new QAction(QIcon(QPixmap(":/image/resources/button-bmp.png")), "SaveBmp", this);
saveFrameMenu->addAction(saveRawAction);
saveFrameMenu->addAction(saveJpgAction);
saveFrameMenu->addAction(saveBmpAction);
ui.toolButtonSaveFrame->setMenu(saveFrameMenu);
ui.toolButtonSaveFrame->setDefaultAction(saveJpgAction);
ui.toolButtonSaveFrame->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui.toolButtonSaveFrame->setPopupMode(QToolButton::MenuButtonPopup);
```

View File

@@ -0,0 +1,9 @@
Qt有許多UI元件都可以另外設定相依的「資料」例如`QCombobox`,這樣我們就不必老是依賴選項的文字來做許多麻煩的處理。
例如,`QCombobox.addItem()`可以夾帶一個`QVariant`,這個`QVariant`就可以夾帶一個pointer。
要讓`QVariant`支援`std::shared_ptr`的方式很簡單只要在你程式的最上方加入你要使用的type就可以例如我要支援我自己的class AwSentinelDevice就這樣寫
```cpp
#include <memory>
Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
Q_DECLARE_METATYPE(std::shared_ptr<AwSentinelDevice>);
```

View File

@@ -0,0 +1,734 @@
## Prepare
### Windows 10 SDK
下載並安裝[Windows 10 SDK](https://developer.microsoft.com/zh-tw/windows/downloads/windows-10-sdk/),安裝
**Debugging Tools for Windows**就可以了。
![[chrome_20210309_110223_934x685.png]]
然後在QtCreator裡面設置Debugger。
![[NVIDIA_Share_20210309_110738_1920x1080.png]]
### Visual Studio 2017
- ![[Pasted image 20210312144218.png]]
- ![[Pasted image 20210312144242.png]]
- ![[Pasted image 20210312144405.png]]
- ![[Pasted image 20210312144523.png]]
## PRO file 語法
### 導入lib
```
QT += <lib> <lib2>
```
### 導入include path
```
INCLUDEPATH += <INCLUDE_PATH>
```
若使用相對路徑必須相對於makefile生成的路徑。
也可以使用`$$PWD`來生成絕對路徑,例:
```
INCLUDEPATH += $$PWD/../../include
```
> 可以用`message()`來印出變數:`message($$PWD)`
### 導入lib
`-L`指定library path`-l`指定libraray name。例
```
LIBS += -L"../../lib" -lopencv_world320
```
### 指定執行目錄
```
DESTDIR += ../../bin
```
### 指定執行檔檔名
```
TARGET = <NEW_FILENAME>
```
- Import QT pro file into Visual Studio.
- `qmake -tp vc <QT_PROJECT_FILE>`
- 使用VS工具import。
## 基本類別class
### QIODevice
An abstract class that wraps input and output for most devices
### QDataStream
依序寫入的資料也可以依序被讀出來。資料會被編碼過所以用Text editor會看到亂碼。
### QTextStream
可以依序寫入字串也可以依序被讀出來因為寫入的是字串所以用Text editor就可以看到資料。
### QLockFile
創造一個在操作時可以獨佔的檔案。避免race condition的情形。
```cpp
QLockFile lock(fileName() +".lock");
lock.setStaleLockTime(30000);
if(lock.tryLock()) {
...
lock.unlock();
} else {
qInfo() << "Could not lock the file!";
qint64 pid;
QString host;
QString application;
if(lock.getLockInfo(&pid,&host,&application)) {
qInfo() << "The file is locked by:";
qInfo() << "Pid: " << pid;
qInfo() << "Host: " << host;
qInfo() << "Application: " << application;
} else {
qInfo() << "File is locked, but we can't get the info!";
}
}
```
### QtMessageHandler
可以用來重新導向或攔截`qInfo()``qDebug()``qWarning()``qCritical()``qFatal()`這些function
```cpp
const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
void lhandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
QString txt;
switch (type) {
case QtInfoMsg:
txt = QString("Info: %1 in %2").arg(msg);
break;
case QtDebugMsg:
txt = QString("Debug: %1").arg(msg);
break;
case QtWarningMsg:
txt = QString("Warning: %1").arg(msg);
break;
case QtCriticalMsg:
txt = QString("Critical: %1").arg(msg);
break;
case QtFatalMsg:
txt = QString("Fatal: %1").arg(msg);
break;
}
...
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInstallMessageHandler(lhandler);
qInfo() << "This is a info message";
qDebug() << "This is a debug message";
qWarning() << "This is a warning message";
qCritical() << "This is a critical message";
qFatal("THIS IS SPARTA!!!");
return a.exec();
}
```
也可以把這機制封裝在class裡面
```cpp
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QDebug>
#include <QFile>
#include <QDateTime>
#include <QDir>
#include <iostream>
#include <QTextStream>
class logger : public QObject
{
Q_OBJECT
public:
explicit logger(QObject *parent = nullptr);
static bool logging;
static QString filename;
static void attach();
static void handler(QtMsgType type, const QMessageLogContext &context, const QString & msg);
signals:
public slots:
};
#endif // LOGGER_H
```
```cpp
// logger.cpp
#include "logger.h"
QString logger::filename = QDir::currentPath() + QDir::separator() + "log.txt";
bool logger::logging = false;
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
logger::logger(QObject *parent) : QObject(parent)
{
}
void logger::attach()
{
logger::logging = true;
qInstallMessageHandler(logger::handler);
}
void logger::handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
// Here, write log message to file
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
```
### Logging category
Logging category可以用來把輸出的log前面加上一個字串以便分類。
```cpp
#include <QCoreApplication>
#include <QDebug>
#include <QLoggingCategory>
//Declare a logging category
Q_DECLARE_LOGGING_CATEGORY(network);
Q_LOGGING_CATEGORY(network, "network");
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInfo() << "test"; // stdout: "test"
qInfo(network) << "test"; // stdout: "network: test"
//turn it off
QLoggingCategory::setFilterRules("network.debug=false");
//Will not log
qDebug(network) << This message will not shown;
if(!network().isDebugEnabled()) {
QLoggingCategory::setFilterRules("network.debug=true");
qDebug(network) << "We turned it back on";
}
qDebug(network) << "You see this because you turn the message on";
return a.exec();
}
```
## QThread
1. 繼承自QThread。
2. 重載`run()`
3. 呼叫`start()`來開始thread。
## QWidget
### 設定signal與對應的slot
1. 在class header裡面加入`Q_OBJECT`
2. 在constructor裡呼叫`connect()`來建立對應關係:`connect(SOURCE_OBJECT, SIGNAL(clicked()), DEST_OBJECT, SLOT(close()))`
### Postion
1. `QRect geometry();` 取得x, y, width, height
2. `setGeometry();` 設定x, y, width, height
3. `x()`, `y()`, `width()`, `height()` 單獨取得x, y, width, height
4. `move(x, y);` 只設定x, y
5. `resize(width, height);` 只設定width, height
### Window state
1. `setWindowState(Qt::WindowMaximized);`
1. `Qt::WindowMaximized` -> `showMaximized()`
2. `Qt::WindowMinimized` -> `showMinimized()`
3. `Qt::WindowNoState` -> `showNormal()`
4. `Qt::WindowFullScreen` -> `showFullScreen()`
### Window Style
1. `CustomizeWindowHint()`
2. `setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint)`
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
3. `setWindowFlag(Qt::WindowCloseButtonHint, false)` // 取消close按鈕
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
4. 沒有邊框的視窗:`setWindowFlags(Qt::FramelessWindowHint)`
5. 透明背景: `setAttribute(Qt::WA_TranslucentBackground, true)`
6. Style sheet: https://doc.qt.io/qt-5/stylesheet-syntax.html#style-rules
### Layout
- QVBoxLayout
```cpp
QVBoxLayout* pVlayout = new QVBoxLayout();
widget->setLayout(pVlayout);
// Add button to layout
QPushButton* button = new QPushButton("My Button");
pVlayout->addWidget(button);
```
- `setContentsMargin()`設定元與layout的距離
- `setSpacing()`:設定元件與元件之間的距離
- QFormLayout
- Iterate all items
```cpp
QFormLayout* layout = (QFormLayout*)this->layout();
for (int i = 0; i < layout->rowCount(); i++) {
QLayoutItem* item = layout->itemAt(i, QFormLayout::FieldRole);
QWidget* widget = (QWidget*)item->widget();
if (widget) {
QString className = widget->metaObject()->className();
if (className == "QLineEdit") {
QLineEdit* edit = (QLineEdit*)widget;
edit->setText("Text changed");
}
} else {
continue;
}
}
```
#### QSizePolicly
- QSizePolicly::GrowFlag必要時可以超過
- QSizePolicly::ExpandFlag放到最大
- QSizePolicly::ShrinkFlag必要時可以小於建議尺寸
- QSizePolicly::IgnoreFlag忽略
## QLabel
- `QLabel.setTextIneractionFlags()`
## QSlider
### Style
#### groove
```
QSlider::groove {
border: 1px solid #999999;
height: 28px;
background: rgba(155, 155, 155, 200);
border-radius: 10px;
}
```
#### handle
```
QSlider::handle {
border: 1px solid #FF0000;
width: 20px;
margin: -10px 0;
background: rgba(255, 0, 0, 200);
border-radius: 10px;
}
```
#### add-page
```
QSlider::add-page {
background: rgba(255, 0, 0, 200);
}
```
#### sub-page
```
QSlider::add-page {
background: rgba(0, 255, 0, 200);
}
```
![[Pasted image 20210318115725.png]]
## QListWidget
- Insert a QLineEdit
```cpp
QLineEdit* lineEdit = new QLineEdit("A line edit here");
ui->listWidget->setItemWidget(new QListWidgetItem("", ui->listWidget), lineEdit);
```
- Insert a QSpinBox
```
QSpinBox* spinBox = new QSpinBox();
ui->listWidget->setItemWidget(new QListWidgetItem("Test item 5", ui->listWidget), spinBox);
```
### Stylesheet example
```
QTableView {
selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FF92BB, stop: 1 white);
}
```
```
QTableView QTableCornerButton::section {
background: red;
border: 2px outset red;
}
```
```
QTableView::indicator:unchecked {
background-color: #d7d6d5
}
```
## QTableWidget
- setColumnCount()
- https://doc.qt.io/qt-5/qtablewidget.html#setColumnCount
- setHorizontalHeaderItem(column_index, new QTableWidgetItem("Header1"))
- https://doc.qt.io/qt-5/qtablewidget.html#setHorizontalHeaderItem
- setColumnWidth(column_index, column_width)
- https://doc.qt.io/qt-5/qtableview.html#setColumnWidth
- Bind Header's event
- `connect(ui.tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(SectionClicked(int)));`
- Style
```
QHeaderView::section:checked{ /* Focus */
background-color: rgba(55, 55, 55,255 );
}
QHeaderView::section:hover{ /* 滑鼠懸停 */
background-color: rgba(85, 10, 10,255 );
}
QTableWidget{
background-color: rgb(27, 122, 239);
alternate-background-color: rgb(26, 81, 232);
color:white;
}
```
## QTreeWidget
- Data create & insert
- `insertTopLevelItem(int index, QTreeWidgetItem*)`
- `addTopLevelItem(QTreeWidgetItem*)`
- `new QTreeWidgetItem(QTreeWidget*)`
- Insert widget
- `void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget)`
- Iterate items
- `QTreeWidgetItem* topLevelItem(int index) const`
- `int topLevelItemCount() const`
- Get item
- `QList<QTreeItemWidget*> selectedItems() const`
- Style
```
/* 有旁節點,沒有與其他元素相鄰 */
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(vline.png) 0;
}
/* 有旁節點,與其他元素相鄰 */
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(branch-more.png) 0;
}
/* 沒有子節點,沒有旁節點,與其他元素相鄰 */
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(branch-end.png) 0;
}
/* 收合狀態,有子節點,沒有旁節點 */
QTreeView::branch:closed:has-children:!has-siblings,
/* 收合狀態,有子節點,有旁節點 */
QTreeView::branch:closed:has-children:has-siblings {
border-image: none; image: url(branch-closed.png);
}
/* 打開狀態,有子節點,沒有旁節點 */
QTreeView::branch:open:has-children:!has-siblings,
/* 打開狀態,有子節點,有旁節點 */
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(branch-open.png);
}
```
## QStatusBar
- `showMessage(QString, int miniSeconds)`: 顯示文字,一段時間後消失
## QToolBar
- `addAction(QIcon, QString)`
- `addAction(QString)`
- `setToolButtonStyle(int)`
## QDockWidget
- https://blog.csdn.net/czyt1988/article/details/51209619
## QJson
- [C++(Qt5)入門範例各種Json範例](https://lolikitty.pixnet.net/blog/post/206254516)
## QFile
Read UTF-8 content from file.
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
## String
### UTF-8
1. 對於n bytes的字元第一個byte的前n個bits是1第n+1個bit是0第2個byte以後都以`10`開頭。
1. 例:
2. 1 byte character: 0x0XXXXXXX。
3. 2 bytes character: 0x110XXXXXX 0x10XXXXXX。
4. 3 bytes character: 0x1110XXXXX 0x10XXXXXX 0x10XXXXXX。
5. 4 bytes character: 0x11110XXXX 0x10XXXXXX 0x10XXXXXX 0x10XXXXXX。
2. 例:
1. 「A」是一個1 byte字元編碼為`0x01000001`。
2. 「Á」是一個2 bytes字元編碼為`0x11000011 0x10000001`。
3. 「好」是一個3 bytes字元編碼為`0x11100101 0x10100101 0x10111101`。
3. [UTF-8 to binary轉換網站](https://onlineutf8tools.com/convert-utf8-to-binary)
2. BOM
1. Big endian: `FE FF`
2. Little endian: `FF FE`
### QString
- 16 bits QChar(unsigned short), UNIDODE 4.0
- `==`: compare string
- `isNull()`: If has a value
- `isEmpty()`: If contained value == ""
- `QString::number()`: 接收一個數字,把數字轉為字串。
- `QString::toInt()`、`QString::toDouble()`把字串轉為Int或Double。
- 讀取UTF-8的字串
```
codec = QTextCodec::codecFromName("UTF-8");
QTextCodec::setCodecForLocale(codec);
```
然後用`QString::fromLocal8Bit()`來讀取字串該字串就會被當作UTF-8來處理。
- `QTextCodec::availableCodecs()`可以列出QT所支援的格式。
- 在Visual Studio中強制文件用UTF-8來存檔`#pragma execution_character_set("UTF-8")`
- 在Visual Studio中預設是以CP950存檔所以字串**都不是**unicode而是**multi-character**所以要從Windows讀進來的字串一律需要使用`QString::fromLocal8Bit("")`而一旦轉為QString之後就是UTF-8了若需要再輸出回Windows則需要`QString.toLocal8Bit().toStdString()`。
- 例:
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
- Visual Studio 2017 debug QString: [调试时直接显示QString的字符值包含windows和linux](https://blog.csdn.net/iamdbl/article/details/78343642)
#### Search
- `QString.indexOf()`
- `QString.lastIndexOf()`
#### Trim
- `QString.chop(5)`去掉結尾的5個字元inplace轉換沒有回傳新字串。
- `QString.left(5)`取最左邊5個字元
- `QString.right(5)`取最右邊5個字元
#### Replace
- `QString.replace("original", "new")`:將"original"替換為"new"。
- 也支援regular expression
#### Split
- `QString.split(",")`:用","把字串分割,並回傳一個`QStringList`。
#### Regular Expression
- `.`匹配任意1個字元
- `+`此類型的匹配字元可以1個以上
- `*`:匹配任意多個字元
- `^`:表示開始位置
- `$`:表示結束位置
- `?`:表示前一個匹配條件不一定要成立
- `[]`:用括號來限制要匹配的字元
## Event
- 如果有特殊需求,可以重載`QWidget::event(QEvent*)`來達成。
### MouseEvent
- Get coordinate of component: `QMouseEvent::x()` & `QMouseEvent::y()`
- Get coordinate of window: `QMouseEvent::windowPos().x()` & `QMouseEvent::windowPos().y()`
- Get coordinate of screen: `QMouseEvent::screenPos().x()` & `QMouseEvent::screenPos().y()`
- `QPoint mapToGlobal(QPoint)`: Translates the widget coordinate pos to global screen coordinates.
- `QCursor::pos().x()` & `QCursor::pos().x()`: 也可以獲得screen coordinate。
- Get click
```
if (mouseEvent->buttons() & Qt::LeftButton) // or Qt::RightButton, Qt::MiddleButton
{
}
```
### MouseCursor
- 設定cursor click的位置`QCursor cursor = QCursor(pixmap, -1, -1)`
- (-1, -1) 表示用cursor中心點為click點
- ( 0, 0) 表示用cursor左上角為click點
- 更換cursor`setCursor(Qt::ArrowCursor)`
### ResizeEvent
- `void resizeEvent(QResizeEvent*)`
## QOpenGLWidget
- `void paintGL()`
- `void initialGL()`
- `void resizeGL(int w, int h)`
## QGLShaderProgram
- `addShaderFromSourceCode`
- `bindAttributeLocation`:設定傳入的變數
- `uniformLocation`:取得變數
## GL概念
### Vertex
#### Coordinate
```
float *vertexData = new float[12] {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
};
```
### Texture
#### Coordinate
材質座標永遠都在第一象限
```
float* textureVertexData = new float[8] {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
```
#### 建立材質
- `glGenTextures(1, t);`
- `glBindTexture(GL_TEXTURE_2D, *t);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);`
#### 寫入材質
- `glActiveTexture(GL_TEXTURE0);`
- `glBindTexture(GL_TEXTURE_2D, idY);`
- `glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixelW, pixelH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]);`
```
glTexImage2D(
GL_TEXTURE_2D,
0, // 細節預設0
GL_LUMINANCE, // GPU內部格式
pixelW,
pixelH,
0,
GL_LUMINANCE, // Pixel format
GL_UNSIGNED_BYTE, // Data format
plane[0]);
```
- `glTexSubImage2D()`修改texture
- `glUniform1i(textureUniformY, 0);`
- `glDrawArray(GL_TRIANGLE_STRIP, 0, 4);`
## GLSL
### 內建變數
- `gl_FragColor`
### 3種變數類型
- `varying`vertex與shader共用vertex運算完之後傳給shader使用。
- `attribute`vertex使用由`bindAttributeLocation`傳入
- `uniform`由user傳入的資料由`uniformLocation`得到pointer address再由`glUniform1i(textureUniformY, 0)`來設定。
### Vertex Shader
```
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main() {
gl_position = vertexIn;
textureOut = textureIn;
}
```
### Fragment Shader
```
varying vec2 textureOut;
uniform sampler2D texY;
uniform sampler2D texU;
uniform sampler2D texV;
void main() {
vec3 yuv;
vec3 rgb;
yuv.x = textue2D(texY, textureOut).r;
yuv.y = textue2D(texU, textureOut).r - 0.5;
yuv.z = textue2D(texV, textureOut).r - 0.5;
rgb = mat3(
1, 1, 1,
0, -0.39465, 2.03211,
1.13983, -0.5806, 0) * yuv;
gl_FragColor = vec4(rgb, 1);
}
```
### APIs
畫出矩形與材質
- `glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);`
- `glEnableVertexAttribArray(ATTRIB_VERTEX);`
- `glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);`
- `glEnableVertexAttribArray(ATTRIB_TEXTURE);`
# 檔案操作
## 開啟檔案對話框
```c++
QString selectedFilepath = QFileDialog::getOpenFileName(
this,
tr("Select test plan file"), // 檔案選擇對話框左上角的提示
tr("."), // 檔案選擇對話框一開始的路徑
"(*.json);;All files(*.*)"); // 可選擇的副檔名
```
使用者所選擇的檔名會放在`selectedFilepath`,可以用`selectedFilepath.isEmpty()`來判斷使用者是不是真的有選擇檔案或是按了取消鍵。
## 存檔對話框
```c++
QString fileName = QFileDialog::getSaveFileName(
this,
tr("Save Address Book"), "", // 檔案選擇對話框左上角的提示
tr("Address Book (*.abk);;All Files (*)")); // 存檔的副檔名
```
# MISC
## 教學文
- [Qt教程Qt5编程入门教程非常详细](http://c.biancheng.net/qt/)

View File

@@ -0,0 +1,734 @@
## Prepare
### Windows 10 SDK
下載並安裝[Windows 10 SDK](https://developer.microsoft.com/zh-tw/windows/downloads/windows-10-sdk/),安裝
**Debugging Tools for Windows**就可以了。
![[chrome_20210309_110223_934x685.png]]
然後在QtCreator裡面設置Debugger。
![[NVIDIA_Share_20210309_110738_1920x1080.png]]
### Visual Studio 2017
- ![[Pasted image 20210312144218.png]]
- ![[Pasted image 20210312144242.png]]
- ![[Pasted image 20210312144405.png]]
- ![[Pasted image 20210312144523.png]]
## PRO file 語法
### 導入lib
```
QT += <lib> <lib2>
```
### 導入include path
```
INCLUDEPATH += <INCLUDE_PATH>
```
若使用相對路徑必須相對於makefile生成的路徑。
也可以使用`$$PWD`來生成絕對路徑,例:
```
INCLUDEPATH += $$PWD/../../include
```
> 可以用`message()`來印出變數:`message($$PWD)`
### 導入lib
`-L`指定library path`-l`指定libraray name。例
```
LIBS += -L"../../lib" -lopencv_world320
```
### 指定執行目錄
```
DESTDIR += ../../bin
```
### 指定執行檔檔名
```
TARGET = <NEW_FILENAME>
```
- Import QT pro file into Visual Studio.
- `qmake -tp vc <QT_PROJECT_FILE>`
- 使用VS工具import。
## 基本類別class
### QIODevice
An abstract class that wraps input and output for most devices
### QDataStream
依序寫入的資料也可以依序被讀出來。資料會被編碼過所以用Text editor會看到亂碼。
### QTextStream
可以依序寫入字串也可以依序被讀出來因為寫入的是字串所以用Text editor就可以看到資料。
### QLockFile
創造一個在操作時可以獨佔的檔案。避免race condition的情形。
```cpp
QLockFile lock(fileName() +".lock");
lock.setStaleLockTime(30000);
if(lock.tryLock()) {
...
lock.unlock();
} else {
qInfo() << "Could not lock the file!";
qint64 pid;
QString host;
QString application;
if(lock.getLockInfo(&pid,&host,&application)) {
qInfo() << "The file is locked by:";
qInfo() << "Pid: " << pid;
qInfo() << "Host: " << host;
qInfo() << "Application: " << application;
} else {
qInfo() << "File is locked, but we can't get the info!";
}
}
```
### QtMessageHandler
可以用來重新導向或攔截`qInfo()``qDebug()``qWarning()``qCritical()``qFatal()`這些function
```cpp
const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
void lhandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
QString txt;
switch (type) {
case QtInfoMsg:
txt = QString("Info: %1 in %2").arg(msg);
break;
case QtDebugMsg:
txt = QString("Debug: %1").arg(msg);
break;
case QtWarningMsg:
txt = QString("Warning: %1").arg(msg);
break;
case QtCriticalMsg:
txt = QString("Critical: %1").arg(msg);
break;
case QtFatalMsg:
txt = QString("Fatal: %1").arg(msg);
break;
}
...
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInstallMessageHandler(lhandler);
qInfo() << "This is a info message";
qDebug() << "This is a debug message";
qWarning() << "This is a warning message";
qCritical() << "This is a critical message";
qFatal("THIS IS SPARTA!!!");
return a.exec();
}
```
也可以把這機制封裝在class裡面
```cpp
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QDebug>
#include <QFile>
#include <QDateTime>
#include <QDir>
#include <iostream>
#include <QTextStream>
class logger : public QObject
{
Q_OBJECT
public:
explicit logger(QObject *parent = nullptr);
static bool logging;
static QString filename;
static void attach();
static void handler(QtMsgType type, const QMessageLogContext &context, const QString & msg);
signals:
public slots:
};
#endif // LOGGER_H
```
```cpp
// logger.cpp
#include "logger.h"
QString logger::filename = QDir::currentPath() + QDir::separator() + "log.txt";
bool logger::logging = false;
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
logger::logger(QObject *parent) : QObject(parent)
{
}
void logger::attach()
{
logger::logging = true;
qInstallMessageHandler(logger::handler);
}
void logger::handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
// Here, write log message to file
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
```
### Logging category
Logging category可以用來把輸出的log前面加上一個字串以便分類。
```cpp
#include <QCoreApplication>
#include <QDebug>
#include <QLoggingCategory>
//Declare a logging category
Q_DECLARE_LOGGING_CATEGORY(network);
Q_LOGGING_CATEGORY(network, "network");
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInfo() << "test"; // stdout: "test"
qInfo(network) << "test"; // stdout: "network: test"
//turn it off
QLoggingCategory::setFilterRules("network.debug=false");
//Will not log
qDebug(network) << This message will not shown;
if(!network().isDebugEnabled()) {
QLoggingCategory::setFilterRules("network.debug=true");
qDebug(network) << "We turned it back on";
}
qDebug(network) << "You see this because you turn the message on";
return a.exec();
}
```
## QThread
1. 繼承自QThread。
2. 重載`run()`
3. 呼叫`start()`來開始thread。
## QWidget
### 設定signal與對應的slot
1. 在class header裡面加入`Q_OBJECT`
2. 在constructor裡呼叫`connect()`來建立對應關係:`connect(SOURCE_OBJECT, SIGNAL(clicked()), DEST_OBJECT, SLOT(close()))`
### Postion
1. `QRect geometry();` 取得x, y, width, height
2. `setGeometry();` 設定x, y, width, height
3. `x()`, `y()`, `width()`, `height()` 單獨取得x, y, width, height
4. `move(x, y);` 只設定x, y
5. `resize(width, height);` 只設定width, height
### Window state
1. `setWindowState(Qt::WindowMaximized);`
1. `Qt::WindowMaximized` -> `showMaximized()`
2. `Qt::WindowMinimized` -> `showMinimized()`
3. `Qt::WindowNoState` -> `showNormal()`
4. `Qt::WindowFullScreen` -> `showFullScreen()`
### Window Style
1. `CustomizeWindowHint()`
2. `setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint)`
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
3. `setWindowFlag(Qt::WindowCloseButtonHint, false)` // 取消close按鈕
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
4. 沒有邊框的視窗:`setWindowFlags(Qt::FramelessWindowHint)`
5. 透明背景: `setAttribute(Qt::WA_TranslucentBackground, true)`
6. Style sheet: https://doc.qt.io/qt-5/stylesheet-syntax.html#style-rules
### Layout
- QVBoxLayout
```cpp
QVBoxLayout* pVlayout = new QVBoxLayout();
widget->setLayout(pVlayout);
// Add button to layout
QPushButton* button = new QPushButton("My Button");
pVlayout->addWidget(button);
```
- `setContentsMargin()`設定元與layout的距離
- `setSpacing()`:設定元件與元件之間的距離
- QFormLayout
- Iterate all items
```cpp
QFormLayout* layout = (QFormLayout*)this->layout();
for (int i = 0; i < layout->rowCount(); i++) {
QLayoutItem* item = layout->itemAt(i, QFormLayout::FieldRole);
QWidget* widget = (QWidget*)item->widget();
if (widget) {
QString className = widget->metaObject()->className();
if (className == "QLineEdit") {
QLineEdit* edit = (QLineEdit*)widget;
edit->setText("Text changed");
}
} else {
continue;
}
}
```
#### QSizePolicly
- QSizePolicly::GrowFlag必要時可以超過
- QSizePolicly::ExpandFlag放到最大
- QSizePolicly::ShrinkFlag必要時可以小於建議尺寸
- QSizePolicly::IgnoreFlag忽略
## QLabel
- `QLabel.setTextIneractionFlags()`
## QSlider
### Style
#### groove
```
QSlider::groove {
border: 1px solid #999999;
height: 28px;
background: rgba(155, 155, 155, 200);
border-radius: 10px;
}
```
#### handle
```
QSlider::handle {
border: 1px solid #FF0000;
width: 20px;
margin: -10px 0;
background: rgba(255, 0, 0, 200);
border-radius: 10px;
}
```
#### add-page
```
QSlider::add-page {
background: rgba(255, 0, 0, 200);
}
```
#### sub-page
```
QSlider::add-page {
background: rgba(0, 255, 0, 200);
}
```
![[Pasted image 20210318115725.png]]
## QListWidget
- Insert a QLineEdit
```cpp
QLineEdit* lineEdit = new QLineEdit("A line edit here");
ui->listWidget->setItemWidget(new QListWidgetItem("", ui->listWidget), lineEdit);
```
- Insert a QSpinBox
```
QSpinBox* spinBox = new QSpinBox();
ui->listWidget->setItemWidget(new QListWidgetItem("Test item 5", ui->listWidget), spinBox);
```
### Stylesheet example
```
QTableView {
selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FF92BB, stop: 1 white);
}
```
```
QTableView QTableCornerButton::section {
background: red;
border: 2px outset red;
}
```
```
QTableView::indicator:unchecked {
background-color: #d7d6d5
}
```
## QTableWidget
- setColumnCount()
- https://doc.qt.io/qt-5/qtablewidget.html#setColumnCount
- setHorizontalHeaderItem(column_index, new QTableWidgetItem("Header1"))
- https://doc.qt.io/qt-5/qtablewidget.html#setHorizontalHeaderItem
- setColumnWidth(column_index, column_width)
- https://doc.qt.io/qt-5/qtableview.html#setColumnWidth
- Bind Header's event
- `connect(ui.tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(SectionClicked(int)));`
- Style
```
QHeaderView::section:checked{ /* Focus */
background-color: rgba(55, 55, 55,255 );
}
QHeaderView::section:hover{ /* 滑鼠懸停 */
background-color: rgba(85, 10, 10,255 );
}
QTableWidget{
background-color: rgb(27, 122, 239);
alternate-background-color: rgb(26, 81, 232);
color:white;
}
```
## QTreeWidget
- Data create & insert
- `insertTopLevelItem(int index, QTreeWidgetItem*)`
- `addTopLevelItem(QTreeWidgetItem*)`
- `new QTreeWidgetItem(QTreeWidget*)`
- Insert widget
- `void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget)`
- Iterate items
- `QTreeWidgetItem* topLevelItem(int index) const`
- `int topLevelItemCount() const`
- Get item
- `QList<QTreeItemWidget*> selectedItems() const`
- Style
```
/* 有旁節點,沒有與其他元素相鄰 */
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(vline.png) 0;
}
/* 有旁節點,與其他元素相鄰 */
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(branch-more.png) 0;
}
/* 沒有子節點,沒有旁節點,與其他元素相鄰 */
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(branch-end.png) 0;
}
/* 收合狀態,有子節點,沒有旁節點 */
QTreeView::branch:closed:has-children:!has-siblings,
/* 收合狀態,有子節點,有旁節點 */
QTreeView::branch:closed:has-children:has-siblings {
border-image: none; image: url(branch-closed.png);
}
/* 打開狀態,有子節點,沒有旁節點 */
QTreeView::branch:open:has-children:!has-siblings,
/* 打開狀態,有子節點,有旁節點 */
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(branch-open.png);
}
```
## QStatusBar
- `showMessage(QString, int miniSeconds)`: 顯示文字,一段時間後消失
## QToolBar
- `addAction(QIcon, QString)`
- `addAction(QString)`
- `setToolButtonStyle(int)`
## QDockWidget
- https://blog.csdn.net/czyt1988/article/details/51209619
## QJson
- [C++(Qt5)入門範例各種Json範例](https://lolikitty.pixnet.net/blog/post/206254516)
## QFile
Read UTF-8 content from file.
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
## String
### UTF-8
1. 對於n bytes的字元第一個byte的前n個bits是1第n+1個bit是0第2個byte以後都以`10`開頭。
1. 例:
2. 1 byte character: 0x0XXXXXXX。
3. 2 bytes character: 0x110XXXXXX 0x10XXXXXX。
4. 3 bytes character: 0x1110XXXXX 0x10XXXXXX 0x10XXXXXX。
5. 4 bytes character: 0x11110XXXX 0x10XXXXXX 0x10XXXXXX 0x10XXXXXX。
2. 例:
1. 「A」是一個1 byte字元編碼為`0x01000001`。
2. 「Á」是一個2 bytes字元編碼為`0x11000011 0x10000001`。
3. 「好」是一個3 bytes字元編碼為`0x11100101 0x10100101 0x10111101`。
3. [UTF-8 to binary轉換網站](https://onlineutf8tools.com/convert-utf8-to-binary)
2. BOM
1. Big endian: `FE FF`
2. Little endian: `FF FE`
### QString
- 16 bits QChar(unsigned short), UNIDODE 4.0
- `==`: compare string
- `isNull()`: If has a value
- `isEmpty()`: If contained value == ""
- `QString::number()`: 接收一個數字,把數字轉為字串。
- `QString::toInt()`、`QString::toDouble()`把字串轉為Int或Double。
- 讀取UTF-8的字串
```
codec = QTextCodec::codecFromName("UTF-8");
QTextCodec::setCodecForLocale(codec);
```
然後用`QString::fromLocal8Bit()`來讀取字串該字串就會被當作UTF-8來處理。
- `QTextCodec::availableCodecs()`可以列出QT所支援的格式。
- 在Visual Studio中強制文件用UTF-8來存檔`#pragma execution_character_set("UTF-8")`
- 在Visual Studio中預設是以CP950存檔所以字串**都不是**unicode而是**multi-character**所以要從Windows讀進來的字串一律需要使用`QString::fromLocal8Bit("")`而一旦轉為QString之後就是UTF-8了若需要再輸出回Windows則需要`QString.toLocal8Bit().toStdString()`。
- 例:
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
- Visual Studio 2017 debug QString: [调试时直接显示QString的字符值包含windows和linux](https://blog.csdn.net/iamdbl/article/details/78343642)
#### Search
- `QString.indexOf()`
- `QString.lastIndexOf()`
#### Trim
- `QString.chop(5)`去掉結尾的5個字元inplace轉換沒有回傳新字串。
- `QString.left(5)`取最左邊5個字元
- `QString.right(5)`取最右邊5個字元
#### Replace
- `QString.replace("original", "new")`:將"original"替換為"new"。
- 也支援regular expression
#### Split
- `QString.split(",")`:用","把字串分割,並回傳一個`QStringList`。
#### Regular Expression
- `.`匹配任意1個字元
- `+`此類型的匹配字元可以1個以上
- `*`:匹配任意多個字元
- `^`:表示開始位置
- `$`:表示結束位置
- `?`:表示前一個匹配條件不一定要成立
- `[]`:用括號來限制要匹配的字元
## Event
- 如果有特殊需求,可以重載`QWidget::event(QEvent*)`來達成。
### MouseEvent
- Get coordinate of component: `QMouseEvent::x()` & `QMouseEvent::y()`
- Get coordinate of window: `QMouseEvent::windowPos().x()` & `QMouseEvent::windowPos().y()`
- Get coordinate of screen: `QMouseEvent::screenPos().x()` & `QMouseEvent::screenPos().y()`
- `QPoint mapToGlobal(QPoint)`: Translates the widget coordinate pos to global screen coordinates.
- `QCursor::pos().x()` & `QCursor::pos().x()`: 也可以獲得screen coordinate。
- Get click
```
if (mouseEvent->buttons() & Qt::LeftButton) // or Qt::RightButton, Qt::MiddleButton
{
}
```
### MouseCursor
- 設定cursor click的位置`QCursor cursor = QCursor(pixmap, -1, -1)`
- (-1, -1) 表示用cursor中心點為click點
- ( 0, 0) 表示用cursor左上角為click點
- 更換cursor`setCursor(Qt::ArrowCursor)`
### ResizeEvent
- `void resizeEvent(QResizeEvent*)`
## QOpenGLWidget
- `void paintGL()`
- `void initialGL()`
- `void resizeGL(int w, int h)`
## QGLShaderProgram
- `addShaderFromSourceCode`
- `bindAttributeLocation`:設定傳入的變數
- `uniformLocation`:取得變數
## GL概念
### Vertex
#### Coordinate
```
float *vertexData = new float[12] {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
};
```
### Texture
#### Coordinate
材質座標永遠都在第一象限
```
float* textureVertexData = new float[8] {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
```
#### 建立材質
- `glGenTextures(1, t);`
- `glBindTexture(GL_TEXTURE_2D, *t);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);`
#### 寫入材質
- `glActiveTexture(GL_TEXTURE0);`
- `glBindTexture(GL_TEXTURE_2D, idY);`
- `glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixelW, pixelH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]);`
```
glTexImage2D(
GL_TEXTURE_2D,
0, // 細節預設0
GL_LUMINANCE, // GPU內部格式
pixelW,
pixelH,
0,
GL_LUMINANCE, // Pixel format
GL_UNSIGNED_BYTE, // Data format
plane[0]);
```
- `glTexSubImage2D()`修改texture
- `glUniform1i(textureUniformY, 0);`
- `glDrawArray(GL_TRIANGLE_STRIP, 0, 4);`
## GLSL
### 內建變數
- `gl_FragColor`
### 3種變數類型
- `varying`vertex與shader共用vertex運算完之後傳給shader使用。
- `attribute`vertex使用由`bindAttributeLocation`傳入
- `uniform`由user傳入的資料由`uniformLocation`得到pointer address再由`glUniform1i(textureUniformY, 0)`來設定。
### Vertex Shader
```
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main() {
gl_position = vertexIn;
textureOut = textureIn;
}
```
### Fragment Shader
```
varying vec2 textureOut;
uniform sampler2D texY;
uniform sampler2D texU;
uniform sampler2D texV;
void main() {
vec3 yuv;
vec3 rgb;
yuv.x = textue2D(texY, textureOut).r;
yuv.y = textue2D(texU, textureOut).r - 0.5;
yuv.z = textue2D(texV, textureOut).r - 0.5;
rgb = mat3(
1, 1, 1,
0, -0.39465, 2.03211,
1.13983, -0.5806, 0) * yuv;
gl_FragColor = vec4(rgb, 1);
}
```
### APIs
畫出矩形與材質
- `glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);`
- `glEnableVertexAttribArray(ATTRIB_VERTEX);`
- `glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);`
- `glEnableVertexAttribArray(ATTRIB_TEXTURE);`
# 檔案操作
## 開啟檔案對話框
```c++
QString selectedFilepath = QFileDialog::getOpenFileName(
this,
tr("Select test plan file"), // 檔案選擇對話框左上角的提示
tr("."), // 檔案選擇對話框一開始的路徑
"(*.json);;All files(*.*)"); // 可選擇的副檔名
```
使用者所選擇的檔名會放在`selectedFilepath`,可以用`selectedFilepath.isEmpty()`來判斷使用者是不是真的有選擇檔案或是按了取消鍵。
## 存檔對話框
```c++
QString fileName = QFileDialog::getSaveFileName(
this,
tr("Save Address Book"), "", // 檔案選擇對話框左上角的提示
tr("Address Book (*.abk);;All Files (*)")); // 存檔的副檔名
```
# MISC
## 教學文
- [Qt教程Qt5编程入门教程非常详细](http://c.biancheng.net/qt/)

View File

@@ -0,0 +1,2 @@
* 好盈電變調整中立點:[【RC】FLYSKY 富斯NB4 遥控器基本功能使用教程_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1pJ411y7yB/)
* [(28) 陳春期--HB D812 差速器調教教學 - YouTube](https://www.youtube.com/watch?v=dSYQBz3T6gU)

View File

@@ -0,0 +1,134 @@
### 登入
- 帳號:`pi`
- 密碼:`<你自己設的密碼>`
### 設定固定IP
#### Ethernet
打開`/etc/dhcpcd.conf`,設定如下:
```
interface eth0
static ip_address=192.168.1.20/24
static routers=192.168.1.1
static domain_name_servers=168.95.1.1 1.1.1.1
```
#### WIFI
打開`/etc/dhcpcd.conf`,設定如下:
```
interface wlan0
static ip_address=192.168.1.21/24
static routers=192.168.1.1
static domain_name_servers=168.95.1.1 1.1.1.1
```
確定`dhcpcd``networking`是enabled不確定可以直接用下面的命令來啟動。
```shell
systemctl enable dhcpcd ;\
systemctl enable networking
```
### 更新記憶卡容量
- [Taiwan-RaspberryPi | 台灣樹莓派 - (1)更新記憶卡容量](https://www.taiwan-raspberrypi.com/start/setting/1%E6%9B%B4%E6%96%B0%E8%A8%98%E6%86%B6%E5%8D%A1%E5%AE%B9%E9%87%8F/)
### 設定免密碼登入
1. 將自己的public key copy到RaspberryPi上
```
scp ~/.ssh/rpi.pub pi@<IP_ADDR>:~/.ssh/
```
2. 登入到RaspberryPi這一次要輸入密碼
```shell
ssh pi@<IP_ADDR>
```
3. 現在是在RaspberryPi上操作把剛剛的public key加到`authorized_keys`裡面:
```bash
cd ~/.ssh ;\
cat id_rsa.pub >> authorized_keys
```
## 安裝
### Python3
```shell
sudo apt install python3
```
### Docker
#### Install
```shell
# Install some required packages first
sudo apt update ; sudo apt install -y apt-transport-https ca-certificates curl gnupg2 software-properties-common
# Get the Docker signing key for packages
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add -
# Add the Docker official repos
echo "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list
# Install Docker
sudo apt update ; sudo apt install -y --no-install-recommends docker-ce cgroupfs-mount
```
#### Confirm installation
```shell
sudo docker run --rm hello-world
```
如果成功,會有如下的訊息:
![[Pasted image 20210125183754.png]]
### docker-compose
```shell
# Install required packages
sudo apt update ; sudo apt install -y python3-pip libffi-dev
# Install Docker Compose from pip (using Python3)
# This might take a while
sudo pip3 install docker-compose
```
#### Confirm installation
```shell
docker-compose -version
```
會輸出docker-compose的版本類似下面
```
docker-compose version 1.28.0, build unknown
```
## 更新
```shell
sudo apt-get update `# 1. 更新 /etc/apt/sources.list 底下的套件清單。` ;\
sudo apt-get upgrade `# 2. 比對套件清單決定是否需要更新,但如果要更新的套件有相依性問題,則放棄更新。` ;\
sudo apt-get dist-upgrade `# 3. 會處理新版本套件與相依性套件的衝突,並試著安裝/移除有問題的套件來完成更新。` ;\
sudo apt-get autoremove `# 4. 刪除之前因為有相依性而安裝,但現在已經不再使用的套件(非必要)。` ;\
sudo apt-get autoclean `# 5. 清除下載到 /var/cache/apt/archives 的 .deb 套件檔(非必要)` ;\
sudo rpi-update `# 6. 更新核心和韌體到最新版本(可能不穩定),因此更新前一定要先備份重要資料`
```
## 備份
1. 下載必須的工具
```
wget https://raw.githubusercontent.com/Drewsif/PiShrink/master/pishrink.sh
sudo chmod +x pishrink.sh
sudo mv pishrink.sh /usr/local/bin
```
1. 插入隨身碟並將隨身碟mount起來。記住mount的路徑這邊假設是`/mnt/usb0`。
2. 用`lsblk`確認SD卡的device node這邊假設SD卡是`/dev/mmcblk0`。
3. 輸入以下指令將整片microSD卡備份到USB隨身碟
```
sudo dd if=/dev/mmcblk0 of=/mnt/usb0/PiBackup_20220513.img bs=1M
```
這個命令會將整張SD卡dummp到隨身碟SD卡有多大產生的img檔就會有多大。
這個命令會花上好一點時間。
> `dd`命令並不會顯示任何進度或是訊息,若是希望在執行過程中看到一些訊息,可以加入`status=progress`,將可以看到速度的訊息。
> 但是`progress`這個小工具是需要另外安裝的,可以用`sudo apt install progress`來安裝。
> 例:`sudo dd if=/dev/mmcblk0 of=/mnt/usb0/PiBackup_20220513.img bs=1M status=progress`
5. 切換到隨身碟的目錄:`cd /mnt/usb0`。
6. 用以下命令來把img變小
```
sudo pishrink.sh -z PiBackup_20220513.img
```
這個命令也會花上好一點時間。
### 參考
- [[Raspberry Pi] 備份樹莓派系統Micro SD卡的最佳做法_PiShrink @ skybow](https://skybow.pixnet.net/blog/post/121176894-%5Braspberry-pi%5D-%E5%82%99%E4%BB%BD%E6%A8%B9%E8%8E%93%E6%B4%BEsd%E5%8D%A1%E7%9A%84%E6%9C%80%E4%BD%B3%E5%81%9A%E6%B3%95)

View File

@@ -0,0 +1 @@
* [gaoxiang12/slambook](https://github.com/gaoxiang12/slambook)

View File

@@ -0,0 +1,30 @@
## youtube-dl
### List format
```shell
youtube-dl -F <YOUTUBE_URL>
```
e.g.
```shell
youtube-dl -F https://www.youtube.com/watch?v=Pbzn79TSRO0
```
### Download
```shell
youtube-dl -f <FORMAT_ID> <YOUTUBE_URL>
```
### Download video-only & audio-only & merge to MKV
```shell
youtube-dl -f <VIDEO_ONLY_ID>+<AUDIO_ONLY_ID> --merge-output-format mkv <YOUTUBE_URL>
```
e.g.
```shell
youtube-dl -f 313+251 --merge-output-format mkv https://www.youtube.com/watch?v=Pbzn79TSRO0
```
### Download video to MP3
```shell
youtube-dl -i --extract-audio --audio-format mp3 --audio-quality 0 https://www.youtube.com/watch?v=IruVsWrfPqs
```

View File

@@ -0,0 +1,80 @@
# Documentation
- [Introduction - Storj](https://documentation.storj.io/)
## Setup
Pull image
```shell
sudo docker pull storjlabs/storagenode:latest
```
Do this **once**.
```shell
sudo docker run --rm -e SETUP="true" \
--mount type=bind,source="/volume1/homes/awin/Storj/key",destination=/app/identity \
--mount type=bind,source="/volume1/docker/Storj/data",destination=/app/config \
--name storagenode storjlabs/storagenode:latest
```
## Run
```shell
sudo docker run -d --restart unless-stopped --stop-timeout 300 \
-p 28967:28967 \
-p 28967:28967/udp \
-p 14002:14002 \
-e WALLET="0x9Ce80345355Ad8C17991620E13d8423900CEDcd0" \
-e EMAIL="awinhuang@gmail.com" \
-e ADDRESS="awin.myds.me:28967" \
-e STORAGE="2TB" \
--mount type=bind,source="/volume1/homes/awin/Storj/key",destination=/app/identity \
--mount type=bind,source="/volume1/docker/Storj/data",destination=/app/config \
--name storagenode storjlabs/storagenode:latest
```
## Update configuration
### Linux
1. Stop docker container
```shell
sudo docker stop -t 300 storagenode ;\
sudo docker rm storagenode ;\
sudo docker pull storjlabs/storagenode
```
2. Run docker again. Check [[Storj#Run]]. [^1]
3. Or, create a task, and run below command:
```shell
echo "Stop storagenode" ;\
sudo docker stop -t 300 storagenode ;\
sudo docker rm storagenode ;\
echo "Pull storagenode" ;\
sudo docker pull storjlabs/storagenode ;\
echo "Start storagenode" ;\
sudo docker run -d --restart unless-stopped --stop-timeout 300 \
-p 28967:28967 \
-p 14002:14002 \
-e WALLET="0x9Ce80345355Ad8C17991620E13d8423900CEDcd0" \
-e EMAIL="awinhuang@gmail.com" \
-e ADDRESS="awin.myds.me:28967" \
-e STORAGE="2TB" \
--mount type=bind,source="/volume1/homes/awin/Storj/key",destination=/app/identity \
--mount type=bind,source="/volume1/docker/Storj/data",destination=/app/config \
--name storagenode storjlabs/storagenode:latest ;\
sudo docker ps -a
```
## Start/Stop service
### Windows
#### Start service
In PowerShell:
```powershell
Start-Service storagenode
```
#### Stop service
In PowerShell:
```powershell
Stop-Service storagenode
```
[^2]
[^1]: [How do I change values like wallet address or storage capacity? - Storj](https://documentation.storj.io/resources/faq/how-do-i-change-my-parameters-such-as-payout-address-allotted-storage-space-and-bandwidth)
[^2]: [How do I shutdown my node for system maintenance? - Storj](https://documentation.storj.io/resources/faq/system-maintenance)

View File

@@ -0,0 +1,7 @@
## Cache to Ramdisk
1. 關閉 Chrome
2. 開啟檔案總管至以下路徑:`%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default`
3. 刪除 Cache 資料夾
4. 用管理員權限開啟 cmd
5. 輸入以下指令以建立 Symbolic Link: `mklink /d "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\Cache\" "R:\TEMP\Chrome\"`
6. (Optional) 如果有多個使用者的話: `mklink /d "%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Profile 1\Cache\" "R:\TEMP\Chrome\"`

View File

@@ -0,0 +1,274 @@
---
tags:
aliases:
date: 2022-05-26
time: 14:51:12
description:
---
## YAML front matter
官方說明:[YAML front matter - Obsidian Help](https://help.obsidian.md/Advanced+topics/YAML+front+matter)
目前使用的front matter是[[front matter]]。
## 自動產生時間日期
Obsidian在抽入template的時候可以使用`{{date}}`來表示日期,用`{{time}}`來表示時間。
### `{{date}}` & `{{time}}`
`{{date}}`產生的時間格式為`2022-05-26`可以使用參數來更改格式例如不要讓月份與日期自動補0可以用`{{date:YYYY-M-D}}`
時間也一樣,`{{time}}`產生的格式為`14:48:45`,也有其他參數來變更格式,其他更詳細參考[Moment.js | Docs](https://momentjs.com/docs/#/displaying/)。
## Customize CSS
路徑是`<valut_name>\.obsidian\snippets\obsidian.css`
Obsidian預設定義了很多CSS class只要更改這些class就可以定義自己喜歡的外觀。
- [Obsidian自定义样式修改教程 - 知乎](https://zhuanlan.zhihu.com/p/373888121)
- [obsidian-css-snippets](https://github.com/Dmytro-Shulha/obsidian-css-snippets/tree/master/Snippets)
- [使用 CSS 代码片段增强 Obsidian 视觉效果(一) | ReadingHere](https://www.readinghere.com/blog/using-css-snippets-to-enhance-obsidian-visuals-cn/)
## My CSS
The CSS content:
```css
/*************************************************************
* Headers
*/
.cm-header-1, .markdown-preview-section h1 {
color: #081f37;
}
.cm-header-2, .markdown-preview-section h2 {
color: #1e549f;
}
.cm-header-3, .markdown-preview-section h3 {
color: #2e79ba;
}
.cm-header-4, .markdown-preview-section h4 {
color: #5fc9f3;
}
.cm-header-5, .markdown-preview-section h5 {
color: #415865;
}
.cm-header-6, .markdown-preview-section h6 {
color: #7a9eb1;
}
/*************************************************************
* List
*/
.cm-list-1 {
font-family: Cascadia Code;
}
.cm-list-2 {
font-family: Cascadia Code;
}
.cm-list-3 {
font-family: Cascadia Code;
}
.cm-list-4 {
font-family: Cascadia Code;
}
.cm-list-5 {
font-family: Cascadia Code;
}
span.cm-formatting-list-ul {
visibility: hidden !important;
}
span.cm-formatting-list-ul:after {
content: '• '; /* ITS theme; for Blue Topaz */
margin-left: -12px;
color: var(--accent); /* ITS theme; for Blue Topaz --text-normal */
visibility: visible !important;
}
ol {
font-family: Cascadia Code;
padding-inline-start: 2.5rem;
}
ul {
font-family: Cascadia Code;
padding-inline-start: 2.4rem;
list-style-type: disc;
}
/*Make list marker to be a circle*/
input[type="checkbox"],
.cm-formatting-task {
-webkit-appearance: none;
appearance: none;
border-radius: 50%;
border: 1px solid var(--text-faint);
padding: 0;
vertical-align: middle;
}
.cm-s-obsidian span.cm-formatting-task {
color: transparent;
width: 1.1em !important;
height: 1.1em;
display: inline-block;
}
input[type="checkbox"]:focus {
outline: 0;
}
input[type="checkbox"]:checked,
.cm-formatting-task.cm-property {
background-color: var(--text-accent-hover);
border: 1px solid var(--text-accent-hover);
background-position: center;
background-size: 60%;
background-repeat: no-repeat;
background-image: url('data:image/svg+xml; utf8, <svg width="12px" height="10px" viewBox="0 0 12 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(-4.000000, -6.000000)" fill="%23ffffff"><path d="M8.1043257,14.0367999 L4.52468714,10.5420499 C4.32525014,10.3497722 4.32525014,10.0368095 4.52468714,9.8424863 L5.24777413,9.1439454 C5.44721114,8.95166768 5.77142411,8.95166768 5.97086112,9.1439454 L8.46638057,11.5903727 L14.0291389,6.1442083 C14.2285759,5.95193057 14.5527889,5.95193057 14.7522259,6.1442083 L15.4753129,6.84377194 C15.6747499,7.03604967 15.6747499,7.35003511 15.4753129,7.54129009 L8.82741268,14.0367999 C8.62797568,14.2290777 8.3037627,14.2290777 8.1043257,14.0367999"></path></g></g></svg>');
}
/*************************************************************
* External link & Internal link
*/
.cm-s-obsidian span.cm-link,
.cm-s-obsidian span.cm-url,
.cm-s-obsidian span.cm-hmd-internal-link {
color: #0000EE;
font-family: Cascadia Code;
}
a.external-link {
color: #0000EE;
font-family: Cascadia Code;
}
/*************************************************************
* Outline
*/
.outline {
font-size: 0.8rem;
font-weight: 200;
}
.outline .tree-item {
line-height: 1.3;
}
.outline .tree-item-self {
padding-top: 0.2rem;
padding-bottom: 0.1rem;
padding-left: 0.5rem;
padding-right: 0.3rem;
}
.outline .tree-item-collapse {
left: 0.1rem;
}
.outline .tree-item-inner{
position:relative;
padding-top: 0.2rem;
/* padding-left: 1rem; */
padding-left: 1.7em;
text-indent: -0.8em;
margin-left: 0.2rem;
/* font-size: 0.9em; */
}
.outline .tree-item-children {
margin-left: 0.7rem;
padding-left: 0.5rem;
margin-top: -0.3rem;
padding-top: 0.3rem;
border-left: 1px solid var(--sidebar-marks, var(--background-modifier-border));
border-radius: 4px;
transition:all 0.5s ease-in-out;
}
.outline .tree-item-children:hover {
border-left-color: var(--sidebar-marks-hover, var(--background-secondary));
}
.outline .collapse-icon + .tree-item-inner {
font-weight: 400;
padding-left: 0.2rem;
/* margin-left: 0rem; */
/* font-size: 1em; */
}
.outline .collapse-icon {
margin-top: 0.2rem;
margin-left: -0.4rem;
margin-right: -0.4rem;
width: 2rem;
}
/*************************************************************
* Sidebar
*/
/*Vault name*/
.nav-folder.mod-root > .nav-folder-title {
padding-left: 6px;
font-size: 14px;
font-weight: bolder;
top: -6px; /* higher */
cursor: default;
color: var(--base2);
}
.nav-folder-title,
.nav-file-title {
font-size: 0.8em;
font-family: consolas;
line-height: 0.8em;
}
.nav-folder-title {
font-weight: bold;
color: #132743;
}
.nav-file-title {
color: #407088;
}
.nav-folder,
.nav-file {
margin: 0 !important;
border-left: 1px solid rgba(118, 158, 165, 0.2);
}
.cm-quote {
line-height: 1.2em;
/*font-style: italic;*/
}
/*************************************************************
* Quote
*/
.cm-formatting-quote:before {
margin-right: -0.1em;
color: var(--color-10) !important;
font-family: "ico";
content: "\edd5";
}
/*************************************************************
* Code block(inline)
*/
.cm-s-obsidian span.cm-inline-code {
/*font-size: 1em;*/
}
```
## 參考
- [使用 CSS 代码片段增强 Obsidian 视觉效果(一) | ReadingHere](https://www.readinghere.com/blog/using-css-snippets-to-enhance-obsidian-visuals-cn/)
- [obsidian-css-snippets](https://github.com/Dmytro-Shulha/obsidian-css-snippets/tree/master/Snippets)
- [Obsidian自定义样式修改教程 - 知乎](https://zhuanlan.zhihu.com/p/373888121)

View File

@@ -0,0 +1,24 @@
```json
// Settings in here override those in "Default/Preferences.sublime-settings",
// and are overridden in turn by syntax-specific settings.
{
"close_windows_when_empty": true,
"draw_indent_guides": false,
"font_face": "Fira Code",
"font_size": 11,
"highlight_modified_tabs": true,
"ignored_packages":
[
"Vintage"
],
"show_full_path": true,
"show_tab_close_buttons": false,
"spell_check": false,
"tab_size": 4,
"translate_tabs_to_spaces": true,
"use_tab_stops": true,
"draw_white_space": "all",
"trim_trailing_white_space_on_save": true,
"word_separators": "./\\()\"'-:,.;<>~!@#%^&*|+=[]{}`~?"
}
```

View File

@@ -0,0 +1,6 @@
## 讓Virtualbox與Hyper-V並存
用Administrator打開Powershell輸入以下指令
```
cd "C:\Program Files\Oracle\VirtualBox"
./VBoxManage.exe setextradata global "VBoxInternal/NEM/UseRing0Runloop" 0
```

View File

@@ -0,0 +1,328 @@
# 快速鍵
## 顯示快速鍵列表
- Windows: `Ctrl + k` + `Ctrl + s`
- Mac: `⌘ + k` + `⌘ + s`
## 分割目前視窗
- Windows: `Ctrl + \`
- Mac: `⌘ + \`
## 程式格式化
### 格式化整個文件
`Shift + Alf + f`
### 格式化選取的範圍
`Ctrl + k` + `Ctrl + f`
### setting.json
- `"editor.formatOnType": true`:輸入一行後,自動格式化目前這一行。
- `"editor.formatOnSave": true`:儲存時格式化檔案。
- `"editor.formatOnPaste": true`:貼上程式碼時格式化貼上的內容。
```json
{
"python.terminal.activateEnvironment": true, // 自動啟動環境
"python.linting.pylintEnabled": true, // 需要 pip install pylint
"python.linting.enabled": true,
"python.autoComplete.addBrackets": true, // 自動為自動完成的function加上括號
"python.languageServer": "Pylance",
"python.analysis.completeFunctionParens": true,
"jupyter.sendSelectionToInteractiveWindow": true,
"jupyter.interactiveWindowMode": "perFile",
"terminal.integrated.fontFamily": "Fira Code", // For Windows
"terminal.integrated.profiles.windows": {
"Command Prompt": {
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [],
"icon": "terminal-cmd"
},
"Git Bash": {
"source": "Git Bash"
},
"PowerShell -NoProfile": {
"source": "PowerShell",
"args": ["-NoProfile"]
}
},
"terminal.integrated.defaultProfile.windows": "Command Prompt",
"terminal.integrated.cwd": "${fileDirname}",
// "editor.fontFamily": "Cascadia code Light",
// "editor.fontFamily": "PragmataPro Liga",
// "editor.fontFamily": "Fira Code Retina",
"editor.fontFamily": "Iosevka Expanded",
// "editor.fontFamily": "Hasklig Light",
"editor.fontSize": 14,
"editor.fontLigatures": true,
"editor.fontWeight": "normal",
"editor.minimap.renderCharacters": false,
"editor.renderWhitespace": "boundary",
"editor.renderIndentGuides": false,
"editor.insertSpaces": true,
"editor.tabSize": 4,
"editor.bracketPairColorization.enabled": true,
"outline.showVariables": false,
"sync.gist": "aaee0ee8733879ef2da2eb1b4bf8a993",
"sync.quietSync": false,
"sync.removeExtensions": true,
"sync.syncExtensions": true,
"sync.autoDownload": false,
"sync.autoUpload": false,
"sync.forceDownload": false,
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"explorer.confirmDelete": false,
"workbench.colorTheme": "One Dark Pro",
"workbench.iconTheme": "material-icon-theme",
"workbench.editorAssociations": {
"*.ipynb": "jupyter.notebook.ipynb"
},
"oneDarkPro.vivid": true,
"gitlens.advanced.messages": {
"suppressImproperWorkspaceCasingWarning": true
},
"workbench.colorCustomizations": {
"editor.lineHighlightBackground": "#1073cf2d",
"editor.lineHighlightBorder": "#9fced11f"
},
"editor.wordWrap": "off",
"diffEditor.wordWrap": "off",
"editor.guides.indentation": false,
"editor.guides.bracketPairs": false,
}
```
## 折疊程式碼
### 收起目前區塊
- Windows: `Ctrl + Shift + [`
- Mac: `⌥ + ⌘ + [`
### 打開目前區塊
- Windows: `Ctrl + Shift + ]`
- Mac: `⌥ + ⌘ + ]`
### 收起目前區塊(整個檔案)
- Windows: `Ctrl + (K => 0) (zero)`
- Mac: `⌘ + (K => 0) (zero)`
### 打開目前區塊(整個檔案)
- Windows: `Ctrl + (K => J) `
- Mac: `⌘ + (K => J)`
## 在「已開啟的檔案」間跳轉
`Ctrl + tab`
# Plugin
## Setting Sync
- 參考:[https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync](https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync)
~~- GIST Token: `e0f7c5233e3c6dafee77047f61ea74f0d01d24e1`~~
- GIST Token: `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`
- GIST ID: [`aaee0ee8733879ef2da2eb1b4bf8a993`](https://gist.github.com/AwinHuang/aaee0ee8733879ef2da2eb1b4bf8a993)
- GIST address: [https://gist.github.com/AwinHuang/aaee0ee8733879ef2da2eb1b4bf8a993](https://gist.github.com/AwinHuang/aaee0ee8733879ef2da2eb1b4bf8a993)
# Code snippets
## html.json
```json
{
"HTML template": {
"prefix": "HTML_template",
"body": [
"<!doctype html>",
"",
"<html lang=\"en\">",
"<head>",
" <meta charset=\"utf-8\">",
" <meta name=\"description\" content=\"Awin's HTML template\">",
" <meta name=\"author\" content=\"Awin Huang\">",
" <script src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js\"></script>",
" <title>A HTML template</title>",
"</head>",
"",
"<body>",
" <H1>Awin's HTML template</H1>",
" <p>Click to hide</p>",
"</body>",
"</html>",
"",
"<script>",
" $(document).ready(function(){",
" $(\"p\").click(function(){",
" $(this).hide();",
" });",
" });",
"</script>",
],
"description": "HTML template"
}
}
```
## python.json
```json
{
"Python template": {
"prefix": "Python_Template",
"body": [
"import sys",
"import argparse",
"",
"",
"def main(args=None):",
" ${1:pass}",
"",
"",
"if __name__ == '__main__':",
" parser = argparse.ArgumentParser()",
" parser.add_argument(\"first_file\", help=\"The first file\")",
" parser.add_argument(\"-s\", \"--sample_args\", default=\"sample_args\", help=\"Modify this arguments for you\")",
" args = parser.parse_args()",
" main(args)",
" sys.exit(0)"
],
"description": "Python script template"
},
"F Print": {
"prefix": "f-print",
"body": [
"print(f'$1 = {$1}')"
],
"description": "print() with f-string and default {}"
},
"Q Print": {
"prefix": "q-print",
"body": [
"print('$1 = {}, '.format($1))"
],
"description": "print() with f-string and default {}"
},
"Debug RobotRun": {
"prefix": "debug_robotrun",
"body": [
"import os",
"import sys",
"sys.path.insert(0, 'D:/codes/logitech/')",
"import RobotRun",
"print('+------------------------------------------------------------------------------+')",
"print('| |')",
"print('| RobotRun: {}'.format(RobotRun.__file__))",
"print('| |')",
"print('+------------------------------------------------------------------------------+')",
],
"description": "Change RobotRun to local version"
},
"Flask template": {
"prefix": "Flask_Template",
"body": [
"## Flask template",
"## Author: Awin Huang",
"## Date: 2020/04/09",
"",
"import os, sys",
"import datetime",
"import json",
"from flask import Flask, render_template, request",
"",
"",
"app = Flask(__name__)",
"",
"## Setup log",
"handler = logging.FileHandler('flask.log', delay=False)",
"handler.setLevel(logging.INFO)",
"app.logger.addHandler(handler)",
"app.logger.setLevel(logging.INFO)",
"",
"",
"def info_log(msg):",
" app.logger.info(msg)",
" # print(msg)",
"",
"",
"def error_log(msg):",
" app.logger.error(msg)",
"",
"",
"@app.route('/')",
"def index():",
" info_log('Return main page to user.')",
" return 'Hello, this is main page'",
"",
"",
"## Receive a GET request",
"@app.route('/get_get', methods=['GET'])",
"def run_testcase():",
" command = request.args.get('command')",
" value = 'This is value for GET'",
" return {",
" 'command': command,",
" 'value': value",
" }",
"",
"",
"## Receive a POST request",
"@app.route('/get_post', methods=['POST'])",
"def get_post():",
" command = request.form['command']",
" value = 'This is value for POST'",
" return {",
" 'command': command:",
" 'value': value",
" }",
"",
"",
"if __name__ == '__main__':",
" app.debug = True",
" app.run(host='0.0.0.0')",
],
"description": "Flask template"
},
"Datetime now": {
"prefix": "now_dt",
"body": [
"datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S:%f')"
],
"description": "Get datetime.now() with format '%Y-%m-%d %H:%M:%S:%f'"
},
"Run process": {
"prefix": "runprocess",
"body": [
"import multiprocessing as mp",
"",
"process = mp.Process(target=task_set.run, args=(self.task_sync, args))",
"process.start()",
"process.join()"
],
"description": "Run function in a process"
},
"Sleep with dots": {
"prefix": "sleepdots",
"body": [
"for i in range($1):",
" import time",
" print(\".\", end=\"\", flush=True)",
" time.sleep(1)",
],
"description": "Sleep and print \".\" every second"
},
"Sleep with numbers": {
"prefix": "sleepnum",
"body": [
"for i in range($1):",
" print(f\"{i+1} \", end=\"\", flush=True)",
" time.sleep(1)",
],
"description": "Sleep and print number every second"
},
}
```

View File

@@ -0,0 +1,165 @@
### Install tools
#### 先安裝
- 手動安裝[Google Drive](https://www.google.com/drive/download/),以取得本檔案。
#### 自動安裝
1. 安裝[Chocolatey](https://chocolatey.org/)用Administrator身份打開powershell輸入下列指令
```
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
```
2. 用安裝常用的工具
```
choco install 7zip adobereader vscode hxd sublimetext4 microsoft-windows-terminal androidstudio intellijidea-community git winmerge freefilesync freedownloadmanager gsudo firacode cascadiacode sourcecodepro --yes
```
列表:
- 7zip
- adobereader
- vscode
- hxd
- [sublimetext4](https://community.chocolatey.org/packages/sublimetext4/4.0.0.412100)
- microsoft-windows-terminal
- androidstudio
- [intellijidea-community](https://chocolatey.org/packages/intellijidea-community)
- git
- winmerge
- [freefilesync](https://chocolatey.org/packages/freefilesync)
- [freedownloadmanager](https://chocolatey.org/packages/FreeDownloadManager)
- [gsudo](https://community.chocolatey.org/packages/gsudo)
- [firacode](https://community.chocolatey.org/packages/FiraCode)
- [cascadiacode](https://community.chocolatey.org/packages/cascadiacode)
- [sourcecodepro](https://community.chocolatey.org/packages/SourceCodePro)
移除:
- [TeraCopy](https://chocolatey.org/packages/TeraCopy)
- googlechrome
- make
#### 手動安裝
1. Google drive
2. Google drive(Logitech)
3. Python 3.6.3
4. Python 3.9
6. Visual Studio 2017
7. Visual Studio 2019
8. Office 365
9. Lightroom
10. [Enpass](https://www.enpass.io/)
11. [ShareX](https://getsharex.com/)
12. [win32diskimager](https://sourceforge.net/projects/win32diskimager/)
13. [卡巴斯基](https://www.kaspersky.com.tw/)
#### Portable App
1. Aegisub portable
2. Audacity 2.3.3
3. Cheat Engine 7.0
4. cmder v1.3.12
5. ConEmu
6. ConvertZZ.1.0.0.3
7. CrystalDiskMark 6.0.1 x64
8. EzMeta
9. ffmpeg-2020-09-20-full_build
10. FileZillaPortable
11. Geek Uninstaller 1.4.7
12. HxDPortable
13. ImgBurnPortable
14. IntelliJ IDEA
15. JDownloader 2.0
16. PhraseExpress
17. Process Explorer 16.21
18. Q-Dir 9.01
19. Rufus
20. Sandboxie
21. [Speccy](https://www.ccleaner.com/speccy)
22. [ThunderbirdPortable](https://portableapps.com/apps/internet/thunderbird_portable)
23. [WindowGrid 1.3.11](http://windowgrid.net/)
24. [wiztree_3_35_portable](https://wiztreefree.com/download)
### Upgrade
#### Upgrade by Chocolately
```
choco upgrade all -y
```
### Setup
#### Setup doskey in **Command Prompt**
1. 切換到`Document`資料夾。
2. 建立`cmdinit.cmd`,內容如下:
```
@echo off
doskey sl="C:\Program Files\Sublime Text 3\sublime_text.exe"
doskey ll=dir
doskey rrp="cd C:\Python363\lib\site-packages\RobotRun" $T C:
doskey rra=cd "G:\My Drive\codes\Projects\RobotRunApplications" $T G:
doskey gpull=git pull origin master
doskey gpush=git push origin master
doskey gs=git status
doskey gd=git diff
doskey e.=explorer.exe .
```
#### Setup bashrc in **Git bash**
1. 打開`~/.bashrc`
2. 內容如下:
```bash
export PATH="/c/Users/ahuang11/AppData/Local/Android/Sdk/platform-tools:$PATH"
alias adb="/c/EasyAVEngine/Tools/Android/ADB/adb.exe"
alias sl="/c/Program\ Files/Sublime\ Text/subl.exe"
alias fastboot='/c/Users/ahuang11/AppData/Local/Android/Sdk/platform-tools/fastboot.exe'
alias rrp='cd /c/Python363/lib/site-packages/RobotRun'
alias rra='cd /c/RobotRun'
alias rrd='cd /g/My\ Drive/codes/Projects/RobotRunDocs'
alias rro='cd /c/RobotRun/Output'
alias coderrp='cd "/c/Python363/lib/site-packages/RobotRun" ; code "/c/Python363/lib/site-packages/RobotRun"'
alias coderra='cd "/c/RobotRun"; code "/c/RobotRun"'
alias coderras='code "/d/GoogleDriveLogi/codes/Projects/RobotRunAutoServer"'
alias ctest='code "/g/My Drive/codes/test"'
alias ad='adb devices'
alias fd='fastboot devices'
alias e.='explorer.exe .'
##----- Connection -----
alias gods='ssh awin@192.168.1.11'
alias gorp320='ssh pi@192.168.1.20'
##----- Git -----
alias gs="git status"
alias gd="git diff"
alias gpull='git pull origin master'
alias gpush='git push origin master'
alias gpushmain='git push origin main'
alias gc='git clone'
alias gclogi='git clone --config user.name="Awin Huang" --config user.email=ahuang11@logitech.com $@'
##----- Python enviroment swich -----
alias pyv='echo PY_PYTHON=$PY_PYTHON'
function set_py() {
echo "Original Python verison is \"$PY_PYTHON\""
export PY_PYTHON=$1
echo " New Python verison is \"$PY_PYTHON\""
if [ ! -z "$2" ]
then
py "${@:2}"
fi
}
function py36() {
set_py "3.6.3" "$@"
}
function py39() {
set_py "3.9" "$@"
}
```
#### Setup Windows Terminal
1. 開啟Windows Terminal。
2.`ctrl + ,`打開設定,之後參考[[Windows Terminal]]。
#### Setup WSL2
- [[安裝筆記] Windows 10 安裝 Linux 子系統 (WSL2) | Kenmingの鮮思維](http://www.kenming.idv.tw/note_window10_install_wsl2/)
- [[安裝筆記] Windows 10 WSL 2 安裝 Docker Desktop (含更改 Docker Image 路徑) | Kenmingの鮮思維](http://www.kenming.idv.tw/win10_wsl2_install_docker-desktop/)

View File

@@ -0,0 +1,159 @@
## Setup
### New tab as Administrator
- [使用系統管理員身分開啟 Windows Terminal 分頁](https://blog.poychang.net/run-windows-terminal-as-administrator-with-elevated-admin-permissions/)
### Use powerline in Git-Bash
- [Light & simple powerline theme for Git bash for windows](https://github.com/diesire/git_bash_windows_powerline)
#### Install
```shell
cd $HOME
mkdir -p .bash/themes/git_bash_windows_powerline
git clone https://github.com/diesire/git_bash_windows_powerline.git .bash/themes/git_bash_windows_powerline
```
And add following lines to `~/.bashrc`.
```
# Theme
THEME=$HOME/.bash/themes/git_bash_windows_powerline/theme.bash
if [ -f $THEME ]; then
. $THEME
fi
unset THEME
```
參考:
- [powerline/fonts: Patched fonts for Powerline users.](https://github.com/powerline/fonts)
## Settings.json
```json
// This file was initially generated by Windows Terminal 1.6.10571.0
// It should still be usable in newer versions, but newer versions might have additional
// settings, help text, or changes that you will not see unless you clear this file
// and let us generate a new one for you.
// To view the default settings, hold "alt" while clicking on the "Settings" button.
// For documentation on these settings, see: https://aka.ms/terminal-documentation
// This file was initially generated by Windows Terminal 1.2.2381.0
// It should still be usable in newer versions, but newer versions might have additional
// settings, help text, or changes that you will not see unless you clear this file
// and let us generate a new one for you.
// To view the default settings, hold "alt" while clicking on the "Settings" button.
// For documentation on these settings, see: https://aka.ms/terminal-documentation
{
"$schema": "https://aka.ms/terminal-profiles-schema",
"defaultProfile": "{00000000-0000-0000-ba54-000000000002}",
// You can add more global application settings here.
// To learn more about global settings, visit https://aka.ms/terminal-global-settings
// If enabled, selections are automatically copied to your clipboard.
"copyOnSelect": false,
// If enabled, formatted data is also copied to your clipboard
"copyFormatting": false,
// Start position
"initialCols": 205,
"initialRows": 30,
"initialPosition": "15,400", // x,y
// A profile specifies a command to execute paired with information about how it should look and feel.
// Each one of them will appear in the 'New Tab' dropdown,
// and can be invoked from the commandline with `wt.exe -p xxx`
// To learn more about profiles, visit https://aka.ms/terminal-profile-settings
"profiles":
{
"defaults":
{
// Put settings here that you want to apply to all profiles.
},
"list":
[
{
// Make changes here to the powershell.exe profile.
"guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
"name": "Windows PowerShell",
"commandline": "powershell.exe",
"hidden": false
},
{
"guid": "{41dd7a51-f0e1-4420-a2ec-1a7130b7e950}",
"name": "Windows PowerShell(Administrator)",
"commandline": "gsudo.exe powershell.exe",
"hidden": false,
"colorScheme": "Solarized Dark",
"fontFace": "Fira Code",
"icon" : "C:\\Users\\awinh\\OneDrive\\圖片\\icon\\console_red.png"
},
{
// Make changes here to the cmd.exe profile.
"guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}",
"name": "Command Prompt",
"commandline": "cmd.exe",
"hidden": false
},
{
"guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}",
"name": "Azure Cloud Shell",
"source": "Windows.Terminal.Azure",
"hidden": false,
},
{
"guid": "{00000000-0000-0000-ba54-000000000002}",
"name" : "Bash",
"commandline": "%PROGRAMFILES%/git/usr/bin/bash.exe -i -l",
"icon": "%PROGRAMFILES%/Git/mingw64/share/git/git-for-windows.ico",
"startingDirectory" : "D:\\GoogleDrive\\codes",
"fontFace" : "Fira Code",
"fontSize" : 11,
"historySize" : 9000,
},
]
},
// Add custom color schemes to this array.
// To learn more about color schemes, visit https://aka.ms/terminal-color-schemes
"schemes": [],
// Add custom keybindings to this array.
// To unbind a key combination from your defaults.json, set the command to "unbound".
// To learn more about keybindings, visit https://aka.ms/terminal-keybindings
"keybindings":
[
// Copy and paste are bound to Ctrl+Shift+C and Ctrl+Shift+V in your defaults.json.
// These two lines additionally bind them to Ctrl+C and Ctrl+V.
// To learn more about selection, visit https://aka.ms/terminal-selection
{ "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" },
{ "command": "paste", "keys": "ctrl+v" },
// Press Ctrl+Shift+F to open the search box
{ "command": "find", "keys": "ctrl+shift+f" },
// Press Alt+Shift+D to open a new pane.
// - "split": "auto" makes this pane open in the direction that provides the most surface area.
// - "splitMode": "duplicate" makes the new pane use the focused pane's profile.
// To learn more about panes, visit https://aka.ms/terminal-panes
{ "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" },
// Full screen
{ "command": "toggleFullscreen", "keys": "alt+x" },
// Open new default tab
{ "command": "newTab", "keys": "ctrl+t" },
// Close current pane
{ "command": "closePane", "keys": "ctrl+w" },
// Split pane in vertical
{ "command": { "action": "splitPane", "split": "vertical"}, "keys": "ctrl+shift+E" },
// Split pane in horizontal
{ "command": { "action": "splitPane", "split": "horizontal"}, "keys": "ctrl+shift+O" },
// Goto next tab
{ "command": "nextTab", "keys": "ctrl+pagedown" },
// Goto previous tab
{ "command": "prevTab", "keys": "ctrl+pageup" },
]
}
```
## Reference
- [Windows Terminal 美化 for WSL 2 Ubuntu (zsh + zim + powerlevel10k)](http://www.kenming.idv.tw/windows-terminal-%e7%be%8e%e5%8c%96-for-wsl-2-ubuntu-zsh-zim-powerlevel10k/)

View File

@@ -0,0 +1,5 @@
1. ![[Pasted image 20210321201359.png]]
2. ![[Pasted image 20210321201503.png]]
參考:
- [FreeFileSync: RealTimeSync - YouTube](https://www.youtube.com/watch?v=9KXo6yOhTWo)

View File

@@ -0,0 +1,27 @@
### `~/.vimrc`
```vim
set t_Co=256
colorscheme koehler
set nocompatible
syntax on
set showmode
set showcmd
set encoding=utf-8
set cindent
set expandtab
set tabstop=4
set softtabstop=4
set shiftwidth=4
set number
set cursorline
"set textwidth=80
set ruler
set showmatch
set hlsearch
set incsearch
set ignorecase
```

View File

@@ -0,0 +1,24 @@
## 準備
1. 新增一台container先更新container
```
apt update ;\
apu upgrade -y ;\
apt install curl vim -y
```
2. 安裝trojan
```
mkdir -p ~/trojan ;\
cd ~/trojan
curl -O https://raw.githubusercontent.com/atrandys/trojan/master/trojan_mult.sh ; chmod +x trojan_mult.sh ; ./trojan_mult.sh
```
3. ![trojan](https://lab.twidc.net/wp-content/uploads/2020/12/2-1.jpg)
4. ![3 1](https://lab.twidc.net/wp-content/uploads/2020/12/3-1.jpg)
5. ![4 1](https://lab.twidc.net/wp-content/uploads/2020/12/4-1.jpg)
6. ![5 1](https://lab.twidc.net/wp-content/uploads/2020/12/5-1.jpg)
-----
參考:
1. [Trojan 上網架設(替代VPN)爬梯子 科學上網 利器 - TWIDC](https://lab.twidc.net/trojan-%E4%B8%8A%E7%B6%B2%E6%9E%B6%E7%BD%AE%E6%9B%BF%E4%BB%A3vpn/)
2. [Trojan搭建私人VPN - Lingme](https://lingmin.me/2020/03/03/TrojanVPS/)
3. [[转载+修改]使用Trojan-Go科学上网 - 996 RIP](https://typecho.996.rip/index.php/archives/20/)
4. Source code: [Releases · trojan-gfw/trojan](https://github.com/trojan-gfw/trojan/releases)

View File

@@ -0,0 +1,130 @@
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52b6c5b4b26a40bf88dd85a54a441ef0~tplv-k3u1fbpfcp-watermark.image)
玩轉設計模式 - 看懂UML類圖
================
前言
--
作為設計模式系列的第一篇我準備先分享下如何看懂UML類圖看完這篇文章你就能看懂類圖中各個類之間的關係以及線條、箭頭都代表什麼意思。 同時,我們會將類圖所表達的含義和最終的代碼實現對應起來; 有了這些知識,看後面章節的設計模式結構圖就沒有什麼問題了。
先看一個例子
------
下面是我畫的一張類圖使用的工具是StarUMLMac版本[下載地址](https://macwk.com/soft/staruml)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/63e4f3342c734005bff326e52adf45c7~tplv-k3u1fbpfcp-watermark.image)
- 數碼是斜體的,表示數碼是一個抽象類
- 數碼有兩個繼承類,分別是手機和電腦,他們之間的關係是**實現**關係,用帶空心箭頭的虛線表示
- iPhone與手機之間也是繼承關係他們之間的關係是**泛化**關係,用帶空心箭頭的實線表示
- 手機由屏幕和主板等組合而成,他們之間的關係是**組合**關係,用帶實心菱形的實線表示
- 程序員上班必須要用電腦,他們之間的關係是**依賴**關係,用帶箭頭的虛線表示
- 程序員與公司之間是**聚合**關係,用帶空心菱形的實線表示
- 程序員一般都會有自己的工牌,他們之間的關係是**關聯**關係,用實線表示
以上例子和圖中我都把類的六種關係特別標註出來了,下面我們具體講講這六種關係
類之間的關係
------
類的繼承結構表現在UML中為**實現**和**泛化**
繼承關係為 is-a的關係兩個對象之間如果可以用 is-a 來表示,就是繼承關係
### 實現Realization
“數碼”是一個抽象概念在現實中並不能直接用來定義對象必須指明它的子類比如手機和電腦。“數碼”這個類在java中可以用接口或者抽象類表示在C++中用抽象類表示。
代碼實現:實現關係表現為繼承抽象類。
### 泛化Generalization
手機在現實中有實現可以用來定義具體的對象iphone是手機的子類手機和iphone的關係為泛化關係。
代碼實現:泛化關係表現為繼承非抽象類。
### 聚合Aggregation
聚合表示兩個對象之間是整體和部分的**弱關係,生命週期不同步**,部分的生命週期可以超越整體,比如程序員和公司,公司倒閉了,但程序員還在,還可以去其他公司。
代碼實現:聚合關係以**成員變量**的形式實現,只是成員變量的賦值時機是在**類方法**裡,代碼如下:
```java
//第一種是在set方法裡直接設置子類的實例
class Company {
private Programmer programmer;
public void setProgrammer(JavaProgrammer javaProgrammer) {
programmer = javaProgrammer;
}
}
//第二種方式是在set方法裡直接寫死直接定義一個初始值
class Company {
private Programmer programmer;
public void setProgrammer() {
programmer = new JavaProgrammer();
}
}
複製代碼
```
### 組合Composition
組合表示兩個對象之間是整體和部分的**強關係,生命週期一致**,部分的生命週期不能超越整體,或者說組合中的整體不能缺少部分。就像手機不能缺少屏幕和主板一樣。
代碼實現:組合關係以**成員變量**的形式實現,只是成員變量的賦值時機是在**類構造方法**裡,代碼如下:
```java
//手機由屏幕組成
class Phone {
private Screen screen;
//創建手機的時候同時創建屏幕
public Phone() {
screen = new Screen();
}
}
複製代碼
```
### 關聯Association
關聯表示的是兩個不同對象之間存在的固定的對應關係,**它是一種靜態關係,和運行狀態無關**。
代碼實現:關聯關係也是以成員變量的形式實現,只是成員變量的賦值時機是在聲明這個變量的時候,代碼如下:
```java
//程序員都會有工牌
class Programmer {
private Card card = new Card();
}
複製代碼
```
### 依賴Dependency
依賴表示的是**一個對象在運行期間會用到另一個對象的關係**,與關聯關係不同的是,它是一種臨時性的關係,通常在**運行期間**產生,並且隨著運行時的變化,依賴關係也可能發生變化。比如程序員在工作的時候會用到電腦,程序員是依賴電腦的。
代碼實現:依賴關係主要可以通過**方法參數、方法局部變量、靜態方法**三種方式實現,方法參數代碼如下:
```java
class Programmer {
//程序員工作的時候需要用到電腦
public void work(Computer computer) {
computer.work();
}
}
複製代碼
```
總結
--
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e419db264b724e899767abb2adb5aed9~tplv-k3u1fbpfcp-watermark.image)
-----
- [玩轉設計模式 - 看懂UML類圖](https://juejin.cn/post/6914946238756421639)

View File

@@ -0,0 +1 @@
- [自建最強科學上網5+V2Ray + Caddy + Tls + HTTP/2 - 美博園](https://allinfa.com/v2ray-caddy-tls-http2-plus.html)

View File

@@ -0,0 +1 @@
- [技术面试最后反问面试官的话](https://github.com/yifeikong/reverse-interview-zh)

View File

@@ -0,0 +1,25 @@
## Container
### list container
`docker ps`會列出執行中的container但是停止的不會
```bash
sudo docker ps
```
如果也要列出已停止的container
```bash
sudo docker ps -a
```
### 刪除container
Container必須是停止狀態才可以刪除
```bash
sudo docker rm <CONTAINER_ID>
```
## Image
### list images
```bash
sudo docker image ls
or
sudo docker images
```

View File

@@ -0,0 +1,4 @@
frp是一個可用於內網穿透的高性能的反向代理應用可以作為兩個內網機器通過公網IP進行橋接的橋樑。通過其支持各種服務和傳輸協議我們就可以實現一系列遠程控制操作。frp的 Github 主頁上也用結構圖解釋了具體的工作原理,感興趣的話可以前往了解。)
- [用开源免费的内网穿透工具 frp实现远程桌面和文件传输 - 少数派](https://sspai.com/post/60852)

View File

@@ -0,0 +1,36 @@
#### apply
`git diff` 生出一個 diff 檔,而 `git apply` 把這個 diff 檔 apply 到某個 branch 上。
```
git diff ${A_COMMIT_HASH} ${B_COMMIT_HASH} > xxx.patch
git apply xxx.patch
```
- 如果在 `git apply` 的過程中遇到 trailing whitespace error 的話,可以參考這篇文章:[git - My diff contains trailing whitespace - how to get rid of it? - Stack Overflow](https://stackoverflow.com/questions/14509950/my-diff-contains-trailing-whitespace-how-to-get-rid-of-it),透過加入 `--whitespace=warn``--whitespace=nowarn` 參數來解決。
- [^1]
[^1]: [(2018 iThome 鐵人賽) Day 11: 使用 Git 時如何做出跨 repo 的 cherry-pick - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天](https://ithelp.ithome.com.tw/articles/10194972)
### Trouble shooting
- 如果在Windows上git clone遇到例如`error: invalid path``fatal: unable to checkout working tree`
```
$ gclogi git@github.com-logi:LogiVideoFW/VC_Bolide_TableHub.git
Cloning into 'VC_Bolide_TableHub'...
remote: Enumerating objects: 159, done.
remote: Counting objects: 100% (159/159), done.
remote: Compressing objects: 100% (134/134), done.
remote: Total 85001 (delta 71), reused 84 (delta 21), pack-reused 84842
Receiving objects: 100% (85001/85001), 599.21 MiB | 6.18 MiB/s, done.
Resolving deltas: 100% (17824/17824), done.
error: invalid path 'zynqLNX/kernel-source/drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.c'
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry with 'git restore --source=HEAD :/'
```
有可能是因為檔名與NTFS規則有所衝突解法
```
cd <REPO_NAME>
git config core.protectNTFS false; git reset --hard HEAD
```

View File

@@ -0,0 +1,27 @@
### `~/.vimrc`
```vim
set t_Co=256
colorscheme koehler
set nocompatible
syntax on
set showmode
set showcmd
set encoding=utf-8
set cindent
set expandtab
set tabstop=4
set softtabstop=4
set shiftwidth=4
set number
set cursorline
"set textwidth=80
set ruler
set showmatch
set hlsearch
set incsearch
set ignorecase
```

View File

@@ -0,0 +1,11 @@
- 初聞不知曲中意,再聽已是曲中人。
- 讀書,就是要先將厚書讀薄,再將薄書讀厚。
- 種一棵樹最好的時候,一個是過去,一個是現在 - dead aid by Dambisa Moyo [^1] ^901833
- [[20201224 - 寫作是最好的自我投資#^d7f87c|葉勝陶先生:「語言是有聲無形的文章,文章是有形無聲的語言。」]]
- [[20201224 - 寫作是最好的自我投資#^fad99d|蘇格拉底:「未經審查的人生沒有價值。」]]
- [[20201224 - 寫作是最好的自我投資#^fd93cf|「專業,二十一世紀你唯一的生存之道。」 - 大前研一]]
- [[20201224 - 寫作是最好的自我投資#^a00bcf|「任何一個好產品都是聰明人用笨功夫做出來的」 - 咪蒙]]
- [[20201224 - 寫作是最好的自我投資#^7e896d|「天才的唯一秘密,就在於刻意練習,用自己一套系統性的方法,不斷突破自己的邊界」 - 刻意練習,安德斯.艾瑞克森]]
[^1]: [“種一棵樹最好的時間是十年前,其次是現在”出自哪裡?](https://zhidao.baidu.com/question/652202353537726525.html)

View File

@@ -0,0 +1,12 @@
##### 2020-11-16 - 披薩,謙謙
##### 2020-11-23 - 汕頭火鍋Jiachi
##### 2020-11-30 - 瓦城Awin
##### 2020-12-07 - 福星Jiachi
##### 2020-12-13 - 21世紀睿睿 ^98f39d
##### 2020-12-21 - Coco謙謙 ^a0b44c
##### 2020-12-28 - 翰林茶坊Awin
##### 2021-01-04 - 一風堂老婆、謙謙、燒肉丼飯Awin、睿睿 ^347d91
##### 2021-01-14 - Coco睿睿
##### 2021-01-18 - 貝里尼披薩,謙謙
##### 2021-02-22 - 三合院Awin
##### 2021-03-02 - CocoJiachi

View File

@@ -0,0 +1,2 @@
# 介紹文
- [VisuAlgo - visualising data structures and algorithms through animation](https://visualgo.net/en?fbclid=IwAR1b6wbTcgKJg3T14ehAPMpHO_QWvnj1evMxrshrAvqukHH2ZPXJUWgAvd4)

View File

@@ -0,0 +1,2 @@
- [GitHub - Qv2ray/Qv2ray: Linux / Windows / macOS 跨平台 V2Ray 客户端 | 支持 VMess / VLESS / SSR / Trojan / Trojan-Go / NaiveProxy / HTTP / HTTPS / SOCKS5 | 使用 C++ / Qt 开发 | 可拓展插件式设计](https://github.com/Qv2ray/Qv2ray)
- [Project V · Project V 官方网站](https://www.v2ray.com/)

View File

@@ -0,0 +1,111 @@
## Installation
### Install on Synology NAS
1. 從[synology-wireguard release](https://github.com/runfalk/synology-wireguard/releases)下載對應的SPKDS1513+是WireGuard-cedarview-1.0.20200729.spk。若不知道該下載哪一個版本可以查看[這個對照表](https://www.synology.com/en-global/knowledgebase/DSM/tutorial/Compatibility_Peripherals/What_kind_of_CPU_does_my_NAS_have)。
2. 在套件中心裡面手動安裝
3. 用SSH登入
### Install on Ubuntu 20.04
安裝: `sudo apt install wireguard resolvconf`
1. 打開firewall port
```
sudo ufw allow 50100/udp
```
2. 打開port forwarding
`sudo vim /etc/sysctl.conf`
然後加入這一行,存檔離開
`net.ipv4.ip_forward=1`
套用
`sudo sysctl -p`
## Setup Wireguard
1. Make a folder to store key and config
```
mkdir ~/wireguard ; cd ~/wireguard
```
2. 生成server的private/public key: `wg genkey | tee server_privateKey | wg pubkey > server_publicKey`
3. 在`/etc/wireguard`裡面,建立`wg0.conf`,如下:
```
[Interface]
Address = 10.0.0.1/24
ListenPort = 50100
PrivateKey = 8EELc7SWYbZswluhP0ZEzSkTAINXLlXqdE8J34eak3g=
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s3 -j MASQUERADE
SaveConfig = true
DNS = 8.8.8.8
# Awin
[Peer]
PublicKey = dB9l0rROSHyp3C6Odykdu69sU1k7XrOEa33ibx10I00=
AllowedIPs = 10.0.0.2/32
# Dean
[Peer]
PublicKey = N8kOoy3x4rsM1XDekrzLVQJ7Eo9Cb/vcQ07btzEK41Q=
AllowedIPs = 10.0.0.3/32
```
注意文中的`[Interface]`中的`PrivateKey`要替換成你自己生成的key可以用`cat server_privateKey`與`cat server_publicKey`來取得。
4. 生成user private/public key: `wg genkey | tee <user_name>_privateKey | wg pubkey > <user_name>_publicKey`
1. 例如要給awin的key: `wg genkey | tee awin_privateKey | wg pubkey > awin_publicKey`
5. 建立user的config例如給awin的config:
建立`awin.conf`,內容如下:
```
[Interface]
PrivateKey = OBN3ORMdpaz7pHTSlkyCXHvgLTbXnmB2kxJTCyrr3F4=
Address = 10.0.0.2/24
DNS = 8.8.8.8
[Peer]
PublicKey = 15Sy2MRW1yKWLzA03MciOkR7qvpxSXfmQtkMj9xOzj0=
AllowedIPs = 0.0.0.0/0, ::0/0
Endpoint = vpn.awin.one:50100
```
6. 把user config生成QR code方便掃描:
- `sudo grep -v '^#' /etc/wireguard/<user_name>.conf | qrencode -t ansiutf8`
- `qrencode -t ansiutf8 < <user_name>.conf`
- 兩個都可以
7. 重啟Wireguard
```
sudo wg-quick up wg0; \
sleep 5; \
sudo wg-quick down wg0; \
sleep 5; \
sudo wg-quick up wg0
```
另一個:
`sudo wg-quick down wg0 ; sudo cp ./wg0.conf /etc/wireguard/wg0.conf ; sudo wg-quick up wg0 ; sudo wg show wg0`
8. 查看Wireguard狀態: `sudo wg`
## Troubleshooting
That will tell you whether your packets are reaching the remote server, or if they're not getting through the tunnel.
- On the remote server: `sudo tcpdump -i wg0`
- On local machine: `ping -c1 <server_ip>`
## Helper
寫了一個script來copy config這樣就可以在Windows直接編輯。
```
#!/bin/env bash
sudo cp /volume1/homes/awin/Temp/wg0.conf .
sudo cp /volume1/homes/awin/Temp/awin.conf .
sudo cp /volume1/homes/awin/Temp/dean.conf .
sudo wg-quick down wg0
sleep 5
sudo wg-quick up wg0
sleep 5
sudo wg-quick down wg0
sleep 5
sudo wg-quick up wg0
```
----------
參考資料:
- https://github.com/runfalk/synology-wireguard
- https://notes.wadeism.net/linux/680/
- [『Atrandys』wireguard配置文件讲解 | 配置多用户 - YouTube](https://www.youtube.com/watch?v=X4doKJmjE4o&feature=youtu.be)

View File

@@ -0,0 +1,183 @@
### 1. 熱情這回事
- 大量練習可以改變大腦(讓你學會你以為你永遠不會的事)
- 光憑熱情,芭芭拉也沒有太多選擇。
- 來一場「圖畫散步」。 ^57c7a7
- 千萬不要堅持在閱讀下一頁(或下一章)的時候,就一定要搞懂現在正在讀的東西。
- 大概瀏覽整本書的內容,了解其大綱與結構,可以幫助你更快組織好你準備理解的資訊。
### 2. 慢慢來
- 思考有兩種:「專注模式」、「發散模式」 ^1c4ddf
- 專注模式 ^23ddb6
- 專注模式會「打開」某些腦部位(取決於你學什麼)。
- 要成功解決問題,需要**專注模式**。
- 發散模式
- 腦部放鬆和空閒的狀態。
- 可以幫助你進行概念之間的連結。
- 休息可以讓你有解決問題的新觀點。
- 在最有精神的時候,先從最難的科目開始讀。
### 3. 我待會兒就做,真的!
- 拖延會痛苦(島葉皮質或開始作用)
- 但行動20分鐘之後島葉皮質就會平靜下來。也就是說「做就對了不要拖延。」
- 番茄鐘工作法 ^c6b5d8
- 關閉一切會讓你分心的事物。
- 計時25分鐘。
- 在這25分鐘內專注於你的任務。
- 休息一下,犒賞一下自己。(但是不可以太久喔)
- 在這25分鐘內要是發現自己分心了就趕快把自己拉回來分心是正常的就是因為你會分心才需要番茄鐘工作法嘛
- 要是時間到了,覺得自己想繼續努力,那就繼續吧,但別忘了但一段落之後休息與犒賞自己。
- 食砷者的故事,每天吃一點毒,看似無害,但傷害卻仍然慢慢累積。拖延也是一樣,幾分鐘幾分鐘的累積,日子一久就變成難以挽回的後果。
- 「積極回想」,在閱讀之下,移開視線或閉上眼睛,回想一下剛剛學習的內容,或是講出來也可以,愈是覺得困難的部分,就愈要常使用這方法。 ^da7fba
### 4. 腦連結與太空異形
![[neuron.png]]
- 神經元愈頻繁的傳訊息給另一個神經元,兩者間的連結就變得愈牢固。
- 神經元愈多,突觸愈強大,腦連結也就愈強大。 ^7ac072
- 神經元是用不完的!
- 比喻可以讓你更快理解事物。 ^6acb4a
- 練習可以強化腦連結。
### 5. 老師書桌的另一邊
這一章由艾爾當主角艾爾也是個「文科生」他的理科也不好當他聽到芭芭拉說我們可以重塑阿腦線路的時候他決定重新學習化學。這章述說了42歲的他如何一起重當學生學化學的故事。
艾爾使用的方法有:
- 番茄鐘工作法每次努力個25分鐘然後休息。第3章的[[20201201 - 學習如何學習#^c6b5d8|番茄鐘工作法]]
- 對別人解釋自己學到的東西。艾爾是對著他的狗說話。
- 努力回想課程學到的東西。第3章的[[20201201 - 學習如何學習#^da7fba|積極回想]]
- 不會的就請教別人。
- 交錯。
- 大致翻閱每一個章節弄清楚接下來的內容。第1章的[[20201201 - 學習如何學習#^57c7a7|圖畫散步]]
- 腦補一些搞笑畫面。第4章的[[20201201 - 學習如何學習#^6acb4a]]
很多技巧是前面幾個章節就提到的。
本章佳句:
- 這章有提到之前已經紀錄的佳句:[[名言佳句#^901833|種樹最好的時機是二十年前,其次是現在]]
### 6. 邊睡邊學
- 科學家楊光發現神經元的確會改變。
- 睡覺可以鞏固腦連結,讓神經的[[20201201 - 學習如何學習#^7ac072|突觸]]生長。
- 但是沒用到的又會消失喔。
- 每天學習(或複習)可以讓腦連結更強,讓你更容易回想與記憶。
- 所以,很重要:**「每天複習比一口氣讀很久來的重要,也讓自己不那麼累」**。
- 腦中的房間是用不完的,**學就對了**。
### 7. 書包、置物櫃和注意力章魚
- 工作記憶
- 就像書包,小,但是拿取快速
- 工作記憶可以比喻成一隻「注意力章魚」有四隻手表示它能一次抓住4個東西這也說明了為什麼我們沒辦法一次處理太多事情。
- 每個人的「注意力章魚」擁有的手臂數量不一樣,反正不要讓自己一次處理太多事情。
- 注意力章魚住在大腦的「前額葉皮質」這地方。
- [[20201201 - 學習如何學習#^23ddb6|專注模式]]才能讓注意力章魚好好工作。
- 長期記憶: ^13eaf6
- 就像置物櫃,可以放很多東西,但是距離遠,也不容易搜尋。
- 擁有無限空間
- 但是必須透過練習才能好好地取用資料。
### 8. 強化記憶
- [[20201201 - 學習如何學習#^13eaf6|長期記憶]]就像置物櫃,要在置物櫃貼一張圖很容易,要把在外面的牙膏擠回去則很難。
- 事實就像牙膏,記圖像比記憶事實容易。
- 事實是抽象的,無法輕易描繪,因此也難以保存。
- 五個記憶訣竅
1. 專注:愈常練習你的注意力,就愈能保持專注
2. 練習
3. 將事物圖像化
4. 儲存:關聯!將這些訊息與你已經知道的事物關聯起來,很誇張也沒關係
5. 回想、回想、再回想:[[20201201 - 學習如何學習#^da7fba|積極回想]]
### 9. 腦連結
- 透過練習可以建立「腦連結」,不同的腦連結可以互相連結,讓我們的工作記憶可以迅速處理資訊。專家在其領域都有大量且堅固的腦連結組,這也是為什麼專家可以快速而且有效率的處理資訊。
- 刻意練習可以建立更強固的腦連結組。
- 不停的切換「腦連結組」,會讓注意力章魚非常疲累,這也是為什麼我們應該避免分心和任務切換。
- 認知負荷有它的限度。如果工作記憶有太多的資訊需要處理,我們就會陷入困惑,很難將他們弄明白,這是「資訊超載」。
- 學習任務的最初階段,通常都是最困難的。有時你不喜歡某些事,是因為你還沒精通它們。
### 10. 社群共學,發覺你的使命
- 這章由泰瑞.索諾斯基講他的學習經驗
### 11. 鍛鍊大腦
- 睡覺時,我們學過的東西會由海馬迴轉移到大腦皮層。
- 而大腦皮層負責長期記憶。
- 也就是:「睡眠可以幫助學習」。
- 運動有助於神經元的生長。運動時,大腦會產生 brain-derived neurotrophic factor腦源性神經營養因子簡稱BDNF。
- 尤里烏斯.葉戈可以成為冠軍不只是觀看大量學習影片,他也做了大量練習。
### 12. 形成腦連結
- 腦連結組是一條充分練習的思維路徑。
- 創造腦連結的方式
1. 刻意練習:[[20201201 - 學習如何學習#9 腦連結]]
2. 交錯:[[20201201 - 學習如何學習#5 老師書桌的另一邊]]
3. 專注
- 如果一直練習你本來就會的東西,那就不是刻意練習,而是懶惰學習,這對學習沒有幫助。
### 13. 問自己重要的問題
- 要觀察自己。譬如說對於**讀書應不應該聽音樂**這件事,即使科學家也沒法告訴你好或是不好,但是你可以自己實驗看看,哪一種對你比較有益。
- 在不同的地方念書。不要讓你的「注意力章魚」過於習慣一個地方,換個地方念書,你的「注意力章魚」不會習慣於特定地方,變成總是從你的「長期記憶」來拿取東西,這有助於建立你腦連結。
- 多元感官學習。試著用其他方式學習,譬如聽書或是做些什麼,總是用同一種方式學習(譬如「看」書),會讓其他學習方式弱化。
- 睡覺!睡覺!睡覺!睡眠可以清除腦中的有毒物質。
- 先吃青蛙。先從不喜歡或是從困難的開始。
- 設定停工時間。這裡我把它理解為「該休息的時候就休息」。
### 14. 學習帶來的驚喜
- 電玩。動作型電玩可以訓練專注力,空間型電玩可以訓練空間感。但有**上癮**的缺點。
- 學習不一樣的事物。我們可能會陷入「慣性思考」,你的心智變得太過習慣沿著特定神經通道奔跑,因此不容易改變,讓你的思考不夠靈活。
- 「移轉」。無論你學什麼,你的大腦都會找到辦法,讓這些新概念對你原本的興趣產生用處,通常是透過「比喻」,而這就是「移轉」。你在某個部位創造出腦連結,讓你容易在不同的部位也建立起連結。
- 用「手寫」來做筆記。這對腦部的刺激比打字更多。
- 「工作記憶」欠佳不好嗎?
- 因為「工作記憶」不好,表示你的章魚沒有足夠的手來抓住腦中的想法,所以你必須「簡化」、「連結」這些概念。
- 會比較容易有「單純化」和「跳耀的創造力」。
- 徒步腦 vs. 賽車腦
- 徒步腦,學得比較慢,但可能比較細緻與深入
- 賽車腦,學得快,但可能忽略了細節。
- 你的腦不會是固定的型態,你可能在某些科目是徒步腦,另外某些是賽車腦。
- 即使你是徒步腦,你還是能學會東西,只是比較慢而已,千萬不要有「反正我就是學不會」的放棄想法。
### 15. 如何考出好成績
- 考前準備檢核表
1. [ ] 你在考試前一晚的睡眠足夠嗎?(如果你的答案是「否」,那麼其他問題的答案可能就不重要了。)
2. [ ] 你在課堂上做完筆記後會盡快複習嗎?在複習時,你有運用「積極回想」的技巧,測試自己能否記得重點嗎?
3. [ ] 平常你有每天用功讀一點書,而不是等到考前最後一刻才臨時抱佛腳嗎?
4. [ ] 讀書時,你有全神貫注,避免分心嗎?
5. [ ] 你有換不一樣的地方念書嗎?(休息時間例外)
6. [ ] 你是否會仔細閱讀課本或參考書?(只是搜尋你正在解決的問題答案不算數。)當你閱讀時,你是否會避免在課本上劃太多底線和做重點提示?你是否會對重點概念做簡單的筆記,並不時移開視線,看看能否加以回想?
7. [ ] 如果你的作業中有問題要解決,你是否靠自己的力量去解題,使這些步驟能組織成腦連結,需要時可以快速想起?
8. [ ] 你是否和同學一起討論作業中遇到的問題,或者,至少你會跟同學對一下答案?
9. [ ] 你是否主動練習過每一道作業題?
10. [ ] 當你看不懂課本的內容時,你是否會請教老師,或者找其他同學來幫助你理解?
11. [ ] 你是否花了大量時間專注在你覺得較困難的部分?換句話說,你是否「刻意練習」?
12. [ ] 你是否「交錯」學習?換句話說,你有沒有練習在什麼時候運用不同的技巧來解題?
13. [ ] 你是否會用有趣的比喻和圖像,像自己或別人解釋重要的概念?
14. [ ] 你在學習過程中,有沒有偶爾休息一下,例如站起來,活動一下身體?
- 硬啟動技巧
1. 先瀏覽考題,找出覺得難的題目
2. 先作難的
3. 一旦卡住(或說花太多時間,譬如5分鐘了還沒解出來),就跳去做簡單的 <- 先吃青蛙
- 轉換考試壓力,要正面思考
- 深呼吸
- 檢查考卷(驗算)
### 16. 從被動到主動
- 讓學習有意義,把「我必須學習」轉換成「我要學習」。
- 學習時該做的事
- 充分利用高強度的「專注模式」和放鬆「發散模式」。
- 藉由練習、重複和回想來創造腦連結。
- 交錯
- 分散學習。每天學一點好過臨時抱佛腳。
- 自我測試。測驗以及教導別人。
- 使用「比喻」。
- 利用番茄鐘工作法。
- 先吃青蛙。
- 找到讓自己積極學習的方式與意義。
- 學習時不該做的事
- 沒有充足的睡眠
- 消極的反覆閱讀。用「積極回想」來取代反覆閱讀。
- 做重點標記或劃底線。
- 看一眼解答就以為自己了解了。
- 死記硬背。
- 懶惰的學習。
- 忽略課本。
- 沒搞清楚困惑的部分。
- 分心
- 和朋友聊天
- 堅持是學習最重要的部分。堅持,不代表不間段的一直做某件事情,而是在發散模式休息之後,持續返回你的工作。

View File

@@ -0,0 +1,604 @@
### 1. Kotlin應用開發初體驗
- 安裝[IntelliJ IDEA](https://www.jetbrains.com/idea/)
- 在.kt檔案寫一個`main()`,旁邊會出現小箭頭,就可以直接執行。
![[Pasted image 20201225114228.png]]
- 註解
- `//`: 以兩個斜線(`/`)開頭就是註解,會被編譯器忽略
- `/* 這行文字是註解 */`: 這是另一種註解,被`/*``*/`包圍起來的區段都是註解,這種方可以跨行註解,但是注意,`/* */`裡面不可以再有另一個`/* */`方式的註解。
### 2. 變數、常數和類型
#### 定義一個「可變」變數
```Kotlin
var experiencePoints: Int = 5
--- --- -
^ ^ ^
| | |
| | Assign value
| Type
Keyword
```
使用`var`所定義的變數可以再度被改變,例如:
```
experiencePoints = 10
```
就會把`experiencePoints`的值變為10。
#### 定義一個「唯讀」變數
使用`val`所定義的變數不可以再被改變,例如,定義一個名叫`myLuckyNumber`的「唯讀」變數:
```Kotlin
val myLuckyNumber: Int = 7
myLuckyNumber = 10 <-- ERROR!
```
#### 要用哪一種方式來定義?
應該優先使用`val`來定義變數,遇到變數需要改變的時候再來把`val`換成`var`。這總比有人寫出了bug不小心改動了變數而造成玲成錯誤來的好。明確的錯誤總是比較容易解決。
#### 類型推斷
Kotlin是一個強型別的語言每一個變數都要有一個明確的「類型」。但Kotlin也有類型推斷的能力。例如
```
val myLuckyNumber: Int = 7
```
因為很明確的`myLuckyNumber`要指定為77是一個整數`Int`),所以`Int`可以忽略,如下:
```
val myLuckyNumber = 7
```
在IntellJ中把游標停在變數上按下`Ctrl + Shift + P`你會看到它推斷出來的類型:
![[Pasted image 20201225120335.png]]
#### 常數
`const val`來定一一個常數(不會變的)
```
const val MAX_SCORE = 100
```
`const val`是編譯時就定義好的,不同於`val`是執行時期才設定的。
#### 深入學習
Java中有兩種類型「參照類型」reference types與「基礎類型」primitive types
參照類型有對應的code大都是一個class。基礎類型則沒有由keyword表示。
Java的「參照類型」都是大寫開頭例如
```
Integer point = 5;
```
基礎類型則是
```
int point = 5;
```
但在Kotlin中只有「參照類型」也就是說基礎類別都是大寫像是`Int``String``Double``Boolean`...等等。
雖然說Compiler會有條件的把參照類型轉為基礎類型來增加效率但對於開發者來說大都是不需在意的。
### 3. 條件運算式
#### if/else
語法
```kotlin
if (<condition>) {
// code block if <condition> is true
} else {
// code block if <condition> is false
}
```
`<condition>``true`的時候,`if`區塊裡面的code就會被執行
##### 多重條件
當有多個condition的時候可以用`else if`來增加條件,如下:
```kotlin
if (<condition_1>) {
// code block if <condition_1> is true
} else if (<condition_2>) {
// code block if <condition_2> is true
} else if (<condition_3>) {
// code block if <condition_3> is true
else {
// code block if doesn't match any conditions.
}
```
##### 比較運算子
Kotlin使用的比較運算子如下
- `<`: 左側值是否「小於」右側值
- `<=`: 左側值是否「小於等於」右側值
- `>`: 左側值是否「大於」右側值
- `>=`: 左側值是否「大於等於」右側值
- `==`: 左側值是否「等於」右側值
- `!=`: 左側值是否「不等於」右側值
- `===`: 左側的reference是否「等於」右側的reference
- `!==`: 左側的reference是否「不等於」右側的reference
##### 邏輯運算子
- `&&`: AND
- `||`: OR
- `!`: NOT
##### 條件運算式
`if/else`運算式可以直接指派給一個變數,區塊內的最後一行會被當成回傳值設定給變數,例如:
```
val color = if (type == "tree") {
println("type is tree")
"GREEN" // <- 最後一行會是return value
} else {
println("type is not tree")
"WHITE" // <- 最後一行會是return value
}
```
#### range
Kotlin使用`..`來代表一個範圍,例如`1..5`會等於`1, 2, 3, 4, 5`
`if/else`裡面可以用來代替`>`, `<`之類的邏輯運算,例如:
```
if (score in 90..100) {
println("A")
} else if (score in 80..89) {
println("B")
} else {
println("C")
}
```
`..`必須左邊小於右邊,若是要由大到小必須使用`downTo`
```
3 downTo 1 // 3, 2, 1
```
另外,跟`..`類似的`until`,差異是`until`不包含右邊的值:
```
1 until 3 // 1, 2
1..3 // 1, 2, 3
```
上述操作也可以轉成list呼叫`.toList()`即可:
```
(1..3).toList() // [1, 2, 3]
(3 downTo 1).toList() // [3, 2, 1]
```
#### when
`when`類似C語言的`switch`,但是`when`更加靈活。先看一個例子:
```
val comment = when (score) {
100 -> "Excellent"
in 90..99 -> "A"
in 80..89 -> "B"
else -> "C"
}
```
`when`會將score與`->`左邊的值做比較,要是成立就執行`->`右邊的區塊,跟`if/else`的條件運算一樣執行區塊內的最後一行會被return並指派給變數
```
val score = 99
val name = "Bond"
val comment = when (score) {
100 -> {
val message = "$name, you're Excellent
message // <- 最後一行會是return value
}
in 90..99 -> {
val message = "$name, you got A"
message // <- 最後一行會是return value
}
in 80..89 -> {
val message = "$name, you got B"
message // <- 最後一行會是return value
}
else -> {
val message = "$name, you got C"
message // <- 最後一行會是return value
}
}
```
#### String範本
`$`開頭可以將變數的值帶入字串之中,例如:
```
val score = 100
println("My score is $score") // -> 印出"My score is 100"
```
另外,若用`${}`Kotlin會先將`{}`區塊求值,如此一來就可以很方便地在字串內做一些簡單處理或運算:
```
val a = 5
val b = 6
val result = "Result = ${a + b}"
```
### 4. 函數
#### 函數的結構
一個函數的結構如下
```kotlin
private fun functionName(arg1: String, arg2: Int): String {
// function body
}
```
- `private`是「可見性修飾符」若是在檔案中則這個function只有在檔案中是可見的。
- `fun functionName`是「函數名稱宣告」宣告一個函數的開始其中functionName可以自己命名。
- `(arg1: String, arg2: Int)`: 參數。每一組參數由`,`隔開。開頭是參數的名稱,`:`後面是參數的類型。以此例來說有2組參數第一組的參數叫做`arg1`,類型是`String`。第二組的參數叫做`arg2`,類型是`Int`
- `{ }`裡面是是函數運算本體。
#### 預設引數
參數可以有一個預設值。例如下例:
```
fun sayHello(name: String): String {
return "Hello $name!"
}
```
一定要傳入一個名字,我們可以預設讓它接受一個空字串,讓他單純說聲"Hello"就好。
```
fun sayHello(name: String=""): String {
return "Hello $name!"
}
```
如此一來,使用者可以直接呼叫`sayHello()`就可以得到字串了。
#### 單運算式函數
對於單純只有一行的函數,我們可以簡化函數的寫法,把大括號省略掉。以上面`sayHello()`的例子來說,可以簡略如下:
```
fun sayHello(name: String=""): String = "Hello $name!"
```
#### Unit函數
對於沒有返回值的函數,其返回值不是`void`,而是`Unit`。這類函數叫做「Unit函數」。
#### 具名函數引數
呼叫函數時,一定要按照函數所定義的參數順序來填寫,否則會出錯,假設有一個函數定義如下:
```
fun getDiscountPrice(price: float, discount: float): float {
return price * (1.0 - discount)
}
```
價錢與折數的順序要是錯位就會造成錯誤,這時候,呼叫函數時明確寫出參數名字可以避免這個情形:
```
val newPrice = getDiscountPrice(price = 1000.0,
discount = 0.3)
```
一旦使用具名引數,順序不對也沒有關係,像下面這樣寫也是可以的:
```
val newPrice = getDiscountPrice(discount = 0.3,
price = 1000.0)
```
#### Nothing類型
`Nothing`表示不可能執行成功。Kotlin標準程式庫的`TODO()`可以給一個經典的用法:
```
public inline fun TODO(): Nothing = throw NotImplementedError()
```
我們可以把TODO()用在還沒完成的函數上,例如:
```
fun notOkFunc(arg1: Int): Int {
TODO("Someone finsih this")
println("I don't want to implemnt this...") // <- This line is unreachable
}
```
因為某種原因`notOkFunc()`沒有實作完成,它也沒有返回一個`Int`的結果。但因為TODO()返回Nothing的關係所以編譯器就忽略了這個檢查反正它會執行失敗。
#### 奇怪的函數名
一般來說函數的名字並不可以有空白或是一些特殊符號但是Kotlin支援用「反引號」來定義有特殊名字的函數。例如
```
fun `** Click to login **`(): Int {
...
}
```
呼叫時就變成:
```
val loginResult = `** Click to login **`()
```
但支援這種語法的主要原因是為了可以呼叫Java的API例如Java有一個叫做`is()`的函數,但是`is`是Kotlin的保留字用來檢查類型所以在Kotlin裡面要呼叫Java的`is()`就必須使用這個方法,例如:
```
fun callJavaIsInKotlin() {
`is`() // <- Invokes is() function from Java
}
```
### 5. 匿名函數與函數類型
#### 匿名函數
`{}`裡面,沒有名字的就是匿名函數,定義一個簡單的匿名函數:
```
{
println("Hello")
}
```
呼叫這個匿名函數:
```
{
println("Hello")
}()
```
其實就跟呼叫一般函數一樣,只是`()`之前是一個函數本體,而不是函數名稱。
匿名函數在Kotlin叫做lambda以後都用lambda來稱呼匿名函數。
#### 隱式返回
lambda預設會返回「最後一行」而且**不能呼叫**`return`。這是因為編譯器不知道返回資料是來自於lambda或是lambda的呼叫者。
下面這個無聊的lambda會返回一個字串
```
{
"Hello"
}
```
#### lambda類型
lambda本身是一個類型type所以lambda也可以指定給變數。如以下例子
```kotlin
val get5 = {
val a = 2
val b = 3
a + b // 最後一行為返回值
}
// 呼叫lambda
val number = get5()
```
lambda類型是由lambda的輸入參數、輸出類型所定義的。
#### lambda的參數與返回值
方法1將參數類型與返回值定義在變數裡
```kotlin
val addResult: (a: Int, b: Int) -> Int = {
a + b
}
```
上面的例子定義了`addResult`這個lambda輸入參數有`a``b`兩個,兩個的類型都是`Int`,返回值也是`Int``{}`內則是實作。
方法二:將參數類型與返回值定義在變數裡,參數命名則在函數本體裡
```kotlin
val addResult: (Int, Int) -> Int = {a, b ->
a + b
}
```
上面的例子定義了`addResult`這個lambda輸入是兩個類型為Int的參數返回類型也是Int參數名稱`a``b`則定義在`{}`這也表示參數名稱可以由lamdba提供者隨意修改。
#### lambda的類型推斷
如同編譯器可以自東推斷變數的類型lambda的類型也可以自動推斷例如
```kotlin
val returnHello: () -> String = {
"Hello"
}
```
可以簡化成:
```kotlin
val returnHello = {
"Hello"
}
```
很顯然的這一個沒有輸入參數回傳值String的lambda。
對於有多個參數的lambda則需要清楚的把參數的名字與類型都寫出來例如
```kotlin
val sayHello = { name: String, age: Int
"Hello, I'm $name, $age years old."
}
```
`sayHello()`的推斷類型是輸入有兩個參數一個是型別為String的name另一個是型別為Int的age然後根據[[20201218 - Kotlin權威2.0#隱式返回]]最後一行是回傳值所以返回值是String型別。這寫法跟下面的寫法同意
```kotlin
val sayHello: (String, Int) -> String = { name, age
"Hello, I'm $name, $age years old."
}
```
#### `it`關鍵字
當lambda只有一個參數的時候可以用it來當參數的名字例如
```kotlin
val add5: (Int) -> Int = {
it + 5
}
```
使用it雖然方便但是對可讀性卻沒有比較好這點自己權衡使用。
#### 將lambda當作參數
lambda可以當作參數傳給函數只要在函數內定義好lambda的類型即可。例如我們可以設計一個函數接收不同「打招呼lamdba」來產生打招呼字串我們先定義3個打招呼lambda
1.
```kotlin
val sayHi = { name: String,
"Hi $name"
}
```
2.
```kotlin
val sayHello = { name: String,
"Hello $name"
}
```
3.
```kotlin
val sayGoodMornig = { name: String,
"Good mornig, $name"
}
```
這三個lambda都有同樣的型別型別都是 (String) -> String也就是輸入參數是一個String返回值也是String。
接下來,定義我們要使用的函數:
```kotlin
fun greet(name: String, greetFunc: (String) -> String): String {
return greetFunc(name)
}
```
然後我們就可以這樣用:
```kotlin
val greetString1 = greet("John", sayHi)
val greetString2 = greet("John", sayHello)
val greetString3 = greet("John", sayGoodMornig)
```
#### 將lambda當作參數的簡略語法
如果lambda參數是函數的最後一個參數那麼便可以使用簡略語法我們用上面的例子一步一步來看。例如我們直接將`sayHi` lambda寫在`greet()`裡面:
```kotlin
val greetString1 = greet("John", { name: String ->
"Hi $name"
})
```
因為lambda是最後一個所以可以將lambda移到外面來
```kotlin
val greetString1 = greet("John") { name: String ->
"Hi $name"
}
```
又因為這個lambda只有一個參數所以可以用`it`來簡化它,變成:
```kotlin
val greetString1 = greet("John") {
"Hi $it"
}
```
#### inline function
若想要避免lambda產生記憶體開銷就可以使用`inline`關鍵字,`inline`關鍵字會函數在使用的地方展開例如剛剛的sayHi例子我們將使用lambda的`greet()`函數加上inline變成
```kotlin
inline fun greet(name: String, greetFunc: (String) -> String): String {
return greetFunc(name)
}
```
那麼`greet()`就會在呼叫處直接展開,就好像:
```kotlin
{
val result = "Hi $name"
return result
}
```
#### 將函數當作參數
用fun定義的函數也可以像lambda一樣當作參數只是要在呼叫的時候在函數名前面加上`::`,例如:
```kotlin
fun sayHi(name: String): String {
return "Hi $name"
}
fun greet(name, greetFunc: (String) -> String): String {
val greetString = greetFunc(name)
return greetString
}
// 呼叫
greet("John", ::sayHi)
```
#### 在函數裡返回一個函數
若是將函數的返回值定義為相對應的函數類型即可以返回函數例如我們可以設計一個函數這個函數接受1、2、3三種數字當使用者輸入1的時候我們返回`sayHi`來讓使用呼叫當使用者輸入2的時候我們返回`sayHelllo`來讓使用呼叫當使用者輸入2的時候我們返回`sayGoodMornig`來讓使用呼叫:
```kotlin
fun selectGreet(number: Int): (String) -> String {
when (number) {
1 -> sayHi,
2 -> sayHello,
3 -> sayGoodMornig
}
}
```
可以看到`selectGreet`的回傳值是`(String) -> String`,接下來我們可以這樣用:
```kotlin
val greetFunc = selectGreet(2)
val greetString = greetFunc("John") // greetString會是"Hi John"
```
#### lambda也是closure
若是在函數裡面回傳一個lambda的時候回傳的那個lambda在被呼叫的時候還是可以使用當初函數所在位置的變數這便是closure。例如
```kotlin
fun countGreet(): (String) -> String {
var count = 0
return { name ->
count = count + 1
"[$count] Hi $name"
}
}
```
當我們呼叫它的時候:
```kotlin
val greetFunc = countGreet()
val countString1 = greetFunc("John") // countString會等於"[1] Hi Jhon"
val countString2 = greetFunc("John") // countString會等於"[2] Hi Jhon"
val countString3 = greetFunc("John") // countString會等於"[3] Hi Jhon"
```
雖然`count``countGreet()`內的區域變數,但是`greetFunc()`還是能繼續取用它。
能夠接受函數或是lambda當參數或是返回函數的函數又叫做**高階函數**。
### 6. Nullability
Kotlin預設是型別都不可以是null。如果有一個型別是`String`的變數把它設為null的話compiler就會報錯。
```kotlin
var name = "John"
name = null <-- Error!
```
如果一定要設為null那麼必須在宣告的時候`?`符號告訴compiler說這個變數必須是「可以null的」。
```kotlin
var name: String? = "John"
name = null <-- OK
```
`?`也可以用來判斷函數的回傳值例如有一個函數它會回傳一個字串或一個null
```kotlin
fun getString(number: Int): String? {
if (number > 90) {
return "good"
} else {
return null
}
}
```
那我們在呼叫這個函數之後,可以方便的用`?`來串接下一個步驟:
```kotlin
val status = getString(50)?.capitialize()
```
上面例子是我們在得到"good"字串後,呼叫`String.capitialize()`來把字串的第一個字元變成大寫。`?`符號可以幫我們判斷`getString()`回傳的是不是null如果不是就接著呼叫`capitialize()`如果是null`capitialize()`就不會被呼叫status將會是null。上面例子跟下面的程式是一樣的效果但是明顯簡短的多
```kotlin
val status = getString(50)
var statusCapital: String? = null
if (status) {
statusCapital = status.capitialize()
}
```
如果串接函數很多個的時候,更能看出效果:
```kotlin
val number = funcA()?.funcB()?.funcC()?.funcD()
```
#### `!!` operator
`!!`[not-null assertion](https://kotlinlang.org/docs/reference/null-safety.html#the--operator) 用來讓compiler忽略null的檢查例如
```kotlin
var name: String? = null
name.capitialize() <-- 會報錯
name!!.capitialize() <-- 不會報錯但是runtime會錯
```
#### `?:` Elvis operator
`?:` 就是「要是左邊為false就執行右邊」`?:`可以很方便的用來設定變數的預設值,例如前面舉過的例子:
```kotlin
val number = funcA()?.funcB()?.funcC()?.funcD()
```
要是`funcA()``funcB()``funcC()`中任何一個的回傳是null那麼number都會因為無法求值而被設為null我們可以用`?:`來給它一個預設值:
```kotlin
val number = funcA()?.funcB()?.funcC()?.funcD() ?: "Default value"
```
#### 異常Exception
`throw`來拋出一個異常,例如:
```kotlin
throw IllegalStateException("Oh! Oh!")
```
#### 自訂異常
可以用繼承來建立自己的異常:
```kotlin
class MyIllegalException(): IllegalStateException("I like new Exception")
```
#### 處理異常
`try/catch`來處理異常:
```kotlin
var name: String? = null
name = somefunctionCall()
try: {
val newName = name.capitialize()
}
catch (e: Exception) {
println(e)
}
```
#### 先決條件
類似C++中的`assert()`在符合判斷的條件下發出Exception。Kotlin內建5個先決條件函數
| Funtion | Description |
| ------------------ | ---------------------------------------------------------------------------------------|
| `checkNotNull()` | `checkNotNull(condition, String)`如果condition是`null`,就發出`IllegalStateException` |
| `require()` | `require(condition, String)`如果condition是`false`,就發出`IllegalStateException` |
| `requireNotNull()` | `requireNotNull(condition, String)`如果condition是`null`,就發出`IllegalStateException` |
| `error` | `error(condition, String)`如果condition是`null`,就發出`IllegalStateException` |
| `assert` | `assert(condition, String)`如果condition是`false`,就發出`AssertError` |

View File

@@ -0,0 +1,331 @@
### 1. 會說的人很多,能寫的人太少
- 本章在倡導寫作的好處,文中所舉的例子有:北大薛兆豐老師在「得到」平台的收益一年有一千多萬人民幣。還有作者本身在畢業兩年之內,收入千萬。
- 寫作可以
- 自我認識,思考更縝密
- 提升思考,對世界更敏感
- 推銷理念,認知更深刻
- 擴展人脈
- 不寫作的問題
- 職場身分不夠明顯(作者是認為「正在失去職場身分」)
- 職場邊緣化。作者認為在職場上人脈是重要的資源,寫作可以連結這些資源,反過來說,不寫作的人無法擴展人脈,自然是被邊緣化。
- 失去話語權。寫作可以宣傳自己的理念,也展現本身的技能與觀點。不寫作的人就像是不說話的人,沒有人了解你。
- 職場影響力
- 綜觀上述,強烈的職場身分與優先的話語權,會保障或者開闊你的職場道路。
- 薪水代表過去,品牌代表未來
### 2. 坐下來,開始寫
這一章解釋不寫作的心理與解決方式,也說了高品質輸出的方法。
- 不寫作的心理
- 擔心自己沒東西可寫
- 擔心自己無法堅持
- 擔心自己的思想太空洞被別人笑話
- 覺得自己寫得不夠好,浪費時間
- 寫作時,總是有一個審判者
- 擔心寫出來的東西被別人認為沒有價值,覺得自己寫的文章很幼稚
- 擔心寫出來的文章沒人看
- 擔心投入時間卻得不到回報
- 不寫作的心理,解決方案
- 擔心自己的思想太空洞被別人笑話 -> 其實太陽底下沒有新鮮事,道理誰都懂,如果你能提供一個新的角度,或者提供一段親身體驗的經歷,並且真誠的表達出來,這就是你的獨特價值。
- 寫作時,總是有一個審判者 -> 先關閉、忽略審判者,寫完之後隔個一、二天再回頭審視修改。
- 擔心寫出來的東西被別人認為沒有價值,覺得自己寫的文章很幼稚 -> 允許自己有「寫出世上最爛垃圾」的自由。
- 寫作的心態
- 初學寫作,先寫自己想寫的。把那些如鯁在喉,不吐不快的感受記錄下來,用你自己滿意的方式準確、流暢的講清楚,一篇文章就有不錯的基礎。
- 寫就對了,先求有再求好
- 刻意練習,把寫東西變成「習慣」
- 寫作要誠實
- 不要有心理上的完美主義,不要只在心中只看到你幻想中的完美結局。對事情抱有過高的期望,但和現實差距很大,導致成為「行動上的矮子」。所以**完成比完美更重要**。
- 養成規律寫作的習慣。村上春樹認為:「做長期工作時,規律性會具有重要意義。」
- 保持高品質輸出的五個關鍵詞
- 死嗑,保持持續的輸出
- 敢拚,不恐懼
- 要有自己的立場和鮮明的觀點
- 專注
- 野心,把格局做大
- 好奇心,好奇心就跟肌肉一樣,你愈用它,它就愈能幹。
### 3. 寫作,是注意力的爭奪
- 這一章說明了如何吸引「注意力」,也說明了讓文章有吸引力的方法。
- 寫作的目的:
- 依對象來分:
- 給自己看的
- 有特定對象的
- **沒有特定對象的**
- 依目的性來分:
- 自我表達
- **影響他人**
- 寫作的注意力要求「沒有特定對象的」與「影響他人」,也就是對「不特定的對象產生影響」。
- 從四個面向來看寫文章時的考量:
- 情境:網路(手機)時代,閱讀時間已經碎片化
- 用戶思維: ^1a3dd1
- 滿足讀者的閱讀動機,要有同理心
- 認知同理心:能從別人的角度看問題
- 情感同理心:能體會別人的情緒
- 讀者想要得到滿足
- 說出讀者的心聲 ^fc39a6
- 適應讀者的閱讀情境
- 不冗長
- 有刺激點
- 產品思維:文章解決什麼問題 ^8a2433
- 提升專業度
- 獨家視角提升辨識度
- 社交思維
- 未來的一切商業都會連接網路
- 社交會成為一切商業的底層架構
- 心態
- 把格局放大
- 不以傳播率為目標
- 長期經營
- 「取乎其上,得乎其中;取乎其中,得乎其下;取乎其下,則無所得矣。」
### 4. 如何寫出吸引人的好文章?
這一章從「如何訂標題」跟「內文的結構與修改」兩方面來說明好文章的特性。
#### 標題的特點
- 引發共鳴:說到痛點或是心聲
- 製造懸念:引起好奇心 ^cbfc4c
- 引起爭議:提出質疑、選邊站的觀點
- 顛覆認知
#### 訂出好標題的方法
- 觀察、分析
- 模仿、套用
- 重視市場回饋
#### 內文的特點
- 講故事,不要講道理
- 開頭:人物、時間、地點
- 誰的故事
- 故事情節是故事的主體
- 故事加金句 ^edd2d6
- 設懸念:讓人有好奇心
- 帶入,製造熟悉感
- 製造讀者熟悉的問題
- 順著對方的意願,解釋問題發生的原因,贏得對方的信任
- 給出自己的觀點和對策
- 反向鋪陳
#### 提升文采
- 文采:文章帶來審美上的愉悅感
- 1. 口語化
- 避免過長的從句
- 避免多層的邏輯
- 避免連續的形容詞
- 2. 用文字畫出圖
- 多用動詞和名詞
- 善抓細節
- 多用比喻
- 3. 具體化
- 利用那些本身容易帶來感官和心理刺激的詞彙,把抽象的概念「刻」在讀者的腦子裡。
#### 內文修改
- 檢查
- 想寫的東西表達清楚了嗎?
- 語氣是否合適?
- 資訊完整嗎?
- 故事案例是否可信、吸引人?
- 邏輯是否有問題?
- 觀點是否簡潔有力?
- 有無錯別字?標點錯誤?文法錯誤?
- 修改文章就是修改思想
- 思路不清晰文章也會不通順
- 培養良好的語言習慣
- 葉勝陶先生:「語言是有聲無形的文章,文章是有形無聲的語言。」 ^d7f87c
- 講話講重點。
- 不自誇,不自責
- 先把文章改到自己滿意,覺得改無可改。
- 誠實面對自己的內心,面對自己不成熟的作品。
- 不要過分挑剔,對自己嘮叨不停。
### 5. 如何寫好一個故事?
- 文章需要故事(比喻)。
- 卡夫卡說:「書必須是鑿破我們心中冰封海洋的一把斧。」
- 這裡利用了第4章的[[20201224 - 寫作是最好的自我投資#^edd2d6|故事加金句]]技巧。
- 用故事介紹自己,以獲取信任。
- 用故事連結他人,以更好的說服別人。
- 重點是和讀者有什麼關係。
- 用對方熟悉和認可的故事,將溝通關係由「對抗」轉為「對話」。
- 說故事要注意的三件事
- 情感真實
- 表達克制
- 徹底改變思維方式
- 村上春樹:「把內容盡量改以簡單的語言來說,把意圖轉換成容易理解的說法,描寫時盡量削掉多餘的贅肉,縮小整體型態,以便放進空間有限的容器裡。」
- 相信市場,而非相信靈感
- 故事的三要素
- 鳳頭:開頭像鳳頭一樣精美
- 豬肚:主體有裡有據像豬肚
- 豹尾:結尾像豹尾一樣,簡潔有力又有平衡感
- 用懸念、衝突建構吸引人的敘事結構
- 再次提到「懸念」:[[20201224 - 寫作是最好的自我投資#^cbfc4c|製造懸念:引起好奇心]]
- 渲染氣氛、刻畫人物
- 場景
- 氛圍
- 人物
- 結尾,給讀者驚喜的最後一個機會
- 布魯斯.德席瓦:「結尾是你把小說的主旨釘在讀者記憶中,並讓他回想數天的最後一次機會。」
### 6. 如何訓練你的邏輯思維?
- 表達就是邏輯問題,邏輯就是文章的條理。
- 修正邏輯不通的方法
- 不使用模糊或多義的語詞
- 複雜的事情簡單說
- 慎用雙重否定
- 不要觀點,卻沒有堅實的推導
- 確認事情
- 要克制情緒,主觀看法不應變成客觀事實
- 迷信專家說法
- 有思想,沒有思考
- 這一項我把他解讀成:相信某一種說法、思想,卻沒有去思考背後的脈絡與原則。
- 蘇格拉底:「未經審查的人生沒有價值。」 ^fad99d
- 對容易的想法與結論要感到警惕。
- 確保深入了解再給出觀點。
- 使用歸納法來表達意見。
- 把別人的質疑與反駁當作深入思考的機會。
- 三個邏輯思考的技巧
- 列出提綱:把觀點想法**分類**。
- 框架比細節重要
- 分析層面
- 闡述的觀點順序
- 有邏輯的連接
- 循序漸進
- 有理論的推導
- 有結論、有反思
### 7. 職場專業文章寫作的方法論
這一章的主題在專業文章的寫作技巧、題材選定與技術進步的方法。
- 「專業,二十一世紀你唯一的生存之道。」 - 大前研一 ^fd93cf
- 寫職場乾貨文,是透過持續的輸出,爭奪職場話語權的方法。
- 一篇公開發表的文章,能讓你省掉無數次重複勞動。
- 專業文章寫作的兩個要點
- 保持理性、冷靜
- 注重邏輯
- 職場是講道理的地方,不適講感情的地方。
1. 選題的「加」與「減」
1. 文章要有飽腹感,也就是讀者看完後明確知道收穫了什麼。
2. 明確的利益點,想清楚這篇文章對讀者在哪個點上有幫助,圍繞此點綻開論述。
3. 文章的「深度」、「廣度」、「角度」至少選一項。
4. 開頭的文風與行業同調。結論先行或問題先行。
- 不同的行業有不同的風格。
2. 表達的「深」與「淺」
1. 情節 -> 分析 -> 觀點 -> 結論
- 寫好產業分析報告的五個步驟
1. 明確目標:為什麼你要做這個分析?
2. 界定問題
- 拆分問題、各個擊破
3. 蒐集資訊
- 沒有調查就沒有發言權。
- 資訊愈多、愈雜,就愈容易互相印證。
4. 研究分析
1. 五力分析模型Five Forces Model
2. SWOT態勢分析法
1. S: Strength
2. W: Weakness
3. O: Opportunity
4. T: Threat
3. 合作競爭
5. 輸出成果
- 金字塔結構
- 構思專題的四格個原則
- 專題有整體的規劃
- 好的專題就像一部連續劇,會讓觀眾追劇。
- 專題的面向:深度 or 時效
- 起步階段,切入點要小
- 找一個目標(假想敵)
- 累積素材的四個方式
- 累積專業知識
- 從內部觀察行業裏的人和事
- 從外部、社會的層面來觀察行業
- 整合產業分析
- 透過行業內的專業媒體
- 透過會層面的報告
- 置身第一現場(速度)
- 成為社群好召者,進入專業的圈子。
- 與高手過招(角度)
- 「世界上沒有兩片相同的樹葉」
- 高手有敏銳的目光與洞察力,特殊的視角。
### 8. 你所理解的新媒體寫作,也許都是錯的
- 「任何一個好產品都是聰明人用笨功夫做出來的」 - 咪蒙 ^a00bcf
- 新媒體的本質
- 新媒體是一種工具 -> 增加傳播範圍,加快傳播速度
- 競爭,是高效率淘汰低效率的過程 -> 技術的本質不是讓所有人變得更美好,而是把之前同樣的一群人分成兩類。
- 掌握新技術 -> 成為贏家
- 沒辦法掌握新技術 -> 淘汰
- 累積客戶群 -> 得到信任
- 新媒體寫作的基本原則
- 始於刺激,限於選題,忠於邏輯,癡於文筆,止於經營
- 刺激,也就是標題,「標題決定打開率,內容決定轉發率」
- 選題,也就是戳中痛點,例如:
- 事業上的激進與保守
- 生活上的穩定與冒險
- 認知成長的前與後
- 能力與平台的博弈
- 邏輯
- 文筆,也就是文采,不光是內容的精彩,也包括文章段落的分配,字型的選用,大小的設定
- 顏值即正義
- 沒有人有義務透過你邋遢的外表看到你閃光的內心
- 建立有力的口號
- 經營
- 功能介紹
- 個人定位
- 關注回復
- 按鈕設置
- 視覺風格
- 連結設置
> ### 心得
> 這一章針對網路文章的特性多加說明然後再一次解說了把文章寫好的方法。但其實這一篇就是重複第3章、第4章、第5章的內容再重新加以編排。
> 例如:
> 刺激講的是[[20201224 - 寫作是最好的自我投資#3 寫作,是注意力的爭奪]]與[[20201224 - 寫作是最好的自我投資#標題的特點]]。
> 選題是再講第3章的[[20201224 - 寫作是最好的自我投資#^1a3dd1|用戶思維]]。
> 邏輯與文筆是第5章的[[20201224 - 寫作是最好的自我投資#5 如何寫好一個故事?]]。
> 經營則是分享手機上的設定技巧。
### 9. 人人都能寫出爆款文
- 「天才的唯一秘密,就在於刻意練習,用自己一套系統性的方法,不斷突破自己的邊界」 - 刻意練習,安德斯.艾瑞克森 ^7e896d
- 不知道怎麼寫是因為看得太少,寫的不好是因為寫得太少。
- 如果可以借助熱點,讓更多人看到我,讓我寫的有價值的東西更好的傳播出去,遠比我一昧死守不蹭熱點要強。
- 跟風口
- 成為風口 -> [[20201224 - 寫作是最好的自我投資#訂出好標題的方法]]
- 尋找情緒能量匯集的點
- 揮正拍消解情緒,反手拍引爆情緒
- 觀點中庸,等於平庸
- 角度決勝負
- 標題是入口,主題是情緒推進 -> [[20201224 - 寫作是最好的自我投資#內文的特點]]
- 幫讀者表達想法 -> [[20201224 - 寫作是最好的自我投資#^fc39a6]]
- 讓讀者塑造自己的形象
- 幫讀者進行比較,那怕是曬優越
- 文章本身就能幫助別人 -> [[20201224 - 寫作是最好的自我投資#^8a2433]]
- 標題定乾坤 -> [[20201224 - 寫作是最好的自我投資#內文的特點]] & [[20201224 - 寫作是最好的自我投資#提升文采]]
- 立畫面
- 有代入
- 表情緒
- 做鋪陳
- 善用萬能框架 -> [[20201224 - 寫作是最好的自我投資#5 如何寫好一個故事?]]
- 亮觀點
- 說現象
- 做分析
- 下結論
- 活用行文走筆 -> [[20201224 - 寫作是最好的自我投資#提升文采]]
- 文字密度:不要囉嗦,精簡幹練
- 畫面感:細節具體,案例鮮活
- 情緒化:觀點一定要愛恨分明
- 結尾刺激轉發
- 找準發布時間
- 騰訊統計的閱讀高峰時間
- 上午7-9點
- 中午12點-下午2點
- 晚上6-8點
- 晚上10點以後
- 作者的統計
- 上午10-12點
- 晚上8-10點
> ### 心得
> 這一章作者拿以前自己寫過的一篇文章來分析文章架構,用的也是前面幾章所用的技巧,可以看到好多段落都是前面章節的重新拆分組購,其實概念是一樣的。
>
> 這個作者利用這招式把寫文章的方法(第4章:[[20201224 - 寫作是最好的自我投資#4 如何寫出吸引人的好文章?]]不斷重新組構用不同的方式講解一次就變成一本書了其實看到第7章就有點膩了本著不要半途而廢的心態還是把它給讀完了。
>
> 雖然說很多部分是重複的,讀起來也有厭煩的感覺,但作者確實很會找金句,立金句,這一點是可以學習的。
> 另外,作者也很會下提綱,他也是先立題綱,然後一題綱展開鋪陳,所以雖然很多部分概念相同,多有似曾相似的感覺

View File

@@ -0,0 +1,50 @@
書名:中產悲歌
日期2021/01/19
# 第一章:我們談的中產階級究竟是什麼
目前看到第一章,有些看不太懂,句子不適很流暢,或者說用詞有些饒舌。
目前的感想是,作者認為中產階級並不存在,是資本主義、資本家所建構出來,用以說服勞動階級安心於工作的方法。作者認為中產階級這概念之所以能夠吸引工人,「財產私有化」是一個重要的因素,工人為了追求私人房屋、汽車,以及日後的生活保障,像是退休金、儲蓄,會更努力工作以追求更好的薪資,或是追求更好的學位以保障日後對於財產保護的能力。
作者舉了很多例子來說明中產階級所追求的財產與投資其實都是被操弄的結果,資方所設計的金融商品看似對我們有利,但中產階級同時也因為這個遊戲規則而變得身陷其中,到後來,我們追求的投資利益與穩定,反過來變成自身的枷鎖,我們變成幫助資方剝削自身的人。作者認為中產階級其實不存在,它是一個概念,美好的概念,它說著「自我提升」的漂亮話,但自從資本主義誕生之後,我們的努力和投資就被動員與利用,用來幫忙資方創造盈餘而已。第一章的最後一句是:「我們不僅現在不是中產階級,設置從來都不是。」。
# 第二章:財產的低調魅力
說到財產,說為什麼我們追求財產,我們希冀財產為我們帶來什麼,但也說了這個時代,財產是如何地變的波動、不穩定,以及財產面臨的風險。當然也說到了資本主義是如何引導我們,讓我們追求投資以累積財產,我們在資本主義的影響下,變得追求投資利潤大過於儲蓄(因為通膨)。而我們在面對風險的時候,又是如何在投資上分散風險來企圖降低損失,而我們所做的,到頭來又變成了另一種風險來危害我們。這說明了財產的基礎是多麼的脆弱。
# 第三章:太人性了
作者再一次的批評資本主義,但是將討論點轉到教育上,作者認為,在資本主義中,我們的教育是要我們也成為資本中的一環,稱為「人力資本」。回到資本主義帶給我們的中產階級想像,中產階級認為自己有辦法藉著自身的努力來達成階級流動,反過來說,要是無法向上提升,那就是你自己不努力,而人力資本就是在這個基礎上將教育也「資本化」。
人力資本讓父母對自身與小孩的技能要求提高,進而將資源投入教育與證照之中,但是在資本化的教育市場下,環境的變化加快,生產變的靈活,也讓就業市場變得更不穩定,這表示投入在教育與證照的投資不一定會有預期的回饋。而教育市場為了因應不穩定與快速變化的市場,又推出更多更新更高階的證照,進一步提升就業的門檻與高度,而我們因為害怕脫節,為了不落人後,害怕就就業市場中輸掉競爭,只能再一次的追求更新更高階的「產品」,進一步地再次投資在教育裡面。
而人力資本不同於物質資本的一點在於:人力資本是無法傳承的。一個人可以藉由繼承家產來獲得金錢,但只要這金錢不足以讓他一輩子生活無虞,那他也勢必要培養自己的技能、文化、教養等等的,來培養自己的人力資本。
另外,資本家不停尋找更佳優秀的技術者來降低其生產成本,而其他同業也會跟進競爭、追趕,然後再下一次的科技與創新中又形成下一輪的競爭。
無法傳承與技術競爭造成人力資本的不斷投資,這項投資甚至可以說是無止盡的。這造成一種「滑坡」,停在原地不動是不可能的,要是不讓自己努力追上新的技術、潮流、文化,就會發現自己已經過時了。但是投資這些追求或是可以讓我們獲得短暫的地位,卻不一定能再在之後的時間上賺到錢,因為你將會再一次投資到另一個潮流裡。
但是在中產階級對於自我提升的追求之下,人力資本隱藏了投資沒有回報的事實,就像所有的金融投資都宣傳著賺錢的美好,隱藏背後虧損的風險。
人力資本就像其他資產一樣,其價值再過一段時間之後就會被抽離。
最後談到人力資本與家庭的關係,父母不只追求自身的人力資本,也被要求要培養子女的人力資本,但因為人力資本無法直接轉移,父母必須在子女的教育與培養上進行多年的投資,接下來還需要子女自身的進一步投資,而且受到子女個性與經濟潮流的影響,這些投資的成果,不一定能有明確的回報。隨著這種人力資本投資流傳了幾代,它塑造了家庭關係,也反過來被家庭關係塑造。
# 第四章:後會有期了價值觀,再見了政治
第四章作者試著再一次的論述資本主義與政治家是如何建構出「中產階級」這個價值觀。資本主義將工作者從市場性質中的生計分離出來,是他們在就業、住房;教育等重要議題上互相競爭。資本主義讓這些資源維持在足夠稀缺的狀態來維持高報酬率。同時又讓工作者以投資者的身份加入,讓他們甘願的「維護自身利益」以維護已經擁有的東西(並設法獲得更多),這也激發了工作者一種「個人自由」的感覺,並藉著鼓勵投資再一次的強化這種感覺。
投資的報酬讓餐與投資的工作者嘗到了自由,並受到了鼓舞。但一旦他們的投資回報受到了風險,他們也往往無能為力,他們的策略也因為物質壓力與動機糾纏而受挫,工作者或許會積極採取行動,參加抗爭,但這些抗爭往往並不是要求工作者在政治賞或結構上的勝利,而只要一個秩序和穩定(以確保工作者的投資),但其結果都會造成集體的脆弱性和不利情勢。普遍的不安全感愈高,競爭壓力愈猛烈,而這又讓投資行動愈顯合理。
# 結論
作者再一次重申了中產主義意識形態是處於資本主義操作下的結果,藉由讓「自認為中產階級」的人們,對於控制自己人生這件事產生滿族,並熱衷於投資,讓這些「自認為中產階級」的人們有一種「一切操之在我」的感覺,促使他們不斷的進行投資,即使在虧損之後仍持續投入,這行為促使了資本主義所需要的持續性,並讓這主意更加盛行與根深蒂固。
作者一再強調至些是對於我們的剝削,我們進行的投資只會讓我們更身不由己地繼續參加這個資本主義的遊戲,我們投資於無法肯定有報酬的項目(包括學習、資產、金融),卻又認為它會在未知的某一天產生報酬,但又對報酬的多寡無法肯定。雖然作者在文末說這種矛盾終將引發我們自身的反省,但卻沒有說明應該如何來對抗這種困境,反倒是讓人覺得作者反對資本主義,認為資本主義用制度與意識形態將我們操作於中,但又不知應該如何改變,讓人有一種就是討厭資本主義的厭世感。
但是對於書面的副標題:「面對薪資停滯、金融危機、稅賦不公,中產階級如何改寫未來?」這個問號卻沒有有所回答,這讓我讀完之後仍然有一種茫茫不知所以然的困惑感。
# 發現
雖然說作者沒有對於中產階級的困境提出一個解法,但確實說出了目前中產階級的問題:不停的競爭,包括職場上的競爭、教育的競爭、以及為了維護日後失去工作時所進行的儲蓄與投資,這都是現代人不得不(也覺得理所當然)要進行的事項。這也是作者在書中一再提起的。這生活與過去緩慢以及一分耕耘一分收獲的農村生活已經大不相同,而生活步調更是再也回不去了。我想我們能做的應對不多,我們無法從這個環境中脫離,但我們可以在某個程度叛逆一下,例如讓工作與假日徹底分離,為自己有興趣投入時間,陪伴孩子更多,而不只限於要求他們有更好的功課與競爭力。當然我還還是無法避免在工作競爭力上面持續投資加強以避免日後失業的可能性,但我們可以多注重一下生活,讓激烈競爭的氛圍降低一些,這需要大家一起行動,這必須一項全民運動才會有效果。
# TODO
- 生活不是只有競爭,也要試著享受生活。
- 在追求作競爭力提升的同時,也要讓心靈感到充實。
- 孩子的人生不只是為了他們長大之後的競爭,也應該享受當下的生活,學會放鬆與愛人。
-----
- [博客來-中產悲歌:面對薪資停滯、金融危機、稅賦不公,中產階級如何改寫未來?](https://www.books.com.tw/products/0010874434?sloc=main)

View File

@@ -0,0 +1,36 @@
書名:最高學習法
日期2021/02/20
# 筆記
- 電影打從落幕那一刻才是正式開始
- 04以寫感想為前提來閱讀。
- 要是先有「寫」感想的預設的話,在讀書的時候,就更容易注意到想要、可以寫下的東西或材料。
- 帶著隨意的心態是無法認真吸收的。
- 具體閱讀方法:
- 看到重點處立刻畫線做記號
- 在之後想重新閱讀或做引用的地方貼上標籤
- 閱讀過程中有任何發現或衍生的想法,全都要寫下來
- 在書中選出一句「最佳名言」
- 寫下從書中得到的「最大的發現」
- 寫下今天就想實踐的「做想做的事」
- 每讀完一本書,一定要寫出感想,即便只是短文也好
- 43資訊和知識的最佳比例是3:7收集來的資訊經過消化之後還能用的就叫「知識」。就像超商特價的食物一樣吃不完的食物買再多也沒有用資訊也一樣太多的資訊只是之後又忘記而已不如吸收剛好可以消化的資訊將之吸收之後變成知識。
- 79睡前喝酒是造成睡眠障礙的原因。喝酒雖然多少可以幫助入睡但是卻會妨礙持續睡眠。在適量的情況下作者認為**「開心」的喝酒**,把喝酒當成溝通的潤滑劑也是很好的一件事。
- P246輸入之後馬上做輸出。最好的輸出時刻就是輸入的當下。
- 把大腦濟助的東西什麼都不要看的直接寫出來這種行為稱為「提取練習」retrieval practice
- 這說法跟[[20201201 - 學習如何學習#^da7fba|積極回想]]一樣。
- 「全面輸出」包含了「附加」、「關聯」、「整理」、「彙整」、「審查」等所有「精緻化」的要素,因此可以捯到強烈的增強記憶的效果。
- 開會和討論也一樣,結書之後馬上把想到的所有東西,全部寫在筆記上。包括「問題點」、「疑問」、「未解決事項」、「提議」等。
- P248使用曼陀羅計畫表來打造「腦內情報圖書館」。
# 心得
這本算是學習工具書作者將很多心法轉化為行動直接告訴你直接這樣做最有效也因此這本書的編排就像是一部準則一樣洋洋灑灑的列出數十條的「分類方法」讓讀者可以依據想要改進的方向來查閱。身為讀者想要一口氣實踐所有的方法也是很不實際的這時候作者在「貪心學習」P254的方法就可以派上用場了。雖然也是有不少是我們熟知的老生常談但這樣再一次重新複習也是讓自己有再一次審視自我的機會藉著書中所列出的種種行動方針再看看是否有可以加強補足的地方讓自己變得更有效率、比昨天更好。
# TODO
- 建立的新的讀書筆記樣板,加入了:
- 書名
- 日期
- 心得
- TODO
這些欄位

View File

@@ -0,0 +1,19 @@
書名:最高學以致用法
日期2021-03-20(週六)
# 筆記
這本書基本上內容跟[[20210220 - 最高學習法]]是差不多的,但是一本強調「輸出」的書,整本書為了讓讀者有所改變,能夠採取行動去「輸出」,整本書提供了很多方法,連「自我介紹」都變成了其中一個方法了,當然很多方法我們身為大人可能早就知道了,像是列出代辦事項啦,隨時用筆記本紀錄靈感啦,等等我們已經聽過很多次的技巧。作者透過一連串的方法轟炸無非就是希望我們能夠採取行動,那怕只用了幾個方法也好。所以不要再想了,輸出是為了變得更好,而行動才是第一步。
# 心得
像準則一樣的書,光是看過一次是不夠的,也不可能一次就學會、採用書中的每一個方法,這種書必須先從最有可能的方法來實踐,然後不停地回頭翻閱與修正,然後再使用下一個方法,只要可以這樣逐步實踐,應該就會變得跟作者一樣厲害吧。
海因利奇法Heinrichs law法則很有趣這個運用在勞動傷害的法則說明的是如果有1件重大的事故就代表其背後有29件較輕微的事故甚至它的背景是300件「有驚無險的事件」讓人感到緊張害怕或嚇一跳的事件
這是由美國工程師赫伯海因利奇Herbert William Heinrich根據勞動傷害的統計數據導出的法則。
這個法則,經常運用在失誤或客訴等負面事件上。
舉個例子如果有1件很嚴重的失誤那麼其背後就有29件小失誤以及300件一邊覺得「糟了」一邊先含混過去的事件。
# TODO
- 是該認真地的把讀書筆記變成BLOG了吧

View File

@@ -0,0 +1,27 @@
書名:
日期2021-04-06(週二)
# 筆記
- Herman Miller是cradle-to-creadle認證的廠商。
- Fisher Space Pen Infinium.
- 模組化洗衣機L'increvable。
# 心得
精準購買用一種永續的精神來勸導我們持續消費的壞處,我對於這點其實很有感,我自己也是一個很喜歡買東西,尤其買到好用精巧東西的時候更是感到沾沾自喜,覺得自己找到了一個省時省力或是值得賞玩賞味的好東西,但往往在一陣子之後,那東西就慢慢地失去了當初的新鮮感,或者不在那麼有耐性地去維護它,漸漸的,它就被冷落了。
另外,也許是年紀大了,腦袋漸漸地不喜歡記太多東西,「阿~太久沒充電壞掉了」、「這件事用剪刀也可以吧為什麼還要多維護一個工具阿」、「哀阿又多一個規格全部都type-c不行嗎」腦袋對於簡化事情這件事愈來愈熱烈甚至可以說強烈的希望周遭的事情簡單就好這也讓我再買新的東西尤其是科技產品不在那麼衝動譬如說我會希望新買的3C都是type-c接頭如果我屬意的產品還沒有type-c沒關係那我就等等吧如果在我等待的過程中退燒了那更好這筆錢就省下來了。
這種觀念延續到生活周遭的其他東西的話,那就更容易發現其他的冗餘物品,像是忍不住多買的抱枕、在遊樂場換回來的廉價玩具、因為肚子餓而多買的麵包、一時衝動多買的隨身碟等等,諸如此類你用過幾次(有些甚至只用過一次)就束之高閣的東西,你會發現其實它們不存在對你也不會有什麼影響。精準購買其實像是「斷、捨、離」的進階功夫,如果我們買的東西都是對我們有用的,必須的,那我們又何必在之後進行「斷、捨、離」整理術呢?
在精準購買這本書中,強調的不只是購買想的「必須性」,也強調了產品的「永續性」,書中不是要讀者一昧的省錢,只買便宜的東西,而是要讀者買使用上可以「最大化」的東西,「最大化」包含了耐用性、必須性,也包含了我們製造與回收的能源消耗與物料消耗,也包含了產品維修的可能性。所以作者認為多花一點錢,甚至多花上一大筆錢,來買一個可以用上一輩子的東西得很值得的。譬如說鍋子、可維修的家電、雨傘、可以升級的智慧型手機,作者在書中舉了一些例子,也給初這些推薦產品的價錢,讀者可以自己衡量有沒有道理。我自己是滿認同的,我一直認為使用熟悉的工具與物品可以簡化我們的生活邏輯,讓我們不必花太多時間在找東西,穿衣服,查說明書,甚至因為對於新東西的不習慣而操作錯誤,相對的,我們可以有更多時間來把工作做好、陪家人、進修,簡單來說,我們可以把時間花在更有意義的地方。
我認為精準購買所傳達的觀念跟筆記整理、「斷、捨、離」整理術、極簡生活這些書所傳達的觀念是類似的,如果我們不需要維護太多東西,我們可以對身邊的事物瞭若指掌,我們對於它們的使用就可以最大化,我們的心理負擔與幸福感就會更高,時間上也可以更充裕,對於人生意義的追尋也會更容易吧!
文中有介紹一些作者認為可以「終身受用」的品牌與產品,在此紀錄一下:
- [Fisher Space Pen infinium鉻色系列](https://24h.pchome.com.tw/prod/CBAP2V-A64532635?fq=/S/DXBH0L)
- [Sennheiser HD 25 專業型監聽耳機](https://24h.pchome.com.tw/prod/DCAYMP-A900AJCLM?gclid=CjwKCAjwjbCDBhAwEiwAiudBy_Wws3XJxCeAaUpjlNy_2YrTrhjkLNL1q2xFH1D8jGOBDyaQMbdNWhoC7H4QAvD_BwE)
- [Buy Fairphone 3+](https://shop.fairphone.com/en/fairphone-3-plus)
- [永續智慧型手機 Fairphone 3 登場,環保又方便維修](https://technews.tw/2019/08/31/ethical-smartphone-fairphone-3/)
# TODO
- [x] 把5.11 UCR賣掉吧。 ✅ 2022-06-01

View File

@@ -0,0 +1,21 @@
書名:高手學習
日期2021-07-23(週五)
# 筆記
- eudaimonia希臘詞意指一種特殊的幸福透過從事某種有意義的活動把自己的全部潛能都發揮出來。
- 也許你身上真的有某種創作天賦。帶著這個天賦默默死去,是很遺憾的事情;能把這樣的天賦充分發揮出來,那就很幸福。
- 大腦負責認知的兩個區域
- 前扣帶迴皮質ACC
- 負責監控
- 外側前額葉皮質LPFC
- 大腦中的決策系統
- 當ACC發現異常便向LPFC發出警告讓大腦能夠直接介入而不是用潛意識的方法來處理。
# 心得
這種書看了幾本以後,會發現其實中心思想都是類似的,例如說「練習」,就會提到要練習不會的,而不是一直練習你已經熟悉的。其實這部份就和[[20201201 - 學習如何學習]]中所提到的[[20201201 - 學習如何學習#9 腦連結]]是一樣的,也就是所謂的刻意練習。刻意練習在[[20201224 - 寫作是最好的自我投資]]也有提到。
再來說創造力,書中提到的發散思維其實也跟[[20201201 - 學習如何學習]]中提到的[[20201201 - 學習如何學習#^1c4ddf|發散模式]]是一樣的。
也就是說概念上都相同,只是作者用一種更加通俗與心靈雞湯的方式講給你聽,如果你已經熟悉了這些方法,其實是老調重彈的,有點無聊的。不過這也是看書的一個樂趣,戲法人人會變,巧妙各有不同,有時候我們就是難以在一本書裡面去理解作者的所有解釋與觀念,與抱著同一本書反覆推敲而不得其道,有時倒不如換個老師講給你聽。如果你也喜歡說書式的口吻,甚至權威口吻,那這本書還滿適合的。
# TODO

View File

@@ -0,0 +1,11 @@
書名:深入淺出設計模式 第二版
日期2022-05-26(週四)
Link[深入淺出設計模式, 2/e (Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software, 2/e)](https://www.tenlong.com.tw/products/9789865029364?list_name=srh)
# 筆記
紀錄在[[Design Pattern]]。
# 心得
# TODO

View File

@@ -0,0 +1,3 @@
- [學財報有用嗎?](https://statementdog.com/blog/archives/85)
- [買雞排懂財務報表](https://statementdog.com/blog/archives/164?utm_source=user_mailer&utm_medium=email&utm_campaign=send_edm)
- [買雞排懂毛利率](https://statementdog.com/blog/archives/268?utm_source=user_mailer&utm_medium=email&utm_campaign=send_edm)

View File

@@ -0,0 +1,7 @@
### 有興趣
- [ ] [論傑作 ——拒絕平庸的文學閱讀指南|讀書共和國網路書店](https://www.bookrep.com.tw/?md=gwindex&cl=book&at=bookcontent&id=15017)
- [ ] [增壓的 Python讓程式碼進化到全新境界 (Supercharged Python: Take Your Code to the Next Level) | 天瓏網路書店](https://www.tenlong.com.tw/products/9789865024055)
- [ ] [架構模式|使用 Python (Architecture Patterns with Python: Enabling Test-Driven Development, Domain-Driven Design, and Event-Driven Microservices) | 天瓏網路書店](https://www.tenlong.com.tw/products/9789865025960)
- [ ] [博客來-軟體預先架構之美學](https://www.books.com.tw/products/0010322321)
- [ ] [程序員的數學3 : 線性代數 | 天瓏網路書店](https://www.tenlong.com.tw/products/9787115417749)
- [ ] [圖解:機率‧統計【全新修訂版】 | 天瓏網路書店](https://www.tenlong.com.tw/products/9789864590681)

View File

@@ -0,0 +1,2 @@
# 介紹文
- [量子電腦:從原理、實作到應用](https://medium.com/@kelispinor/%E9%87%8F%E5%AD%90%E9%9B%BB%E8%85%A6%E6%A5%B5%E7%B0%A1%E4%BB%8B-short-introduction-to-quantum-computer-a7b159861786)

View File

@@ -0,0 +1,4 @@
電池的容量是用"mAH毫安培小時"來表示的例如一塊電池標有3000mAH 當我們用3000mA的電流也就是3A放電時可以放電一個小時則電池的放電倍率是1C如果以6A電流放電則可以用30分鐘則屬於2C。
# 鋰離子電池多少C是什麼意思
C用來表示電池充放電電流大小的比率即倍率。充放電倍率=充放電電流/額定容量如1200mAh的電池0.2C表示240mA1200mAh的0.2倍率1C表示1200mA1200mAh的1倍率。50C表示50 * 1200 = 6000mA。