Skip to content

Commit 1d464d8

Browse files
merge
1 parent 08aaadd commit 1d464d8

File tree

11 files changed

+446
-9
lines changed

11 files changed

+446
-9
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ hardware/Dummy.cpp
243243
hardware/EcoDevices.cpp
244244
hardware/EnOceanESP2.cpp
245245
hardware/EnOceanESP3.cpp
246+
hardware/Ec3kMeterTCP.cpp
246247
hardware/evohome.cpp
247248
hardware/ETH8020.cpp
248249
hardware/Fitbit.cpp

hardware/Ec3kMeterTCP.cpp

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
#include "stdafx.h"
2+
#include "Ec3kMeterTCP.h"
3+
#include "../main/Logger.h"
4+
#include "../main/Helper.h"
5+
#include <iostream>
6+
#include "../main/localtime_r.h"
7+
//#include "../main/mainworker.h"
8+
#include "../hardware/hardwaretypes.h"
9+
#include "../json/json.h"
10+
11+
#include <sstream>
12+
13+
/*
14+
Extract readings from the json messages posted by the
15+
Energy count 3000/ NETPBSEM4 / La crosse energy meters
16+
collected by the EC3K software originally from
17+
https://github.com/avian2/ec3k
18+
The required server is added to the fork at:
19+
https://github.com/llagendijk/ec3k
20+
The server version of this software sends json messages
21+
with the following contents:
22+
23+
{
24+
"data": {
25+
"energy": 52810582,
26+
"power_current": 34.4,
27+
"reset_counter": 1,
28+
"time_total": 1356705,
29+
"time_on": 1356613,
30+
"power_max": 42.2
31+
},
32+
"id": "e1a2"
33+
}
34+
*/
35+
36+
#define RETRY_DELAY 30
37+
#define SENSOR_ID "id"
38+
#define DATA "data"
39+
#define WS "energy"
40+
#define W_CURRENT "power_current"
41+
#define W_MAX "power_max"
42+
#define TIME_ON "time_on"
43+
#define TIME_TOTAL "time_total"
44+
#define RESET_COUNT "reset_counter"
45+
46+
Ec3kMeterTCP::Ec3kMeterTCP(const int ID, const std::string &IPAddress, const unsigned short usIPPort) :
47+
m_szIPAddress(IPAddress)
48+
{
49+
m_HwdID=ID;
50+
m_bDoRestart=false;
51+
m_stoprequested=false;
52+
m_usIPPort=usIPPort;
53+
m_retrycntr = RETRY_DELAY;
54+
m_limiter = new(Ec3kLimiter);
55+
}
56+
57+
Ec3kMeterTCP::~Ec3kMeterTCP(void)
58+
{
59+
}
60+
61+
bool Ec3kMeterTCP::StartHardware()
62+
{
63+
m_stoprequested=false;
64+
m_bDoRestart=false;
65+
66+
//force connect the next first time
67+
m_retrycntr=RETRY_DELAY;
68+
m_bIsStarted=true;
69+
70+
//Start worker thread
71+
m_thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&Ec3kMeterTCP::Do_Work, this)));
72+
return (m_thread!=NULL);
73+
}
74+
75+
bool Ec3kMeterTCP::StopHardware()
76+
{
77+
m_stoprequested=true;
78+
try {
79+
if (m_thread)
80+
{
81+
m_thread->join();
82+
}
83+
}
84+
catch (...)
85+
{
86+
//Don't throw from a Stop command
87+
}
88+
if (isConnected())
89+
{
90+
try {
91+
disconnect();
92+
} catch(...)
93+
{
94+
//Don't throw from a Stop command
95+
}
96+
}
97+
98+
m_bIsStarted=false;
99+
return true;
100+
}
101+
102+
void Ec3kMeterTCP::OnConnect()
103+
{
104+
_log.Log(LOG_STATUS,"Ec3kMeter: connected to: %s:%ld", m_szIPAddress.c_str(), m_usIPPort);
105+
m_bDoRestart=false;
106+
m_bIsStarted=true;
107+
108+
sOnConnected(this);
109+
}
110+
111+
void Ec3kMeterTCP::OnDisconnect()
112+
{
113+
_log.Log(LOG_STATUS,"Ec3kMeter: disconnected");
114+
}
115+
116+
void Ec3kMeterTCP::Do_Work()
117+
{
118+
bool bFirstTime=true;
119+
int sec_counter = 0;
120+
while (!m_stoprequested)
121+
{
122+
sleep_seconds(1);
123+
sec_counter++;
124+
125+
if (sec_counter % 12 == 0) {
126+
m_LastHeartbeat=mytime(NULL);
127+
}
128+
129+
if (bFirstTime)
130+
{
131+
bFirstTime=false;
132+
connect(m_szIPAddress,m_usIPPort);
133+
}
134+
else
135+
{
136+
if ((m_bDoRestart) && (sec_counter % 30 == 0))
137+
{
138+
connect(m_szIPAddress,m_usIPPort);
139+
}
140+
update();
141+
}
142+
}
143+
_log.Log(LOG_STATUS,"Ec3kMeter: TCP/IP Worker stopped...");
144+
}
145+
146+
void Ec3kMeterTCP::OnData(const unsigned char *pData, size_t length)
147+
{
148+
boost::lock_guard<boost::mutex> l(readQueueMutex);
149+
ParseData(pData,length);
150+
}
151+
152+
void Ec3kMeterTCP::OnError(const std::exception e)
153+
{
154+
_log.Log(LOG_ERROR,"Ec3kMeter: Error: %s",e.what());
155+
}
156+
157+
void Ec3kMeterTCP::OnError(const boost::system::error_code& error)
158+
{
159+
if (
160+
(error == boost::asio::error::address_in_use) ||
161+
(error == boost::asio::error::connection_refused) ||
162+
(error == boost::asio::error::access_denied) ||
163+
(error == boost::asio::error::host_unreachable) ||
164+
(error == boost::asio::error::timed_out)
165+
)
166+
{
167+
_log.Log(LOG_STATUS, "Ec3kMeter: Can not connect to: %s:%ld", m_szIPAddress.c_str(), m_usIPPort);
168+
}
169+
else if (
170+
(error == boost::asio::error::eof) ||
171+
(error == boost::asio::error::connection_reset)
172+
)
173+
{
174+
_log.Log(LOG_STATUS, "Ec3kMeter: Connection reset!");
175+
}
176+
else
177+
_log.Log(LOG_ERROR, "Ec3kMeter: %s", error.message().c_str());
178+
}
179+
180+
bool Ec3kMeterTCP::WriteToHardware(const char *pdata, const unsigned char length)
181+
{
182+
if (!mIsConnected)
183+
{
184+
return false;
185+
}
186+
return true;
187+
}
188+
189+
190+
void Ec3kMeterTCP::ParseData(const unsigned char *pData, int Len)
191+
{
192+
std::string buffer;
193+
buffer.assign((char *)pData, Len);
194+
195+
// Validty check on the received json
196+
197+
Json::Value root;
198+
Json::Reader jReader;
199+
200+
bool ret = jReader.parse(buffer, root);
201+
if (!ret)
202+
{
203+
_log.Log(LOG_ERROR, "Ec3kMeter: invalid data received!");
204+
return;
205+
}
206+
if (root [SENSOR_ID].empty() == true)
207+
{
208+
_log.Log(LOG_ERROR, "Ec3kMeter: id not found in telegram");
209+
return;
210+
}
211+
if (root [DATA].empty() == true)
212+
{
213+
_log.Log(LOG_ERROR, "Ec3kMeter: data not found in telegram");
214+
return;
215+
}
216+
217+
Json::Value data = root["data"];
218+
if (data[WS].empty() ==true)
219+
{
220+
_log.Log(LOG_ERROR, "Ec3kMeter: energy (ws) not found in telegram");
221+
return;
222+
}
223+
224+
if (data[W_CURRENT].empty() ==true)
225+
{
226+
_log.Log(LOG_ERROR, "Ec3kMeter: current consumption not found in telegram");
227+
return;
228+
}
229+
if (data[W_MAX].empty() ==true)
230+
{
231+
_log.Log(LOG_ERROR, "Ec3kMeter: maximum consumption not found in telegram");
232+
return;
233+
}
234+
if (data[TIME_ON].empty() ==true)
235+
{
236+
_log.Log(LOG_ERROR, "Ec3kMeter: time on not found in telegram");
237+
return;
238+
}
239+
if (data[TIME_TOTAL].empty() ==true)
240+
{
241+
_log.Log(LOG_ERROR, "Ec3kMeter: total time not found in telegram");
242+
return;
243+
}
244+
if (data[RESET_COUNT].empty() ==true)
245+
{
246+
_log.Log(LOG_ERROR, "Ec3kMeter: reset count not found in telegram");
247+
return;
248+
}
249+
250+
// extract values from json
251+
252+
std::stringstream ssId;
253+
ssId << std::hex << root[SENSOR_ID].asString();
254+
//int id = root[SENSOR_ID].asInt();
255+
int id;
256+
ssId >> id;
257+
258+
// update only when the update interval has elapsed
259+
if (m_limiter->update(id))
260+
{
261+
int ws = data[WS].asInt();
262+
float w_current = data[W_CURRENT].asFloat();
263+
float w_max = data[W_MAX].asFloat();
264+
int s_time_on = data[TIME_ON].asInt();
265+
int s_time_total = data[TIME_TOTAL].asInt();
266+
int reset_count = data[RESET_COUNT].asInt();
267+
268+
// create suitable default names and send data
269+
270+
std::stringstream sensorNameKwhSS;
271+
sensorNameKwhSS << "EC3K meter " << std::hex << id << " Usage";
272+
const std::string sensorNameKwh = sensorNameKwhSS.str();
273+
SendKwhMeter(id, 1, 255, w_current, (double)ws/3600/1000, sensorNameKwh);
274+
275+
std::stringstream sensorNameWMaxSS;
276+
sensorNameWMaxSS << "EC3K meter " << std::hex << id << " maximum";
277+
const std::string sensorNameWMax = sensorNameWMaxSS.str();
278+
SendWattMeter(id, 2, 255, w_max, sensorNameWMax);
279+
280+
// TODO: send times + reset_count?
281+
}
282+
}
283+
284+
285+
Ec3kLimiter::Ec3kLimiter(void)
286+
{
287+
no_meters = 0;
288+
}
289+
290+
Ec3kLimiter::~Ec3kLimiter(void)
291+
{
292+
}
293+
294+
bool Ec3kLimiter::update(int id)
295+
{
296+
int i;
297+
for (i = 0; i < no_meters; i++)
298+
{
299+
if (meters[i].id == id)
300+
{
301+
// Allow update after at least update interval seconds
302+
if (( time(NULL) - meters[i].last_update) >= EC3K_UPDATE_INTERVAL)
303+
{
304+
meters[i].last_update = time(NULL);
305+
return true;
306+
}
307+
else
308+
{
309+
return false;
310+
}
311+
}
312+
}
313+
// Store new meter and allow update
314+
meters[no_meters].id = id;
315+
meters[no_meters].last_update = time(NULL);
316+
no_meters++;
317+
return true;
318+
}

hardware/Ec3kMeterTCP.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#pragma once
2+
3+
#include <deque>
4+
#include <iostream>
5+
#include "ASyncTCP.h"
6+
#include "DomoticzHardware.h"
7+
8+
9+
#define MAX_EC3K_METERS 64
10+
#define EC3K_UPDATE_INTERVAL 60
11+
12+
/*
13+
typedef struct {
14+
int id;
15+
time_t last_update;
16+
} Ec3kMeters_t;
17+
*/
18+
19+
class Ec3kLimiter
20+
{
21+
public:
22+
Ec3kLimiter();
23+
~Ec3kLimiter();
24+
bool update(int id);
25+
26+
private:
27+
int no_meters;
28+
struct {
29+
int id;
30+
time_t last_update;
31+
} meters[MAX_EC3K_METERS];
32+
};
33+
34+
class Ec3kMeterTCP : public CDomoticzHardwareBase, ASyncTCP
35+
{
36+
public:
37+
Ec3kMeterTCP(const int ID, const std::string &IPAddress, const unsigned short usIPPort);
38+
~Ec3kMeterTCP(void);
39+
bool isConnected(){ return mIsConnected; };
40+
bool WriteToHardware(const char *pdata, const unsigned char length);
41+
public:
42+
// signals
43+
boost::signals2::signal<void()> sDisconnected;
44+
private:
45+
int m_retrycntr;
46+
bool StartHardware();
47+
bool StopHardware();
48+
Ec3kLimiter *m_limiter;
49+
protected:
50+
std::string m_szIPAddress;
51+
unsigned short m_usIPPort;
52+
bool m_bDoRestart;
53+
54+
void Do_Work();
55+
void OnConnect();
56+
void OnDisconnect();
57+
void OnData(const unsigned char *pData, size_t length);
58+
void OnError(const std::exception e);
59+
void OnError(const boost::system::error_code& error);
60+
61+
void ParseData(const unsigned char *pData, int Len);
62+
boost::shared_ptr<boost::thread> m_thread;
63+
volatile bool m_stoprequested;
64+
};
65+

0 commit comments

Comments
 (0)