A power logger. Logs to file, in tab separated format.
LibreOffice Calc refuses to understand the time format.
But if that works, some nice plots can be made this way.
Todo: Direct visual graph display of data.
Current issue: Programs ends on connection loss.
Code: Select all
#include "string.bi"
#include "vbcompat.bi"
#include once "SNC/snc.bi"
'#include once "json_parse.bas"
const as string ServerIP = "HS110-1" '"192.168.0.1"
const as ushort ServerPort = 9999
const EXIT_OK = 0, EXIT_NOK = 1 'Not Ok
const as string KEY_ESC = chr(27)
const as string LOG_FILE = "power_log.txt"
'--------------------------------- json parse-----------------------------------
const DQ = chr(34) 'double quote
const SQ = "'" 'single quote
function getJsonValue(jsonStr as string, key_ as string) as string
dim as string key = !"\"" + key_ + !"\""
dim as integer keyPos = instr(jsonStr, key)
if keyPos = 0 then return "FAIL_NO_KEY" 'key not found
dim as integer jsonStrLen = len(jsonStr)
dim as integer cursor = keyPos + len(key) - 1
dim as integer startPos, endPos '0 is first char
'skip spaces
while 1
if cursor >= jsonStrLen then return "FAIL_END_SPC1"
if jsonStr[cursor] <> asc(" ") then exit while
cursor += 1
wend
'check for colon
if jsonStr[cursor] = asc(":") then
cursor += 1
else
return "FAIL_NO_COLON"
end if
'skip spaces again
while 1
if cursor >= jsonStrLen then return "FAIL_END_SPC2"
if jsonStr[cursor] <> asc(" ") then exit while
cursor += 1
wend
'check if char: [, {, ", number
select case jsonStr[cursor]
case asc("[")
return "FAIL_IS_ARRAY"
case asc("{")
return "FAIL_IS_OBJECT"
case asc(DQ)
cursor += 1 'skip '"'
startPos = cursor
while 1
if cursor >= jsonStrLen then return "FAIL_END_DQ"
if jsonStr[cursor] = asc(DQ) then exit while
cursor += 1
wend
endPos = cursor
case asc("0") to asc("9"), asc("+"), asc("-")
startPos = cursor 'include this last one in return value
cursor += 1
while 1
if cursor >= jsonStrLen then return "FAIL_END_NUM"
if jsonStr[cursor] < asc("0") or jsonStr[cursor] > asc("9") then exit while
cursor += 1
wend
endPos = cursor
case else
return "FAIL_NO_SELECT"
end select
return mid(jsonStr, startPos + 1, endPos - startPos)
end function
function jsonKeyValue(jsonStr as string, key as string) as string
return key + " : " + getJsonValue(jsonStr, key)
end function
'--------------------------------- Functions -----------------------------------
function encrypt(byref cmd as string) as string
if len(cmd) > 0 then
dim as ubyte key = 171 'same as -85
for i as integer = 0 to len(cmd) - 1
dim as ubyte a = key xor cmd[i]
key = a
cmd[i] = a
next
end if
return cmd
end function
function decrypt(byref cmd as string) as string
if len(cmd) > 0 then
dim as ubyte key = 171 'same as -85
for i as integer = 0 to len(cmd) - 1
dim as ubyte a = key xor cmd[i]
key = cmd[i]
cmd[i] = a
next
end if
return cmd
end function
'Prepend 4-byte length to string (max len = 255)
function TxLenStr(cmdStr as string ) as string
return chr(0, 0, 0, len(cmdStr)) & cmdStr
end function
'Text -> "Text"
function quote(str1 as string) as string
const DQ = chr(34)
return DQ + str1 + DQ
end function
enum E_CMD
E_CMD_OFF 'Turn Device Relay Off
E_CMD_ON 'Turn Device Relay On
E_CMD_INFO 'Get Device Info
E_CMD_GET_TIME 'Get device date/time
E_CMD_GET_TZONE 'Get device time zone
E_CMD_SET_TZONE 'Set device time zone & date/time
E_CMD_SET_AP 'Connect to AP with given SSID and Password
E_CMD_SET_ALIAS 'Set Device Alias
E_CMD_GET_IV_GAIN 'Get EMeter VGain and IGain Settings
E_CMD_GET_IV_NOW 'Get Realtime Current and Voltage Reading
end enum
function generateCmdStr(iCmd as E_CMD) as string
dim as string cmdStr
select case iCmd
case E_CMD_OFF
cmdStr = !"{\"system\":{\"set_relay_state\":{\"state\":0}}}"
case E_CMD_ON
cmdStr = !"{\"system\":{\"set_relay_state\":{\"state\":1}}}"
case E_CMD_INFO
cmdStr = !"{\"system\":{\"get_sysinfo\":null}}"
case E_CMD_GET_TIME
cmdStr = !"{\"time\":{\"get_time\":null}}"
case E_CMD_GET_TZONE
cmdStr = !"{\"time\":{\"get_timezone\":null}}"
case E_CMD_SET_TZONE
'~ Dim as String Year = Right(Date,4)
'~ Dim as String Month = Left(Date,2)
'~ Dim as String Day = Mid(Date,4,2)
'~ Dim as String Hour = Left(Time,2)
'~ Dim as String Min = Mid(Time,4,2)
'~ Dim as String Sec = Right(Time,2)
'~ 'Dim as String TZ = "00" 'Unknown
'~ '{"time":{"set_timezone":{"year":2016,"month":1,"mday":1,"hour":10,"min":10,"sec":10,"index":42}}}
'~ cmdStr = !"{\"time\":{\"set_timezone\":{\"year\":YYYY,\"month\":MM,\"mday\":DD,\"hour\":0,\"min\":mm,\"sec\":ss,\"index\":0}}}"
'~ Cnt = Instr(cmdStr, "YYYY")
'~ Mid(cmdStr, Cnt, 4) = Year
'~ Cnt = Instr(cmdStr, "MM")
'~ Mid(cmdStr, Cnt, 2) = Month
'~ Cnt = Instr(cmdStr, "DD")
'~ Mid(cmdStr, Cnt, 2) = Day
'~ Cnt = Instr(cmdStr, "hh")
'~ Mid(cmdStr, Cnt, 2) = Hour
'~ Cnt = Instr(cmdStr, "mm")
'~ Mid(cmdStr, Cnt, 2) = Min
'~ Cnt = Instr(cmdStr, "ss")
'~ Mid(cmdStr, Cnt, 2) = Sec
'~ 'Cnt = Instr(cmdStr, "TZ")
'~ 'Mid(cmdStr, Cnt, 2) = TZ
case E_CMD_SET_AP
'~ dim as string ssid, password
'~ input "AP ssid: "; ssid
'~ input "password: "; password
'~ const KEY_WPA2 = 3
'~ cmdStr = !"{\"netif\":{\"set_stainfo\":{\"ssid\":" & quote(ssid)
'~ cmdStr &= !",\"password\":" & quote(password)
'~ cmdStr &= !",\"key_type\":" & KEY_WPA2
'~ cmdStr &= !"}}}"
case E_CMD_SET_ALIAS
cmdStr = !"{\"system\":{\"set_dev_alias\":{\"alias\":\"StupidPlug\"}}}"
case E_CMD_GET_IV_GAIN
cmdStr = !"{\"emeter\":{\"get_vgain_igain\":{}}}"
case E_CMD_GET_IV_NOW
cmdStr = !"{\"emeter\":{\"get_realtime\":{}}}"
case else
print "Invalid option" : end
end select
return cmdStr
end function
type powerData_type
dim as single voltage
dim as single current
dim as single power
dim as single cos_phi
end type
'extract data from json string
function getPowerData(RxData as string) as powerData_type
dim as powerData_type tempData
with tempData
.voltage = val(getJsonValue(RxData, "voltage_mv") ) * 1e-3
.current = val(getJsonValue(RxData, "current_ma")) * 1e-3
.power = val(getJsonValue(RxData, "power_mw")) * 1e-3
.cos_phi = .power / (.voltage * .current)
end with
return tempData
end function
'display values on screen
sub printPowerData(powerData as powerData_type)
print "U [V]:", format(powerData.voltage, "0.000"),
print "I [A]:", format(powerData.current, "0.000"),
print "P [W]:", format(powerData.power, "0.000"),
print "Power factor:", format(powerData.cos_phi, "0.000")
end sub
'write values to file in append mode
function writePowerData(fileName as string, powerData as powerData_type) as integer
dim as integer fileNum = freefile()
dim as string outputStr
dim as integer writeHeader = iif(fileexists(fileName), 0, 1)
if open(fileName, for append, as fileNum) = 0 then
if writeHeader then
print "New file created: " & fileName
print #fileNum, !"Time [hh:mm:ss]\tU [V]\tI [A]\tP [W]\tPower factor"
end if
outputStr = time & !"\t"
outputStr &= format(powerData.voltage, "0.000") & !"\t"
outputStr &= format(powerData.current, "0.000") & !"\t"
outputStr &= format(powerData.power, "0.000") & !"\t"
outputStr &= format(powerData.cos_phi, "0.000")
print #fileNum, outputStr
close(fileNum)
else
return -1 'NOK
end if
return 0 'OK
end function
'----------------------------------- Main --------------------------------------
dim as E_CMD iCmd 'enum type
dim as string RxData, TxData, cmdStr
dim as double tEnd, tLoopWait, logInterval = 5
dim as zstring ptr pBuffer
dim as integer nBytes, quit = 0
dim as networkClient client = NetworkClient(ServerIP, ServerPort)
dim as networkConnection ptr pConnection = client.GetConnection()
iCmd = E_CMD_GET_IV_NOW
cmdStr = generateCmdStr(iCmd)
TxData = TxLenStr(encrypt(cmdStr))
print "Program started. Logs to file every " & logInterval & " seconds"
while quit = 0
tLoopWait = timer + logInterval
'print "wait for allow sending data"
tEnd = timer + 3.0
while pConnection->CanPut() <> 1
sleep 1
if timer > tEnd then print "CanPut() timeout" : end(EXIT_NOK)
wend
'print "send data"
if pConnection->PutData(strptr(TxData), len(TxData)) <= 0 then
print "PutData() fail, LastError: " & pConnection->GetLastError
end(EXIT_NOK)
end if
'print "wait for incomming data"
tEnd = timer + 3.0
while pConnection->CanGet() <> 1
sleep 1
if timer > tEnd then print "CanGet() timeout" : end(EXIT_NOK)
wend
'print "receive data"
nBytes = pConnection->GetData(pBuffer, 1024, 1)
if nBytes <= 0 then
print "GetData() fail, LastError: " & pConnection->GetLastError
end(EXIT_NOK)
end if
if nBytes > 4 then
RxData = mid(*cast(zstring ptr, pBuffer + 4), 1, nBytes - 4)
'RxData = *cast(zstring ptr, pBuffer + 4)
decrypt(RxData)
'check for set error field
if getJsonValue(RxData, "err_code") = "0" then
if iCmd = E_CMD_GET_IV_NOW then
dim as powerData_type powerData = getPowerData(RxData)
if writePowerData(LOG_FILE, powerData) <> 0 then
print "Error: writePowerData()"
end if
printPowerData(powerData)
else
print RxData
end if
else
print "RxData Error"
print RxData : end(EXIT_NOK)
endif
else
print "TxData Error"
print RxData : end(EXIT_NOK)
end if
while timer < tLoopWait
if inkey = KEY_ESC then quit = 1 : exit while
sleep 1
wend
wend
'free the buffer (allocated by snc.bi)
deallocate pBuffer
end(EXIT_OK)