2017年7月20日 星期四

[無聊的心得] Overhead

Overhead,名詞
https://en.wikipedia.org/wiki/Overhead_(computing)

中文叫做經常開支,為了達成某種目的所必須付出的與目標無關的代價

講到Overhead我就想到酬載(Payload),用貨車載貨來聯想:想像一台20噸重的卡車載了一顆20克的蘋果, 此時20克的蘋果就是酬載(Payload),如果送到目的地可以賺10塊錢,而推動20噸車體到目的地所需的油料消耗是3000塊,屬於經常開支(Overhead),恩...好一門美麗的賠錢生意。

思緒回到資訊世界,標頭會帶來Overhead,以XML格式資料為例,角括號裡面的是屬性(Property)名稱,而屬性間的包覆關係是階層格式描述,無論是角括號本身、頭尾的包覆這些都是經常開支(Overhead),因為其實你只在乎<></>之間的值(Value)資訊

看看以下酬載少於經常開支:
 
<themotherfuckerextremelongpropertyname>
1
</themotherfuckerextremelongpropertyname>

那麼傳輸時所花費的時間、硬體損耗都是花在經常開支而不是酬載(花在傳輸PropertyName)大部分資源都花在傳輸Header,沒意義的Overhead導致效率不彰,就要開始動腦筋改善這問題嚕

比方說每次傳輸時格式都是固定,我能不能不要傳Header,以隱含的方式表達資訊?還是縮短PropertyName?好像都不錯

[.NET] 執行序同步慣用Pattern心得 - I


MSDN說:
需要使用者互動的軟體必須儘快回應使用者的活動,以便提供豐富的使用者經驗。不過,同時它也必須執行需要的計算以儘快提供使用者資料。
在個人目前的工作領域,使用者體驗並非放在第一(甚至是最後一名),大部分時間都花在Coding工作處理常式(Routines),主要實現概念為狀態機(State-Machine)與多工處理(Multi-Task),在Main Thread執行

常式內的多工工作是純粹的商業邏輯,少有牽涉I/O存取,處理頻率穩定近乎週期性,但來源資訊難免要透過外部裝置取得,如網路介面、PCI裝置、使用者輸入...等,幾乎是慢速、頻率較低的資料源,為了避免影響Main Thread的多工輪詢,此時就可考慮用其他Thread分擔低速工作。

整理三個與狀態機搭配的常用分緒招,共通點是在Main Thread輪詢某資料狀態,該輪詢函式必須不能阻塞(Blocking)住Main Thread,否則會影響到其他多工作業

(沒實測過這些方法的具體效能,比較在乎應用上的設計模式(Pattern)是否容易嵌入既有架構)
  • Task
    • 發動:
      • 將要執行的Method裝載到Task物件中後,呼喚Start()開始執行緒作業
      • 也可以利用工廠模式,呼叫共用方法Task.Run()開始執行緒作業,同步狀態透過傳回的Task實例來操作
    • 同步:
      • Worker Thread輪詢Task.IsComplete至狀態為True
    • 心得:
      • Task封裝了Thread與若干執行緒同步元件(如Mutex),因此包含了執行緒操作與同步操作
      • 據說是在執行緒池內作業(待確認
  • ThreadPool
    • 發動:
    • 同步:
      • 無法得知方法最終被放到哪個具體執行緒,也沒封裝同步操作方法,所以必須在Method裡自行設計同步物件來辨識是否方法執行完成
      • 個人慣用Concurrent執行緒安全元件,如ConcurrentQueueConcurrentDictionary傳遞資訊到Worker Thread
      • Worker Thread輪詢Concurrent元件
    • 心得:
      • 最簡單易用赤裸的異步編成模式,不含任何同步方法的純粹執行緒池操作最?似乎懂了些什麼了?
  • MulticastDelegate
    • 發動:
      • 將要執行的Method裝載到Delegate中
      • 呼叫該Delegate的BeginInvoke(),必須帶入回呼方法(Callback)
      • 必須呼叫EndInvoke()作為確認該執行緒執行完成的辨識?
    • 同步:
      • 輪詢呼叫該Delegate時傳回的IAsyncResult.IsCompleted
      • 或是在Main Thread呼叫EndInvoke()(但是可能阻塞)
    • 心得:
      • 與Task一樣,封裝了執行緒操作與同步操作,只是設計模式上透過IAsyncResult或EndInvoke達成同步
      • 據說是在執行緒池內作業(待確認
class Program
    {


        static void Main(string[] args)
        {
            int state = 0;

            Action __delegate = null; 
            Task __task = null;
            IAsyncResult handle = null;
            ConcurrentQueue exchange = new ConcurrentQueue();

            int outValue = 0;
            //the main state machine
            Program.method();
            while (state != 500)
            {
                switch (state)
                {
                    case 0:
                        // use Task 
                        __task = new Task(new Action(Program.method));
                        __task.Start();
                        state +=10;
                        break;
                    case 10:
                        // wait until task done
                        if (__task.IsCompleted)
                            state +=10;
                        break;
                    case 20:
                        // use thread pool
                        ThreadPool.QueueUserWorkItem((object foo)=>{
                            Program.method();
                            exchange.Enqueue(0); // enqueue some meaningfll result
                        },null);
                        state+=10;
                        break;
                    case 30:
                        //wait until method had been scheduled and executed
                        if (exchange.TryDequeue(out outValue))
                            state +=10;
                        break;
                    case 40:
                        // use delegate/IAsyncResult 
                        __delegate =  new Action(method);
                        handle = __delegate.BeginInvoke((IAsyncResult ar)=> 
                            {
                                method();
                                __delegate.EndInvoke(ar); // you had to call this to release ar
                            },null);
                        break;
                    case 50:
                        // wait until method had been executed
                        if (handle.IsCompleted)
                            state = 500;
                        break;
                    default:
                        break;
                }
                Thread.Sleep(0);// yield to other thread to run
            }//while
        }

        static void method()
        {
            //reflect the current thread
            Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId.ToString());
        }

    }

後記1:MSDN的異步編程模式概述,對於非同步作業可有概略且完整的認識,其中APM(
Asynchronous Programming Model,就是Delegate/IAyncResult)已經漸漸式微變成不太推薦的Pattern

後記2:在.NET Core(MAC)上實測Delegate.BeginInvoke是會有例外的,原因在此,大致上是被開發小組視為Deprecated方法,未來可能會被幹掉

2017年7月17日 星期一

[.NET] REGEX 利用 Group 捕捉(Capture)重複字串(Repetitive Pattern)

Regular Expression(正規表達式),是一種抽象語言,用來表達字串的結構,比方[A-Za-z]+能表達所有的英文單字,應用上常用來驗證輸入,或是捕捉(Capture)具有特徵的字串片段,利用Regular Expression可以大幅簡化字串處理的繁瑣作業

這次的問題如下,假設某種字串格式的具體內容是以前綴(prefix)後綴(post-fix)框住,怎麼利用正規表示式驗證並捕捉(Capture)重複的片段?

好比以下類似座標的紀錄檔:
Model:M1
Coordinates:
X1.62 Y1.01
X9.53 Y2.3
X4.94 Y2.1
X8.55 Y3.15
X2.56 Y1.25

X5.57 Y2.72

首先分析一下文本的結構:
  • 標頭如同前兩行,形式與內容固定
  • 重複資料部分按行表達
  • 單筆座標以X<浮點數> Y<浮點數>表示
    • 座標值是我們最在意的部分
    • 希望可以一組一組捕捉分離,才好批次後處理解析數值
模式字串(Pattern)的設計想法如下:
  • 浮點數的表達不用傷腦筋,網路上很容易查到
  • 座標值的部分用()群組住單組X,Y座標(也是一種Symbol),後面以+表達此組Symbol會重複1到N次
  • 單組座標內又再次以()群組住浮點數字串表達
  • 考慮強健性,安插些泛空白字元在各個單元交接處
於是我們交出了以下的模式字串 :

"Model:[\\w]+[\\s]+Coordinates:[\\s]+(X([-+]?[0-9]*\\.?[0-9]+)[\\s]*Y([-+]?[0-9]*\\.?[0-9]+)[\\s]*)+"

其中"[-+]?[0-9]*\.?[0-9]+"就是浮點數的表達

這樣丟下去REGEX跑字串,模式字串中的()可以透過Match.Group(Index)存取,在這裡的模式字串我們共用到三個(),按照()在模式字串裡的順序,Group依序是:

  • Index-0:原始字串
  • Index-1:單組座標的群組
  • Index-2:X座標浮點數字串
  • Index-3:Y座標浮點數字串



String __input = 
"Model:M1 Coordinates: X1.62 Y1.01 X9.53 Y2.3 X4.94 Y2.1 X8.55 Y3.15 X2.56 Y1.25 X5.57 Y2.72";
String __patternUnnamed = 
"Model:[\\\\w]+[\\\\s]+Coordinates:[\\\\s]+(X([-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*Y([-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*)+";
List __coords = new List();
Match __matchUnnamed = Regex.Match(__input,__patternUnnamed);
//-----------------------------------
//  Unnamed         
//-----------------------------------
__coords.Clear();
for (int i = 0; i < __matchUnnamed.Groups[1].Captures.Count;i++)
{
// use X,Y group restore each coordinates
float[] eachCoord = new float[]{0,0};
eachCoord[0] = float.Parse(__matchUnnamed.Groups[2].Captures[i].Value);
eachCoord[1] = float.Parse(__matchUnnamed.Groups[3].Captures[i].Value);
__coords.Add(eachCoord);
}
//check all coordinates
Console.WriteLine(\"Unnamed\");
foreach (float[] item in __coords)
{
Console.WriteLine(String.Format(\"{0},{1}\",
item[0],
item[1]));
}
若模式字串裡()太多,或是覺得用INDEX標定群組可讀性太差,此時可以套入具名群組(Named Group)的概念,把()所圈住的Symbol具名化:

Model:[\\w]+[\\s]+Coordinates:[\\s]+(?<coord>X(?<X>[-+]?[0-9]*\\.?[0-9]+)[\\s]*Y(?<Y>[-+]?[0-9]*\\.?[0-9]+)[\\s]*)+

具名化後,便可以用Match.Group(Name)方式存取群組了

String __patternNamed = 
"Model:[\\\\w]+[\\\\s]+Coordinates:[\\\\s]+(?X(?[-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*Y(?[-+]?[0-9]*\\\\.?[0-9]+)[\\\\s]*)+";
Match __matchNamed = Regex.Match(__input,__patternNamed);
//-----------------------------------
//  Named
//-----------------------------------
//now we get the \"coord\" group
__coords.Clear();
for (int i = 0; i < __matchNamed.Groups[\"coord\"].Captures.Count; i++)
{
// use X,Y group restore each coordinates
float[] eachCoord = new float[]{0,0};
eachCoord[0] = float.Parse(__matchNamed.Groups[\"X\"].Captures[i].Value);
eachCoord[1] = float.Parse(__matchNamed.Groups[\"Y\"].Captures[i].Value);
__coords.Add(eachCoord);
}


後記1:.NET的正規表達式相容於Perl5,也與這裡相容,可以先快速的先驗表達式
後記2:據說.NET的Regular Expression模組是難得可以幫你Capture住群組值(Value)的函式庫

程式碼Repo

2017年7月16日 星期日

[.NET] 記憶體洩漏(Memory Leak)可能原因 - XML Serializer


承接此篇

.NET最爽的就是背後有GC(Garbage Collection)罩著,想配置物件就配置物件(NEW),用完拍拍屁股就走了,等下就會有服務生來收碗盤了,多爽!

可是如果你點到的菜是XML Serializer,可能就沒辦法那麼幸運了,GC是不收這道菜的,亂點到最後...整個餐廳桌上都會是你之前吃剩的XML Serializer

GC無法自動回收XML Serializer,(原因Dynamically Generated Assemblies段落),過於任性大量配置Serializer的話,記憶體可是會爆的

既然如此只能讓自己勤勞點,MSDN推薦的作法是,快取(Cache)住使用過的XML Serializer,每次欲利用XML Serializer時,統一向管理函式索取,若快取庫內沒有,則重新配置一個,若有,則取出既有的參考傳回,如此一來再利用時便不用重新配置,也就避免了潛在的記憶體洩漏問題了

參考以下程式碼:

  • 函式原型:
    • 引數 - 要序列化的物件型別(若物件是衍生自某基底型別,則透過陣列傳入具體衍生型別)
    • 傳回值 - XML Serializer實例
    • 視野(Scope): public static,視為工具函式(Utility)



 
 class utilities
    {
        /// <summary>
        /// Used to store key-serializer
        /// </summary>
        /// <returns></returns>
        private static Hashtable __dictionary = new Hashtable(); 
        /// <summary>
        /// Generate key according to contents of type list
        /// </summary>
        /// <param name="typeList"></param>
        /// <returns></returns>
        protected static int generateKey(List<Type> typeList)
        {
            //hash code summarize
           return typeList.Sum(__type => __type.GetHashCode());
        }
        /// <summary>
        /// Always fetch serializer from this function
        /// (Never allocate by yourself)
        /// </summary>
        /// <param name="mainType"></param>
        /// <param name="extraTypes"></param>
        /// <returns></returns>
        public static XmlSerializer getSerializer(Type mainType,Type[] extraTypes){
            
            //merge the list
            List<Type> __typeList = new List<Type>();

            __typeList.Add(mainType);
            __typeList.AddRange(extraTypes);

            var key = generateKey(__typeList);

            if (__dictionary.ContainsKey(key))
            {
                // this kind of serializer had been cached/used before , 
                // get the cached serializer
                return (XmlSerializer)__dictionary[key];
                }
            else
            {
                // never been used , need to create a new one and push into cache
                XmlSerializer __serializer = new XmlSerializer(mainType,extraTypes);
                __dictionary.Add(key,__serializer);
                return __serializer;
             }
        }//getserializer
    }//utilities

參考程式碼:https://github.com/smandyscom/cached_xmlSerializer

2017年7月8日 星期六

[VS Code x C#] VS Code加入參考


最近用起了VS Code,只是想單純地做一些C#程式範例(Snippet),起初照了美女Demo
(她真的很甜)安裝設定,簡要如下:


  1. 安裝VS Code(只是一個Open Architecture文字編輯器
  2. 安裝.NET Core(就是.NET Framework 核心肌群
  3. 在VS Code下安裝種種必須Plugin
  4. 下指令初始化專案資料結構
    1. $dotnet new 
  5. 開寫C#

可是呢,當我嘗試想用一些命名空間組件時就開始鬼打牆,好比說System.Collections.Hashtable,明明已經在最前面寫Using System.Collections
可VS Code不會讓你Build過,原因是 xxx Type or name could not be found

原因很簡單,你沒替專案加入參考
如同在Visual Studio下,要引用其他組件除了要在程式碼最開頭帶入Namespace(Using xxxxx),也必須在專案下加入參考(歐~想起來了)

以下紀錄一下如何替VS Code C# Project加入參考

首先打開下面的 終端機(Terminal)以以下指令下載Package,好像是可以想成下載函式庫
#dotnet add package <AssemblyName>

為什麼是System.Collections.NonGeneric?參考的Assembly(.dll/動態函式庫)怎麼查詢?
可看API Reference,裡面的Assembly就是組件名稱,以Hashtable為例,必須要加入System.Collections.NonGeneric,所以下
#dotnet add package System.Collections.NonGeneric

系統會跑出一些訊息表示正在Download,最終會處理完成,如果來了一些Error,比較大可能性是"打錯字",好比System.Collections打成System.Collection(少一個s)

下完以後,再下
#dotnet restore
會自動幫你在.csproj內引入dependencies

你的.csproj會多出dependencies片段:

 
<project sdk="Microsoft.NET.Sdk">
  <propertygroup>
    <outputtype>Exe</outputtype>
    <targetframework>netcoreapp1.1</targetframework>
  </propertygroup>
  <itemgroup>
    <packagereference include="System.Collections" version="4.3.0">
    <packagereference include="System.Collections.NonGeneric" version="4.3.0">
    <packagereference include="System.Runtime.Extensions" version="4.3.0">
    <packagereference include="System.Xml.XmlSerializer" version="4.3.0">
  </packagereference></packagereference></packagereference></packagereference></itemgroup>
</project>

這些是原先新專案結構初始化後所沒有的片段
加入組件到此就沒問題了

咦,若是要加入其他參考專案呢?
可以參考這個指令,dot add reference

後記:網路上會查到很多 手動改project.json以加入dependencies的建議,看起來是比較過去的作法,目前版本的.NET Core SDK似乎要嘗試以.csproj做統整(與Visual Studio一致),捨棄project.json作為專案結構的描述,也許是如此一來專案比較好在VS Studio與VS Code兩個平台上操作而不會有轉換的困擾

2017年6月30日 星期五

[Windows Form] Property Grid 使用心得 - Part III(EditorAttribute)


(承接此篇)

EditorAttibute

在該欄位點下"編輯"後,系統會找尋該欄位的是否有EditorAttribute,若有,則根據你指定的Editor型別配置(Allocate)一個新實例(Instance),並呼叫該實例的EditValue函式,最後將EditValue傳回的值Set回該Property

實作方式是宣告一Class,繼承UITypeEditor,並複寫(override)以下函式:

  • GetEditStyle
    • 傳回值型別為UITypeEditorEditStyle,傳回不同值可改變欄位編輯入口樣式
      • Model:該欄位會提供(...)讓使用者點擊,通常要另外提供表單(Form)讓使用者操作
      • DropDown:顧名思義是提供下拉式選單
      • None:沒得編輯
  • EditValue
    • 就是Editor最主要的函式,函式執行完畢後傳回值即為編輯完成的值
    • 慣用起手式:
      • 另外配置(Allocate)一個表單(Form)
      • 帶入(Controls.Add)自己的UserControl,i.e TextBox.Text
      • 顯示該表單,通常使用ShowDialog,使EditValue被Block在此行
      • 在使用者關閉表單後,ShowDialog返回
      • 從剛剛的UserControl取得使用者輸入的值,i.e TextBox.Text
      • 將輸入值傳回(Return)
 
class stringUIEditor :
        UITypeEditor
    {
        public override UITypeEditorEditStyle GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
        {
            return UITypeEditorEditStyle.Modal;
        }

        public override object EditValue(System.ComponentModel.ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
            Form __form = new Form();       // allocating a form , used to show-up editable facility 
            TextBox __tb = new TextBox();
            __form.Controls.Add(__tb);
            __form.AutoSize=true;
            __form.ShowDialog(); // show-up the form 
            return __tb.Text; //return the text value the user just input.
        }
    }


註:EditValue的輸入引數有許多妙用,可以用來判斷呼叫對象的特性,若要打造比較萬能的Editor就得要深入了解

從(...)呼喚的UIEditor
參考:
程式碼參考

[Windows Form] Property Grid 使用心得 - Part II(TypeConvertor)

再稍微整理一下Property Grid的一些概念:
  1. 顯示的欄位名稱及內容是依SelectedObject內宣告的Properties展開
  2. 顯示樣式則由各Property所擁有的屬性(Attribute)所定義
    1. 值(Value)以外的顯示樣式由以下屬性定義
      1. Display - 控制顯示名稱
      2. Category - 控制群組分類
      3. Description - 控制下方描述
      4. Browsable - 控制是否隱藏
    2. 值(Value)的樣式控制有以下幾方面
      1. Readonly - 控制值可否給使用者輸入
      2. 顯示樣式的控制,由TypeConvertor屬性決定
      3. 編輯器的控制,由EditorAttribute屬性決定
PropertyGrid各顯示樣式控制對應屬性



TypeConvertor
概念上就像是"顯示樣式轉換器",把該欄位的值丟進去對應的Convertor,根據你自訂的邏輯處理後將傳回的值顯示,實作方式為,宣告一Class,繼承TypeConvertor,並複寫
  • ConvertTo
    • 將Object轉為顯示值,通常是String
  • ConvertFrom
    • 將顯示值轉回Object
對於Enum的顯示樣式轉換則需要自訂EnumConvertor,其實EnumConvertor也是繼承自TypeConvertor,尚不清楚此層繼承多了什麼細節,實作方式亦為宣告一Class,並繼承EnumConvertor,複寫ConvertTo/ConvertFrom


 
 class optionEnumConvertor :
        EnumConverter
    {
        /// <summary>
        /// Convert From String To Enum Type
        /// </summary>
        /// <param name="context" />
        /// <param name="culture" />
        /// <param name="value" />
        /// <returns></returns>
        public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            switch (value.ToString())
            {
                case "選項1":
                    return MyEnum.OPTION1;
                case "選項2":
                    return MyEnum.OPTION2;
                default:
                    return null;
            }
        }
        /// <summary>
        /// Convert From Enum Type To String Type
        /// </summary>
        /// <param name="context" />
        /// <param name="culture" />
        /// <param name="value" />
        /// <param name="destinationType" />
        /// <returns></returns>
        public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            switch ((MyEnum)value)
            {
                case MyEnum.OPTION1:
                    return "選項1";
                case MyEnum.OPTION2:
                    return "選項2";
                default:
                    return null;
            }
        }

       public optionEnumConvertor(Type type):base(type)
       {
       }
    }
最後就是在對應的Property上加註TypeConvertor屬性,PropertyGrid就可以根據其指定的TypeConvertor進行樣式轉換了
 
 [DisplayName("想要選什麼?")] // much more human-readable?
        [TypeConverter(typeof(optionEnumConvertor))]
        public MyEnum Property3 { get; set; }

  

註: Enum比較特別的是,可以加註在屬性上,也可以加註在宣告上
 
  [TypeConverter(typeof(optionEnumConvertor))]
    enum MyEnum
    {
        OPTION1,
        OPTION2,
    }


Enum轉String


疑:Code-prettify如何顯示</summary>...不懂
自問自答:XML註解在HTML內當然是要Escaping呀傻傻,把程式貼到這邊Escaping完再貼入<pre>Block中

參考:
程式碼參考

2016年5月7日 星期六

[Raspberry Pi]DHT11溫濕度模組 x Node.js = DHT11 Server


這次要來嘗試自DHT11取得的溫溼度資訊以網頁(Web)的形式公布,也就是HTML Server
最後要突破路由器的限制逆風高灰,讓任何的客戶端都能從網際網路連上伺服器,上面就寫著DHT11讀到的溫濕度資訊

整個架構概念如下:
  1. 將DHT11定時(1分鐘一次)讀出的溫濕度寫到某個檔案(th.out)裡
  2. 開啟一個HTML Server,在客戶端連入時,讀取th.out內的資訊,秀在主頁面上
  3. 為了能穿透路由器,讓客戶端能從網際網路連入,我們需要某種類似DDNS的穿牆服務
OK,戰鬥開始!第一步,將DHT11的讀出值定時寫入特定檔案

從先前的Adafruit提供的python程式,我們已經可以很輕鬆的讀出資訊顯示在Console上,只要再利用Bash Script做點小小加工就可以讓DHT11全天候運轉偵測溫濕度:

開啟新Script
#vim outputTh.sh
設定為可執行
#chmod u+x outputTh.sh

在Script寫下:
#!/bin/bash
while :
do
        date > th.out #先輸出當前時間
        $(./Adafruit_Python_DHT/examples/AdafruitDHT.py 11 4) >> th.out   #輸出DHT11資訊,注意相對路徑
        sleep 60 #在此暫停60秒

done

主要功能是開啟一個無限迴圈,每60秒讀出目前時間以及DHT11資訊,並輸出(覆蓋)th.out

執行此Script!(因為牽涉到底層存取,請給他sudo下去)
#sudo ./outputTh.sh

在此先做個確認,理論上輸出檔案內容應該會長這樣:
20160507 14:14:03
Temp=29.0*C  Humidity=62.0%

最後讓此Script在背景以nohup執行
$nohup sudo ./outputTh.sh &


第二步,設定DHT11 Server(HTML Server)

這裡選擇最好上手的Node.js,這是個整合的很好的伺服器端執行環境,可以把他想像成事件驅動架構,對開發者而言,只要搞懂各事件(Event)的發出時機,並且寫下相對應的處理常式(Event Handler)即可

首先是安裝Node.js
#sudo apt-get install -y nodejs

接著開啟一個Java Script:
#vim server.js

內容寫這樣:

var http = require("http")  '引入http模組

http.createServer(function(request,response){
 response.writeHead(200 , {"Content-type":"text/plain"});
 response.write(fs.readFileSync('./date.out'));
 response.end();

}).listen(8888);

以上Script要這樣解釋:


  1. 程式走到第三行時,會從http模組呼叫createServer函式
  2. createServer()引數1是客戶端連上時要做的回應動作,既然是一連串的動作所以是一個函式而不是變數,就是所謂的回呼啦(Call-back)
  3. 在這裡的回應動作是,讀出th.out的內容在主畫面上輸出
  4. 而createServer()會吐回一個伺服器物件,呼叫此物件的listen(),表示啟動監聽連入需求,這裡使用的監聽埠口為8888

值得一提的是,node.js的任何函式幾乎都有同步/非同步兩個版本,一開始使用了fs.readfile,(註:讀取檔案的非同步版本),結果實際讀取檔案的時機不見得在函式呼叫後,所以可能會在response.end()前都沒讀出buffer,常常以exception收場

所以這裡讀取檔案的函式必須要用同步呼叫(Blocking IO),即為fs.readFileSync()的return值就是讀出的字串

最後以Node.js啟動剛剛寫好的Script
#nodejs server.js

此時來做個小小測試,看看是否能在區域網路內看到網頁,開瀏覽器連上:
http://192.168.1.1:8888


好的,區域網路內連上DHT11 Server沒問題了,最後記得讓DHT11 Server登出後也能在背景執行:
$nohup nodejs server.js &


第三步,先別管DHT11 Server了,你聽過ngrok嗎?看看他們官網的圖示,他們就是有辦法讓你的伺服器暴露在路由器之外,

首先是安裝,在apt-get上沒有此套件,我們直接下載執行檔解壓縮即可

下載:
$wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
解壓縮:
$unzip ngrok.zip

取得!接著的動作十分簡單,可以想像這個軟體做的動作就是左耳進右耳出,把封包轉送出/ngrok伺服器,啟動ngrok,引數打入:

$ngrok http 8888

意義就是以http協定轉接DHT11 Server監聽埠口(port:8888),嘿!逼哩八啦他還給你一個很神秘的網址,把他貼上瀏覽器就知道………哇!DHT11 Server走入人群了!


用手機從3G網路測試看看…嗯…沒問題!
以後回家前就可以先知道家裡溫濕度如何了(那又怎樣




2016年1月19日 星期二

[Raspberry Pi] Ad Hoc


Ad Hoc,拉丁原文有"將就的、隨意的"的語意,在Wifi協定中代表"無線隨意網路",也就是不需要中央基地台,通訊端之間即能夠構成的網絡

相較於Ad Hoc模式,Infrastructure就是日常生活中熟悉的中心輻射式網路拓樸,網路內通訊統一透過中央基地台分發

在Linux下設定Ad Hoc模式的步驟非常簡單,只要改/etc/network/interface內設定再重開裝置即可!
參考:http://spin.atomicobject.com/2013/04/22/raspberry-pi-wireless-communication/

步驟:

  1. $sudo vim /etc/network/interface #改網路設定
  2. wlan0設定如下
    1. iface wlan0 inet static   #靜態IP
    2. address 192.168.1.1      #設定IP
    3. netmask 255.255.255.0    #設定子網路遮罩
    4. wireless-channel 1  #訊號通道
    5. wireless-essid RPiAdHocNetwork #暴露在可用的Wifi清單中的名稱
    6. wireless-mode ad-hoc #模式:ad-hoc
  3. 存檔
  4. $sudo ifdown --force wlan0  #先將wlan0關閉(強制下架)
  5. $sudo ifup wlan0 #再將wlan0重新打開,套用方才設定的設定值

如此一來就可以了,果真隨意簡單

原先以為通訊上不用經過HUB或許傳輸上會變快,殊不知ad hoc是比Infrastructure更為古老的架構,許多針對Wifi傳輸最佳化的方法可能都不支援,而且還帶有一些毛病,參考此篇:Limitations of Ad Hoc Mode Wireless Networking


實際用iperf測速,使用Ad Hoc相較於Infrastructure,傳輸速度完全沒有提昇,該是嘗試Wifi Direct看看





2016年1月10日 星期日

[.NET] Memory Leaks


前陣子在工作上遇到了"OutOfMemory"的例外,深感驚訝,以為在GC環境下,這種像是慢性病的東西可以從此與我絕緣,真要下手查BUG時,突然理解到,關於GC,關於記憶體分配模型,我理解的太模糊,才會感覺每一行指令好像都值得懷疑似的(最後發現是TextBox內容控制機制有問題)

.NET的記憶體分配模型:


  • 堆疊(Stack):
    • 何時被分配?
      • 進入函式,配置實值物件,或宣告實例參考時
      • 依序連續分配,像是盤子一層層疊起來所以稱為Stack
    • 什麼東西會被分配?
      • 實值型別(Value Type)物件
        • 註:可以從物件是否繼承自System.ValueType來確定是否為實值型別
        • 例如:Int , Float , 使用Structure宣告的物件
      • 參考型別(Reference Type)參考(Reference),可稱為Handle,概念上可以想成指標
    • 何時被回收?
      • 當函式退出(Return)時,按照分配順序一層層移除,後進先出
      • 註:所以若在此函式範圍做了 Object a = new Object,在退出此函式時參考a會被沒收,此時跟a鏈結的物件,就成了無主遊魂,很快地會被牛頭馬面GC(Garbage Collector)回收,帶到陰曹地府等待投胎轉世(輪迴?)

  • 託管堆積(Managed Heap):
    • 何時被分配?
      • 隨時隨地,在你使用New配置物件時
    • 什麼東西會被分配?
      • 參考型別物件,可稱為Instance
        • 註:String是參考型別物件,分配在堆積上,只是其屬性為不可修改(Immutable)
    • 何時會被回收?
      • 當GC掃描到此Instance並無任何參考指向他,便會標記起來,接著自動回收

  • 非託管堆積(Unmanaged Heap):
    • 何謂非託管?
      • 就是非在CLR上執行的程式,不受.NET管理,好比你用C語言基於Standard Library寫出來的程式
    • 何時被分配?
      • 隨時隨地,在你使用New/malloc配置物件時
    • 什麼東西會被分配?
      • 任何你用New/malloc配置的東西
    • 何時會被回收?
      • 必須手動明確(Explicit)地呼叫Delete/Free,此物件所佔據的記憶體空間才會被釋放

可能的記憶體洩漏原因:

  • 有問題的非託管程式碼
    • 物件被分配(New/malloc)出來了,但忘記寫下Delete(Free),典型的錯誤
    • 最可怕的是第三方供應商的函式庫有問題,你沒程式碼他又不想理你
  • 託管程式碼:
    • 對於IDisposable物件,使用過後未呼叫Dispose()
      • 非託管程式碼或是運用到獨佔資源(Resource)的物件可能會實作此介面,作為提供呼叫端明確呼叫釋放時機的可能性
      • 例如:Filestream、SqlConnection、Usercontrol
      • 需要手動控制釋放時機的物件才實做IDisposable(程式結束時釋放的那種不算)
      • 對於所有IDisposable物件,盡量使用Using,減少人為失誤造成的困擾
    • 物件已失去參考,但隸屬的EventHandler仍與活動中的Event連結
      • 由於成員函式隱含實例作為參考,可能會使得EventHandler對應的物件無法被釋放(這點相當有趣,物件成員函式預設至少有一引數為自己)
    • 其他,自己應用上的失誤
      • Queue:Enqueue沒有Dequeue的快,結果就是一直累積
      • 傾盡CPU全力,狂寫檔案(StreamWriter.Write()),緩衝區被玩爆(這種問題很快會爆發,好查)
      • 記憶體控制機制設計有問題

分析記憶體洩漏的好工具,perfmon.exe

    • 心得:
      • 帶出的數據非常完整,而且可以設定固定採集時間長度,記錄在不同的採集檔內,比起自己在程式碼內寫LOG,用這工具比較能確保實驗在固定的資料搜集條件下進行
    • 抓漏心法:
      • 找出可能的嫌疑點,開perfmon設定長度N分鐘週期M秒採樣,時間到後觀察增長趨勢
      • 沒改善,表示找錯方向,改動其他嫌疑點,重複上面過程,再看一遍
    • 詳細使用方法:
    • 非常好的評論:


參考資料:
經常性的字串處理你該用StringBuilder
Using using as grantee (even for the filestream):
Close and Dispose
Heap、Stack、Reference Type、Value Type、Boxing、Unboxing

2016年1月4日 星期一

[Raspberry PI] PWM


PWM,Pulse Width Modulation,以數位方式控制高低電位週期比例來達到模擬類比電壓的效果,比方說控制馬達速度、LED燈亮度...

值得一提的是,因為是模擬的方式所以PWM訊號頻率其實也會出現在輸出訊號的頻譜裏,當然也要輸出裝置頻寬夠高才能夠被察覺.

樹莓派身為一塊萬用開發版當然有辦法輸出PWM,目前查到有下面幾個方法:


  • RPi.GPIO
    • 指令下達方式:Python
    • PWM生成方式:軟體模擬
    • 安裝
        • #sudo apt-get install python-dev python-rpi.gpio
    • 用法:
        • #sudo python #進入Python
        • >>import RPi.GPIO as GPIO
        • >>GPIO.setmode(GPIO.BCM)
        • >>GPIO.setup(18, GPIO.OUT)    #設定GPIO18為輸出腳
        • >>pwm = GPIO.PWM(18, 500)   #建立PWM實例,在GPIO18輸出,頻率為500Hz
        • >>pwm.start(100) #開始輸出DUTY=100%的脈波
    • 心得:
      • 用在伺服馬達(Servo)上會感覺到顫振,確實是用軟體方式來輸出,持續輸出脈寬的重複性不佳,大概只適合做LED燈控制等人機應用.
      • 軟體輸出方式大概是以系統內建計時器,去計時控制輸出腳位為高位/低位,由於Linux並非即時系統,各行程(process)無法保證被定時呼喚,因此即便系統計時器是可靠的,在上位做控制的行程也沒辦法總是在計時到達的當下去處理.



  • RPIO
    • 指令下達方式:Python
    • PWM生成方式:半軟體模擬?(文件上寫著透過DMA通道,不太清楚背後機制)
    • 安裝
        • #sudo apt-get install python-setuptools
        • #sudo easy_install -U RPIO
    • 用法:
        • #sudo python #進Python
        • >> from RPIO import PWM
        • >> servo = PWM.Servo()           #建立一個Servo實例,預設週期為50Hz
        • >> servo.set_servo(18,1500)    #在GPIO18上持續輸出脈寬為1500us的脈波
        • >> servo.set_servo(19,10000)  #在GPIO19上持續輸出脈寬為10000us的脈波
    • 心得:
        • 發現如果將其中一隻Pin輸出關閉,所有輸出都會被關閉,不知道是不是BUG?
        • 可以多通道輸出(但不曉得通道代表什麼意思),比方說
          • >> servo0 = PWM.Servo(0)
          • >> servo1 = PWM.Servo(1)
          • 如此就建立了兩個Servo物件對應到不同的DMA通道,但操作起來仍發現會相互干擾
        • 顫陣比GPIO小,但仍能感受到.


  • pigpio
    • 指令下達方式:pipe 、c-interface、python
    • PWM生成方式:軟體模擬(p指令)、硬體輸出(hp指令)

      • #git clone https://github.com/joan2937/pigpio #跟上最新版
      • cd PIGPIO
      • make
      • sudo make install

    • 用法(pipe方式):

      • #sudo pigpiod #喚起掌管輸出入的專用daemon
      • #pigs  p 19 255 #在GPIO19上輸出DUTY 100%的PWM信號(軟體模擬輸出)
      • #pigs hp 18 100 500000#在GPIO18上輸出頻率為100Hz DUTY為50%的PWM信號(硬體直接輸出)

    • 心得:
      • "硬"PWM,直接用樹莓派BCM2835晶片內建兩個底層PWM輸出單元生成PWM信號,直接以IC輸出信號重複性一定是最穩定的,也就是脈寬不會有忽大忽小的問題
        • pigpio只是作為寫入晶片上特定registor的API罷了
      • BCM2835只有兩個PWM通道(Channel),甚至在Pi2上只有一隻引腳被接到介面IO上,而Raspberry PI B+則有GPIO18,GPIO19可用.
      • 操作起來真的是顫陣最低的(滿意)!
      • 另外要說pigpio的shell指令很豐富,初期拿來測試硬體相當順手,文件也建立的很完整(滿意)!
      • 各型Raspberry PI GPIO表:http://elinux.org/RPi_BCM2835_GPIOs
從BCM2835文件可以發現這塊晶片頂多只能輸出兩個PWM信號,真要再多的話就要以通訊方式(I2C UART)送出命令透過PWM IC代為輸出穩定的PWM信號了,類似透過這樣的晶片

後記:
即便是用了Hardware PWM,功率電路之間的相互干擾也是另一令人頭痛的因素....尚待努力


其他參考:
BCM2835硬體Sheet



2015年10月10日 星期六

[Arduino Uno當作Atmel ISP燒錄器] - 燒錄失敗解之一


最近與朋友一起做了一塊板子,用的是Atmel32U4作為MCU,想用順手的Arduino IDE作為開發環境,只是光是燒錄測試碼就遭遇小困難…

首先參考葉難的將Arduino Uno當做一台ISP線上燒錄器,燒錄bootloader或sketch到麵包板上的ATmega328P-PU晶片,按部就班的做

基本上有幾件事要確實做好:

  • 從燒錄器(Arduino UNO)到目標板(Atmel32U4)的硬體線路,共計有
    • MOSI
    • MISO
    • SCK
    • RESET
    • 5V
    • GND
  • 燒錄器本身是否準備完成?
    • 打開範例->ArduinoISP,燒錄到燒錄器內(Arduino UNO)
  • 最後就是將bootloader燒進目標板(Atmel32U4)
    • 工具->燒錄bootloader


此時若 Arduino IDE->檔案->偏好設定,顯示詳細輸出皆勾選的話,可能會得到錯誤訊息:

燒錄bootloader時發生錯誤。

Reading | ################################################## | 100% 0.05s

avrdude: Device signature = 0x00ff00
avrdude: Expected signature for ATmega32U4 is 1E 95 87

         Double check chip, or use -F to override this check.

avrdude done.  Thank you.


signature指的是ATmega32U4晶片的辨識ID,顯然辨識錯誤

在AVRFreak找到可能解法之一,需要在下avrdude命令時加上 -B8選項(-B 與 8之間沒有空格!!) ,使得燒錄時的運作頻率不要那麼快。

加上此選項進行燒錄需要在命令列執行,我是在Windows下用Git shell:

$avrdude -CC:\Program Files (x86)\Arduino/hardware/tools/avr/etc/avrdude.conf -v -patmega32u4 -cstk500v1 -PCOM7 -B8 -b19200 -e -Ulock:w:0x3F:m -Uefuse:w:0xcb:m -Uhfuse:w:0xd8:m -Ulfuse:w:0xff:m


This method worked for me :).


參考資料:
avrdude reference 


2015年8月10日 星期一

[.NET]Thread.Yield()


另一個Thread切換執行的方式:Thread.Yield()

  • Yield()
    • 呼叫時會確認此CPU上是否有其他等待被呼叫的執行緒,若是,切換到其他執行去執行,若否,則會讓當前執行緒繼續執行;切換到何執行緒由作業系統自動選擇
    • 高CPU利用率
    • 關鍵:不管有沒有讓成功,CPU還是會執行你行程上的任一執行緒
  • Sleep(Time)
    • 將當前執行緒暫停一段指定時間,指定時間內當前執行緒絕對不會被執行,而空出來的CPU資源則交由作業系統去運用;在指定時間到達後,此執行緒重新進入分配佇列內等待執行
    • Thread.Sleep(0)有短暫讓出的效果,會檢查是否有等待執行的執行緒,若否,則本執行緒不會被暫停會繼續跑下去

    • 較低CPU利用率
    • 關鍵:在Sleep期間,此執行緒絕對不會被執行

[Windows Form] Property Grid 使用心得 - Part I


 Windows Form 架構中,有個視覺元件叫做Property Grid,她展現的Layout大概就是像設計工具中屬性頁的樣子,可以把物件中標定為Property的欄位顯示出來

這東西有著許多好處:


  • 可以很快速地開發出反映一個Class中的Property的基本UI(在一些不重視視覺效果的應用領域奇方便,例如…自動化設備軟體(說溜嘴了?)
  • 由於是反映Property,所以可在Property中限制參數的變動範圍,提供一種基本的、制式的使用者輸入防呆機制 
  • 搭配Type Convertor還有其他若干屬性設定後,可以以此為框架延伸出看來很有一致性的UI架構,而且只需要少量的程式碼
  • 總體來說我覺得用在不需要很Fancy但講究開發效率的領域中非常的適合(就是在說自己的工作)
  • 最基本用法:
  1. 定義一個Class 裡面有若干個Property,並宣告一個Instance出來
  2. 拉入一個Property Grid到你的Form內,將SelectedObject指向剛剛宣告的Instance
  3. 執行,Property Grid會完全把Class內的Property給反映出來
其中有趣的是,Property Grid會自動限制輸入為變數可接受的型態,宣告為Integer你就不可能輸入abc、宣告為boolean選項就只會有true/false,若宣告為某種Enum,就會以ComboBox的形式提供有限個選項,選項會以Enum項目名稱顯示。
 enum MyEnum
    {
        OPTION1,
        OPTION2,
        OPTION3,
    }

    class MyClass
    {
        public int Property1 
        {
            get
            {
                return __innerVariableInteger;
            }
            set 
            {
                // constrained range
                if (value > 10 && value < 20)
                    __innerVariableInteger = value;
            }
        }
        public string Property2
        {
            get
            {
                return __innerVariableString;
            }
            set
            {
                __innerVariableString = value;
            }
        }
        public MyEnum Property3 { get; set; }

        protected string __innerVariableString = "";
        protected int __innerVariableInteger = 15;

    }
若是型別為Enum則會自動提供ComboBox選項



如果我想限制參數1數值必須大於10小於20,只要在Property中的Set函式內寫一些拒絕輸入的機制,使用Property Grid的使用者就不可能突破此關卡,例如:
public int Property1 
        {
            get
            {
                return __innerVariableInteger;
            }
            set 
            {
                // constrained range
                if (value > 10 && value < 20)
                    __innerVariableInteger = value;
            }
        }
你永遠沒辦法Key-in這範圍以外的值。

  • 使用Attribute標籤補述Property:

若是以最陽春的作法直接反映Property,可以注意到顯示出來的變數名稱都是以程式的宣告變數名為顯示,這樣是很難被使用者接受的(又有底線、名詞又不平實誰看得懂)

又或是某些Property並不想提供給使用者編輯,更甚者不想給人家看見

此時就要以Attribute描述的方式給予Property一些敘述,常用的敘述有:

    • Browsable         // 控制屬性是否可顯示在Grid上
    • ReadOnly          // 控制屬性是否可編輯
    • DisplayName     // 控制屬性顯示出來的名稱(就是用此標籤將艱澀的變數名轉譯成友善的敘述)
    • Categrory          // 控制屬性的分類,這可以做出折疊分類的效果
    • Description        // 控制屬性的描述,就是下面灰色的Bar會帶出的描述
加工一下MyClass:
  class MyClass
    {
        [Description("The input value range is 16-19")] //offered the description which would showed on the bar below
        [Category("Catagory A")]            // offered the folded categoring method
        [DisplayName("Integer Parameter")]  // offered the customised display name
        public int Property1 
        {
            get
            {
                return __innerVariableInteger;
            }
            set 
            {
                // constrained range
                if (value > 10 && value < 20)
                    __innerVariableInteger = value;
            }
        }
        [Browsable(false)]  //wont be showed on PropertyGrid
        public string Property2
        {
            get
            {
                return __innerVariableString;
            }
            set
            {
                __innerVariableString = value;
            }
        }
        [DisplayName("想要選什麼?")] // much more human-readable?
        public MyEnum Property3 { get; set; }

        protected string __innerVariableString = "";
        protected int __innerVariableInteger = 15;

    }

  • Property1顯示名稱改變嚕,而且有了分類
  • Property2隱藏嚕
  • Property3更讓人看得懂意義

咦,但是選項還是以程式語言的形式呈現阿?Boolean選項是true/false,怎麼就不能叫做 "是/否",Enum選項是英文,阿公阿罵哪看得懂,而且還帶了宅味在其中,這該怎麼去除呢?接著就要應用TypeConvertor,將選項做適當的轉譯,是退散宅味的好秘方,字字句句都可以成為很口語與詳實的描述。(之後再寫…)

後記:

一切的入手是從這篇開始的,非常感謝
最近發現這篇做了更多進階的介紹,讚
屬性化程式設計概念
關於TypeConvertor的MSDN原始說明


2015年7月11日 星期六

[LINUX] 使用憑證登入SSH

為何要使用憑證登入ssh呢? 因為如此一來登入ssh的動作就是認機不認人,簡單的說,往後登入SSH不需Key-in密碼,這在某些應用上是方便的,比如說 Github Private Server,若是透過ssh做push的話就不用每次都要打密碼,也可以很方便的用Git的圖形介面做同步(Sync)動作。

我用的是CentOS , 基本上是照著官方文件操作的

參考來源: http://wiki.centos.org/HowTos/Network/SecuringSSH#head-9c5717fe7f9bb26332c9d67571200f8c1e4324bc


概念如下:

在終端機上:

產生RSA憑證
#ssh-keygen -t rsa
如果一路enter到底的話,會在你的本機~/.ssh資料夾下產生id_rsa.pub檔案,即為公鑰

接著將此公鑰傳遞到伺服器上,
#scp ~/.ssh/id_rsa.pub user@192.168.0.1:/tmp
// 將id_rsa.pub拷貝到 192.168.0.1的tmp資料夾下,以user登入


在伺服器上(假設登入為user):

在~/.ssh資料夾下新建authorized_keys
#touch ~/.ssh/authorized_keys
接著匯入方才傳到伺服器上的公鑰內容到authorized_keys檔案內
#cat /tmp/id_rsa.pub >> ~/.ssh/authorized_keys

最後設定一下SELinux(效果不明,不大清楚為何要做)
#restorecon -Rv ~/.ssh

官方文件就寫到此了,但是!!!!!本魯我卡在這裡仍是無法透過憑證登入!!!
原來還少幾個設定……(價值3小時)

在伺服器上(登入為root)
修改sshd設定
#vim /etc/ssh/sshd_config

裡頭有幾行設定預設是未開啟的,請把他們打開:
PubkeyAuthentication yes
AuthorizedKeysFile      .ssh/authorized_keys

接著重開sshd
#service sshd restart

如此一來才能透過RSA憑證登入,GOOGLE了好久最後自己發現

後記1:檢視公鑰碼(有時候弄到亂掉,可以下此指令比對終端機與伺服器上的公鑰是否一致)
#ssh-keygen -lf id_rsa.pub

後記2:CentOS 的 useradd 指令沒有 --disabled-password這個選項,只要單純的useradd user即能做到不允許密碼登入(因為密碼未設定)



2015年6月21日 星期日

[Raspberry Pi]DHT11溫濕度模組 x Google sheet

很久以前參加了台灣樹莓派的社群聚會,認識了DHT11這款溫濕度監控模組,其主要是顆溫濕度感測器,可以輸出溫度、相對濕度兩種資訊,但對應用者而言,在乎的是怎麼跟這小東西交換資訊。

在那場聚會裡,主講者sosorry先生分享了如何實做底層Linux Driver去實現One-wire protocol去跟DHT11溝通取得資料,實做Linux Driver是件精彩的事,但需要花點功夫理解,小弟不才一直沒有動手操作了一遍,一直到最近,在國外網站Adafruit發現已有非常簡單的應用方法,甚至在文末追加了存取google sheet的方法,使得你量測到的資料可以追加在google sheet,實現了隨時隨地透過google監控家中溫/濕度,甚至是簡單的紀錄與分析,十分有趣!

原文出處:https://learn.adafruit.com/downloads/pdf/dht-humidity-sensing-on-raspberry-pi-with-gdocs-logging.pdf
在這邊分享(翻譯)出來:

1.硬體準備:

因為原文是用Adafruit的轉接板作為範例,這裡忽略掉原文的說明,可以參考這裡的圖示連接
基本上只需連接三隻腳,3.3V , GND , 以及訊號線。


NOTE: 我是去光華商場買了長得像這樣的DHT11模組,他是將原本的DHT11焊在PCB板上,並加上了PULL-UP電阻,大概幾十塊錢。

其中S腳是訊號線,-是地,中間那根則是3.3V電源
因為已經加上了PULL-UP電阻,所以訊號線的部份不需要額外掛電阻,這點跟上面範例稍微不同,需要稍加注意。

2. 軟體部份:

下載Adafruit分享的git package,裡面有範例程式,以及Adafruit撰寫的DHT11驅動
#git clone https://github.com/adafruit/Adafruit_Python_DHT.git

準備好Python環境
#sudo apt-get update
#sudo apt-get install build-essential python-dev python-openssl

安裝Adafruit 驅動套件
#cd Adafruit_Python_DHT       //去剛剛git clone下載好的位置
#sudo python setup.py install    //安裝套件

準備到這裡就已經完成,接著就是執行Adafruit已準備好的測試程式來看看
#cd examples     //前往範例資料夾
#sudo ./AdafruitDHT.py 11 4    //執行測試程式

由於存取底層GPIO需要ROOT權限,所以記得要打sudo
這程式需要兩個引數,第一個引數是Sensor形式,第二個引數是訊號線對應的GPIO號碼

Sensor共有2302(AM2302) , 11(DHT11) , 22(DHT22)三個選項

如果電路系統沒有問題的話,應該會印出溫溼度資訊,到此時就打完收工啦! 有夠簡單

3. 將量測資訊寫到Google sheet內

接著來嘗試把溫溼度資訊以試算表的形式逐行記錄下來,透過Google sheet甚至可以在異地監測呢

首先在你的Google Drive裡裡開一個新的Google試算表,將這個試算表取個好名字,例如:"DHT11好好看看我家到底有多熱" (中文在後面可能會卡關,還是取個英文的就好)

原文建議你留下第一列打上標頭,將其他列刪除掉,不然程式會從第1000行寫起,這會有點瞎

接著遵照這裡的指示開啟你的OAuth2權限

如果上述步驟有確實做完的話,會得到一個.json檔案
將這個檔案以筆記本 或是 vim 打開的話,會有一行長得像這樣:
"client_email": "149345334675-md0qff5f0kib41meu20f7d1habos3qcu@developer.gserviceaccount.com",

就是一個Email,這個Email位置就是你要分享為共同編輯的對象,程式會以共同編輯者的身份對表格寫資料。
打開你剛剛建立的Google試算表,右上角有個分享,填入這個email,記得權限要開成可以編輯。

接著回頭回來處理板子上的運作環境,此時還需要安裝額外的套件:
#sudo apt-get install python-pip
#sudo pip install gspread oauth2client

安裝完畢後,請將剛剛的.json檔,拷貝到 Adafruit_Python_DHT/examples下
然後對Adafruit_Python_DHT/examples/google_spreadsheet.py 稍做編輯,
需要額外編輯處有五處:

#DHT_TYPE = Adafruit_DHT.DHT11  // 將你的Sensor形式設定好
#DHT_PIN = 4 //將你訊號線連接的GPIO腳位設定好
#GDOCS_OAUTH_JSON = 'xxxxx.json'     //將剛剛拷貝到examples下的的.json檔案名稱設定好
#GDOCS_SPREADSHEET_NAME = 'DHT11好好看看我家到底有多熱'    //將你要寫入的google表格名稱設定好
#FREQUENCY_SECONDS = 300  //設定多少秒量測一次並寫入google表格

設定動作就到這邊,沒了!

接著就是執行google_spreadsheet.py開始量測溫/濕度並寫入表格啦!
#sudo ./google_spreadsheet.py

喔耶


後記:
開始持續記載溫濕度以後就嘗試了各種實驗如何讓房間更舒服點……

[2015/07/02] 登出ssh後繼續執行(Nohup指令)
若要在登出ssh後繼續執行程式,下以下指令:
#nohup sudo ./google_spreadsheet.py &






2015年6月14日 星期日

[Raspberry Pi]連結無線網路…連了這麼久阿

好一陣子沒玩Raspberry PI了,弄個無線網路就弄老半天…記起來!

連結到已知AP,使用wpa_supplicant,步驟如下

1. 建立wpa資訊
#wap_passphrase "your_essid" "your_password" > /etc/wpa_supplicant/wps_supplicant.conf
此時會在 /etc/wpa_supplicant/wps_supplicant.conf 檔案裡建立一個block :
 network = {
      ssid = "your_essid"
      psk = "your_password"
}

NOTE: 此檔案可有多個 network = {}  block,即為AP清單

2. 使用wpa_supplicant連結AP,此時呼叫下列指令開啟deamon
#wpa_supplicant -B  -i wlan0 -c /etc/wpa_supplicant/wps_supplicant.conf

NOTE1: 若deamon已開啟,則需要kill -9 先把deamon幹掉再重開,另尋正當方法中…
NOTE2: 另外方式就是重開機重跑wpa_supplicant

3. 確認AP是否已連結,使用iwconfig
#iwconfig wlan0
確認印出的資訊裡是否有ESSID:"your_essid",有了就是連上惹。

3. 最後,向AP要求動態IP
#dhclient wlan0

4. 用ifconfig確認wlan0是否已經獲得IP
看看inet addr 是否有值嚕

PS1: 若wpa_supplicant已經在作用中,使用iwconfig是無效的(不要浪費時間在這裡惹)

PS2: 開機後就使用wpa_supplicant管理無線網路:

設定/etc/network/interfaces,將wlan0的組態改為如下:
allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf

如此一來開機後就會以wpa_supplicant.conf的內容找尋是否有可用的AP,然後自動連接

2014年12月7日 星期日

[Raspberry-Pi] 相機模組 x Live555MediaServer




想找到可以遠端觀看Pi-Camera Live-Stream的方法,而且要有一定的即時性,只是嘗試過幾招都不是很理想:


  • Motion 

偵測到背景改變後才開始錄影,就監視攝影機來說是省去錄影空間的好解法,但也因此CPU運算個沒停,即時影像輸出是很不流暢的。

參考1:http://www.instructables.com/id/Raspberry-Pi-as-low-cost-HD-surveillance-camera/?ALLSTEPS

參考2:http://www.instructables.com/id/Raspberry-Pi-Completely-Wireless-IP-Camera-Solar/


  • MJPG-Streamer(live streamer only)
只做影像的live-streaming,跑起來也比motion順暢多,但採用的是HTTP Live Stream協定,強調資料傳送的準確性,而非時間性,實際運作也稱不上流暢

參考1:http://blog.miguelgrinberg.com/post/stream-video-from-the-raspberry-pi-camera-to-web-browsers-even-on-ios-and-android
參考2:https://dotblogs.com.tw/bowwowxx/2015/06/08/151511
參考3:解決/dev/video0 does not support streaming i/o
參考4:照此官方步驟安裝uv4l(驅動Raspberry原廠相機至/dev/video0)
20150526備註:
消掉UVCIOC_CTRL_ADD - Error: Inappropriate ioctl for device的作法
$mjpg_streamer -i "input_uvc.so -n" -o "output_http.so -w ./www" //input_uvc.so引數加上-n

最後因緣際會瞭解了RTSP(Real Time Streaming Protocol),是種強調即時傳送串流的協定,看了維基百科的介紹,想像起來可能是偏向時間性的傳送協定,所以目標就暫時聚焦在找到實現RTSP的服務啦!

很幸運的Linux上有Live555這個開源軟體,可以讓我們架起簡單的Media Server
官方參考:Live555 Media Server

簡單作法如下:




#wget http://www.live555.com/liveMedia/public/live555-latest.tar.gz   //下載Live555原始碼
#tar -zxvf live555-latest.tar.gz  //解壓縮
#cd live //至live 資料夾
#./genMakefiles linux //按linux架構產生Make檔案
註1:live資料夾下有許多開頭為config.xxxx的檔案,即為對應的genMakefiles組態檔案。
註2:以"linux"架構編譯的執行檔是可以在Pi上運作的,其他架構的還沒試過。

#make //編譯Live555
#make install //佈署編譯後的Live555
註:在Pi上部屬後的Live555執行檔們會被複製到 /usr/local/bin下


  • Step2-啟動Live555MediaServer


#cd /usr/local/bin                // 進到Live555的佈署資料夾
#./live555MediaServer &   // 啟動live555MediaServer,會秀出簡單的說明

此時live555MediaServer就被啟動了並開始連入需求的監聽,播放來源檔案路徑是相對於live5執行檔位置開始計算,也就是說,想要被播放的檔案必須與live555MediaServer執行檔放在一起,或是在其位置下的子資料夾


  • Step3-啟動Pi-Camera

(在此之前要先設定好Pi-Camera唷,可以參考http://www.raspberrypi.org/learning/python-picamera-setup/

#touch rpi.264         // 開一空檔案準備給Pi-Camera作緩衝
註:live555以副檔名辨認播放格式,此處以h264來錄影與播放
 #raspivid -t 999999 -h 180 -w 270 -o rpi.264 //啟動Pi-Camara,並將串流存在rpi.264檔案裡

好的,做到這邊就完成RTSP Server側的簡單設定了,接著要到Client側開串流來看看。


  • Step4-使用VLC觀看線上串流(On Windows)


在VLC下的檔案->開啟網路串流
網址輸入:
rtsp://hostname:8554/rpi.264(因為Step3的串流是輸出到與live555Server同層的檔案裡)

就可以看見流暢的影像啦!
只是…怎麼Time-shifting這麼多哩…覺得是影像在Pi上緩衝太多的關係
這點解決的話就完美了!
To Be Continued....


其他參考:
網路後端的工作筆記
大隻熊的部落格
Python-Camera      // 用Python來控制嚕,進階使用

[LINUX] 快速設定網路磁碟機-SAMBA

最近猛烈的玩起了PI,有時候要從WINDOWS筆電傳檔案給PI,此時不得不倚靠SAMBA了。
這篇筆記主要參考自鳥哥(鳥哥:第十六章、檔案伺服器之二: SAMBA 伺服器)但在這裡只紀錄慣用懶人設定嚕

設定目標:分享LINUX下的帳號資料夾(home),權限管控為LINUX上的User 


  • Step1-安裝SAMBA: 


#apt-get install samba
#apt-get  install samba-common-bin // 在Pi上,這套件是用來取testparm指令的

註解:在Raspbian上安裝後,啟動smbd會有錯誤(nmbd無法啟動,大概是被其他服務卡住),必須重開機,smbd(SAMBA服務)從此過後會於init.d自動載入




  • Step2-設定SAMBA: 


#cp /etc/samba/smb.conf  /etc/samba/smb.conf.master    //複製一份原始參數檔來修改
#vim /etc/init.d/samba/smb.conf .master                          //改在複製品上

[global]下的設定都不用改!預設的登入方式會是user(以帳號登入)
[home]下改變設定:
browseable = yes               //此分享可被瀏覽
read only = no                   //開放家資料夾可寫入
create mask = 0775            //開放檔案讀寫權限
directory mask = 0775        //開放資料夾讀寫權限

註:當valid users = %s , 只能以\\server\username的方式掛載網路磁碟機 否則,可用\\server\home的方式 或是 \\server\labelname 的方式 掛載網路磁碟機 (標籤名等於分享名稱)

在上面的Comment欄位內,原作者建議使用testparm來管理smb.conf,會有檢查參數以及精簡設定的效果

#testparm -s smb.conf.master > smb.conf  //用testparm檢查參數語法是否正確,並套用在smb.conf上


  • Step3-設定SAMBA帳號


鳥哥:在 SAMBA 上面的使用者帳號,必須要是 Linux 帳號中的一個!

所以先在LINUX環境下設定要登入的SAMBA帳號密碼
#useradd username
#passwd username

接著在SAMBA帳號資料庫加入帳號
#pdbedit -a -u username // username必須要在linux帳號內存在

建立samba user , 建立時會要求輸入密碼 , 此密碼是用在WINDOWS掛載網路磁碟機用的!

註解:在遠端側瀏覽、寫入、讀取檔案,全部透過linux uid 做檔案權限控制,簡單的說,根據username在linux下的權限賦予遠端操作檔案權限


  • Step4-重啟smbd 


 #service smbd restart


  • Step5-WINDOWS側設定  


在我的電腦上右鍵->連線網路磁碟機
代號隨便填,
資料夾填\\server\username(這是因為SAMBA設定valid user = %s的關係)

帳號:剛剛加入的帳號名 username
密碼:pdbedit指令設定的密碼

備註: 在CentOS下,SAMBA服務可能會被selinux擋住,此時要去作selinux進階設定
鳥哥-SELinux 的相關議題

 大功告成~

後記1:pdbedit -L -v (列出所有使用者,忘記有哪些帳號時可以回想一下)