From cd4f70118f5ac16b98e6608f1191b542ae6c6c9f Mon Sep 17 00:00:00 2001 From: irekzielinski Date: Sat, 11 Jun 2016 20:22:00 +0100 Subject: [PATCH] Added EEPROM download code --- Libraries/PMax/FixedSizeQueue.h | 83 ++++ Libraries/PMax/MemoryMap.h | 129 ++++++ Libraries/PMax/pmax.cpp | 589 ++++++++++++++++++++++++++-- Libraries/PMax/pmax.h | 98 ++++- PowerMaxEsp8266/PowerMaxEsp8266.ino | 43 +- PowerMaxWin/PowerMax.cpp | 41 +- PowerMaxWin/PowerMax.vcproj | 2 + PowerMaxWin/pmax_windows.cpp | 339 ++++++++++++++++ 8 files changed, 1259 insertions(+), 65 deletions(-) create mode 100644 Libraries/PMax/FixedSizeQueue.h create mode 100644 Libraries/PMax/MemoryMap.h diff --git a/Libraries/PMax/FixedSizeQueue.h b/Libraries/PMax/FixedSizeQueue.h new file mode 100644 index 0000000..b047001 --- /dev/null +++ b/Libraries/PMax/FixedSizeQueue.h @@ -0,0 +1,83 @@ +#pragma once +/// +/// Fixed size queue (does not call new/delete, so will not lead to memory fragmentation (important on embeded systems)). +/// Items must be POD suitable for memcpy. +/// +template +class FixedSizeQueue { + struct Node + { + T item; + bool used; + }; + + Node m_queue[QCNT]; + + public: + FixedSizeQueue (){ clear(); } + + void clear() + { + memset(&m_queue, 0, sizeof(m_queue)); + } + + bool push (const T& i) + { + int pos = findEmpty(); + if(pos == -1) + { + return false; + } + + memcpy(&m_queue[pos].item, &i, sizeof(T)); + m_queue[pos].used = true; + return true; + } + + // pop an item from the queue. + T pop () + { + T res = m_queue[0].item; + + //shift all down: + for(int ix=1; ix +//content is populated only once, on download, so it's OK to use dynamic memory allocation even on embeded devices +#define MAX_PAGES_CNT 15 +#define MAX_PAGE_SIZE 0x100 +class MemoryMap +{ + unsigned char* m_tblPages[MAX_PAGES_CNT]; + + //prevent copy operations: + MemoryMap(const MemoryMap& rhs); + MemoryMap& operator=(const MemoryMap& rhs); + + int Access(bool read, int iPage, int iIndex, int iToAccessBytes, unsigned char* buffer) + { + if(read && Exist(iPage) == false) + { + return 0; + } + + int totalAccessed = 0; + while(iToAccessBytes > 0) + { + unsigned char* pPage = GetPage(iPage); + if(pPage == NULL) + { + //DEBUG(LOG_ERR, "Requested too large page: %d", iPage); + break; + } + + if(iIndex + iToAccessBytes <= MAX_PAGE_SIZE) + { + //read/write fits the page + if(read) + { + memcpy(buffer, pPage+iIndex, iToAccessBytes); + } + else + { + memcpy(pPage+iIndex, buffer, iToAccessBytes); + } + + totalAccessed += iToAccessBytes; + buffer += iToAccessBytes; + iToAccessBytes = 0; + } + else + { + //read/write spans more than one page, let's remaining ammount from current one + int iFree = MAX_PAGE_SIZE - iIndex; + if(read) + { + memcpy(buffer, pPage+iIndex, iFree); + } + else + { + memcpy(pPage+iIndex, buffer, iFree); + } + + totalAccessed += iFree; + buffer += iFree; + iToAccessBytes -= iFree; + + //Set it to the next page and index to 0 + iPage += 1; + iIndex = 0; + } + } + + return totalAccessed; + } + +public: + MemoryMap() + { + memset(m_tblPages, 0, sizeof(m_tblPages)); + } + + ~MemoryMap() + { + Clear(); + } + + void Clear() + { + for(int ix=0; ix=MAX_PAGES_CNT) + { + return false; + } + + return m_tblPages[iPage] != NULL; + } + + unsigned char* GetPage(int iPage) + { + if(iPage<0 || iPage>=MAX_PAGES_CNT) + { + return NULL; + } + + if(m_tblPages[iPage] != NULL) + { + return m_tblPages[iPage]; + } + + m_tblPages[iPage] = new unsigned char[MAX_PAGE_SIZE]; + memset(m_tblPages[iPage], 0xFF, MAX_PAGE_SIZE); + return m_tblPages[iPage]; + } + + int Read(int iPage, int iIndex, int iToReadBytes, void* bufferOut) + { + return Access(true, iPage, iIndex, iToReadBytes, (unsigned char*)bufferOut); + } + + int Write(int iPage, int iIndex, int iToWriteBytes, const void* bufferIn) + { + return Access(false, iPage, iIndex, iToWriteBytes, (unsigned char*)bufferIn); + } +}; \ No newline at end of file diff --git a/Libraries/PMax/pmax.cpp b/Libraries/PMax/pmax.cpp index 3c4610d..52013e4 100644 --- a/Libraries/PMax/pmax.cpp +++ b/Libraries/PMax/pmax.cpp @@ -14,6 +14,70 @@ const char* GetStr##tblName(int index)\ return "??";\ } +/* +'######################################################## +' PowerMax/Master send messages +'######################################################## + +' ### ACK messages, depending on the type, we need to ACK differently ### +Private VMSG_ACK1 As Byte[] = [&H02] 'NONE +Private VMSG_ACK2 As Byte[] = [&H02, &H43] 'NONE + + + +Private VMSG_ARMDISARM As Byte[] = [&HA1, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'NONE - MasterCode: 4 & 5 +Private VMSG_STATUS As Byte[] = [&HA2, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'A5 +Private VMSG_EVENTLOG As Byte[] = [&HA0, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'A0 - MasterCode: 4 & 5 + +' #### PowerMaster message ### +Private VMSG_PMASTER_STAT1 As Byte[] = [&HB0, &H01, &H04, &H06, &H02, &HFF, &H08, &H03, &H00, &H00, &H43] 'B0 STAT1 + + + +' ### PowerMax download/config items (some apply to PowerMaster too) ### +' --- +' Pos. 0 1 2 3 4 5 6 7 8 9 A +' E.g. 3E 00 04 20 00 B0 00 00 00 00 00 +' 1=Index, 2=Page, 3=Low Length, 4=High Length, 5=Always B0? +' --- +' PowerMaster30: +' Pos. 0 1 2 3 4 5 6 7 8 9 A +' E.g. 3E FF FF 42 1F B0 05 48 01 00 00 +' 1=FF 2=FF 3=Low Length, 4=High Length, 5=Always B0, 6=Index, 7=Page +' ---*/ +//Private VMSG_DL_PANELFW As Byte[] = [0x3E, 0x00, 0x04, 0x20, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_ZONESTR As Byte[] = [0x3E, 0x00, 0x19, 0x00, 0x02, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_SERIAL As Byte[] = [0x3E, 0x30, 0x04, 0x08, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//'Private VMSG_DL_EVENTLOG As Byte[] = [0x3E, 0xDF, 0x04, 0x28, 0x03, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_TIME As Byte[] = [0x3E, 0xF8, 0x00, 0x20, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private vMSG_DL_COMMDEF As Byte[] = [0x3E, 0x01, 0x01, 0x1E, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_USERPINCODES As Byte[] = [0x3E, 0xFA, 0x01, 0x10, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +#define VMSG_DL_OTHERPINCODES {0x3E, 0x0A, 0x02, 0x0A, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00} +#define VMSG_DL_PHONENRS {0x3E, 0x36, 0x01, 0x20, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00} +//Private VMSG_DL_PGMX10 As Byte[] = [0x3E, 0x14, 0x02, 0xD5, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_PARTITIONS As Byte[] = [0x3E, 0x00, 0x03, 0xF0, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_ZONES As Byte[] = [0x3E, 0x00, 0x09, 0x78, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_KEYFOBS As Byte[] = [0x3E, 0x78, 0x09, 0x40, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_2WKEYPADS As Byte[] = [0x3E, 0x00, 0x0A, 0x08, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_1WKEYPADS As Byte[] = [0x3E, 0x20, 0x0A, 0x40, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_SIRENS As Byte[] = [0x3E, 0x60, 0x0A, 0x08, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_X10NAMES As Byte[] = [0x3E, 0x30, 0x0B, 0x10, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_ZONENAMES As Byte[] = [0x3E, 0x40, 0x0B, 0x1E, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//'Private VMSG_DL_ZONECUSTOM As Byte[] = [0x3E, 0xA0, 0x1A, 0x50, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +// +//' ### PowerMaster download/config items ### +//Private VMSG_DL_MASTER_SIRENKEYPADSZONE As Byte[] = [0x3E, 0xE2, 0xB6, 0x10, 0x04, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER_USERPINCODES As Byte[] = [0x3E, 0x98, 0x0A, 0x60, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER_SIRENS As Byte[] = [0x3E, 0xE2, 0xB6, 0x50, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER_KEYPADS As Byte[] = [0x3E, 0x32, 0xB7, 0x40, 0x01, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER_ZONENAMES As Byte[] = [0x3E, 0x60, 0x09, 0x40, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER_ZONES As Byte[] = [0x3E, 0x72, 0xB8, 0x80, 0x02, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER10_EVENTLOG As Byte[] = [0x3E, 0xFF, 0xFF, 0xD2, 0x07, 0xB0, 0x05, 0x48, 0x01, 0x00, 0x00] '0x3F +//Private VMSG_DL_MASTER30_EVENTLOG As Byte[] = [0x3E, 0xFF, 0xFF, 0x42, 0x1F, 0xB0, 0x05, 0x48, 0x01, 0x00, 0x00] '0x3F +// +//Private VMSG_SET_DATETIME As Byte[] = [0x46, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF] '0x46 + + // {{0xAA,0x12,0x34,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x43},12 ,"Enable bypass" ,NULL}, // {{0xAA,0x12,0x34,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Disable bypass" ,NULL}, bool PowerMaxAlarm::sendCommand(PmaxCommand cmd) @@ -22,14 +86,22 @@ bool PowerMaxAlarm::sendCommand(PmaxCommand cmd) { case Pmax_ACK: { - unsigned char buff[] = {0x02,0x43}; - return sendBuffer(buff, sizeof(buff)); + if(m_ackTypeForLastMsg == ACK_1) + { + unsigned char buff[] = {0x02}; + return sendBuffer(buff, sizeof(buff)); + } + else + { + unsigned char buff[] = {0x02,0x43}; + return sendBuffer(buff, sizeof(buff)); + } } case Pmax_GETEVENTLOG: { unsigned char buff[] = {0xA0,0x00,0x00,0x00,0x12,0x34,0x00,0x00,0x00,0x00,0x00,0x43}; addPin(buff); - return sendBuffer(buff, sizeof(buff)); + return QueueCommand(buff, sizeof(buff), "Pmax_GETEVENTLOG", 0xA0, "PIN:MasterCode:4"); } case Pmax_DISARM: @@ -62,22 +134,111 @@ bool PowerMaxAlarm::sendCommand(PmaxCommand cmd) return sendBuffer(buff, sizeof(buff)); } + /* + The enrollment process + The (emulated) Powerlink needs a pin code in order to: + - Get some information from the Powermax + - Use the bypass command + - Use the disarm and arm commands + During the enrollment process the Powerlink will create a pin and register this pin at the Powermax. The advantage is that none of the already pins is required. + On the Powermax+ and Powermax Pro the (emulated) Powerlink can be enrolled via the installer menu, on the Powermax Complete the Powerlink will be registered automatically. + + When the (emulated) Powerlink is connected and the 'Install Powerlink' option is selected from the installer menu the Powermax sends the following message: + CODE: SELECT ALL + 0xAB 0x0A 0x00 0x01 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x43 + + The (emulated) Powerlink should respond with the following message: + CODE: SELECT ALL + 0xAB 0x0A 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x43 + + where and are the digits of the pin code that needs to be registered in order to be used by the Powerlink. When the enrollment process is completed successfully, a beep will be sounded. + Note: When a new Powerlink module needs to be registered while there is already a Powerlink registered, the previous registered one needs to be uninstalled. You can do so by selecting 'Install Powerlink' from the installer menu en then press the disarm button. + + On PowerMax Complete: trigger enrolment by asking to start download, this will fail on access denied, and PM will ask for enrolment. + */ case Pmax_ENROLLREPLY: { - unsigned char buff[] = {0xAB,0x0A,0x00,0x00,0x12,0x34,0x00,0x00,0x00,0x00,0x00,0x43}; - return sendBuffer(buff, sizeof(buff)); + m_bDownloadMode = false; + + unsigned char buff[] = {0xAB,0x0A,0x00,0x00,0x12,0x34,0x00,0x00,0x00,0x00,0x00,0x43}; addPin(buff); + return QueueCommand(buff, sizeof(buff), "Pmax_ENROLLREPLY"); } - case Pmax_REENROLL: + case Pmax_RESTORE: { unsigned char buff[] = {0xAB,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43}; - return sendBuffer(buff, sizeof(buff)); + return QueueCommand(buff, sizeof(buff), "Pmax_RESTORE"); } - case Pmax_GETVERSION: + case Pmax_INIT: { - unsigned char buff[] = {0x3E,0x00,0x04,0x36,0x00,0xB0,0x30,0x30,0x33,0x35,0x35}; - return sendBuffer(buff, sizeof(buff)); + unsigned char buff[] = {0xAB,0x0A,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43}; + return QueueCommand(buff, sizeof(buff), "Pmax_INIT"); + } + + // ### PowerMax download/config items (some apply to PowerMaster too) ### + // --- + // Pos. 0 1 2 3 4 5 6 7 8 9 A + // E.g. 3E 00 04 20 00 B0 00 00 00 00 00 + // 1=Index, 2=Page, 3=Low Length, 4=High Length, 5=Always B0? + // --- + // PowerMaster30: + // Pos. 0 1 2 3 4 5 6 7 8 9 A + // E.g. 3E FF FF 42 1F B0 05 48 01 00 00 + // 1=FF 2=FF 3=Low Length, 4=High Length, 5=Always B0, 6=Index, 7=Page + // --- + case Pmax_DL_PANELFW: + { + unsigned char buff[] = {0x3E, 0x00, 0x04, 0x20, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00}; + return QueueCommand(buff, sizeof(buff), "Pmax_DL_PANELFW", 0x3F); + } + + case Pmax_DL_SERIAL: + { + unsigned char buff[] = {0x3E, 0x30, 0x04, 0x08, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00}; + return QueueCommand(buff, sizeof(buff), "Pmax_DL_SERIAL", 0x3F); + } + + case Pmax_DL_ZONESTR: + { + unsigned char buff[] = {0x3E, 0x00, 0x19, 0x00, 0x02, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00}; + return QueueCommand(buff, sizeof(buff), "Pmax_DL_ZONESTR", 0x3F); + } + + case Pmax_DL_GET: + { + unsigned char buff[] = {0x0A}; + return QueueCommand(buff, sizeof(buff), "Pmax_DL_GET", 0x33); + } + + case Pmax_DL_START: //start download (0x3C - DownloadCode: 3 & 4) + { + unsigned char buff[] = {0x24,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; addPin(buff, 3); + return QueueCommand(buff, sizeof(buff), "Pmax_DL_START", 0x3C, "PIN:DownloadCode:3"); + + if(m_bDownloadMode == false) + { + m_bDownloadMode = true; + } + else + { + DEBUG(LOG_WARNING,"Already in Download Mode?"); + } + } + + case Pmax_DL_EXIT: //stop download + { + unsigned char buff[] = {0x0F}; + return QueueCommand(buff, sizeof(buff), "Pmax_DL_EXIT"); + + if(m_bDownloadMode) + { + m_bDownloadMode = false; + } + else + { + DEBUG(LOG_WARNING,"Not in Download Mode?"); + } } default: @@ -87,9 +248,10 @@ bool PowerMaxAlarm::sendCommand(PmaxCommand cmd) //FF means: match anything -struct PlinkBuffer PmaxCommand[] = +struct PlinkCommand PmaxCommand[] = { - {{0x08,0x43 },2 ,"Access denied" ,&PowerMaxAlarm::PmaxAccessDenied}, + {{0x08 },1 ,"Access denied" ,&PowerMaxAlarm::PmaxAccessDenied}, + {{0x08,0x43 },2 ,"Access denied 2" ,&PowerMaxAlarm::PmaxAccessDenied}, {{0xA0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Event Log" ,&PowerMaxAlarm::PmaxEventLog}, {{0xA5,0xFF,0x02,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Status Update Zone Battery" ,&PowerMaxAlarm::PmaxStatusUpdateZoneBat}, {{0xA5,0xFF,0x03,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Status Update Zone tamper" ,&PowerMaxAlarm::PmaxStatusUpdateZoneTamper}, @@ -97,9 +259,15 @@ struct PlinkBuffer PmaxCommand[] = {{0xA5,0xFF,0x06,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Status Update Zone Bypassed",&PowerMaxAlarm::PmaxStatusUpdateZoneBypassed}, {{0xA7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Panel status change" ,&PowerMaxAlarm::PmaxStatusChange}, {{0xAB,0x0A,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x43},12 ,"Enroll request" ,&PowerMaxAlarm::PmaxEnroll}, + {{0xAB,0x03,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x43},12 ,"Ping" ,&PowerMaxAlarm::PmaxPing}, + {{0x3C,0xFD,0x0A,0x00,0x00,0x0E,0x05,0x01,0x00,0x00,0x00 },11, "Panel Info" ,&PowerMaxAlarm::PmaxPanelInfo}, + {{0x3F,0xFF,0xFF,0xFF },-4, "Download Info" ,&PowerMaxAlarm::PmaxDownloadInfo}, //-4 means: size>=4, len can differ + {{0x33,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF },11, "Download Settings" ,&PowerMaxAlarm::PmaxDownloadSettings}, {{0x02,0x43 },2 ,"Acknowledgement" ,&PowerMaxAlarm::PmaxAck}, - {{0x06 },1 ,"Ping" ,&PowerMaxAlarm::PmaxPing} //This might be PM Complete specific, happens every 20seconds -}; + {{0x02, },1 ,"Acknowledgement 2" ,&PowerMaxAlarm::PmaxAck}, + {{0x06 },1 ,"Time Out" ,&PowerMaxAlarm::PmaxTimeOut}, + {{0x0B },1 ,"Stop (Dload Complete)" ,&PowerMaxAlarm::PmaxStop} +}; const char* PmaxSystemStatus[] = { "Disarmed" , @@ -359,14 +527,35 @@ const char* PmaxDefaultZoneNames[] = { "Proxy Tag8" }; +const char* PmaxPanelType[] = { + "PowerMax" , + "PowerMax+" , + "PowerMax Pro" , + "PowerMax Complete" , + "PowerMax Pro Part" , + "PowerMax Complete Part" , + "PowerMax Express" , + "PowerMaster10" , + "PowerMaster30" +}; + IMPEMENT_GET_FUNCTION(PmaxSystemStatus); IMPEMENT_GET_FUNCTION(SystemStateFlags); IMPEMENT_GET_FUNCTION(PmaxDefaultZoneNames); IMPEMENT_GET_FUNCTION(PmaxZoneEventTypes); IMPEMENT_GET_FUNCTION(PmaxLogEvents); +IMPEMENT_GET_FUNCTION(PmaxPanelType); void PowerMaxAlarm::Init() { + memset(&m_lastSentCommand, 0, sizeof(m_lastSentCommand)); + m_bEnrolCompleted = false; + m_bDownloadMode = false; + m_iPanelType = -1; + m_iModelType = 0; + m_bPowerMaster = false; + m_ackTypeForLastMsg = ACK_1; + int count = os_cfg_getZoneCnt(); printf("I have %d zone:\n", count); for (int i = 0; i < count; i++) { @@ -391,11 +580,15 @@ void PowerMaxAlarm::Init() { zone[i].lastEventTime = 0; } - PowerMaxAlarm::sendCommand(Pmax_REQSTATUS); + //if PM is in dowload mode, then it would interfere with out init sequence + //so let's start with DL exit command + PowerMaxAlarm::sendCommand(Pmax_DL_EXIT); - //IZIZTODO: this seems to be not required on PowerMaxComplete, even more: when issued with PMC: it stops panel updates (A7 messages), to re-enable updates power and battery has to be removed - os_usleep(20*os_cfg_getPacketTimeout()); - PowerMaxAlarm::sendCommand(Pmax_REENROLL); + PowerMaxAlarm::sendCommand(Pmax_INIT); + + //Send the download command, this should initiate the communication + //First DL_START request is likely to fail on access denied, and PM should request the enrolment + PowerMaxAlarm::sendCommand(Pmax_DL_START); } unsigned int PowerMaxAlarm::getEnrolledZoneCnt() const @@ -417,29 +610,273 @@ unsigned long PowerMaxAlarm::getSecondsFromLastComm() const return (unsigned long)(os_getCurrentTimeSec()-lastIoTime); } -void PowerMaxAlarm::addPin(unsigned char* bufferToSend) +void PowerMaxAlarm::addPin(unsigned char* bufferToSend, int pos) { int usercode = os_cfg_getUserCode(); - bufferToSend[4]=usercode>>8; - bufferToSend[5]=usercode & 0x00FF ; + bufferToSend[pos]=usercode>>8; + bufferToSend[pos+1]=usercode & 0x00FF ; } - void PowerMaxAlarm::PmaxEnroll(PowerMaxAlarm* pm, const PlinkBuffer * Buff) { + //Remove anything else from the queue, we need to restart + pm->clearQueue(); pm->sendCommand(Pmax_ENROLLREPLY); DEBUG(LOG_INFO,"Enrolling....."); + + //if we're doing enroll, most likely first call to download failed, re-try now, after enrolment is done: + pm->sendCommand(Pmax_DL_START); } -void PowerMaxAlarm::PmaxAck(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +//Should be called when panel is enrolled and entered download mode successfully +void PowerMaxAlarm::PowerLinkEnrolled() +{ + m_bEnrolCompleted = true; + + //Request the panel FW + sendCommand(Pmax_DL_PANELFW); + + //Request serial & type (not always sent by default) + sendCommand(Pmax_DL_SERIAL); + + //Read the names of the zones + //IZIZTODO: PM responds with truncated packets (at least on windows) + //sendCommand(Pmax_DL_ZONESTR); + + //' Retrieve extra info if this is a PowerMaster + //If $bPowerMaster Then + // SendMsg_DL_MASTER_SIRENKEYPADSZONE() + + // ' Only request eventlog in debug mode + // If $bDebug Then + // Select $iPanelType + // Case &H07 ' PowerMaster10 + // SendMsg_DL_MASTER10_EVENTLOG() + // Case &H08 ' PowerMaster30 + // SendMsg_DL_MASTER30_EVENTLOG() + // End Select + // Endif + //Endif + + //Request all other relevant information + sendCommand(Pmax_DL_GET); + + //' We are done, exit download mode + sendCommand(Pmax_DL_EXIT); + + //' auto-sync date/time + //SendMsg_SetDateTime() + + //Lets request the eventlogs + if(m_bPowerMaster == false) + { + //IZIZTOOD: uncomment: + //pm->sendCommand(Pmax_GETEVENTLOG); + } +} + +//Direct message after we do a download start. Contains the paneltype information +void PowerMaxAlarm::PmaxPanelInfo(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +{ + // The panel information is in 5 & 6 + // 6=PanelType e.g. PowerMax, PowerMaster + // 5=Sub model type of the panel - just informational + pm->m_iPanelType = Buff->buffer[6]; + pm->m_iModelType = Buff->buffer[5]; + pm->m_bPowerMaster = (pm->m_iPanelType >= 7); + pm->sendCommand(Pmax_ACK); + + DEBUG(LOG_INFO,"Received Panel Info. PanelType: %s, Model=%d (0x%X)", GetStrPmaxPanelType(pm->m_iPanelType), pm->m_iModelType, (pm->m_iPanelType * 0x100 + pm->m_iModelType)); + + //We got a first response, now we can continue enrollment the PowerMax/Master PowerLink + pm->PowerLinkEnrolled(); +} + +int PowerMaxAlarm::ReadMemoryMap(const unsigned char* sData, unsigned char* buffOut, int buffOutSize) +{ + //The aMsg is in the regular download format, ignore the SubType and only use page, index and length + //NOTE: Length can be more then &HFF bytes, in such we got multiple 3F responses with a "real" download + int iPage = sData[2]; + int iIndex = sData[1]; + int iLength = (sData[4] * 0x100) + sData[3]; + + if(iLength > buffOutSize) + { + DEBUG(LOG_ERR, "ReadMemoryMap, buffer too small, needed: %d, got: %d", iLength, buffOutSize); + return 0; + } + + MemoryMap* pMap = &m_mapMain; + if(iPage == 0xFF && iIndex == 0xFF) + { + pMap = &m_mapExtended; + + //Overrule page/index/data if we have an extended message + iPage = sData[7]; + iIndex = sData[6]; + } + + return pMap->Read(iPage, iIndex, iLength, buffOut); +} + + +//Write the 3C and 3F information into our own memory map structure. This contains all the +//information of the PowerMax/Master and will later be processed in ReadSettings +void PowerMaxAlarm::WriteMemoryMap(int iPage, int iIndex, const unsigned char* sData, int sDataLen) +{ + MemoryMap* pMap = &m_mapMain; + if(iPage == 0xFF && iIndex == 0xFF) + { + pMap = &m_mapExtended; + + //Overrule page/index/data if we have an extended message + iPage = sData[1]; + iIndex = sData[0]; + + //Remove page/index and other 2 bytes, unknown usage + sData += 4; + sDataLen -= 4; + } + + int bytesWritten = pMap->Write(iPage, iIndex, sDataLen, sData); + if(bytesWritten != sDataLen) + { + DEBUG(LOG_ERR, "Failed to write to memory, page: %d, index: %d, len: %d, got: %d", iPage, iIndex, sDataLen, bytesWritten); + } +} + +//MsgType=3F - Download information +//Multiple 3F can follow eachother, if we request more then 0xFF bytes +void PowerMaxAlarm::PmaxDownloadInfo(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +{ + pm->sendCommand(Pmax_ACK); + + //Format is normally: + //If the = FF, then it is an additional PowerMaster MemoryMap + int iIndex = Buff->buffer[1]; + int iPage = Buff->buffer[2]; + int iLength = Buff->buffer[3]; + + + if(iLength != Buff->size-4) + { + DEBUG(LOG_WARNING,"Received Download Data with invalid len indication: %d, got: %d", iLength, Buff->size-4); + } + + //Write to memory map structure, but remove the first 4 bytes (3F/index/page/length) from the data + pm->WriteMemoryMap(iPage, iIndex, Buff->buffer+4, Buff->size-4); +} + +//MsgType=33 - Settings +// Message send after a DL_GET. We will store the information in an internal array/collection +void PowerMaxAlarm::PmaxDownloadSettings(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +{ + pm->sendCommand(Pmax_ACK); + + //Format is: + int iIndex = Buff->buffer[1]; + int iPage = Buff->buffer[2]; + + //Write to memory map structure, but remove the first 3 bytes from the data + pm->WriteMemoryMap(iPage, iIndex, Buff->buffer+3, Buff->size-3); +} + +//This one happens after all items requested by DLOAD_Get has been sent by PM +void PowerMaxAlarm::PmaxStop(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +{ + pm->sendCommand(Pmax_ACK); + DEBUG(LOG_INFO,"Stop (Dload complete)"); +} + +void PowerMaxAlarm::StartKeepAliveTimer() { + //IZIZTODO: +} +void PowerMaxAlarm::StopKeepAliveTimer() +{ + //IZIZTODO: } void PowerMaxAlarm::PmaxPing(PowerMaxAlarm* pm, const PlinkBuffer * Buff) { - pm->sendCommand(Pmax_ACK); - DEBUG(LOG_INFO,"Ping"); + pm->m_bDownloadMode = false; //ping never happens in download mode + pm->sendCommand(Pmax_ACK); + DEBUG(LOG_INFO,"Ping....."); + + //re-starting keep alive timer + pm->StartKeepAliveTimer(); +} + +void PowerMaxAlarm::ProcessSettings() +{ + unsigned char tmpBuff[MAX_BUFFER_SIZE] = {0}; + + { + //Retrieve the installer and powerlink pincodes - they are known/visible + unsigned char msg[] = VMSG_DL_OTHERPINCODES; + int readCnt = ReadMemoryMap(msg, tmpBuff, sizeof(tmpBuff)); + + DEBUG(LOG_INFO, "installer pin: %X%X", tmpBuff[0], tmpBuff[1]); + DEBUG(LOG_INFO, "masterinstaller pin: %X%X", tmpBuff[2], tmpBuff[3]); + DEBUG(LOG_INFO, "powerlink pin: %X%X", tmpBuff[8], tmpBuff[9]); + //$cConfig["pincode"]["installer"] = ByteToHex(sData.Copy(0, 2)) + //$cConfig["pincode"]["masterinstaller"] = ByteToHex(sData.Copy(2, 2)) + //$cConfig["pincode"]["powerlink"] = ByteToHex(sData.Copy(8, 2)) + } + + { + unsigned char msg[] = VMSG_DL_PHONENRS; + int readCnt = ReadMemoryMap(msg, tmpBuff, sizeof(tmpBuff)); + for(int iCnt=1; iCnt<=4; iCnt++) + { + for(int jCnt=0; jCnt<=7; jCnt++) + { + if(tmpBuff[8 * (iCnt - 1) + jCnt] != 0xFF) + { + unsigned char p = tmpBuff[8 * (iCnt - 1) + jCnt]; + p++; + } + } + } + } +} + +void PowerMaxAlarm::PmaxAck(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +{ + if(pm->m_lastSentCommand.size == 1 && + pm->m_lastSentCommand.buffer[0] == 0x0F) //Pmax_DL_EXIT + { + //we got an ack for exit from dload mode: + pm->m_bDownloadMode = false; + + //this will be false for the first Pmax_DL_EXIT that is called from Init() + if(pm->m_bEnrolCompleted) + { + pm->ProcessSettings(); + + //after download is complete, we call restore - this will get other important settings, and make sure panel is happy with comms + pm->sendCommand(Pmax_RESTORE); + } + + //re-starting keep alive timer + pm->StartKeepAliveTimer(); + } +} + +//Timeout message from the PM, most likely we are/were in download mode +void PowerMaxAlarm::PmaxTimeOut(PowerMaxAlarm* pm, const PlinkBuffer * Buff) +{ + if(pm->m_bDownloadMode) + { + pm->sendCommand(Pmax_DL_EXIT); + } + else + { + pm->sendCommand(Pmax_ACK); + } + + DEBUG(LOG_INFO,"Time Out"); } void PowerMaxAlarm::PmaxAccessDenied(PowerMaxAlarm* pm, const PlinkBuffer * Buff) @@ -680,17 +1117,61 @@ unsigned char calculChecksum(const unsigned char* data, int dataSize) { return (unsigned char) checksum; } +void PowerMaxAlarm::SendNextCommand() +{ + if(m_sendQueue.isEmpty()) + { + return; + } + + os_usleep(50*1000); //to prevent messages going too quickly to PM + + PmQueueItem item = m_sendQueue.pop(); + + sendBuffer(item.buffer, item.bufferLen); +} + +//buffer is coppied, description and options need to be in pernament addressess (not something from stack) +bool PowerMaxAlarm::QueueCommand(const unsigned char* buffer, int bufferLen, const char* description, unsigned char expectedRepply, const char* options) +{ + if(m_sendQueue.isFull()) + { + DEBUG(LOG_CRIT,"Send queue is full, dropping packet: %s", description); + return false; + } + + if(bufferLen > MAX_SEND_BUFFER_SIZE) + { + DEBUG(LOG_CRIT,"Buffer to send too long: %d", bufferLen); + return false; + } + + PmQueueItem item; + memcpy(item.buffer, buffer, bufferLen); + item.bufferLen = bufferLen; + item.description = description; + item.expectedRepply = expectedRepply; + item.options = options; + + m_sendQueue.push(item); + return true; +} + bool PowerMaxAlarm::sendBuffer(const unsigned char * data, int bufferSize) { DEBUG(LOG_DEBUG,"Sending the following buffer to serial TTY"); //logBuffer(LOG_DEBUG,Buff); //IZIZTODO - if(bufferSize >= MAX_BUFFER_SIZE-2) //should never happen, but this will prevent any buffer overflows to writeBuffer.buffer + if(bufferSize >= MAX_BUFFER_SIZE-2 || + bufferSize > MAX_SEND_BUFFER_SIZE) //should never happen, but this will prevent any buffer overflows to writeBuffer.buffer { DEBUG(LOG_ERR,"Too long buffer: %d", bufferSize); return false; } + memcpy(m_lastSentCommand.buffer, data, bufferSize); + m_lastSentCommand.size = bufferSize; + PlinkBuffer writeBuffer; writeBuffer.buffer[0]=0x0D; for (int i=0;i<(bufferSize);i++) @@ -739,12 +1220,34 @@ bool deFormatBuffer(struct PlinkBuffer * Buff) { } // compare two buffer, 0xff are used as jocker char -bool findCommand(struct PlinkBuffer * Buff,struct PlinkBuffer * BuffCommand) { - int i=0; - if (Buff->size!=BuffCommand->size) return false; - for (i=0;isize;i++) { - if ((Buff->buffer[i] != BuffCommand->buffer[i]) && (BuffCommand->buffer[i] != 0xFF )) return false; +bool findCommand(const PlinkBuffer * Buff, const PlinkCommand * BuffCommand) { + + if(BuffCommand->size < 0) + { + //ignore len, match begining only + if(Buff->size < (-BuffCommand->size)) + { + return false; + } + + for (int i=0;i<(-BuffCommand->size);i++) + { + if ((Buff->buffer[i] != BuffCommand->buffer[i]) && (BuffCommand->buffer[i] != 0xFF )) return false; + } } + else + { + if (Buff->size!=BuffCommand->size) + { + return false; + } + + for (int i=0;isize;i++) + { + if ((Buff->buffer[i] != BuffCommand->buffer[i]) && (BuffCommand->buffer[i] != 0xFF )) return false; + } + } + return true; } @@ -760,12 +1263,30 @@ bool PowerMaxAlarm::isBufferOK(const PlinkBuffer* commandBuffer) return ok; } +PmAckType PowerMaxAlarm::calculateAckType(const unsigned char* deformattedBuffer, int bufferLen) +{ + //Depending on the msgtype and/or last byte, we will be sending type-1 or 2 ACK + if(bufferLen > 1) + { + if(deformattedBuffer[0] >= 0x80 || (deformattedBuffer[0] < 0x10 && deformattedBuffer[bufferLen-1] == 0x43)) + { + return ACK_2; + } + } + + return ACK_1; +} + void PowerMaxAlarm::handlePacket(PlinkBuffer* commandBuffer) { + m_ackTypeForLastMsg = ACK_1; + if (deFormatBuffer(commandBuffer)) { DEBUG(LOG_DEBUG,"Packet received"); lastIoTime = os_getCurrentTimeSec(); + m_ackTypeForLastMsg = calculateAckType(commandBuffer->buffer, commandBuffer->size); + logBuffer(LOG_DEBUG,commandBuffer); int cmd_not_recognized=1; @@ -777,7 +1298,8 @@ void PowerMaxAlarm::handlePacket(PlinkBuffer* commandBuffer) { cmd_not_recognized=0; break; } - } + } + if ( cmd_not_recognized==1 ) { DEBUG(LOG_INFO,"Packet not recognized"); logBuffer(LOG_INFO,commandBuffer); @@ -787,6 +1309,9 @@ void PowerMaxAlarm::handlePacket(PlinkBuffer* commandBuffer) { else { DEBUG(LOG_ERR,"Packet not correctly formated"); logBuffer(LOG_ERR,commandBuffer); + + //IZIZTODO: + //should we send ack? } //command has been treated, reset the commandbuffer commandBuffer->size=0; diff --git a/Libraries/PMax/pmax.h b/Libraries/PMax/pmax.h index 82987e9..197c9b8 100644 --- a/Libraries/PMax/pmax.h +++ b/Libraries/PMax/pmax.h @@ -1,7 +1,11 @@ #pragma once #include +#include "FixedSizeQueue.h" +#include "MemoryMap.h" #define MAX_BUFFER_SIZE 250 +#define MAX_SEND_BUFFER_SIZE 15 +#define MAX_SEND_QUEUE_DEPTH 15 #define PACKET_TIMEOUT_DEFINED 2000 #define MAX_ZONE_COUNT 31 @@ -9,16 +13,22 @@ class PowerMaxAlarm; enum PmaxCommand { - Pmax_ACK = 0, - Pmax_GETEVENTLOG = 2, - Pmax_DISARM = 3, - Pmax_ARMHOME = 4, - Pmax_ARMAWAY = 5, - Pmax_ARMAWAY_INSTANT = 6, - Pmax_REQSTATUS = 7, - Pmax_ENROLLREPLY = 10, - Pmax_REENROLL = 11, - Pmax_GETVERSION = 12 + Pmax_ACK, + Pmax_GETEVENTLOG, + Pmax_DISARM, + Pmax_ARMHOME, + Pmax_ARMAWAY, + Pmax_ARMAWAY_INSTANT, + Pmax_REQSTATUS, + Pmax_ENROLLREPLY, + Pmax_INIT, + Pmax_RESTORE, + Pmax_DL_START, + Pmax_DL_GET, + Pmax_DL_EXIT, //stop download mode + Pmax_DL_PANELFW, + Pmax_DL_SERIAL, + Pmax_DL_ZONESTR }; enum ZoneEvent @@ -64,6 +74,12 @@ enum SystemStatus SS_Not_Ready = 0x0D }; +enum PmAckType +{ + ACK_1, + ACK_2 +}; + //this abstract class is used by DumpToJson API //it allows to redirect JSON to file, console, www output class IOutput @@ -83,13 +99,18 @@ class ConsoleOutput : public IOutput void write(const char* str); }; -struct PlinkBuffer { - unsigned char buffer[MAX_BUFFER_SIZE]; - unsigned char size; +struct PlinkCommand { + unsigned char buffer[MAX_SEND_BUFFER_SIZE]; + int size; const char* description; void (*action)(PowerMaxAlarm* pm, const struct PlinkBuffer *); }; +struct PlinkBuffer { + unsigned char buffer[MAX_BUFFER_SIZE]; + int size; +}; + struct ZoneState { bool lowBattery; //battery needs replacing bool tamper; //someone tampered with the device @@ -111,6 +132,15 @@ struct Zone { void DumpToJson(IOutput* outputStream); }; +struct PmQueueItem +{ + unsigned char buffer[MAX_SEND_BUFFER_SIZE]; + int bufferLen; + + const char* description; + unsigned char expectedRepply; + const char* options; +}; class PowerMaxAlarm { @@ -137,8 +167,25 @@ class PowerMaxAlarm //used to detect when PowerMax stops talking to us, that will trigger re-establish comms message time_t lastIoTime; + FixedSizeQueue m_sendQueue; + + bool m_bEnrolCompleted; + bool m_bDownloadMode; + int m_iPanelType; + int m_iModelType; + bool m_bPowerMaster; + PmAckType m_ackTypeForLastMsg; + + //used to store data downloaded from PM + MemoryMap m_mapMain; + MemoryMap m_mapExtended; + + PlinkCommand m_lastSentCommand; public: + void Init(); + void SendNextCommand(); + void clearQueue(){ m_sendQueue.clear(); } bool sendCommand(PmaxCommand cmd); void handlePacket(PlinkBuffer * commandBuffer); @@ -151,7 +198,7 @@ class PowerMaxAlarm void DumpToJson(IOutput* outputStream); private: - static void addPin(unsigned char* bufferToSend); + static void addPin(unsigned char* bufferToSend, int pos = 4); bool isFlagSet(unsigned char id) const { return (flags & 1<sendCommand(Pmax_ARMAWAY); } + else if ( c == 'D' ) { + DEBUG(LOG_NO_FILTER,"Direct relay enabled, disconnect to stop..."); + runDirectRelayLoop(); + } #endif if ( c == 'g' ) { @@ -243,12 +271,12 @@ void handleTelnetRequests(PowerMaxAlarm* pm) { pm->sendCommand(Pmax_GETEVENTLOG); } else if ( c == 't' ) { - DEBUG(LOG_NOTICE,"try re-enroll"); - pm->sendCommand(Pmax_REENROLL); + DEBUG(LOG_NOTICE,"Restore comms"); + pm->sendCommand(Pmax_RESTORE); } else if ( c == 'v' ) { DEBUG(LOG_NOTICE,"getting versions string"); - pm->sendCommand(Pmax_GETVERSION); + //IZIZTODO pm->sendCommand(Pmax_GETVERSION); } else if ( c == 'r' ) { DEBUG(LOG_NOTICE,"Request Status Update"); @@ -280,6 +308,7 @@ void handleTelnetRequests(PowerMaxAlarm* pm) { DEBUG(LOG_NO_FILTER,"\t h - Arm Home"); DEBUG(LOG_NO_FILTER,"\t d - Disarm"); DEBUG(LOG_NO_FILTER,"\t a - Arm Away"); + DEBUG(LOG_NO_FILTER,"\t D - Direct mode (relay all bytes from client to PMC and back with no processing, close connection to exit"); #endif DEBUG(LOG_NO_FILTER,"\t g - Get Event Log"); DEBUG(LOG_NO_FILTER,"\t t - Re-Enroll"); @@ -395,7 +424,7 @@ void loop(void){ //pm.Init() should enroll, but sometimes this fails, this is another attempt to do it if Init() fails. delay is added not to do it too frequently in case of problems delay(1000); DEBUG(LOG_WARNING,"No enroll information, trying to re-enroll"); - pm.sendCommand(Pmax_REENROLL); + pm.sendCommand(Pmax_RESTORE); } } else if(pm.getSecondsFromLastComm() > 120) @@ -403,7 +432,7 @@ void loop(void){ //this should not happen (no comms), to re-establish we re-enroll delay(1000); DEBUG(LOG_WARNING,"Communication failure - trying to re-enroll"); - pm.sendCommand(Pmax_REENROLL); + pm.sendCommand(Pmax_RESTORE); } #ifdef PM_ENABLE_TELNET_ACCESS diff --git a/PowerMaxWin/PowerMax.cpp b/PowerMaxWin/PowerMax.cpp index aa99bd3..1a566e1 100644 --- a/PowerMaxWin/PowerMax.cpp +++ b/PowerMaxWin/PowerMax.cpp @@ -9,8 +9,9 @@ #include #include "pmax.h" +void LogBuffer(const char* prefix, const unsigned char* buffer, int bufferLen); -void serialHandler(PowerMaxAlarm* pm) { +bool serialHandler(PowerMaxAlarm* pm) { PlinkBuffer commandBuffer ; memset(&commandBuffer, 0, sizeof(commandBuffer)); @@ -31,9 +32,10 @@ void serialHandler(PowerMaxAlarm* pm) { GetLocalTime(&time); DEBUG(LOG_INFO,"--- new packet %d:%02d:%02d ----", time.wHour, time.wMinute, time.wSecond); + LogBuffer("PM", (const unsigned char*)commandBuffer.buffer, commandBuffer.size); pm->handlePacket(&commandBuffer); commandBuffer.size = 0; - break; + return true; } } } @@ -45,8 +47,12 @@ void serialHandler(PowerMaxAlarm* pm) { { //this will be an invalid packet: DEBUG(LOG_WARNING,"Passing invalid packet to packetManager"); + LogBuffer("P?", (const unsigned char*)commandBuffer.buffer, commandBuffer.size); pm->handlePacket(&commandBuffer); + return true; } + + return false; } ///////////////////////////////////////// @@ -77,11 +83,16 @@ void KeyPressHandling(PowerMaxAlarm* pm) { } else if ( c == 't' ) { DEBUG(LOG_NOTICE,"try re-enroll"); - pm->sendCommand(Pmax_REENROLL); + pm->sendCommand(Pmax_RESTORE); } else if ( c == 'v' ) { DEBUG(LOG_NOTICE,"getting versions string"); - pm->sendCommand(Pmax_GETVERSION); + pm->sendCommand(Pmax_DL_START); + //pm->sendCommand(Pmax_GETVERSION); //IZIZTODO + } + else if ( c == 'V' ) { + pm->sendCommand(Pmax_DL_EXIT); + //pm->sendCommand(Pmax_GETVERSION); //IZIZTODO } else if ( c == 'r' ) { DEBUG(LOG_NOTICE,"Request Status Update"); @@ -107,7 +118,6 @@ void KeyPressHandling(PowerMaxAlarm* pm) { } } - int _tmain(int argc, _TCHAR* argv[]) { if(os_serialPortInit("COM3") == false) @@ -118,17 +128,30 @@ int _tmain(int argc, _TCHAR* argv[]) PowerMaxAlarm pm; pm.Init(); + log_console_setlogmask(LOG_INFO); + DWORD dwLastMsg = 0; while (true) { KeyPressHandling(&pm); - serialHandler(&pm); + + if(serialHandler(&pm) == true) + { + dwLastMsg = GetTickCount(); + } + + if(GetTickCount() - dwLastMsg > 1000) + { + pm.SendNextCommand(); + } + + os_usleep(os_cfg_getPacketTimeout()); - if(pm.getSecondsFromLastComm() > 120) + /*if(pm.getSecondsFromLastComm() > 120) { - pm.sendCommand(Pmax_REENROLL); - } + pm.sendCommand(Pmax_RESTORE); + }*/ } os_serialPortClose(); diff --git a/PowerMaxWin/PowerMax.vcproj b/PowerMaxWin/PowerMax.vcproj index baaf2df..0f66621 100644 --- a/PowerMaxWin/PowerMax.vcproj +++ b/PowerMaxWin/PowerMax.vcproj @@ -61,6 +61,7 @@ /> #include #include /* POSIX terminal control definitions */ +#include //This file contains OS specific implementation for Windows //If you build for other platrorms (like Linux, don't include this file, but provide your own) +#ifdef USE_SERIAL static HANDLE g_hCommPort = INVALID_HANDLE_VALUE; +#endif /* Private storage for the current mask */ static int log_consolemask = 0xFFFF; @@ -54,6 +57,7 @@ unsigned long os_getCurrentTimeSec() return (unsigned long) time(NULL); } +#ifdef USE_SERIAL int os_serialPortRead(void* readBuff, int bytesToRead) { if(g_hCommPort == INVALID_HANDLE_VALUE) @@ -178,6 +182,341 @@ bool os_serialPortInit(const char* portName) { DEBUG(LOG_INFO, "Current Settings\n Baud Rate %d\n Parity %d\n Byte Size %d\n Stop Bits %d", config.BaudRate, config.Parity, config.ByteSize, config.StopBits); return true; } +#else +//CONNECT TO REMOTE HOST (CLIENT APPLICATION) +//Include the needed header files. +//Don't forget to link libws2_32.a to your program as well +#include +SOCKET s = 0; //Socket handle + +HANDLE hLogFile = INVALID_HANDLE_VALUE; + +bool readUntilNewLine(char* buffer, int buffSize) +{ + while( buffSize > 0 ){ + int read = recv(s, buffer, 1, 0); + if ( read == 1 ) + { + if(buffer[0] == '\n') + { + return true; + } + + buffer++; + buffSize--; + } + else + { + return false; + } + } + + return false; +} + +void LogBuffer(const char* prefix, const unsigned char* buffer, int bufferLen) +{ + SYSTEMTIME tm; + GetLocalTime(&tm); + + char szTmp[60] = ""; + sprintf_s(szTmp, 60, "\r\n%s [%02d:%02d:%02d]", prefix, tm.wHour, tm.wMinute, tm.wSecond); + DWORD dwWritten; + WriteFile(hLogFile, szTmp, strlen(szTmp), &dwWritten, NULL); + for(int ix=0; ix 0) + { + sprintf_s(szTmp, 60, ",0x%02X", (unsigned char)buffer[ix]); + } + else + { + sprintf_s(szTmp, 60, "0x%02X", (unsigned char)buffer[ix]); + } + WriteFile(hLogFile, szTmp, strlen(szTmp), &dwWritten, NULL); + } + + if(bufferLen > 1) + { + char szId[100] = ""; + if(buffer[1] == 0x02) + { + strcpy_s(szId, sizeof(szId), " [ACK]"); + } + else if(buffer[1] == 0x08) + { + strcpy_s(szId, sizeof(szId), " [ACCESS DENIED]"); + } + else if(buffer[1] == 0x06) + { + strcpy_s(szId, sizeof(szId), " [TIME OUT]"); + } + else if(buffer[1] == 0x0B) + { + strcpy_s(szId, sizeof(szId), " [STOP]"); + } + else if(buffer[1] == 0xAB && buffer[2] == 0x0A && buffer[4] == 0x01) + { + strcpy_s(szId, sizeof(szId), " [INIT]"); + } + else if(buffer[1] == 0xAB && buffer[2] == 0x0A && buffer[4] == 0x00) + { + strcpy_s(szId, sizeof(szId), " [ENROL REQ]"); + } + else if(buffer[1] == 0xAB && buffer[2] == 0x03) + { + strcpy_s(szId, sizeof(szId), " [PING]"); + } + else if(buffer[1] == 0x3E && buffer[4] == 0x20) + { + strcpy_s(szId, sizeof(szId), " [Pmax_DL_PANELFW]"); + } + else if(buffer[1] == 0x0A) + { + strcpy_s(szId, sizeof(szId), " [Pmax_DL_GET]"); + } + else if(buffer[1] == 0x3E && buffer[4] == 0x08) + { + strcpy_s(szId, sizeof(szId), " [Pmax_DL_SERIAL]"); + } + else if(buffer[1] == 0x3E && buffer[4] == 0x00) + { + strcpy_s(szId, sizeof(szId), " [Pmax_DL_ZONESTR]"); + } + else if(buffer[1] == 0xAB && buffer[2] == 0x06) + { + strcpy_s(szId, sizeof(szId), " [RESTORE]"); + } + else if(buffer[1] == 0xA0) + { + strcpy_s(szId, sizeof(szId), " [EVENT LOG]"); + } + else if(buffer[1] == 0xA1 && buffer[4] == 0x00) + { + strcpy_s(szId, sizeof(szId), " [DISARM]"); + } + else if(buffer[1] == 0xA1 && buffer[4] == 0x04) + { + strcpy_s(szId, sizeof(szId), " [ARM HOME]"); + } + else if(buffer[1] == 0xA1 && buffer[4] == 0x05) + { + strcpy_s(szId, sizeof(szId), " [ARM AWAY]"); + } + else if(buffer[1] == 0xA5 && buffer[3] == 0x02) + { + strcpy_s(szId, sizeof(szId), " [Status Update Zone Battery]"); + } + else if(buffer[1] == 0xA5 && buffer[3] == 0x03) + { + strcpy_s(szId, sizeof(szId), " [Status Update Zone tamper]"); + } + else if(buffer[1] == 0xA5 && buffer[3] == 0x04) + { + strcpy_s(szId, sizeof(szId), " [Status Update Panel]"); + } + else if(buffer[1] == 0xA5 && buffer[3] == 0x06) + { + strcpy_s(szId, sizeof(szId), " [Status Update Zone Bypassed]"); + } + else if(buffer[1] == 0x24) + { + strcpy_s(szId, sizeof(szId), " [DLOAD START]"); + } + else if(buffer[1] == 0x0F) + { + strcpy_s(szId, sizeof(szId), " [DLOAD EXIT]"); + } + else if(buffer[1] == 0x3F) + { + strcpy_s(szId, sizeof(szId), " [DLOAD INFO(DATA)]"); + } + + if(strlen(szId)) + { + DWORD dwWritten; + WriteFile(hLogFile, szId, strlen(szId), &dwWritten, NULL); + } + } +} + +/*static unsigned char g_readCache[MAX_BUFFER_SIZE]; +static int g_cacheSize = 0; + +static int readFromCache(void* readBuff, int bytesToRead) +{ + int bytesToCopy = bytesToRead; + if(bytesToCopy >= g_cacheSize) + { + bytesToCopy = g_cacheSize; + } + + if(bytesToCopy == 0) + { + return 0; + } + + memcpy(readBuff, g_readCache, bytesToCopy); + memmove(g_readCache, g_readCache+bytesToCopy, g_cacheSize-bytesToCopy); + g_cacheSize -= bytesToCopy; + + return bytesToCopy; +} + +int os_serialPortRead(void* readBuff, int bytesToRead) +{ + if(bytesToRead == 0) + { + return 0; + } + + int totalRead = readFromCache(readBuff, bytesToRead); + if(totalRead > 0) + { + return totalRead; + } + + g_cacheSize = recv(s, (char*)g_readCache, sizeof(g_readCache), 0); + if(g_cacheSize < 0) + { + g_cacheSize = 0; + } + return readFromCache(readBuff, bytesToRead); +}*/ + +int os_serialPortRead(void* readBuff, int bytesToRead) +{ + int totalRead = 0; + while( bytesToRead > 0 ){ + int read = recv(s, (char*)readBuff, bytesToRead, 0); + if ( read > 0 ) + { + totalRead += read; + bytesToRead -= read; + readBuff = ((char*)readBuff) + read; + } + else + { + break; + } + } + + return totalRead; +} + +int os_serialPortWrite(const void* dataToWrite, int bytesToWrite) +{ + LogBuffer("PC", (const unsigned char*)dataToWrite, bytesToWrite); + + int totalSent = 0; + while( bytesToWrite > 0 ){ + int written = send(s, (char*)dataToWrite, bytesToWrite, 0); + if ( written > 0 ) + { + totalSent += written; + bytesToWrite -= written; + dataToWrite = ((char*)dataToWrite) + written; + } + else + { + break; + } + } + + return totalSent; +} + +bool os_serialPortClose() +{ + //Close the socket if it exists + if (s) + closesocket(s); + + WSACleanup(); //Clean up Winsock + return true; +} + +bool os_serialPortInit(const char* portName) { + + hLogFile = CreateFile(L"comms.log", GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + SetFilePointer(hLogFile, 0, 0, FILE_END); + + //IZIZTODO: + int PortNo = 23; + const char* IPAddress = "192.168.0.119"; + + WSADATA wsadata; + int error = WSAStartup(MAKEWORD(2,2), &wsadata); + + //Did something happen? + if (error) + return false; + + //Did we get the right Winsock version? + if (wsadata.wVersion != 0x0202) + { + WSACleanup(); //Clean up Winsock + return false; + } + + //Fill out the information needed to initialize a socket… + SOCKADDR_IN target; //Socket address information + + target.sin_family = AF_INET; // address family Internet + target.sin_port = htons (PortNo); //Port to connect on + target.sin_addr.s_addr = inet_addr (IPAddress); //Target IP + + s = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); //Create socket + if (s == INVALID_SOCKET) + { + return false; //Couldn't create the socket + } + + //Try connecting... + if (connect(s, (SOCKADDR *)&target, sizeof(target)) == SOCKET_ERROR) + { + return false; //Couldn't connect + } + + int iTimeout = 500; + int iResult = setsockopt( s, + SOL_SOCKET, + SO_RCVTIMEO, + (const char *)&iTimeout, + sizeof(iTimeout) ); + + iResult = setsockopt( s, + SOL_SOCKET, + SO_SNDTIMEO, + (const char *)&iTimeout, + sizeof(iTimeout) ); + + // Receive until the peer closes the connection + char szBuffer[256] = ""; + if(readUntilNewLine(szBuffer, sizeof(szBuffer)) == false) + { + return false; + } + + //enter direct comm mode with PowerMax + char req = 'D'; + if(send(s, &req, 1, 0) != 1) + { + return false; + } + + //read confirmation: + if(readUntilNewLine(szBuffer, sizeof(szBuffer)) == false) + { + return false; + } + + return true; //Success, direct pipe is established +} + + +#endif + void os_strncat_s(char* dst, int dst_size, const char* src) {