onsdag, desember 02, 2009

DIY strømmåler...

Hardware
For det første må man ha en strømmåler med impulslampe som blinker med ulik rytme, alt ettersom hvor mye strøm som brukes. Antall blink pr. kWh er oppgitt på strømmåleren. Hos meg tilsvarer 600 blink 1kWh.
Deretter trenger vi en enhet som kan avlese disse lys pulsene. Jeg valgte utstyr fra Phidgets Inc. (Canada) Phidgets Inc. - Unique and Easy to Use USB Interfaces.

Jeg valgte PhidgetInterfaceKit 8/8/8 som kobles til PC via USB. Denne har 8 analoge innganger, 8 digitale utganger samt 8 digital innganger. Her har man mange ekstra I/O å leke seg med etterhvert.

Phidgets Inc. - Unique and Easy to Use USB Interfaces

Strømmåleren min har en utgang som med fordel kunne brukes for å telle pulsene. Da kunne denne utgangene kobles til en av inngangene på interface kittet, men elektrisitetsverket nektet meg blankt å koble noe som helst til denne utgangen.

Så derfor valgte jeg heller en lys sensor som settes på strømmåleren for å telle pulsene.

Phidgets Inc. - Unique and Easy to Use USB Interfaces

Denne plasseres (u)elegant foran lys puls indikatoren på strømmåleren ved hjelp av teip.

Dette er ikke den fineste løsningen, men siden det er plassert inni sikringsskapet så er det ikke synlig til daglig.

Mitt server rom er plassert rett under sikringsskapet, så her var det bare å trekke signal kabelen rett ned til interface kittet.

For å visuelt vise at den leser av pulsene, koblet jeg en LED til en av utgangene. Denne styres av programvaren.

Software
Neste steg er nå å lage et program som kan telle pulsene og lagre disse i en database.
Jeg har laget et program i .NET og som leser den analoge inngangen. Phidgets har API'er for Windows, Mac, Linux og Windows Mobile/CE. Samt en rekke andre populære utviklings språk.

InterfaceKit API'et har en event handler som trigges hver gang verdien endres en bestemt mengde. Hvis inngangsverdien endrer seg +/- n, sendes en event. Verdien n kan justeres i programvaren fra 1 og oppover. Etter mye om og men fant jeg ut at denne ikke var helt å stole på. Den rett og slett hoppet over en del pulser. Selv om sensitiviteten på inngangen var satt til 1. Vet ikke om denne har et annet innebygget filter som forårsaker dette. Løsningen ble at jeg laget en egen thread som leser rå verdien kontinuerlig. Ved hjelp av en flanke kan jeg telle pulser på en reell måte.

using System;
using System.Threading;
using Phidgets;

namespace PowerMeter
{
public class PowerMeter : IDisposable
{
private readonly InterfaceKit _ifKit;
private int _lightSensorPort;
private int _ledOutPort;
private long _rawMinTickValue;
private long _tickTimeSpan;
private Thread _lsThread;
private bool _threadRun;
private bool _gThreadRuning;

private static long _kWhCounter;
private static long _tickkWhWrap;
private static long _tickCounter;
private static long _kWhUsage;



///
/// Get the last exception
///
public Exception LastException { get; private set; }



///
/// Hi value trigger for light sensor
/// Default: 3
///
public int LightSensorHiTrigger { get; set; }


///
/// Get current kWh usage
///
public double KWhUsage
{
get { return Interlocked.Read(ref _kWhUsage) / 10.0; }
set { Interlocked.Exchange(ref _kWhUsage, (long)(value * 10.0)); }
}



///
/// Get meter ticks
///
public long KWhTicker
{
get { return Interlocked.Read(ref _tickCounter); }
}



///
/// ticks/kWh (default 600 ticks/kWh)
///
public long KWhTickWrap
{
get { return _tickkWhWrap; }
set { _tickkWhWrap = value; }
}



///
/// Ticks Output port
///
public int LedOutPort
{
get { return _ledOutPort; }
set { _ledOutPort = value; }
}



///
/// Power metere class
///
/// Analog port for reading the light sensor. Default 0
public PowerMeter(int lightSensorPort)
{
InitValues();
_lightSensorPort = lightSensorPort;

// We use one USB local connected interface kit.
_ifKit = new InterfaceKit();
_ifKit.open();

// Create a hi priority thread
_lsThread = new Thread(RunThread) { Priority = ThreadPriority.Highest };
_lsThread.Start();
}



///
/// Initialize all values
///
private void InitValues()
{
_tickkWhWrap = 600;
_lightSensorPort = 0;
LightSensorHiTrigger = 3;

_ledOutPort = -1;
_threadRun = true;
_gThreadRuning = false;
_kWhCounter = 0;
_tickCounter = 0;
_rawMinTickValue = 0;
_tickTimeSpan = 0;
_kWhUsage = 0;

_lsThread = null;
}



///
/// Current kWh usage
///
public long KWhMeter
{
get { return Interlocked.Read(ref _kWhCounter); }
set { Interlocked.Exchange(ref _kWhCounter, value); }
}



///
/// Thread for reading the light sensor
///
private void RunThread()
{
bool hiValue = false;
DateTime startTime = DateTime.Now;
TimeSpan timeUsed;

_gThreadRuning = true;
_threadRun = true;
while (_threadRun)
{
Thread.Sleep(1);
try
{
int value = _ifKit.sensors[_lightSensorPort].RawValue;

//
if (value >= LightSensorHiTrigger)
{
if (hiValue == false)
{
// Calcule the time between ticks
timeUsed = DateTime.Now.Subtract(startTime);
startTime = DateTime.Now;
Interlocked.Exchange(ref _tickTimeSpan, (long)(timeUsed.TotalMilliseconds * 1000.0));

// Safety: When the door is open the triger may run fast. 200ms = 28.6 kWh max power usage
if (timeUsed.TotalMilliseconds > 200)
{
// Calculate kWh usage
if (timeUsed.TotalSeconds > 0.0 && _tickkWhWrap > 0)
{
double usage = ((60.0 / timeUsed.TotalSeconds) * 60.0) / _tickkWhWrap; // KWh
Interlocked.Exchange(ref _kWhUsage, (long)Math.Round(usage * 10.0));
}

// After _tickkWhWrap ticks, increment the kWh counter and resett ticker
Interlocked.Increment(ref _rawMinTickValue);
if (Interlocked.Increment(ref _tickCounter) >= _tickkWhWrap)
{
Interlocked.Exchange(ref _tickCounter, 0); // Zero ticks
Interlocked.Increment(ref _kWhCounter); // inrement kWh meter
}
}

hiValue = true;
if (_ledOutPort > 0) _ifKit.outputs[_ledOutPort] = true;
}
}
else if (hiValue)
{
hiValue = false;
if (_ledOutPort > 0) _ifKit.outputs[_ledOutPort] = false;

}
} catch (Exception er)
{
// catch error - and continue
LastException = er;
}
}
_gThreadRuning = false;
}



///
///
///
/// The current power usage
public override string ToString()
{
return "Current usage " + KWhUsage + " kWh.";
}


///
/// Stop the power meter and release resources.
///
public void Dispose()
{
// Stop the running thread
for (int i = 0; i <>
{
_threadRun = false;
Thread.Sleep(10);
if (_gThreadRuning == false) break;
}
_lsThread.Abort();

// close the connection to the kit
if (_ifKit != null) _ifKit.close();
}


}

}


Presentasjon
Når man nå har utstyr til å lese strømmen må man ha et sted der man kan lese verdiene. Hos oss skal vi ha en SyncMaster LD220Z MultiTouch skjerm som skal henge på veggen.

Denne skal også virke som en sentral oppslags tavle for husstanden med kobling til Google Calendar. Dette vil komme under værvarselet.