-
Notifications
You must be signed in to change notification settings - Fork 7
/
ciaotools.py
208 lines (173 loc) · 5.84 KB
/
ciaotools.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
###
# This file is part of Arduino Ciao
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Copyright 2015 Arduino Srl (http://www.arduino.org/)
#
# authors:
# _giuseppe[at]arduino[dot]org
#
###
import os, logging
import socket, asyncore
import json, time
from threading import Thread
from Queue import Queue
from logging.handlers import RotatingFileHandler
from json.decoder import WHITESPACE
class CiaoThread(Thread, asyncore.dispatcher_with_send):
# "name" must be specified in __init__ method
name = "ciaothread"
#ciao server (default) configuration
host = "127.0.0.1"
port = 8900
write_pending = False
data_pending = None
def __init__(self, shd, connector_queue, ciao_queue = None):
Thread.__init__(self)
self.daemon = True
asyncore.dispatcher_with_send.__init__(self)
self.shd = shd
self.ciao_queue = ciao_queue
self.connector_queue = connector_queue
if "name" in self.shd['conf']:
self.name = self.shd['conf']['name']
# load Ciao (host, port) configuration if present
# otherwise it will use default
if "ciao" in self.shd['conf']:
if "host" in self.shd['conf']['ciao']:
self.host = self.shd['conf']['ciao']['host']
if "port" in self.shd['conf']['ciao']:
self.port = self.shd['conf']['ciao']['port']
# setup logger
self.logger = logging.getLogger(self.name)
while not self.register():
# IDEAS: here we could add a max_retry param
time.sleep(10)
def run(self):
try:
asyncore.loop(0.05)
except asyncore.ExitNow, e:
self.logger.error("Exception asyncore.ExitNow, closing CiaoThread. (%s)" % e)
def stop(self):
#self.socket.exit()
#self.socket.close()
self.join()
def exit(self):
raise asyncore.ExitNow('Connector is quitting!')
# register function (useful when connector start or reconnect)
def register(self):
try:
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
params = { "action" : "register", "name" : self.name }
self.connect((self.host, self.port))
self.socket.send(json.dumps(params))
except Exception, e:
self.logger.error("Problem connecting to server: %s" % e)
return False
else:
return True
# function to handle socket close
def handle_close(self):
self.logger.debug("Handle CLOSE")
self.close()
return
# function to handle error over socket (and close it if necessary)
def handle_error(self):
nil, t, v, tbinfo = asyncore.compact_traceback()
# sometimes a user repr method will crash.
try:
self_repr = repr(self)
except:
self_repr = '<__repr__(self) failed for object at %0x>' % id(self)
self.logger.error('CiaoThread - python exception %s (%s:%s %s)' % (
self_repr, t, v, tbinfo
))
self.logger.debug("Handle ERROR")
return
# this function is really helpful to handle multiple json sent at once from core
def decode_multiple(self, data):
# force input data into string
string = str(data)
self.logger.debug("Decoding data from Core: %s" % string)
# create decoder to identify json strings
decoder = json.JSONDecoder()
idx = WHITESPACE.match(string, 0).end()
self.logger.debug("Decode WHITESPACE match: %d" % idx)
ls = len(string)
result = []
while idx < ls:
try:
obj, end = decoder.raw_decode(string, idx)
self.logger.debug("JSON object(%d, %d): %s" % (idx, end, obj))
result.append(obj)
except ValueError, e:
self.logger.debug("ValueError exception: %s" % e)
#to force functione exit
idx = ls
else:
idx = WHITESPACE.match(string, end).end()
return result
def get_logger(logname, logfile = None, logconf = None, logdir = None):
#logging (default) configuration
conf = {
# level can be: debug, info, warning, error, critical
"level" : "info",
"format" : "%(asctime)s %(levelname)s %(name)s - %(message)s",
# max_size is expressed in MB
"max_size" : 0.1,
# max_rotate expresses how much time logfile has to be rotated before deletion
"max_rotate" : 5
}
# MAP
# log levels implemented by logging library
# to "readable" levels
DLEVELS = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
# LOGGER SETUP
# join user configuration with default configuration
if logconf and "log" in logconf:
conf.update(logconf['log'])
# if no logfile specified setup the default one
if not logfile:
logfile = logname+".log"
# if user specifies a directory
if logdir:
# make sure the logdir param ends with /
if not logdir.endswith(os.sep):
logdir = logdir+os.sep
logfile = logdir+logfile
logger = logging.getLogger(logname)
logger.setLevel(DLEVELS.get(conf['level'], logging.NOTSET))
# create handler for maxsize e logrotation
handler = RotatingFileHandler(
logfile,
maxBytes=conf['max_size']*1024*1024,
backupCount=conf['max_rotate']
)
# setup log format
formatter = logging.Formatter(conf['format'])
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger