├── .gitattributes
├── .gitignore
├── .gitmodules
├── LICENSE.md
├── backend
├── __init__.py
├── backend.pyproj.user
├── components
│ ├── __init__.py
│ ├── base_component.py
│ ├── clock.py
│ ├── dependancy.py
│ ├── element.py
│ ├── modbus_network.py
│ ├── module.py
│ ├── regulation.py
│ └── tests
│ │ ├── __init__.py
│ │ ├── helper_classes.py
│ │ ├── test_base_component.py
│ │ ├── test_clock.py
│ │ ├── test_dependancy.py
│ │ ├── test_element.py
│ │ ├── test_modbus_network.py
│ │ └── test_module.py
├── config
│ ├── add_user.py
│ └── config.py
├── managers
│ ├── __init__.py
│ ├── communication.py
│ ├── logic.py
│ ├── modbus.py
│ └── tests
│ │ ├── test_communication.py
│ │ ├── test_logic.py
│ │ └── test_modbus.py
├── misc
│ ├── __init__.py
│ ├── benchmark.py
│ ├── check_host.py
│ ├── color_logs.py
│ └── sys_types.py
├── start.py
├── sys_database
│ ├── __init__.py
│ ├── database.py
│ └── sys_database.db
└── system_loader.py
├── readme.md
├── server_client
├── __init__.py
├── client
│ ├── background.jpg
│ ├── bootstrap-3.3.6
│ │ ├── css
│ │ │ ├── bootstrap-theme.css
│ │ │ ├── bootstrap-theme.css.map
│ │ │ ├── bootstrap-theme.min.css
│ │ │ ├── bootstrap-theme.min.css.map
│ │ │ ├── bootstrap.css
│ │ │ ├── bootstrap.css.map
│ │ │ ├── bootstrap.min.css
│ │ │ └── bootstrap.min.css.map
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ ├── glyphicons-halflings-regular.woff
│ │ │ └── glyphicons-halflings-regular.woff2
│ │ └── js
│ │ │ ├── bootstrap.js
│ │ │ ├── bootstrap.min.js
│ │ │ └── npm.js
│ ├── favicon.png
│ ├── fonts
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ └── fontawesome-webfont.woff2
│ ├── graphics
│ │ └── logo.jpg
│ ├── init.html
│ ├── init.js
│ ├── login_page.html
│ ├── misc.js
│ ├── navigation_bar.html
│ └── ui.css
├── server
│ ├── __init__.py
│ ├── handlers
│ │ ├── __init__.py
│ │ ├── auth_handler.py
│ │ ├── init_handler.py
│ │ ├── ui_handler.py
│ │ └── websocket.py
│ └── models
│ │ ├── __init__.py
│ │ ├── group.py
│ │ ├── room.py
│ │ ├── system_representation.py
│ │ ├── user.py
│ │ └── visual_element.py
├── server_client.pyproj.user
└── start.py
└── system.VC.db
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | *.egg-info/
24 | .installed.cfg
25 | *.egg
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 | .hypothesis/
47 |
48 | # Translations
49 | *.mo
50 | *.pot
51 |
52 | # Django stuff:
53 | *.log
54 | local_settings.py
55 |
56 | # Flask stuff:
57 | instance/
58 | .webassets-cache
59 |
60 | # Scrapy stuff:
61 | .scrapy
62 |
63 | # Sphinx documentation
64 | docs/_build/
65 |
66 | # PyBuilder
67 | target/
68 |
69 | # IPython Notebook
70 | .ipynb_checkpoints
71 |
72 | # pyenv
73 | .python-version
74 |
75 | # celery beat schedule file
76 | celerybeat-schedule
77 |
78 | # dotenv
79 | .env
80 |
81 | # virtualenv
82 | venv/
83 | ENV/
84 | env/
85 |
86 | # Spyder project settings
87 | .spyderproject
88 |
89 | # Rope project settings
90 | .ropeproject
91 |
92 | *.sln
93 |
94 | *.pyproj
95 |
96 | *.pyc
97 |
98 | *.suo
99 |
100 | *.config
101 |
102 | *.VC.db
103 |
104 | system.VC.VC.opnedb
105 |
106 | documentation/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "anfa_ambient"]
2 | path = anfa_ambient
3 | url = https://github.com/dzon4xx/anfa_ambient
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) [year] [fullname]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/backend/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/__init__.py
--------------------------------------------------------------------------------
/backend/backend.pyproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ProjectFiles
5 |
6 |
--------------------------------------------------------------------------------
/backend/components/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/components/__init__.py
--------------------------------------------------------------------------------
/backend/components/base_component.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class BaseComponent(object):
5 | """Base component for all system components It stores id type name and trivial string representation"""
6 | column_headers_and_types = [['id', 'integer primary key'],
7 | ['type', 'integer'],
8 | ['name', 'text'], ]
9 | COL_ID, COL_TYPE, COL_NAME = 0, 1, 2 # numery kolumn
10 |
11 | def __init__(self, *args):
12 | self.id, self.type, self.name = args
13 |
14 | #if not isinstance(self.id, int):
15 | # raise TypeError("Id must be int")
16 |
17 | if not (isinstance(self.type, int) or isinstance(self.type, Enum)):
18 | raise TypeError("Type must be int or enum")
19 |
20 | if not isinstance(self.name, str):
21 | raise TypeError("name must be str")
22 |
23 | #if self.id < 0:
24 | # raise ValueError("Component id must be positive number")
25 |
26 | def __str__(self, ):
27 | """Representation of object"""
28 | return "".join(["ID: ", str(self.id), "\ttype: ", self.type.name, "\tname: ", self.name])
29 |
30 | def __repr__(self,):
31 | return "".join(["ID: ", str(self.id), " type: ", self.type.name])
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/backend/components/clock.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import time
3 |
4 |
5 | class Clock:
6 | """Holds actual time.
7 | Notifies about actual time
8 | Notifies about week day"""
9 |
10 | _is_initialized = False
11 | _instance = None
12 |
13 | def __new__(cls, *args, **kwargs):
14 | if not cls._instance:
15 | cls._instance = super().__new__(cls, *args, **kwargs)
16 | return cls._instance
17 |
18 | def __init__(self, ):
19 | if not Clock._is_initialized:
20 | Clock._is_initialized = True
21 | self.objects_to_notify_weekday = set()
22 | self.objects_to_notify_time = set()
23 |
24 | self.now = None
25 | self.minute = None
26 | self.weekday = None
27 |
28 | self.system_start = time.time()
29 |
30 | def subscribe_for_weekday(self, who):
31 | self.objects_to_notify_weekday.add(who)
32 |
33 | def subscribe_for_minute(self, who):
34 | self.objects_to_notify_time.add(who)
35 |
36 | def notify_minute(self):
37 | self.minute = self.now.minute
38 | for _object in self.objects_to_notify_time:
39 | _object.notify(self.now)
40 |
41 | def notify_weekday(self):
42 | self.weekday = self.now.weekday()
43 | for _object in self.objects_to_notify_weekday:
44 | _object.notify(self.weekday)
45 |
46 | def evaluate_time(self, ):
47 | """Evaluates current time. If minutes or day changes it notifies related objects"""
48 | self.now = datetime.datetime.now()
49 |
50 | if self.now.minute != self.minute:
51 | self.notify_minute()
52 |
53 | if self.weekday != self.now.weekday():
54 | self.notify_weekday()
55 |
56 | def get_seconds(self):
57 | return time.time() - self.system_start
58 |
59 | def get_millis(self):
60 | return (time.time() - self.system_start)*1000
61 |
62 | def restart(self):
63 | self.system_start = time.time()
64 |
--------------------------------------------------------------------------------
/backend/components/dependancy.py:
--------------------------------------------------------------------------------
1 | import operator
2 |
3 | from backend.components.clock import Clock
4 | from backend.components.element import Element, OutputElement
5 |
6 |
7 | class DependancyConfigError(Exception):
8 | def __init__(self, msg):
9 | self.msg = msg
10 |
11 |
12 | class Condition:
13 | """Condition is a part of a cause.
14 | Condition is true when it's value compared to compare_value returns True
15 | given one of "=", "<", ">", "|" operators"""
16 | operator_dict = {'=': operator.eq,
17 | '<': operator.lt,
18 | '>': operator.gt,
19 | '|': operator.contains}
20 |
21 | def __init__(self, _id, op, comp_val):
22 | self.id = _id
23 | self._op = Condition.operator_dict[op]
24 | self.comp_val = comp_val
25 | self.val = None
26 |
27 | def evaluate(self):
28 | try:
29 | out_val = self._op(self.val, self.comp_val)
30 | return ' ' + str(out_val) + ' ' # Value is returned as a string because it goes to eval()
31 | except TypeError:
32 | return ' ' + str(False) + ' '
33 |
34 | def notify(self, val):
35 | self.val = val
36 |
37 | def __repr__(self, ):
38 | return "".join(["ID: ", str(self.id), "comp_val: ", self.comp_val, "op: ", self._op])
39 |
40 | def __str__(self, ):
41 | """Representation of object"""
42 | return "".join(["ID: ", str(self.id), "\teval: ", self.evaluate()])
43 |
44 |
45 | class Effect:
46 |
47 | clock = Clock()
48 |
49 | def __init__(self, _id, out_el, value, _time):
50 | self.id = _id
51 | self.output_element = out_el
52 | self.value = value
53 | self.prev_value = None
54 | self.time = _time
55 | self.priority = 5
56 |
57 | self.done = True # Should the effect be started. if not done it should be started
58 | self.cause_time = None # Time when cause was True
59 |
60 | def start(self, milis):
61 | """Api for notifing effect that couse changed to True and at what time"""
62 | self.cause_time = milis
63 | self.done = False
64 | self.prev_value = self.output_element.value
65 |
66 | def run(self, ):
67 | """Sets desired value of element if it was not set yet and if the time is right"""
68 | if not self.done:
69 | if self.clock.get_millis()-self.cause_time >= self.time:
70 | self.done = self.output_element.set_desired_value(self.value, self.priority, set_flag=True)
71 | return True
72 | return False
73 |
74 | def revert(self):
75 | """Reverts effect. Sets output_element previous value"""
76 | if self.done: # efect can be reverted only once when it is done
77 | self.output_element.desired_value = self.prev_value
78 |
79 | def __repr__(self,):
80 | return "El id: {} done: {}".format(self.el_id, self.done)
81 |
82 |
83 | class Dependancy:
84 |
85 | table_name = 'dependancy'
86 |
87 | column_headers_and_types = [['id', 'integer primary key'],
88 | ['name', 'text'],
89 | ['dep_str', 'text']]
90 |
91 | cond_start = '[' # flag of condition start
92 | cond_stop = ']'
93 | effect_start_t = '{' # flag of effect start
94 | effect_stop_t = '}'
95 | cond_marker = '!' # flag used by cause parser to mark conditions places.
96 | cause_effect_sep = 'then' # separates cause and effects
97 | time_marker = 't'
98 | day_marker = 'd'
99 | element_marker = 'e'
100 |
101 | day_dict = {'mon': 0,
102 | 'tue': 1,
103 | 'wed': 2,
104 | 'thu': 3,
105 | 'fri': 4,
106 | 'sat': 5,
107 | 'sun': 6, }
108 | clock = Clock()
109 | items = {}
110 |
111 | def __init__(self, _id, name, dep_str):
112 | self.id = _id
113 | Dependancy.items[self.id] = self
114 |
115 | self.name = name
116 | self.dep_str = dep_str
117 | self.conditions = [] # Conditions which make the cause
118 | self.effects = [] # Effects which will happen after condition changes from False to True
119 |
120 | self.num_of_conds = 0
121 | self.num_of_effect = 0
122 |
123 | self.prev_cause_result = False
124 |
125 | self.cause_str, self.effects_str = dep_str.split(Dependancy.cause_effect_sep)
126 | self.cause_template = '' # evaluated conditions are applied to it. Finnally template goes to eval()
127 |
128 | def run(self, ):
129 | """Evaluates cause. If it is true and prev result is false it notifies all efects.
130 | When the cause changes from True to false it restores effects to initial state """
131 |
132 | cause_result = self._evaluate_cause()
133 |
134 | if cause_result and not self.prev_cause_result: # if the cause changes from False to True
135 | for effect in self.effects:
136 | effect.start(self.clock.get_millis())
137 |
138 | # if the cause changes from True to False effects should be undone
139 | if not cause_result and self.prev_cause_result:
140 | for effect in self.effects:
141 | effect.revert()
142 |
143 | self.prev_cause_result = cause_result
144 |
145 | for effect in self.effects:
146 | effect.run() # perform effect
147 |
148 | def _evaluate_cause(self):
149 | eval_cause_string = ''
150 | condition_num = 0
151 | for s in self.cause_template: # Evaluate all conditions and put their results into eval strin
152 | if s == Dependancy.cond_marker:
153 | eval_cause_string += self.conditions[condition_num].evaluate()
154 | condition_num += 1
155 | else:
156 | eval_cause_string += s
157 |
158 | return eval(eval_cause_string)
159 |
160 | def _parse_cause(self, all_elements=dict()):
161 | """Parses cause string"""
162 |
163 | for condition in self._find_condition():
164 |
165 | element, op, comp_value = self._parse_condition(condition)
166 |
167 | if element[0] == Dependancy.element_marker:
168 | element_id = int(element[1:])
169 |
170 | if element_id not in all_elements:
171 | raise DependancyConfigError('Element does not exists: ' + str(element_id))
172 | comp_value = int(comp_value)
173 | subscribe = all_elements[element_id].subscribe
174 |
175 | if element[0] == Dependancy.day_marker:
176 | comp_value = comp_value.split(',')
177 | comp_value = [Dependancy.day_dict[day] for day in comp_value]
178 | subscribe = self.clock.subscribe_for_weekday
179 |
180 | if element[0] == Dependancy.time_marker:
181 | comp_value = comp_value.split(':')
182 | comp_value = [int(val) for val in comp_value]
183 | subscribe = self.clock.subscribe_for_minute
184 |
185 | condition = Condition(self.num_of_conds, op, comp_value)
186 | self.num_of_conds += 1
187 | subscribe(condition)
188 | self.conditions.append(condition)
189 |
190 | def _find_condition(self):
191 | """Yields condition and updates cause template"""
192 | condition = ''
193 |
194 | is_condition = False
195 | for s_pos, s in enumerate(self.cause_str):
196 |
197 | if s == Dependancy.cond_start:
198 | is_condition = True
199 | self.cause_template += Dependancy.cond_marker
200 |
201 | if is_condition:
202 | condition += s
203 | else:
204 | self.cause_template += s
205 |
206 | if s == Dependancy.cond_stop:
207 | yield condition[1:-1] # First and last char are flags of begining and end of condition
208 | is_condition = False
209 | condition = ''
210 |
211 | @staticmethod
212 | def _parse_condition(condition):
213 | """Creates condition objects based on condition string"""
214 |
215 | op_pos = 0
216 | op = None # operator
217 | for s_pos, s in enumerate(condition):
218 | if s in Condition.operator_dict.keys():
219 | op_pos = s_pos
220 | op = s
221 | break
222 |
223 | element = condition[:op_pos]
224 | comp_value = condition[op_pos+1:]
225 |
226 | if op not in Condition.operator_dict.keys():
227 | raise DependancyConfigError("Condition has wrong operator: {}".format(op))
228 |
229 | return element, op, comp_value
230 |
231 | def _parse_effects(self, output_elements=None):
232 | """Creates effect objects based on effect string"""
233 |
234 | effects_array = self.effects_str.strip().rstrip(';').split(';')
235 | for effect_str in effects_array:
236 |
237 | element_id, set_value, _time = self._parse_effect(effect_str)
238 |
239 | if element_id not in output_elements.keys():
240 | raise DependancyConfigError('Output element: ' + str(element_id) + ' not in output elements')
241 |
242 | effect = Effect(self.num_of_effect, output_elements[element_id], set_value, _time)
243 | self.num_of_effect += 1
244 | self.effects.append(effect)
245 |
246 | @staticmethod
247 | def _parse_effect(effect_str):
248 |
249 | effect_str = effect_str.strip()
250 | op_pos = 0
251 | time_pos = None
252 | _time = ''
253 | is_time = False
254 | for s_pos, s in enumerate(effect_str):
255 | if s == '=':
256 | op_pos = s_pos
257 |
258 | if s == Dependancy.effect_start_t:
259 | time_pos = s_pos
260 | is_time = True
261 |
262 | if is_time:
263 | _time += s
264 |
265 | if s == Dependancy.effect_stop_t:
266 | is_time = False
267 |
268 | try:
269 | element_id = int(effect_str[1:op_pos])
270 | set_value = int(effect_str[op_pos + 1:time_pos])
271 | _time = int(effect_str[time_pos+1:-1])*1000 # First and last char are flags of begining and end of time
272 | except:
273 | raise DependancyConfigError('Effect parsing error. Effect string: {}'.format(effect_str))
274 |
275 | if set_value < 0:
276 | raise DependancyConfigError('Set value cant be negative. Effect string: {}'.format(effect_str))
277 |
278 | if _time < 0:
279 | raise DependancyConfigError('Time cant be negative. Effect string: {}'.format(effect_str))
280 |
281 | return element_id, set_value, _time
282 |
283 | def __str__(self, ):
284 | return "".join(["ID: ", str(self.id), "\ttype: ", "\tname: ", self.name, '\tdep_str: ', self.dep_str])
285 |
286 | def __repr__(self):
287 | return "".join(["ID: ", str(self.id), " - ", self.name])
288 |
289 |
290 |
--------------------------------------------------------------------------------
/backend/components/element.py:
--------------------------------------------------------------------------------
1 | from backend.components.base_component import BaseComponent
2 | from backend.misc.sys_types import Et
3 |
4 |
5 | class Element(BaseComponent):
6 |
7 | table_name = "elements"
8 | COL_MODULE_ID, COL_REG, = 3, 4,
9 | column_headers_and_types = BaseComponent.column_headers_and_types + [['module_id', 'integer'], ['register', 'integer']]
10 |
11 | items = {}
12 |
13 | def __init__(self, *args):
14 | super().__init__(args[0], Et(args[1]), args[2]) # inicjalizuj id type, name
15 | Element.items[self.id] = self
16 | self.value = None
17 | self.module_id = args[Element.COL_MODULE_ID]
18 | self.reg_id = args[Element.COL_REG]
19 | self.objects_to_notify = set()
20 | self.new_val_flag = False
21 |
22 | def subscribe(self, who):
23 | self.objects_to_notify.add(who)
24 |
25 | def notify_objects(self, ):
26 | for _object in self.objects_to_notify:
27 | _object.notify(self.value)
28 |
29 | def __str__(self, ):
30 | return "".join([super().__str__(), "\tvalue: ", str(self.value)])
31 |
32 |
33 | class InputElement(Element):
34 |
35 | types = {Et.ds, Et.dht_hum, Et.dht_temp, Et.pir, Et.rs, Et.ls, Et.switch}
36 | items = {}
37 |
38 | def __init__(self, *args):
39 | """arguments: type name """
40 | super().__init__(*args) # inicjalizuj type, name
41 | InputElement.items[self.id] = self
42 |
43 |
44 | class OutputElement(Element):
45 |
46 | types = {Et.led, Et.heater, Et.ventilator, Et.blind}
47 |
48 | defualt_priority = 15
49 | items = {}
50 |
51 | def __init__(self, *args):
52 | super().__init__(*args)
53 | OutputElement.items[self.id] = self
54 | self.desired_value = 0
55 | self.setter_priority = OutputElement.defualt_priority # Default priority can be overriden by everybody
56 |
57 | def set_desired_value(self, value, priority, set_flag=False):
58 |
59 | if priority <= self.setter_priority:
60 | self.desired_value = value
61 |
62 | if set_flag:
63 | self.setter_priority = priority
64 | else:
65 | self.setter_priority = self.defualt_priority
66 |
67 | return True
68 |
69 | else:
70 | return False
71 |
72 | def __str__(self, ):
73 | return "".join([super().__str__(), "\tdesired_value: ", str(self.desired_value)])
74 |
75 | @staticmethod
76 | def str():
77 | string = "\n"
78 | for element in OutputElement.items.values():
79 | el_str = str(element)
80 | string += el_str + "\n"
81 |
82 | return string
83 |
84 |
85 | class Blind(OutputElement):
86 | table_name = "blinds"
87 | column_headers_and_types = Element.column_headers_and_types + [['direction', 'text'], ['other_blind', 'integer']]
88 |
89 | COL_DIRECTION, COL_OTHER_BLIND = 5, 6
90 | items = {}
91 |
92 | def __init__(self, *args):
93 | super().__init__(*args[0:self.COL_DIRECTION])
94 | Blind.items[self.id] = self
95 | self.direction = args[self.COL_DIRECTION]
96 | self.other_blind = args[self.COL_OTHER_BLIND] # if blind is up then it is down and the other way around
97 |
98 | def set_desired_value(self, value, priority, set_flag=False):
99 |
100 | if super().set_desired_value(value, priority, set_flag=False):
101 | self.other_blind.desired_value = 0
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 | #kontaktron, pir
151 | #class Input(Element):
152 | # def __init__(self, name):
153 | # return super().__init__(name)
154 |
155 |
156 | #class Digital_input(Input):
157 | # def __init__(self, name):
158 | # return super().__init__(name)
159 |
160 | #class Analog_input(Input):
161 | # def __init__(self, name):
162 | # return super().__init__(name)
163 |
164 | #class Output(Element):
165 | # def __init__(self, name):
166 | # return super().__init__(name)
167 |
168 | #class Analog_output(Output):
169 | # def __init__(self, name):
170 | # return super().__init__(name)
171 |
172 | #class Digital_output(Output):
173 | # def __init__(self, name):
174 | # return super().__init__(name)
175 |
176 | #class PIR(Digital_input):
177 | # def __init__(self, name):
178 | # return super().__init__(name)
179 |
180 | #class Reed_switch(Digital_input):
181 | # def __init__(self, name):
182 | # return super().__init__(name)
183 |
184 |
185 | #class DS18B20(Analog_input):
186 | # def __init__(self, name):
187 | # return super().__init__(name)
188 |
189 |
190 | #class DHT22(Analog_input):
191 | # def __init__(self, name):
192 | # return super().__init__(name)
193 |
194 | #class Light_sensor(Analog_input):
195 | # def __init__(self, name):
196 | # return super().__init__(name)
197 |
198 |
199 | #class Heater(Digital_output):
200 | # def __init__(self, name):
201 | # return super().__init__(name)
202 |
203 | #class Blind():
204 | # def __init__(self, name):
205 | # pass
206 |
207 | #class Led(Analog_output):
208 | # def __init__(self, name):
209 | # pass
210 |
--------------------------------------------------------------------------------
/backend/components/modbus_network.py:
--------------------------------------------------------------------------------
1 | from timeit import default_timer as t
2 | import serial
3 | import logging
4 | import time
5 | from functools import wraps
6 |
7 | from backend.misc.check_host import is_RPI
8 | from backend.misc.benchmark import Benchmark
9 |
10 |
11 | def pretty_hex(_bytes):
12 | return ''.join(str(_bytes))
13 |
14 |
15 | class ModbusFunction:
16 |
17 | _CRC16TABLE = (
18 | 0, 49345, 49537, 320, 49921, 960, 640, 49729, 50689, 1728, 1920,
19 | 51009, 1280, 50625, 50305, 1088, 52225, 3264, 3456, 52545, 3840, 53185,
20 | 52865, 3648, 2560, 51905, 52097, 2880, 51457, 2496, 2176, 51265, 55297,
21 | 6336, 6528, 55617, 6912, 56257, 55937, 6720, 7680, 57025, 57217, 8000,
22 | 56577, 7616, 7296, 56385, 5120, 54465, 54657, 5440, 55041, 6080, 5760,
23 | 54849, 53761, 4800, 4992, 54081, 4352, 53697, 53377, 4160, 61441, 12480,
24 | 12672, 61761, 13056, 62401, 62081, 12864, 13824, 63169, 63361, 14144, 62721,
25 | 13760, 13440, 62529, 15360, 64705, 64897, 15680, 65281, 16320, 16000, 65089,
26 | 64001, 15040, 15232, 64321, 14592, 63937, 63617, 14400, 10240, 59585, 59777,
27 | 10560, 60161, 11200, 10880, 59969, 60929, 11968, 12160, 61249, 11520, 60865,
28 | 60545, 11328, 58369, 9408, 9600, 58689, 9984, 59329, 59009, 9792, 8704,
29 | 58049, 58241, 9024, 57601, 8640, 8320, 57409, 40961, 24768, 24960, 41281,
30 | 25344, 41921, 41601, 25152, 26112, 42689, 42881, 26432, 42241, 26048, 25728,
31 | 42049, 27648, 44225, 44417, 27968, 44801, 28608, 28288, 44609, 43521, 27328,
32 | 27520, 43841, 26880, 43457, 43137, 26688, 30720, 47297, 47489, 31040, 47873,
33 | 31680, 31360, 47681, 48641, 32448, 32640, 48961, 32000, 48577, 48257, 31808,
34 | 46081, 29888, 30080, 46401, 30464, 47041, 46721, 30272, 29184, 45761, 45953,
35 | 29504, 45313, 29120, 28800, 45121, 20480, 37057, 37249, 20800, 37633, 21440,
36 | 21120, 37441, 38401, 22208, 22400, 38721, 21760, 38337, 38017, 21568, 39937,
37 | 23744, 23936, 40257, 24320, 40897, 40577, 24128, 23040, 39617, 39809, 23360,
38 | 39169, 22976, 22656, 38977, 34817, 18624, 18816, 35137, 19200, 35777, 35457,
39 | 19008, 19968, 36545, 36737, 20288, 36097, 19904, 19584, 35905, 17408, 33985,
40 | 34177, 17728, 34561, 18368, 18048, 34369, 33281, 17088, 17280, 33601, 16640,
41 | 33217, 32897, 16448)
42 |
43 | SLAVE_ADDRESS_POS = 0
44 | FUNCTION_CODE_POS = 1
45 |
46 | NUMBER_OF_CRC_BYTES = 2
47 | FUNCTION_CODE_ERROR_BIT = 7
48 |
49 | WRITE_FUNCTIONS_CODES = {5, 6, 15, 16}
50 | READ_FUNCTIONS_CODES = {1, 2, 3, 4}
51 |
52 | def __init__(self):
53 | self.code = None
54 | self.payload_position = None
55 |
56 | def validate(self, slave_address, response):
57 | """Validate slave board response. Response can be invalid due to Slave failure or communication failure.
58 | Invalid response should not propragate to system. It should be ignored"""
59 | response_length = len(response)
60 |
61 | if response_length == 0:
62 | raise ValueError('Slave {} is not responding'.format(slave_address))
63 |
64 | if response_length < 5:
65 | raise ValueError('Slave {} response too short: {!r}'.format(slave_address, pretty_hex(response)))
66 |
67 | response_address = response[self.SLAVE_ADDRESS_POS]
68 | received_function_code = response[self.FUNCTION_CODE_POS]
69 | received_checksum = response[-self.NUMBER_OF_CRC_BYTES:]
70 |
71 | if response_length == 5:
72 | if received_function_code == self._set_bit_on(self.code, self.FUNCTION_CODE_ERROR_BIT):
73 | raise ValueError('Slave {} is indicating an error. The response is: {!r}'.format(slave_address,
74 | pretty_hex(response)))
75 |
76 | calculated_checksum = self._calculate_crc(response[0: - self.NUMBER_OF_CRC_BYTES])
77 | if received_checksum != calculated_checksum: # check received checksum vs calculated checksum
78 | template = 'Slave {} checksum error. \nExcepted checksum {}\n{}'
79 |
80 | payload = response[self.payload_position: -self.NUMBER_OF_CRC_BYTES]
81 | response_tuple = (response_length, response_address, received_function_code, payload, received_checksum)
82 | text = template.format(slave_address,
83 | pretty_hex(calculated_checksum),
84 | self.pretty_response(*response_tuple))
85 | raise ValueError(text)
86 |
87 | if response_address != slave_address: # Check if return slave address is the same
88 | raise ValueError('Wrong slave address: {} instead of {}. The response is: {!r}'.format(
89 | response_address, slave_address, pretty_hex(response)))
90 |
91 | if received_function_code != self.code: # Check if return function code is the same
92 | raise ValueError('Wrong slave function code: {} instead of {}. The response is: {!r}'.format(
93 | received_function_code, self.code, pretty_hex(response)))
94 |
95 | @staticmethod
96 | def pretty_response(response_length, response_address, received_function_code, payload, received_checksum):
97 | """Formats response in a pleasent readable way"""
98 | return """Recived checksum: {}
99 | Frame length: {}
100 | Address: {}
101 | Function code: {}
102 | Payload: {}
103 | \n""".format(pretty_hex(received_checksum),
104 | response_length,
105 | response_address,
106 | received_function_code,
107 | pretty_hex(payload), )
108 |
109 | @staticmethod
110 | def _set_bit_on(val, bit_num):
111 | """Sets bit on desired position"""
112 | return val | (1 << bit_num)
113 |
114 | def _calculate_crc(self, inputstring):
115 | """Calculates cyclic redundance cheskum.
116 | Thanks to CRC it is easy to recognize if communication packet is corrupted"""
117 | register = 0xFFFF # Preload a 16-bit register with ones
118 |
119 | for char in inputstring:
120 | register = (register >> 8) ^ self._CRC16TABLE[(register ^ char) & 0xFF]
121 | return ModbusFunction._num_to_two_bytes(register, lsb_first=True)
122 |
123 | def _list_to_byte_string(self, _list):
124 | """Converts list with integer values to binary string"""
125 | bytestring = b""
126 | for value in _list:
127 | bytestring += self._num_to_two_bytes(value)
128 |
129 | return bytestring
130 |
131 | @staticmethod
132 | def _byte_string_to_list(byte_string):
133 | """Converts binary string into list o values"""
134 | values = []
135 | two_bytes = [0, 0]
136 | for byte_num, byte in enumerate(byte_string):
137 | two_bytes[byte_num % 2] = byte
138 | if byte_num % 2 == 1:
139 | values.append(ModbusFunction._two_bytes_to_num(two_bytes))
140 | two_bytes = [0, 0]
141 | return values
142 |
143 | @staticmethod
144 | def _two_bytes_to_num(two_bytes):
145 | """Converts two bytes list to integer number"""
146 | return two_bytes[0]*256 + two_bytes[1]
147 |
148 | @staticmethod
149 | def _num_to_two_bytes(value, lsb_first=False):
150 | """Converts integer number to two bytes list.
151 | List may be least siginifican byte first or most significant byte first"""
152 | msb = value >> 8
153 | lsb = value & 255
154 |
155 | if lsb_first:
156 | return bytes([lsb, msb])
157 | else:
158 | return bytes([msb, lsb])
159 |
160 | @staticmethod
161 | def slave_address_and_crc(modbus_fun):
162 | """Decorator that wraps every modbus function run method.
163 | It adds at the begging of the request slave address, modbus function code.
164 | It adds at the enf of the request CRC"""
165 | @wraps(modbus_fun)
166 | def function_wrapper(self, slave_address, *modbus_fun_args):
167 | request = bytes([slave_address, self.code]) # slave address
168 | fun_request_part, num_of_bytes_to_read = modbus_fun(self, *modbus_fun_args) # function specific part of request
169 | request += fun_request_part
170 | request += self._calculate_crc(request)
171 |
172 | return request, num_of_bytes_to_read
173 |
174 | return function_wrapper
175 |
176 | def __repr__(self, ):
177 | return self.__class__.__name__ + " code: " + str(self.code)
178 |
179 |
180 | class ReadRegsFunction(ModbusFunction):
181 | def __init__(self):
182 | super().__init__()
183 | self.min_request_bytes = 5
184 | self.min_response_bytes = 5
185 | self.code = 3
186 | self.payload_position = 3
187 |
188 | @ModbusFunction.slave_address_and_crc
189 | def run(self, start_reg_num, end_reg_num):
190 | num_of_regs = end_reg_num - start_reg_num + 1
191 | request = self._num_to_two_bytes(start_reg_num)
192 | request += self._num_to_two_bytes(num_of_regs)
193 |
194 | number_of_bytes_to_read = self.min_request_bytes + 2*num_of_regs
195 | return request, number_of_bytes_to_read
196 |
197 | def get_payload(self, response):
198 | return self._byte_string_to_list(response[self.payload_position: -self.NUMBER_OF_CRC_BYTES])
199 |
200 |
201 | class WriteRegsFunction(ModbusFunction):
202 |
203 | def __init__(self):
204 | super().__init__()
205 | self.min_request_bytes = 9
206 | self.min_response_bytes = 8
207 | self.code = 16
208 | self.payload_position = 2
209 |
210 | @ModbusFunction.slave_address_and_crc
211 | def run(self, start_reg_num, values):
212 |
213 | num_of_regs = len(values)
214 | byte_count = num_of_regs*2
215 |
216 | request = self._num_to_two_bytes(start_reg_num)
217 | request += self._num_to_two_bytes(num_of_regs)
218 | request += bytes([byte_count])
219 | request += self._list_to_byte_string(values)
220 |
221 | return request, self.min_response_bytes
222 |
223 |
224 | class WriteCoilsFunction(ModbusFunction):
225 |
226 | def __init__(self):
227 | super().__init__()
228 | self.min_request_bytes = 11
229 | self.min_response_bytes = 8
230 | self.code = 15
231 | self.payload_position = 2
232 |
233 | @ModbusFunction.slave_address_and_crc
234 | def run(self, start_coil_num, values):
235 |
236 | num_of_coils = len(values)
237 | out_bytes = self._coils_vals_to_bytes(values)
238 | byte_count = len(out_bytes)
239 |
240 | request = self._num_to_two_bytes(start_coil_num)
241 | request += self._num_to_two_bytes(num_of_coils)
242 | request += bytes([byte_count])
243 | request += out_bytes
244 |
245 | return request, self.min_response_bytes
246 |
247 | @staticmethod
248 | def _coils_vals_to_bytes(values):
249 | """converts coils values to bytes"""
250 | if not values:
251 | return bytes([0, 0])
252 |
253 | out_bytes = bytes([])
254 | byte = 0
255 | byte_iter = 0
256 | for coil_val in values:
257 | if coil_val:
258 | byte |= 1 << byte_iter
259 | byte_iter += 1
260 | if byte_iter == 8:
261 | out_bytes += bytes([byte])
262 | byte = 0
263 | byte_iter = 0
264 |
265 | if byte_iter < 7: # didn't iterated through whole byte
266 |
267 | if len(out_bytes) % 2:
268 | out_bytes += bytes([byte]) # add pending byte
269 | else:
270 | out_bytes += bytes([byte, 0]) # add pennding byte and zero byte for odd len bytes
271 | else: # iterated through whole byte
272 | if len(out_bytes) % 2: # even number of whole bytes
273 | out_bytes += bytes([0]) # add pending byte
274 |
275 | return out_bytes
276 |
277 |
278 | class ModbusNetwork:
279 | """Handles opening and closing of serial port.
280 | Gives API to modbus functions"""
281 |
282 | _instance = None
283 | _is_initialized = False
284 |
285 | def __new__(cls, *args, **kwargs):
286 | if not cls._instance:
287 | cls._instance = super().__new__(cls, *args, **kwargs)
288 | return cls._instance
289 |
290 | def __init__(self):
291 | if not ModbusNetwork._is_initialized:
292 | ModbusNetwork._is_initialized = True
293 | self.logger = logging.getLogger('MODBUS')
294 |
295 | self.baudrate = 38400
296 | self.connected = False
297 | self.port = ""
298 | self.t_3_5 = 1e-3 # (1/baudrate)*9*3.5 # 9 bits per charater. 3.5 characters time sleep
299 | self.sleep_timer = 0
300 | self.correct_frames = 0
301 | self.corrupted_frames = 0
302 | self.consecutive_corrupted_frames = 0
303 |
304 | self.bench = Benchmark(self.logger.level)
305 | self.bench.start()
306 |
307 | if is_RPI:
308 | self.port = "/dev/ttyUSB0"
309 | else:
310 | self.port = "COM4"
311 |
312 | self.serial_port = None
313 |
314 | self.read_regs_obj = ReadRegsFunction()
315 | self.write_regs_obj = WriteRegsFunction()
316 | self.write_coils_obj = WriteCoilsFunction()
317 |
318 | def open(self):
319 | try:
320 | self.serial_port = serial.Serial(port=self.port,
321 | baudrate=self.baudrate,
322 | timeout=0.02,
323 | parity=serial.PARITY_NONE,
324 | stopbits=1)
325 | self.connected = True
326 | return True
327 | except serial.SerialException:
328 | self.logger.error("Can't open port {}".format(self.port))
329 | return False
330 |
331 | def is_available(self):
332 | if self.consecutive_corrupted_frames > 1:
333 | return False
334 | else:
335 | return True
336 |
337 | def reload(self):
338 | self.close()
339 | self.open()
340 | self.consecutive_corrupted_frames = 0
341 | self.logger.warn("Serial reload")
342 |
343 | def close(self):
344 | self.serial_port.close()
345 | self.connected = False
346 |
347 | def write_regs(self, slave_address, start_reg_num, values):
348 | return self.run(self.write_regs_obj, slave_address, start_reg_num, values)
349 |
350 | def write_coils(self, slave_address, start_coil_num, values):
351 | return self.run(self.write_coils_obj, slave_address, start_coil_num, values)
352 |
353 | def read_regs(self, slave_address, start_reg_num, end_reg_num):
354 | return self.run(self.read_regs_obj, slave_address, start_reg_num, end_reg_num)
355 |
356 | def run(self, func_obj, slave_address, *modbus_fun_args):
357 | """ Wrapper for run functions of modbus functions.
358 | It expect function's request part and number of bytes to read .
359 | "It embedes function request with slave address, fun code, crc.
360 | Then it performs write and read, validates response and return either payload or ack, nack.
361 | """
362 |
363 | request, num_of_bytes_to_read = func_obj.run(slave_address, *modbus_fun_args) #
364 | sleep_time = t() - self.sleep_timer
365 | if sleep_time < self.t_3_5: # if there wasn't enaugh silent time, sleep!
366 | time.sleep(sleep_time)
367 |
368 | self.serial_port.write(request)
369 | response = self.serial_port.read(num_of_bytes_to_read)
370 |
371 | self.sleep_timer = t()
372 |
373 | try:
374 | func_obj.validate(slave_address, response)
375 | except ValueError as e:
376 | self.logger.warn(e)
377 | self.corrupted_frames += 1
378 | self.consecutive_corrupted_frames += 1
379 | return False
380 | else:
381 | self.correct_frames += 1
382 | if func_obj.code in ModbusFunction.READ_FUNCTIONS_CODES: # return payload
383 | return func_obj.get_payload(response)
384 |
385 | return True # return ack
386 |
387 | def debug(self,):
388 | if self.bench.loops_per_second():
389 | self.logger.debug("\nCorrect frames: {}\nCorrupted frames: {}".format(self.correct_frames,
390 | self.corrupted_frames))
391 |
392 |
--------------------------------------------------------------------------------
/backend/components/module.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 | from backend.components.base_component import BaseComponent
4 | from backend.components.clock import Clock
5 | from backend.components.modbus_network import ModbusNetwork
6 | from backend.misc.sys_types import Mt, Et
7 |
8 |
9 | class AddElementError(Exception):
10 | def __init__(self, msg):
11 | self.msg = msg
12 |
13 |
14 | class Module(BaseComponent):
15 | """Base class for all modules.
16 | It implements prototype of command that decorates all read and write functions"""
17 |
18 | table_name = 'modules'
19 |
20 | types = {Mt.led_light, Mt.output, Mt.ambient, Mt.input} # Needed for loading objects from database
21 | ID = 0
22 | start_timeout = 10 # timeout in ms
23 | clock = Clock()
24 | items = {}
25 |
26 | def __init__(self, *args):
27 |
28 | super().__init__(args[0], Mt(args[1]), args[2])
29 | Module.items[self.id] = self
30 | self.ports = {} # Module's physical ports. Dictionary stores elements during configuration so not to connect elment twice to same port
31 | self.elements = {} # Module's elements. Keys are registers in which elements values are going to be stored
32 | self.modbus = ModbusNetwork() # Reference to modbus. Modbus is a singleton.
33 |
34 | self.available = True # Flag indicating if there is communication with module
35 | self.last_timeout = 0
36 | self.timeout = Module.start_timeout
37 | self.max_timeout = 2
38 | self.correct_trans_num = 0
39 | self.transmission_num = 0
40 | self.courupted_trans_num = 0
41 |
42 | def is_available(self, ):
43 | """Checks if module is available.
44 | If it is not but timeout expired it makes module
45 | available so there would be communication trial"""
46 | if self.available:
47 | self.timeout = Module.start_timeout
48 | return True
49 |
50 | else:
51 | current_time = self.clock.get_millis()
52 | if current_time - self.last_timeout >= self.timeout:
53 | self.last_timeout = current_time
54 | self.available = True
55 | return True
56 | return False
57 |
58 | @staticmethod
59 | def command(func):
60 | """Decorator for all modbus commands. It counts correct and corupted transmisions.
61 | It sets timeout if the transmission was corrupted """
62 |
63 | @wraps(func)
64 | def func_wrapper(self, ):
65 | self.transmission_num += 1
66 | result = func(self)
67 |
68 | if result: # if there is response from module
69 | self.correct_trans_num += 1
70 | return result
71 | else:
72 | self.available = False
73 | self.courupted_trans_num += 1
74 | if self.timeout <= self.max_timeout:
75 | self.timeout *= 2 # Increase timeout
76 | # TODO notification about module failures
77 | return result
78 | return func_wrapper
79 |
80 | def check_port_range(self, port):
81 | if port > self.num_of_ports-1 or port < 0:
82 | raise AddElementError('Port: ' + str(port) + ' out of range')
83 |
84 | def check_port_usage(self, port):
85 | try:
86 | self.ports[port]
87 | raise AddElementError('Port: ' + str(port) + ' is in use')
88 | except KeyError:
89 | pass
90 |
91 | def check_element_type(self, element):
92 | if element.type not in self.accepted_elements:
93 | raise AddElementError('Element: ' + element.type.name + ' is not valid for ' + self.type.name)
94 |
95 | def check_if_element_connected(self, element):
96 | if element.module_id and element.module_id != self.id and element.type != Et.blind: # roleta moze byc podlaczona 2 razy do jednego modulu - gora i dol
97 | raise AddElementError('Element: ' + str(element.id) + ' already connected to ' + str(element.module_id))
98 |
99 | def add_element(self, port, element):
100 | self.check_element_type(element)
101 | self.check_port_range(port)
102 | self.check_port_usage(port)
103 | self.check_if_element_connected(element)
104 |
105 | self.ports[port] = element
106 | element.reg_id = port
107 | element.module_id = self.id
108 | self.elements[element.id] = element
109 |
110 |
111 | class InputModule(Module):
112 | """Base class for input modules. It implements read decorator command"""
113 | types = {Mt.ambient, Mt.input}
114 | items = {}
115 |
116 | def __init__(self, *args):
117 | super().__init__(*args)
118 | InputModule.items[self.id] = self
119 |
120 | @Module.command
121 | def read(self,):
122 | regs_values = self.modbus.read_regs(self.id, 0, self.num_of_regs)
123 |
124 | if regs_values: # If transmision was correct
125 | for element in self.elements.values():
126 | new_value = regs_values[element.reg_id]
127 | if new_value != element.value:
128 | element.value = new_value
129 | element.new_val_flag = True
130 | return True
131 | return False
132 |
133 |
134 | class OutputModule(Module):
135 | """ Base class for output modules. It implements write decorator command"""
136 | types = {Mt.led_light, Mt.output}
137 | items = {}
138 |
139 | def __init__(self, *args):
140 | super().__init__(*args)
141 | OutputModule.items[self.id] = self
142 | self.values = [0 for _ in range(self.num_of_regs)] # values to be written in board modbus registers
143 |
144 | @staticmethod
145 | def write_command(func):
146 | """Decorator for modbus write commands it checks which elements values differ.
147 | If the communication result is True it updates elements values """
148 | @wraps(func)
149 | def func_wrapper(self):
150 | elements_to_update = []
151 | for element in self.elements.values():
152 | if element.desired_value != element.value: # element value needs to be updated
153 | self.values[element.reg_id] = element.desired_value
154 | elements_to_update.append(element)
155 |
156 | result = func(self)
157 |
158 | if result:
159 | for element in elements_to_update:
160 | if element.desired_value != element.value: # element value needs to be updated
161 | element.value = element.desired_value # element value is updated
162 | element.new_val_flag = True
163 |
164 | return result
165 | return func_wrapper
166 |
167 |
168 | class OutputBoard(OutputModule):
169 |
170 | num_of_ports = 10
171 | num_of_regs = 10
172 | accepted_elements = {Et.led, Et.heater, Et.ventilator, Et.blind}
173 | types = {Mt.output}
174 | items = {}
175 |
176 | def __init__(self, *args):
177 | super().__init__(*args)
178 | OutputBoard.items[self.id] = self
179 |
180 | @Module.command
181 | @OutputModule.write_command
182 | def write(self, ):
183 | return self.modbus.write_coils(self.id, 0, self.values)
184 |
185 |
186 | class LedLightBoard(OutputModule):
187 |
188 | num_of_ports = 3
189 | num_of_regs = 3
190 | accepted_elements = {Et.led}
191 | types = {Mt.led_light}
192 | items = {}
193 |
194 | def __init__(self, *args):
195 | super().__init__(args[0], Mt(args[1]), args[2])
196 | LedLightBoard.items[self.id] = self
197 |
198 | @Module.command
199 | @OutputModule.write_command
200 | def write(self, ):
201 | return self.modbus.write_regs(self.id, 0, self.values)
202 |
203 |
204 | class AmbientBoard(InputModule):
205 |
206 | num_of_ports = 4
207 | num_of_regs = 19
208 | accepted_elements = {Et.ds, Et.dht_hum, Et.dht_temp, Et.ls}
209 | types = {Mt.ambient}
210 | items = {}
211 |
212 | def __init__(self, *args):
213 | InputModule.__init__(self, *args)
214 | AmbientBoard.items[self.id] = self
215 | self.ds18b20_counter = 0
216 |
217 | def add_element(self, port, element):
218 | """For Ambient board ports are not the same as registers. ds18b20 sensors are all working on one port"""
219 | self.check_element_type(element)
220 | self.check_port_range(port)
221 | self.check_if_element_connected(element)
222 | try:
223 | if self.ports[port]: # Check if something is already on the port
224 | if self.ports[port].type == Et.ds: # If on this port is ds18b20
225 | if element.type == Et.ds: # And user wants to connect another ds18b20
226 | pass # everything is fine
227 | else:
228 | raise AddElementError('Port: ' + port + ' in use') # raise error
229 | except KeyError:
230 | self.ports[port] = element # Add element to port
231 |
232 | if element.type == Et.ls: # dodanie elementu do rejestru
233 | element.reg_id = 0
234 | elif element.type == Et.dht_temp:
235 | element.reg_id = 1
236 | elif element.type == Et.dht_hum:
237 | element.reg_id = 2
238 | elif element.type == Et.ds:
239 | element.reg_id = 3 + self.ds18b20_counter
240 | self.ds18b20_counter += 1
241 |
242 | element.module_id = self.id
243 | self.elements[element.id] = element
244 |
245 |
246 | class InputBoard(InputModule):
247 |
248 | num_of_ports = 15
249 | num_of_regs = 15
250 | accepted_elements = {Et.pir, Et.rs, Et.switch}
251 | types = {Mt.input}
252 | items = {}
253 |
254 | def __init__(self, *args):
255 | InputModule.__init__(self, *args)
256 | InputBoard.items[self.id] = self
257 |
258 |
259 |
260 |
261 |
--------------------------------------------------------------------------------
/backend/components/regulation.py:
--------------------------------------------------------------------------------
1 | from backend.components.base_component import BaseComponent
2 | from backend.components.element import OutputElement
3 | from backend.misc.sys_types import Regt
4 |
5 |
6 | class RegulationConfigError(Exception):
7 | def __init__(self, msg):
8 | self.msg = msg
9 |
10 |
11 | class Regulation(BaseComponent):
12 | """Rehulation algoritms for controlling outputs based on inputs values"""
13 |
14 | table_name = "regulation"
15 |
16 | column_headers_and_types = BaseComponent.column_headers_and_types + [['feed_el_id', 'integer'],
17 | ['out_el_id', 'integer'],
18 | ['set_point', 'integer'],
19 | ['deviation', 'integer'], ]
20 |
21 | ID = 0
22 |
23 | ON = 1
24 | OFF = 0
25 |
26 | COL_FEED_EL_ID, COL_OUT_EL_ID, COL_SET_POINT, COL_DEVIATION = 3, 4, 5, 6
27 | items = {}
28 |
29 | def __init__(self, *args):
30 |
31 | super().__init__(args[0], Regt(args[1]), args[2])
32 | Regulation.items[self.id] = self
33 | self.feed_el_id = args[Regulation.COL_FEED_EL_ID]
34 | self.out_element = OutputElement.items[args[Regulation.COL_OUT_EL_ID]]
35 | self.set_point = args[Regulation.COL_SET_POINT]
36 | self.dev = args[Regulation.COL_DEVIATION] # max alowable deviation from set point
37 | self.control = self.proportional_control # Control algorithm
38 |
39 | self.priority = 10
40 | self.feed_val = None
41 |
42 | def notify(self, val):
43 | self.feed_val = val
44 |
45 | def run(self, ):
46 | """calculates whether output element should be on or off"""
47 | out_val = self.control()
48 | if out_val == Regulation.ON:
49 | self.out_element.desired_value = (out_val, self.priority, True)
50 |
51 | if out_val == Regulation.OFF:
52 | self.out_element.desired_value = (out_val, self.priority, False)
53 |
54 | def proportional_control(self, ):
55 | """If feed value is less than set value turn on regulation. Otherwise turn off"""
56 | if not self.feed_val: # if sensor does not returns any valu - its val == None
57 | return Regulation.OFF
58 |
59 | if self.feed_val < self.set_point:
60 | return Regulation.ON
61 |
62 | else:
63 | return Regulation.OFF
64 |
65 | def inverse_control(self, ):
66 | pass
67 |
--------------------------------------------------------------------------------
/backend/components/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/components/tests/__init__.py
--------------------------------------------------------------------------------
/backend/components/tests/helper_classes.py:
--------------------------------------------------------------------------------
1 | class Notifiable:
2 |
3 | def __init__(self):
4 |
5 | self.val = None
6 |
7 | def notify(self, val):
8 |
9 | self.val = val
10 |
--------------------------------------------------------------------------------
/backend/components/tests/test_base_component.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from enum import Enum
3 |
4 | from backend.components.base_component import BaseComponent
5 |
6 |
7 | class TestBaseComponent(unittest.TestCase):
8 |
9 | def test_wrong_attributes(self):
10 | with self.assertRaises(TypeError):
11 | BaseComponent(None, 2, "3")
12 |
13 | with self.assertRaises(TypeError):
14 | BaseComponent(1, "s", "3")
15 |
16 | with self.assertRaises(TypeError):
17 | BaseComponent(1, 2, 5)
18 |
19 | with self.assertRaises(ValueError):
20 | BaseComponent(-6, 2, "3")
21 |
22 | def test_valid_attributes(self):
23 | BaseComponent(0, 2, "dupa")
24 |
25 |
26 | if __name__ == "__main__":
27 | unittest.main()
28 |
--------------------------------------------------------------------------------
/backend/components/tests/test_clock.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import datetime
3 | import time
4 |
5 | from backend.components.clock import Clock
6 |
7 | from backend.components.tests.helper_classes import Notifiable
8 |
9 |
10 | class TestClock(unittest.TestCase):
11 |
12 | def setUp(self):
13 | self.clock = Clock()
14 | self.clock.restart()
15 |
16 | def test_get_seconds(self):
17 | self.assertAlmostEqual(self.clock.get_seconds(), 0, 3)
18 |
19 | def test_get_milis(self):
20 | time.sleep(0.001)
21 | self.assertAlmostEqual(self.clock.get_millis(), 1, 0)
22 |
23 | def test_subscribe(self):
24 |
25 | notifiables = [Notifiable() for _ in range(3)]
26 |
27 | for notifiable in notifiables:
28 | self.clock.subscribe_for_weekday(notifiable)
29 | self.clock.subscribe_for_minute(notifiable)
30 |
31 | for notifiable in notifiables:
32 | self.assertIn(notifiable, self.clock.objects_to_notify_weekday)
33 | self.assertIn(notifiable, self.clock.objects_to_notify_time)
34 |
35 | def test_evaluate_time(self):
36 |
37 | self.clock.evaluate_time()
38 | self.assertEqual(self.clock.now.minute, self.clock.minute)
39 | self.assertEqual(self.clock.now.weekday(), self.clock.weekday)
40 |
41 | def test_notification(self):
42 |
43 | notifiable_minute = Notifiable()
44 | notifiable_weekday = Notifiable()
45 |
46 | self.clock.subscribe_for_minute(notifiable_minute)
47 | self.clock.subscribe_for_weekday(notifiable_weekday)
48 |
49 | self.clock.now = datetime.datetime.now()
50 | self.clock.weekday = 6
51 |
52 | self.clock.notify_minute()
53 | self.clock.notify_weekday()
54 |
55 | self.assertEqual(notifiable_minute.val, self.clock.now)
56 | self.assertEqual(notifiable_weekday.val, self.clock.weekday)
57 |
58 |
--------------------------------------------------------------------------------
/backend/components/tests/test_dependancy.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import time
3 |
4 | from backend.components.dependancy import Condition
5 | from backend.components.dependancy import Effect
6 | from backend.components.dependancy import Dependancy
7 | from backend.components.dependancy import DependancyConfigError
8 |
9 | from backend.components.element import Element, OutputElement
10 | from backend.components.clock import Clock
11 |
12 |
13 | class TestCondition(unittest.TestCase):
14 |
15 | def setUp(self):
16 | id, op, self.comp_val = 1, "=", 20
17 | self.condition = Condition(id, op, comp_val=self.comp_val)
18 |
19 | def test_notify(self):
20 | # when
21 | self.condition.notify(5)
22 |
23 | # then
24 | self.assertEqual(self.condition.val, 5)
25 |
26 | def test_evaluate_false(self):
27 | # given
28 | notify_val = 5
29 |
30 | # when
31 | self.condition.notify(notify_val)
32 | result = self.condition.evaluate()
33 |
34 | # then
35 | self.assertFalse(eval(result))
36 |
37 | def test_evaluate_true(self):
38 | # when
39 | self.condition.notify(self.comp_val)
40 | result = self.condition.evaluate()
41 |
42 | # then
43 | self.assertTrue(eval(result))
44 |
45 |
46 | class TestEffect(unittest.TestCase):
47 |
48 | def setUp(self):
49 | self.clock = Clock()
50 |
51 | el_id, _type, name, mod_id, reg_id = 0, 1, '', 0, 0
52 | self.output_element = OutputElement(el_id, _type, name, mod_id, reg_id)
53 |
54 | ef_id, value, _time = 0, 1, 1
55 | self.effect = Effect(ef_id, self.output_element, value, _time)
56 |
57 | def test_start(self):
58 | # when cause time is 1000ms
59 | self.effect.start(1000)
60 |
61 | # then effects cause time should be set to 1000ms and out_el value should be saved
62 | self.assertEqual(self.effect.cause_time, 1000)
63 | self.assertEqual(self.effect.prev_value, self.output_element.value)
64 |
65 | def test_run_cause_is_before_effect_time(self):
66 | # given effect time 1ms after cause
67 | self.effect.time = 1
68 |
69 | # when
70 | self.effect.start(self.clock.get_millis())
71 | time.sleep(0.001)
72 |
73 | # then
74 | self.assertTrue(self.effect.run()) # effect should happen
75 | self.assertEqual(self.output_element.desired_value, self.effect.value)
76 |
77 | def test_run_more_times(self):
78 | # given
79 | self.effect.time = 0
80 | # when
81 | self.effect.start(self.clock.get_millis())
82 | time.sleep(0.001)
83 |
84 | # then Effect should happen only once
85 | self.assertTrue(self.effect.run()) # effect should happen
86 | self.assertFalse(self.effect.run()) # effect should not happen
87 | self.assertFalse(self.effect.run()) # effect should not happen
88 |
89 | def test_revert_set(self):
90 |
91 | # when normal effect flow
92 | self.effect.start(self.clock.get_millis())
93 | time.sleep(0.001)
94 | self.effect.run()
95 | self.effect.revert()
96 |
97 | # then output_element desired value should be reverted
98 | self.assertEqual(self.output_element.desired_value, self.effect.prev_value)
99 |
100 |
101 | class TestDependancy(unittest.TestCase):
102 |
103 | def setUp(self):
104 | self.clock = Clock()
105 | dep_id, name = 0, ''
106 | dep_str = '[e1=2] and [e2=3] and [d=mon] and [t=5:30] then e3=20{0}; e3=0{200}; e4=1{0}'
107 |
108 | self.dep = Dependancy(dep_id, name, dep_str)
109 |
110 | def test_find_condition(self):
111 |
112 | find_condition = self.dep._find_condition()
113 | self.assertEqual('e1=2', next(find_condition))
114 | self.assertEqual('e2=3', next(find_condition))
115 | self.assertEqual('d=mon', next(find_condition))
116 | self.assertEqual('t=5:30', next(find_condition))
117 |
118 | self.assertEqual(self.dep.cause_template, '! and ! and ! and !')
119 |
120 | def test_parse_condition(self):
121 | # given
122 | condition_str_day = 'd=mon,tue,wed,thu,fri'
123 |
124 | # when
125 | element, op, comp_value = self.dep._parse_condition(condition_str_day)
126 |
127 | # then
128 | self.assertEqual((element, op, comp_value), ('d', '=', 'mon,tue,wed,thu,fri'))
129 |
130 | def test_parse_cause(self):
131 | # given
132 | element_dict = {1: Element(1, 1, '', 1, 1), 2: Element(2, 1, '', 1, 1)}
133 |
134 | # when
135 | self.dep._parse_cause(all_elements=element_dict)
136 |
137 | # then found all conditions
138 | self.assertEqual(self.dep.num_of_conds, 4)
139 |
140 | # then all conditions are subscribed to elemenets
141 | self.assertIn(self.dep.conditions[0], element_dict[1].objects_to_notify)
142 | self.assertIn(self.dep.conditions[1], element_dict[2].objects_to_notify)
143 | self.assertIn(self.dep.conditions[2], self.clock.objects_to_notify_weekday)
144 | self.assertIn(self.dep.conditions[3], self.clock.objects_to_notify_time)
145 |
146 | def test_parse_effect_good_data(self):
147 | self.assertEqual(self.dep._parse_effect('e3=20{0}'), (3, 20, 0))
148 |
149 | def test_parse_effect_wrong_element_marker(self):
150 | with self.assertRaises(DependancyConfigError):
151 | self.dep._parse_effect('3=20{0}')
152 |
153 | def test_parse_effect_negative_element_time(self):
154 | with self.assertRaises(DependancyConfigError):
155 | self.dep._parse_effect('e3=20{-5}')
156 |
157 | def test_parse_effect_negative_value(self):
158 | with self.assertRaises(DependancyConfigError):
159 | self.dep._parse_effect('e3=-20{0}')
160 |
161 | def test_parse_effect_no_element_id(self):
162 | with self.assertRaises(DependancyConfigError):
163 | self.dep._parse_effect('e=20{0}')
164 |
165 | def test_parse_effect_value_is_char(self):
166 | with self.assertRaises(DependancyConfigError):
167 | self.dep._parse_effect('e=s{0}')
168 |
169 | def test_parse_effect_empty_effect_string(self):
170 | with self.assertRaises(DependancyConfigError):
171 | self.dep._parse_effect('')
172 |
173 | def test_parse_effects_all_efects_found(self):
174 | # given
175 | element_dict = {3: Element(3, 1, '', 1, 1), 4: Element(4, 1, '', 1, 1)}
176 |
177 | # when
178 | self.dep._parse_effects(output_elements=element_dict)
179 |
180 | # then
181 | self.assertEqual(self.dep.num_of_effect, 3)
182 |
--------------------------------------------------------------------------------
/backend/components/tests/test_element.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from backend.components.element import Element, OutputElement, Blind
4 | from backend.components.tests.helper_classes import Notifiable
5 |
6 |
7 | class TestElement(unittest.TestCase):
8 |
9 | def setUp(self):
10 | id, type, name, module_id, reg_id = 0, 1, '', 0, 0
11 | self.element = Element(id, type, name, module_id, reg_id)
12 | self.notifiable = Notifiable()
13 |
14 | def test_subscribe_notifiable(self):
15 | # when
16 | self.element.subscribe(self.notifiable)
17 |
18 | # then
19 | self.assertIn(self.notifiable, self.element.objects_to_notify)
20 |
21 | def test_notify_objects(self):
22 | # given
23 | self.element.value = 10
24 |
25 | # when
26 | self.element.subscribe(self.notifiable)
27 | self.element.notify_objects()
28 |
29 | # then
30 | self.assertEqual(self.element.value, self.notifiable.val)
31 |
32 |
33 | class TestOutputElement(unittest.TestCase):
34 |
35 | def setUp(self):
36 | self.output_element = OutputElement(0, 1, '', 0, 0)
37 | self.value, self.priority, self.set_flag = 10, 5, True
38 |
39 | def test_set_desired_value_when_priority_is_lower_than_default(self):
40 | # when
41 | self.output_element.set_desired_value(self.value, self.priority, self.set_flag)
42 |
43 | # then
44 | self.assertEqual(self.output_element.desired_value, self.value)
45 |
46 | def test_not_set_desired_value_when_priority_is_higher_than_default(self):
47 | # given
48 | self.priority = 20
49 |
50 | # when
51 | self.output_element.set_desired_value(self.value, self.priority, self.set_flag)
52 |
53 | # then
54 | self.assertEqual(self.output_element.desired_value, 0)
55 |
56 | def test_set_priority_when_set_flag_true(self):
57 | # when
58 | self.output_element.set_desired_value(self.value, self.priority, self.set_flag)
59 |
60 | # then
61 | self.assertEqual(self.output_element.setter_priority, self.priority)
62 |
63 | def test_reset_setter_priority_when_set_flag_false(self):
64 | #given
65 | self.set_flag = False
66 |
67 | # when
68 | self.output_element.set_desired_value(self.value, self.priority, self.set_flag)
69 |
70 | # then
71 | self.assertEqual(self.output_element.setter_priority, self.output_element.defualt_priority)
72 |
73 | def test_set_other_blind_desired_value_to_0(self):
74 |
75 | # given
76 | id, type, name, module_id, reg_id, = 0, 1, '', 0, 0
77 | blind_up = Blind(0, type, name, module_id, reg_id, 'up', None)
78 | blind_down = Blind(1, type, name, module_id, reg_id, 'down', blind_up)
79 | blind_up.other_blind = blind_down
80 | blind_up.desired_value = 100
81 | blind_down.desired_value = 100
82 |
83 | # when
84 | blind_up.set_desired_value(self.value, self.priority, self.set_flag)
85 |
86 | # then
87 | self.assertEqual(blind_up.desired_value, self.value)
88 | self.assertEqual(blind_up.other_blind.desired_value, 0)
--------------------------------------------------------------------------------
/backend/components/tests/test_modbus_network.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from backend.components.modbus_network import ModbusNetwork
4 | from backend.components.module import AmbientBoard
5 | from backend.components.module import InputBoard
6 | from backend.components.module import LedLightBoard
7 | from backend.components.module import OutputBoard
8 | from backend.system_loader import objects_loader
9 |
10 |
11 | class TestModbus(unittest.TestCase):
12 |
13 | def setUp(self):
14 | self.modbus = ModbusNetwork()
15 | self.modbus.open()
16 |
17 | def tearDown(self):
18 | self.modbus.close()
19 | del self.modbus
20 |
21 | def test_connection(self):
22 | self.assertTrue(self.modbus.connected)
23 |
24 | def test_baudrate_range(self):
25 | self.assertTrue(9600 <= self.modbus.baudrate <= 1000000)
26 |
27 | def test_t_3_5_range(self):
28 | self.assertTrue(1e-3 <= self.modbus.t_3_5 <= 1e-2)
29 |
30 | def test_reload(self):
31 | self.modbus.reload()
32 |
33 | def test_write_coils(self):
34 | self.assertTrue(self.modbus.write_coils(2, 0, [1 for _ in range(10)]), True)
35 | self.assertTrue(self.modbus.write_coils(2, 0, [0 for _ in range(10)]), True)
36 |
37 | def test_write_registers(self):
38 | self.assertTrue(self.modbus.write_regs(3, 0, [100 for _ in range(3)]), True) # max power
39 | self.assertTrue(self.modbus.write_regs(3, 0, [0 for _ in range(3)]), True) # min power
40 |
41 | def test_read_registers(self):
42 | self.assertTrue(self.modbus.read_regs(1, 0, 15), True) # read all regs
43 |
--------------------------------------------------------------------------------
/backend/components/tests/test_module.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from backend.components.module import Module
4 | from backend.components.module import OutputModule
5 | from backend.components.module import InputModule
6 | from backend.components.module import OutputBoard
7 | from backend.components.module import InputBoard
8 | from backend.components.module import LedLightBoard
9 | from backend.components.module import AmbientBoard
10 |
11 |
12 | class TestModule(unittest.TestCase):
13 |
14 | def setUp(self):
15 | _id, _type, name = (0, 1, '')
16 |
17 | self.module = Module(_id, _type, name)
18 |
19 | def test_is_available_when_available(self):
20 | # when
21 | self.module.available = True
22 |
23 | # then
24 | self.assertTrue(self.module.is_available())
25 |
26 | def test_is_available_when_not_available(self):
27 | # when
28 | self.module.available = False
29 |
30 | # then
31 | self.assertFalse(self.module.is_available())
32 |
33 | def test_is_available_when_timeout_passed(self):
34 | # when
35 | self.module.available = False
36 | self.module.timeout = 0
37 |
38 | # then
39 | self.assertTrue(self.module.is_available())
40 |
41 | def test_command_success(self):
42 | # when
43 | self.module.command_function = self.module.command(lambda _: True)
44 |
45 | self.assertTrue(self.module.command_function(self.module))
46 |
47 | def test_command_failure(self):
48 | # when
49 | self.module.command_function = self.module.command(lambda _: False)
50 |
51 | self.assertFalse(self.module.command_function(self.module))
52 | self.assertFalse(self.module.is_available())
53 |
54 | @unittest.skip("not working")
55 | class TestModules(unittest.TestCase):
56 |
57 | def setUp(self):
58 | self.modbus = None
59 | self.modbus.open()
60 |
61 | def tearDown(self):
62 | self.modbus.close()
63 |
64 | def test_connection(self):
65 | self.assertTrue(self.modbus.connected)
66 |
67 | def test_output_board(self):
68 | board_id, output_board = OutputBoard.items.popitem()
69 | output_board.values = [1 for _ in range(OutputBoard.num_of_ports)]
70 | self.assertTrue(output_board.write(), True)
71 |
72 | output_board.values = [0 for _ in range(OutputBoard.num_of_ports)]
73 | self.assertTrue(output_board.write(), True)
74 |
75 | def test_input_board(self):
76 | board_id, input_board = InputBoard.items.popitem()
77 | self.assertTrue(input_board.read(), True)
78 |
79 | def test_ambient_board(self):
80 | board_id, ambient_board = AmbientBoard.items.popitem()
81 | self.assertTrue(ambient_board.read(), True)
82 |
83 | def test_led_light_board(self):
84 | board_id, led_light_board = LedLightBoard.items.popitem()
85 | self.assertTrue(led_light_board.write(), True)
86 |
87 |
88 |
--------------------------------------------------------------------------------
/backend/config/add_user.py:
--------------------------------------------------------------------------------
1 | from server__client.server.models.user import User
2 | from sys_database.database import create_db_object
3 | import hashlib
4 |
5 |
6 | db = create_db_object()
7 |
8 | print("User name: ")
9 | user_name = input()
10 | user = db.read(User, 'name', user_name) # check user in db
11 | if user: # if user exists
12 | print("User {} already registered".format(user_name))
13 | else:
14 | print("Password: ")
15 | pwd = input()
16 | print("Repeat Password: ")
17 | rpt_pwd = input()
18 | if pwd != rpt_pwd:
19 | print("Passwords does not match")
20 | else:
21 | hash_pwd = hashlib.md5(pwd.encode('utf-8'))
22 | db.save(User, (None, user_name, hash_pwd.hexdigest(), 0)) # 0 means user not logged in
--------------------------------------------------------------------------------
/backend/config/config.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from backend.components.dependancy import Dependancy, DependancyConfigError
4 | from backend.components.element import Element, InputElement, OutputElement, Blind
5 | from backend.components.module import Module, OutputBoard, LedLightBoard, AmbientBoard, InputBoard, AddElementError
6 | from backend.components.regulation import Regulation, RegulationConfigError
7 | from backend.misc.color_logs import color_logs
8 | from backend.misc.sys_types import Mt, Et, Rt, Regt
9 | from backend.sys_database.database import create_db_object
10 | from server_client.server.models.room import Room
11 |
12 |
13 | class SystemCreator():
14 | """Factory class for creating system components"""
15 | def __init__(self):
16 | self.db = create_db_object()
17 | self.db.logger.disabled = True
18 | self.logger = logging.getLogger('CONF')
19 | self.room_id = 0
20 | self.element_id = 0
21 | self.module_id = 1 # address 0 is a broadcast address in modbus. Therfore id starts from 1
22 | self.dependancy_id = 0
23 | self.regulation_id = 0
24 | self.number_of_errors = 0
25 |
26 | def create_tables(self, ):
27 |
28 | self.db.create_tables(Element,
29 | Blind,
30 | Module,
31 | Room,
32 | Dependancy,
33 | Regulation, )
34 |
35 | def delete_tables(self, ):
36 | self.db.delete_tables(Element,
37 | Blind,
38 | Module,
39 | Room,
40 | Dependancy,
41 | Regulation, )
42 |
43 | def add_room(self, type=None, name=''):
44 | room = Room(self.room_id, type, name, [], [])
45 | self.room_id += 1
46 | self.logger.info('Created room: ' + str(room))
47 |
48 | def add_module(self, type=None, name=''):
49 |
50 | if type == Mt.ambient:
51 | module = AmbientBoard(self.module_id, type, name)
52 | elif type == Mt.input:
53 | module = InputBoard(self.module_id, type, name)
54 | elif type == Mt.output:
55 | module = OutputBoard(self.module_id, type, name)
56 | elif type == Mt.led_light:
57 | module = LedLightBoard(self.module_id, type, name)
58 |
59 | self.module_id += 1
60 | self.logger.info('Created module: ' + str(module))
61 |
62 | def add_element(self, type=None, name='', room_id=None, module_id=None, port=None,):
63 |
64 | if name == '':
65 | name = 'element ' + str(Element.ID)
66 |
67 | try:
68 | if type == Et.dht:
69 | el1= InputElement(self.element_id, Et.dht_hum, 'Humidity', module_id, port)
70 | self.element_id += 1
71 | el2 = InputElement(self.element_id, Et.dht_temp, 'Temperature', module_id, port)
72 | self.element_id += 1
73 | Module.items[module_id].add_element(port, el1)
74 | Module.items[module_id].add_element(port, el2)
75 | Room.items[room_id].add_element(el1, el2)
76 |
77 | elif type == Et.blind:
78 |
79 | blind_up = Blind(self.element_id, Et.blind, name, module_id[0], port[0], 'up', None)
80 | self.element_id += 1
81 | blind_down = Blind(self.element_id, Et.blind, name, module_id[1], port[1], 'down', None)
82 | self.element_id += 1
83 |
84 | blind_up.other_blind = blind_down.id
85 | blind_down.other_blind = blind_up.id
86 |
87 | Room.items[room_id].add_element(blind_up, blind_down)
88 | Module.items[module_id[0]].add_element(port[0], blind_up)
89 | Module.items[module_id[1]].add_element(port[1], blind_down)
90 |
91 | elif type in InputElement.types:
92 | el = InputElement(self.element_id, type, name, module_id, port)
93 | self.element_id += 1
94 | Room.items[room_id].add_element(el)
95 | Module.items[module_id].add_element(port, el)
96 |
97 | elif type in OutputElement.types:
98 | el = OutputElement(self.element_id, type, name, module_id, port)
99 | self.element_id += 1
100 | Room.items[room_id].add_element(el)
101 | Module.items[module_id].add_element(port, el)
102 |
103 | except AddElementError as e:
104 | self.logger.error(e.msg)
105 | self.number_of_errors += 1
106 |
107 | else:
108 | self.logger.info("Created element: " + type.name + "\troom: " + str(room_id) + "\tmodule: " + str(module_id))
109 |
110 | def add_dependancy(self, name, dependancy_str):
111 |
112 | if name == '':
113 | name = 'dependancy ' + str(Dependancy.ID)
114 |
115 | try:
116 | Dependancy(self.dependancy_id, name, dependancy_str)
117 | self.dependancy_id += 1
118 | except DependancyConfigError as e:
119 | self.logger.error(e.msg)
120 | self.number_of_errors += 1
121 |
122 | def add_regulation(self, name='', feed_el_id=None, out_el_id=None, set_point=None, dev=None):
123 |
124 | if Element.items[feed_el_id].type == Et.ds or Et.dht_temp:
125 | reg_type = Regt.temp
126 | assert Element.items[out_el_id].type == Et.heater
127 |
128 | elif Element.items[feed_el_id].type == Et.dht_hum:
129 | reg_type = Regt.hum
130 | assert Element.items[out_el_id].type == Et.ventilator
131 |
132 | else:
133 | self.logger.error('REGULATION ERROR Feed element: ' + feed_el_id + " not in defined input elements")
134 | self.number_of_errors += 1
135 |
136 | if name == '':
137 | name = 'regulation ' + str(Regulation.ID)
138 | try:
139 | Regulation(self.regulation_id, reg_type, name, feed_el_id, out_el_id, set_point, dev)
140 |
141 | for room in Room.items.values(): # przypisz regulacje do pokoju w ktorym znajduje sie odpowiadajacy jej el wyjsciowy
142 | for el in room.elements:
143 | if el.id == out_el_id:
144 | room.regulations.append(self.regulation_id)
145 |
146 | self.regulation_id += 1
147 | except RegulationConfigError as e:
148 | self.logger.error(e.msg)
149 | self.number_of_errors += 1
150 |
151 | def save(self,):
152 | self.__save_dependancies()
153 | self.__save_elements()
154 | self.__save_modules()
155 | self.__save_regulations()
156 | self.__save_rooms()
157 |
158 | def __save_modules(self, ):
159 | for module in Module.items.values():
160 | self.db.save(Module, (module.id, module.type.value, module.name, ))
161 |
162 | def __save_rooms(self, ):
163 | for room in Room.items.values():
164 | els = ",".join([str(element.id) for element in room.elements])
165 | regs = ",".join([str(regulation_id) for regulation_id in room.regulations])
166 | self.db.save(Room, (room.id, room.type.value, room.name, els, regs))
167 |
168 | def __save_elements(self, ):
169 | for element in Element.items.values():
170 | self.db.save(Element, (element.id, element.type.value, element.name, element.module_id, element.reg_id))
171 | for blind in Blind.items.values():
172 | self.db.save(Blind, (blind.id, blind.type.value, blind.name, blind.module_id, blind.reg_id, blind.direction, blind.other_blind))
173 |
174 | def __save_dependancies(self, ):
175 | for dep in Dependancy.items.values():
176 | self.db.save(Dependancy, (dep.id, dep.name, dep.dep_str,))
177 |
178 | def __save_regulations(self, ):
179 | for reg in Regulation.items.values():
180 | self.db.save(Regulation, (reg.id, reg.type.value, reg.name, reg.feed_el_id, reg.out_el_id, reg.set_point, reg.dev))
181 |
182 | def print(self, ):
183 | self.logger.warn("\n-----------------------SYSTEM--------------------------------")
184 | for module in Module.items.values():
185 | self.logger.info(str(module))
186 | self.logger.info("\n")
187 |
188 | for room in Room.items.values():
189 | self.logger.info(str(room))
190 |
191 | self.logger.info("\n")
192 |
193 | for element in Element.items.values():
194 | self.logger.info(str(element))
195 |
196 | self.logger.info("\n")
197 |
198 | for dep in Dependancy.items.values():
199 | self.logger.info(str(dep))
200 |
201 | self.logger.info("\n")
202 |
203 | for reg in Regulation.items.values():
204 | self.logger.info(str(reg))
205 |
206 | if self.number_of_errors:
207 | self.logger.error("There was {} errors. Scroll up to find them and fix them".format(self.number_of_errors))
208 |
209 | color_logs()
210 |
211 | system = SystemCreator()
212 | system.logger.setLevel('DEBUG')
213 |
214 | system.create_tables()
215 |
216 | print("\n")
217 |
218 | # Add module giving module type and module name
219 | system.add_module(Mt.input, 'Input') # 1
220 | system.add_module(Mt.output, 'Output') # 2
221 | system.add_module(Mt.led_light, 'Led light') # 3
222 | system.add_module(Mt.ambient, 'Ambient') # 4
223 |
224 | print("\n")
225 |
226 | # Add rooms giving room type and room name. Room name is going to be displayed in client application
227 | system.add_room(type=Rt.wc, name='Water Closet') # 0
228 | system.add_room(type=Rt.sleeping_room, name='Sleeping room') # 1
229 | system.add_room(type=Rt.sleeping_room, name='Sleeping room') # 2
230 | system.add_room(type=Rt.corridor, name='Main corridor') # 3
231 | system.add_room(type=Rt.living_room, name='Living room') # 4
232 | system.add_room(type=Rt.kitchen, name='Kitchen') # 5
233 | system.add_room(type=Rt.outside, name='Outside') # 6
234 |
235 | print("\n")
236 |
237 | # Add system elements giving element type (you can find all types in backend.misc.sys_types),
238 | # name
239 | # id of the room in which the element is placed. You have to count rooms from above starting from 0.
240 | # module id. Same as with room
241 | # port. Physical port to which element is connected
242 | system.add_element(type=Et.dht,
243 | name='Humidity and temperature',
244 | room_id=0,
245 | module_id=4,
246 | port=0) # 0,1
247 |
248 | system.add_element(type=Et.led,
249 | name='Led strip',
250 | room_id=0,
251 | module_id=3,
252 | port=0) # 2
253 |
254 | system.add_element(type=Et.pir,
255 | name='Motion',
256 | room_id=0,
257 | module_id=1,
258 | port=0) # 3
259 |
260 | system.add_element(type=Et.switch,
261 | name='Switch',
262 | room_id=0,
263 | module_id=1,
264 | port=9) # 4
265 |
266 | # id 1
267 | system.add_element(type=Et.ds,
268 | name='Temperature',
269 | room_id=1,
270 | module_id=4,
271 | port=1) # 5
272 | system.add_element(type=Et.heater,
273 | name='Heater',
274 | room_id=1,
275 | module_id=2,
276 | port=0) # 6
277 | system.add_element(type=Et.blind,
278 | name='Blind',
279 | room_id=1,
280 | module_id=[2, 2],
281 | port=[1, 2]) # 7 8
282 | system.add_element(type=Et.pir,
283 | name='Motion',
284 | room_id=1,
285 | module_id=1,
286 | port=1) # 9
287 | system.add_element(type=Et.rs,
288 | name='RS window',
289 | room_id=1,
290 | module_id=1,
291 | port=2) # 10
292 | system.add_element(type=Et.switch,
293 | name='Switch',
294 | room_id=1,
295 | module_id=1,
296 | port=10) # 11
297 |
298 | # id 2
299 | system.add_element(type=Et.ds,
300 | name='Temperature',
301 | room_id=2,
302 | module_id=4,
303 | port=1) # 12
304 | system.add_element(type=Et.heater,
305 | name='Heater',
306 | room_id=2,
307 | module_id=2,
308 | port=3) # 13
309 | system.add_element(type=Et.led,
310 | name='Led strip',
311 | room_id=2,
312 | module_id=3,
313 | port=1) # 14
314 | system.add_element(type=Et.switch,
315 | name='Switch',
316 | room_id=2,
317 | module_id=1,
318 | port=11) # 15
319 | system.add_element(type=Et.pir,
320 | name='Motion',
321 | room_id=2,
322 | module_id=1,
323 | port=14) # 16
324 |
325 | # id 3
326 | system.add_element(type=Et.ds,
327 | name='Temperature',
328 | room_id=3,
329 | module_id=4,
330 | port=1) # 17
331 | system.add_element(type=Et.pir,
332 | name='Motion',
333 | room_id=3,
334 | module_id=1,
335 | port=3) # 18
336 |
337 | # id 4
338 | system.add_element(type=Et.ds,
339 | name='Temperature',
340 | room_id=4,
341 | module_id=4,
342 | port=1) # 19
343 | system.add_element(type=Et.heater,
344 | name='Heater',
345 | room_id=4,
346 | module_id=2,
347 | port=4) # 20
348 | system.add_element(type=Et.led,
349 | name='Led strip',
350 | room_id=4,
351 | module_id=3,
352 | port=2) # 21
353 | system.add_element(type=Et.blind,
354 | name='Blind',
355 | room_id=4,
356 | module_id=[2, 2],
357 | port=[5, 6]) # 22 23
358 | system.add_element(type=Et.switch,
359 | name='Switch',
360 | room_id=4,
361 | module_id=1,
362 | port=12) # 24
363 | system.add_element(type=Et.pir,
364 | name='Motion',
365 | room_id=4,
366 | module_id=1,
367 | port=4) # 25
368 | system.add_element(type=Et.rs,
369 | name='RS window',
370 | room_id=4,
371 | module_id=1,
372 | port=5) # 26
373 |
374 | # id 5
375 | system.add_element(type=Et.ds,
376 | name='Temperature',
377 | room_id=5,
378 | module_id=4,
379 | port=1) # 27
380 | system.add_element(type=Et.heater,
381 | name='Heater',
382 | room_id=5,
383 | module_id=2,
384 | port=7) # 28
385 | system.add_element(type=Et.blind,
386 | name='Blind',
387 | room_id=5,
388 | module_id=[2, 2],
389 | port=[8, 9]) # 29 30
390 | system.add_element(type=Et.switch,
391 | name='Switch',
392 | room_id=5,
393 | module_id=1,
394 | port=13) # 31
395 | system.add_element(type=Et.pir,
396 | name='Motion',
397 | room_id=5,
398 | module_id=1,
399 | port=6) # 32
400 | system.add_element(type=Et.rs,
401 | name='RS window',
402 | room_id=5,
403 | module_id=1,
404 | port=7) # 33
405 |
406 | # id 6
407 | system.add_element(type=Et.ds,
408 | name='Temperature',
409 | room_id=6,
410 | module_id=4,
411 | port=1) # 34
412 | system.add_element(type=Et.ls,
413 | name='Light level',
414 | room_id=6,
415 | module_id=4,
416 | port=2) # 35
417 | system.add_element(type=Et.rs,
418 | name='RS Main doors',
419 | room_id=6,
420 | module_id=1,
421 | port=8) # 36
422 |
423 | system.db.clear_table(Regulation)
424 | system.db.clear_table(Dependancy)
425 |
426 | #system.add_dependancy('wlaczanie swiatla w lazience', '[e3=1] then e2=100; e2=0{100};') #light tunr on for 100s after pir detection
427 | system.add_regulation('Temp set', feed_el_id=5, out_el_id=6, set_point=20, dev=2) # room 1 heating
428 | system.add_regulation('Temp set', feed_el_id=12, out_el_id=13, set_point=20, dev=2) # room 2 heating
429 | system.add_regulation('Temp set', feed_el_id=19, out_el_id=20, set_point=20, dev=2) # room 4 heating
430 | system.add_regulation('Temp set', feed_el_id=27, out_el_id=28, set_point=20, dev=2) #room 5 heating
431 |
432 | system.add_dependancy('Turning off heater in parents sleeping room when window opened', '[e10=0] then e6=0;') #light tunr on for 100s after pir detection
433 | system.add_dependancy('Turning off heater in living room when window opened', '[e26=0] then e20=0;') #light tunr on for 100s after pir detection
434 | system.add_dependancy('Turning off heater in kitchen when window opened', '[e33=0] then e28=0;') #light tunr on for 100s after pir detection
435 | system.add_dependancy('Turning off heater in kitchen when window opened', '[e33=0] then e28=0;') #light tunr on for 100s after pir detection
436 | system.add_dependancy('Demo scenario0 switching led in second sleeping room', '[e15=1] then e14=100;') #light tunr on for 100s after pir detection
437 |
438 | system.add_dependancy('Demo scenario1 two inputs', '([e11=1] and [e15=1]) then e21=100{0}; e21=0{1}; e21=100{2}; e21=0{3}; e21=100{4};')
439 |
440 | system.add_dependancy('Demo scenario2 led wc light up', '[e36=1] then e2=100{0}; e2=0{3}; e2=100{6};')
441 | system.add_dependancy('Demo scenario2 blinds up', '[e36=1] then e7=1{2}; e22=1{2}; e29=1{2};')
442 | system.add_dependancy('Demo scenario2 heater sleeping room 1 blink', '[e36=1] then e6=1{0}; e6=0{2}; e6=1{4}; e6=0{5};')
443 | system.add_dependancy('Demo scenario2 heater sleeping room 2 blink', '[e36=1] then e13=1{1}; e13=0{3}; e13=1{5};')
444 | system.add_dependancy('Demo scenario2 sleeping room 2 blink', '[e36=1] then e14=100{0}; e14=0{2}; e14=100{5};')
445 | system.add_dependancy('Demo scenario2 heater sleeping room 2 blink', '[e36=1] then e20=1{1}; e20=0{3}; e20=1{5};')
446 | system.add_dependancy('Demo scenario2 led living room blink', '[e36=1] then e21=100{0}; e21=50{2}; e21=100{5};')
447 | system.add_dependancy('Demo scenario2 heater sleeping kitchen blink', '[e36=1] then e13=1{1}; e13=0{2}; e13=11.{3};')
448 | system.add_dependancy('Demo scenario2 blinds down', '[e36=1] then e7=0{3}; e22=0{4}; e29=0{5};')
449 | system.add_dependancy('Demo scenario2 led johnys light down', '[e36=1] then e14=100{0}; e14=50{3}; e14=10{6};')
450 | #system.add_dependancy('Demo scenario1 led living room blink', '[e0>50] then e21=100{0}; e21=0{1}; e21=100{2}; e21=0{3}; e21=100{4};')
451 |
452 |
453 | system.save()
454 |
455 | system.print()
--------------------------------------------------------------------------------
/backend/managers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/managers/__init__.py
--------------------------------------------------------------------------------
/backend/managers/communication.py:
--------------------------------------------------------------------------------
1 | import websocket
2 | import threading
3 | import logging
4 | import os
5 | import time
6 | import queue
7 |
8 |
9 | class CommunicationManager(threading.Thread):
10 |
11 | url = "ws://localhost:8888/websocket"
12 |
13 | def __init__(self, group=None, target=None):
14 | threading.Thread.__init__(self, group=group, target=target, name='COM')
15 | self.logger = logging.getLogger('COM')
16 | self.in_buffer = queue.Queue(100)
17 | self.out_buffer = queue.Queue(100)
18 |
19 | self.msg = None # msg from websocet
20 |
21 | self.connected = False
22 | while not self.connected:
23 | try:
24 | self.conn = websocket.create_connection(CommunicationManager.url, header={'secret':'f59c8e3cc40bdc367d81f0c6a84b1766'})
25 | self.connected = True
26 | except:
27 | self.logger.error("Can't connect to server")
28 | time.sleep(1)
29 |
30 | wst = threading.Thread(target=self.listen_ws)
31 | wst.setDaemon(True)
32 | wst.start()
33 |
34 | def run(self, ):
35 | self.logger.info('Thread {} start'. format(self.name))
36 | try:
37 | while True:
38 | time.sleep(0.1)
39 | # self.debug()
40 | while not self.in_buffer.empty():
41 | msg = self.in_buffer.get()
42 | self.conn.send(msg)
43 |
44 | except websocket.WebSocketConnectionClosedException:
45 | self.logger.error("Websocket disconnected")
46 | os._exit(1)
47 |
48 | def listen_ws(self, ):
49 | while True:
50 | self.msg = self.conn.recv()
51 | self.out_buffer.put(self.msg)
52 | self.logger.debug(self.msg)
53 |
--------------------------------------------------------------------------------
/backend/managers/logic.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import queue
3 | import threading
4 | import time
5 |
6 | from backend.components.clock import Clock
7 | from backend.misc.sys_types import Et
8 |
9 |
10 | class LogicManager(threading.Thread):
11 |
12 | clock = Clock()
13 |
14 | def __init__(self, args=(),):
15 | threading.Thread.__init__(self, group=None, target=None, name='LOGIC')
16 | self._comunication_out_buffer = args[0]
17 | self._comunication_in_buffer = args[1]
18 | system = args[2]
19 | self.elements = system.elements.all
20 | self.output_elements = system.elements.output
21 | self.output_modules = system.modules.output
22 | self.regulations = system.regulations
23 | self.dependancies = system.dependancies
24 |
25 | self._client_priority = 5
26 | self.logger = logging.getLogger('LOGIC')
27 | self.tasks = queue.Queue()
28 |
29 | def set_desired_value(self, _type, _id, value, _msg):
30 | """Sets elements desired values"""
31 | if _type == 'e':
32 | set_flag = False
33 | if value > 0:
34 | set_flag = True
35 | self.output_elements[_id].set_desired_value(value, self._client_priority,
36 | set_flag) # Client has low priority.
37 | self.logger.debug('Set desired value el: %s val: %s', _id, value)
38 | elif _type == 'r':
39 | self.regulations[_id].set_point = value
40 | self._comunication_in_buffer.put(_msg) # Ack that regulation was set
41 | # self.logger.debug(self.output_elements.str())
42 |
43 | def process_input_communication(self, ):
44 | """Checks if there are any commands from client. """
45 |
46 | def parse_msg(msg):
47 | try:
48 | msg = msg.split(',')
49 | _type = msg[0][0]
50 | _id = int(msg[0][1:])
51 | value = int(msg[1])
52 | except:
53 | return None
54 | return _type, _id, value
55 |
56 | while not self._comunication_out_buffer.empty():
57 | msg = self._comunication_out_buffer.get()
58 | self.logger.debug(msg)
59 | _type, _id, value = parse_msg(msg)
60 | if not msg:
61 | yield None
62 | yield _type, _id, value, msg
63 |
64 | def _check_elements_values_and_notify(self, ):
65 | """Check elements new value flags which are set by modbus.
66 | If there are new values notify interested components and put message to communication thread"""
67 | for element in self.elements.values():
68 | if element.new_val_flag:
69 | self.logger.debug(element)
70 | element.notify_objects() # Notifies objects which are interested
71 | element.new_val_flag = False
72 | if element.type in (Et.pir, Et.rs, Et.switch, Et.heater, Et.blind):
73 | msg = 'e' + str(element.id) + ',' + str(element.value) + ',' + 's'
74 | else:
75 | msg = 'e' + str(element.id) + ',' + str(element.value)
76 | yield msg
77 |
78 | def _run_relations(self, ):
79 | """Runs dependancies and regulations"""
80 |
81 | for dep in self.dependancies.values():
82 | dep.run()
83 |
84 | for reg in self.regulations.values():
85 | reg.run()
86 |
87 | def _generate_new_tasks(self,):
88 | """Generates queue with modules which have elements with changed value"""
89 | modules_to_notify = set()
90 | for out_element in self.output_elements.values():
91 | if out_element.value != out_element.desired_value:
92 | modules_to_notify.add(self.output_modules[out_element.module_id])
93 |
94 | while modules_to_notify:
95 | self.tasks.put(modules_to_notify.pop())
96 |
97 | def run(self, ):
98 | """Main logic loop"""
99 | self.logger.info('Thread {} start'. format(self.name))
100 | while True:
101 | self.clock.evaluate_time()
102 |
103 | for msg in self.process_input_communication():
104 | self.set_desired_value(*msg)
105 |
106 | for ack_msg in self._check_elements_values_and_notify():
107 | self._comunication_in_buffer.put(ack_msg)
108 |
109 | self._run_relations()
110 | self._generate_new_tasks()
111 | time.sleep(0.1)
112 |
113 |
--------------------------------------------------------------------------------
/backend/managers/modbus.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import threading
3 | import time
4 |
5 | from backend.components.modbus_network import ModbusNetwork
6 |
7 |
8 | class ModbusManager(threading.Thread):
9 |
10 | def __init__(self, args=()):
11 | threading.Thread.__init__(self, group=None, target=None, name='MODBUS')
12 |
13 | self.logger = logging.getLogger('MODBUS_MAN')
14 | self.modbus = ModbusNetwork()
15 | self._open_modbus() # loop while modbus is not opened
16 |
17 | self.tasks = args[0]
18 | self.input_modules = args[1]
19 |
20 | def _open_modbus(self):
21 | while not self.modbus.connected:
22 | if not self.modbus.open():
23 | self.logger.error('Modbus connection error. Retrying connection'.format(self.name))
24 | time.sleep(10)
25 | time.sleep(0.5) # sleep to allow slaves to configure themselfs
26 |
27 | def _write_pending_modules(self, ):
28 |
29 | while not self.tasks.empty():
30 | output_module = self.tasks.get() # get output element form task queue
31 | if output_module.is_available():
32 | result = output_module.write()
33 | if result is False:
34 | output_module.available = False
35 |
36 | def run(self, ):
37 | self.logger.info('Thread {} start'. format(self.name))
38 |
39 | while True:
40 | for input_module in self.input_modules.values(): # loop for every input module to get high response speed
41 | self._write_pending_modules() # after every read of in_mod check if there is anything to write to out_mod
42 |
43 | if input_module.is_available():
44 | input_module.read() # reads values and sets them to elements
45 |
46 | if not self.modbus.is_available():
47 | self.modbus.reload()
48 |
49 | # self.modbus.debug()
50 |
--------------------------------------------------------------------------------
/backend/managers/tests/test_communication.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/managers/tests/test_communication.py
--------------------------------------------------------------------------------
/backend/managers/tests/test_logic.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from backend.managers.logic import LogicManager
4 |
5 | class TestLogicManager(unittest.TestCase):
6 |
7 | def setUp(self):
8 | pass
9 |
10 |
--------------------------------------------------------------------------------
/backend/managers/tests/test_modbus.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/managers/tests/test_modbus.py
--------------------------------------------------------------------------------
/backend/misc/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/misc/__init__.py
--------------------------------------------------------------------------------
/backend/misc/benchmark.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import time
3 | from timeit import default_timer as t
4 |
5 |
6 | class Benchmark():
7 | """Calculates number of loops in a second. Useful for testing communication speed"""
8 | def __init__(self, logging_level):
9 | self.logger = logging.getLogger('BENCHMARK')
10 | self.logger.disabled = False
11 | self.logger.setLevel(logging_level)
12 | self.counter = 0
13 | self.snipet_timer = 0
14 | self.lps_timer = 0 # loops per second timer
15 | self.lt_timer = 0 # loop time timer
16 | self.min_lt = 10000
17 | self.max_lt = -1
18 | self.min_loops_ps = 10000000
19 | self.max_loops_ps = 0
20 |
21 | def start(self,):
22 | t()
23 |
24 | def start_timing_snippet(self, ):
25 | self.snipet_timer = t()
26 |
27 | def get_snippet_time(self, ):
28 | return t()-self.snipet_timer
29 |
30 | def loop_time(self):
31 | lt = t() - self.lt_timer
32 | self.lt_timer = t()
33 | return lt
34 |
35 | def loops_per_second(self,):
36 | self.counter += 1
37 | if t() - self.lps_timer >= 1:
38 | self.lps_timer = t()
39 | lps = self.counter
40 | self.counter = 0
41 | #print('Loops per second: {} '.format(lps))
42 | self.logger.debug('Loops per second: {} '.format(lps))
43 | return True
44 | else:
45 | return False
46 |
47 |
48 | if __name__ == "__main__":
49 | bench = Benchmark()
50 | while True:
51 | a = 0
52 | for i in range(100000):
53 | a+=1
54 | #print (bench.loop_time())
55 | lps = bench.loops_per_second()
56 | if lps:
57 | print (lps)
58 | from time import clock as t
59 | lt_timer = 0
60 | def loop_time():
61 | global lt_timer
62 | lt = t() - lt_timer
63 | lt_timer = t()
64 | return lt
65 |
--------------------------------------------------------------------------------
/backend/misc/check_host.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | if os.name == 'posix':
4 | is_RPI = True
5 | else:
6 | is_RPI = False
--------------------------------------------------------------------------------
/backend/misc/color_logs.py:
--------------------------------------------------------------------------------
1 | import os
2 | import coloredlogs
3 |
4 | def color_logs():
5 | os.environ["COLOREDLOGS_LOG_FORMAT"]='[%(name)s] %(asctime)s - %(message)s'
6 | os.environ["COLOREDLOGS_DATE_FORMAT"]='%H:%M:%S'
7 | os.environ["COLOREDLOGS_LEVEL_STYLES"]='debug=green;warning=yellow;error=red;critical=red,bold'
8 | coloredlogs.install(level='DEBUG')
--------------------------------------------------------------------------------
/backend/misc/sys_types.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 |
3 |
4 | class AutoNumber(Enum):
5 | def __new__(cls):
6 | value = len(cls.__members__) + 1
7 | obj = object.__new__(cls)
8 | obj._value_ = value
9 | return obj
10 |
11 |
12 | class Et(AutoNumber):
13 | ds = ()
14 | dht = ()
15 | dht_temp = ()
16 | dht_hum = ()
17 | led = ()
18 | pir = ()
19 | rs = ()
20 | heater = ()
21 | ls = ()
22 | ventilator = ()
23 | blind = ()
24 | switch = ()
25 |
26 |
27 | class Mt(AutoNumber):
28 | input = ()
29 | output = ()
30 | ambient = ()
31 | led_light = ()
32 |
33 |
34 | class Gt(AutoNumber):
35 | blinds = ()
36 | heating = ()
37 | inputs = ()
38 | ventilation = ()
39 | lights = ()
40 | ambient = ()
41 |
42 |
43 | class Rt(AutoNumber):
44 | inside = ()
45 | outside = ()
46 | kitchen = ()
47 | sleeping_room = ()
48 | wc = ()
49 | corridor = ()
50 | living_room = ()
51 |
52 |
53 | class Ut(AutoNumber):
54 | guest = ()
55 | inhabitant = ()
56 | admin = ()
57 |
58 |
59 | class Regt(AutoNumber):
60 | temp = ()
61 | hum = ()
62 |
63 |
64 | class TaskStat(AutoNumber):
65 | new = ()
66 | ready = ()
67 | done = ()
68 | logged = ()
69 |
70 |
71 | class EffectStatus(AutoNumber):
72 | new = ()
73 | done = ()
74 |
--------------------------------------------------------------------------------
/backend/start.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import time
3 |
4 | from backend.managers.communication import CommunicationManager
5 | from backend.managers.logic import LogicManager
6 | from backend.managers.modbus import ModbusManager
7 | from backend.misc.color_logs import color_logs
8 | from backend.system_loader import system_loader
9 |
10 | #zabezpieczyc bufory przed kolizja watkow. Tzn logika usuwa bufor a w tym samym czasie komunikacja pisze do buforu
11 |
12 | time.sleep(1)
13 |
14 | color_logs()
15 |
16 | system = system_loader()
17 |
18 | communication = CommunicationManager()
19 | logic = LogicManager(args=(communication.out_buffer, communication.in_buffer, system))
20 | modbus_manager = ModbusManager(args=(logic.tasks, system.modules.input))
21 |
22 | communication.logger.disabled = False
23 | communication.logger.setLevel("ERROR")
24 |
25 | modbus_manager.logger.disabled = False
26 | modbus_manager.logger.setLevel("ERROR")#DEBUG
27 |
28 | modbus_manager.modbus.logger.disabled = False
29 | modbus_manager.modbus.logger.setLevel("ERROR")
30 |
31 | logic.logger.disabled = False
32 | logic.logger.setLevel("DEBUG")
33 |
34 | communication.setDaemon(True)
35 | logic.setDaemon(True)
36 | modbus_manager.setDaemon(True)
37 |
38 | communication.start()
39 | logic.start()
40 | modbus_manager.start()
41 |
42 | while threading.active_count() > 0:
43 | time.sleep(2)
44 |
45 |
--------------------------------------------------------------------------------
/backend/sys_database/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/backend/sys_database/database.py:
--------------------------------------------------------------------------------
1 | """Database handling class"""
2 |
3 | import sys
4 | import sqlite3 as sql
5 | import logging
6 | from functools import wraps
7 |
8 | from backend.misc.check_host import is_RPI
9 |
10 |
11 | def create_db_object():
12 |
13 | root = None
14 | for path in sys.path:
15 | if path.endswith("system"):
16 | root = path
17 |
18 | if is_RPI:
19 | db_path = "/".join([root, 'backend', 'sys_database', "sys_database.db"])
20 | else:
21 | db_path = "\\".join([root, 'backend', 'sys_database', "sys_database.db"])
22 |
23 | return Database(db_path)
24 |
25 |
26 | def save_create(func):
27 | """Handles connection commit eventual rollback and closing while saving or creating to database"""
28 | @wraps(func)
29 | def func_wrapper(self, *args):
30 | if self.connect():
31 | try:
32 | self.cur = self.con.cursor()
33 | return func(self, *args)
34 | except sql.Error as e:
35 | self.con.rollback()
36 | self.logger.exception("DATABASE SAVE/CREATE Error %s:", e.args[0])
37 | pass
38 | finally:
39 | self.con.commit()
40 | self.close()
41 | else:
42 | pass
43 | return func_wrapper
44 |
45 |
46 | def read_remove(func):
47 | """Handles connection and closing of database while reading or removing from database"""
48 | @wraps(func)
49 | def func_wrapper(self, *args):
50 | if self.connect():
51 | try:
52 | self.cur = self.con.cursor()
53 | return func(self, *args)
54 | except sql.Error as e:
55 | self.logger.exception("DATABASE READ/REMOVE Error %s:", e.args[0])
56 |
57 | finally:
58 | self.close()
59 | else:
60 | self.logger.warning("Cannot open database")
61 | return 0
62 | return func_wrapper
63 |
64 |
65 | class Database:
66 | """ Handles system database """
67 | def __init__(self, path):
68 | self.path = path
69 | self.logger = logging.getLogger('DB')
70 | self.logger.disabled = False
71 | self.logger.setLevel(logging.ERROR)
72 |
73 | self.con = None
74 | self.cur = None
75 | self.__connected = False
76 |
77 | self.table_headers = {}
78 |
79 | self.sql_INSERT_INTO = "INSERT INTO"
80 | self.sql_VALUES = "VALUES"
81 | self.sql_CREATE_TABLE = "CREATE TABLE"
82 | self.sql_SELECT = "SELECT"
83 | self.sql_FROM = "FROM"
84 | self.sql_WHERE = "WHERE"
85 | self.sql_UPDATE = "UPDATE"
86 | self.sql_SET = "SET"
87 | self.sql_DELETE = "DELETE"
88 |
89 | def connect(self):
90 | """Connects to database. If database does not exist creates one"""
91 | self.con = sql.connect(self.path)
92 | self.__connected = True
93 | self.logger.debug("Database opened")
94 | return True
95 |
96 | def close(self):
97 | """Closes connection to database"""
98 | if self.__connected:
99 | self.con.close()
100 | self.__connected = False
101 | self.logger.debug("Database closed")
102 | else:
103 | pass
104 |
105 | @save_create
106 | def create_tables(self, *Objects):
107 | """For every Object in Objects if Object's table does not exists creates one """
108 |
109 | for Object in Objects:
110 | # Check if the table exists
111 | self.cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='" + Object.table_name +"'")
112 | tables = self.cur.fetchall()
113 | if not tables:
114 | # Prepare sql command
115 | column_headers_and_types = [' '.join(col_header_and_type) for col_header_and_type in Object.column_headers_and_types]
116 | column_headers_and_types_str = self._brackets(','.join(column_headers_and_types))
117 | sql_command = self._put_spaces(self.sql_CREATE_TABLE, Object.table_name, column_headers_and_types_str)
118 |
119 | self.cur.execute(sql_command)
120 | self.logger.info("table: " + Object.table_name + " created")
121 |
122 | @read_remove
123 | def get_table(self, Object):
124 | sql_command = self._put_spaces(self.sql_SELECT, '*', self.sql_FROM, Object.table_name)
125 | self.cur.execute(sql_command)
126 | return self.cur.fetchall()
127 |
128 | @read_remove
129 | def clear_table(self, Object):
130 | sql_command = self._put_spaces(self.sql_DELETE, self.sql_FROM, Object.table_name)
131 | self.cur.execute(sql_command)
132 |
133 | def load_objects_from_table(self, Object):
134 | """Functions allows to retrieve objects from table based on object type"""
135 | table = self.get_table(Object)
136 |
137 | try:
138 | types = Object.types.copy()
139 | num_types = set()
140 |
141 | while types:
142 | num_types.add(types.pop().value) # convert enums to ints
143 |
144 | for data in table:
145 | if data[1] in num_types: # if type of object match with requierd type
146 | data = list(data)
147 | Object(*data) # create Object
148 | except AttributeError:
149 | for data in table: # simple load for objects without type
150 | data = list(data)
151 | Object(*data) # create Object
152 |
153 | @read_remove
154 | def delete_tables(self, *Objects):
155 | """Removes tables from database"""
156 | for Object in Objects:
157 | self.cur.execute("DROP TABLE " + Object.table_name)
158 | self.logger.info("Table: " + Object.table_name + " droped")
159 |
160 | @save_create
161 | def save(self, Object, db_values):
162 | """Saves user to database"""
163 |
164 | # Prepare column headers
165 | column_headers = [ col_head_and_type[0] for col_head_and_type in Object.column_headers_and_types]
166 | column_headers_str = self._brackets (",".join(column_headers) )
167 |
168 | # Przygotowanie question marks for sql query
169 | question_marks = ""
170 | for _ in Object.column_headers_and_types:
171 | question_marks += '?,'
172 | question_marks = self._brackets(question_marks.rstrip(','))
173 |
174 | sql_command = self._put_spaces(self.sql_INSERT_INTO, Object.table_name, column_headers_str, self.sql_VALUES, question_marks)
175 | self.cur.execute(sql_command, db_values)
176 |
177 | self.logger.info(str(Object) + " added to DB")
178 |
179 | @read_remove
180 | def read(self, Object, key_name, key):
181 | """Reads object based on given key from database"""
182 | sql_command = self._put_spaces(self.sql_SELECT, "*", self.sql_FROM, Object.table_name, self.sql_WHERE, key_name, '=', '?')
183 | self.cur.execute(sql_command, (key,))
184 | object_data = self.cur.fetchone()
185 | if not object_data:
186 | return False
187 | return Object(*list(object_data))
188 |
189 | @read_remove
190 | def read_simple(self, table_name, key_name, key):
191 | """Reads object based on given key from database"""
192 | sql_command = self._put_spaces(self.sql_SELECT, "*", self.sql_FROM, table_name, self.sql_WHERE, key_name, '=', '?')
193 | self.cur.execute(sql_command, (key,))
194 | object_data = list(self.cur.fetchone())
195 | if not object_data:
196 | return False
197 | return object_data
198 |
199 | @save_create
200 | # def update_user_log_status(self, user):
201 | def update_field(self, object, field_name, field_value):
202 | sql_command = self._put_spaces(self.sql_UPDATE, object.table_name, self.sql_SET, field_name, '=?', self.sql_WHERE, "id=?")
203 | self.cur.execute(sql_command, (field_value, object.id))
204 |
205 | def _brackets(self, str):
206 | return '(' + str + ')'
207 |
208 | def _put_spaces(self, *args):
209 | str = ""
210 | for arg in args:
211 | str += arg + " "
212 |
213 | return str.rstrip(" ")
--------------------------------------------------------------------------------
/backend/sys_database/sys_database.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/backend/sys_database/sys_database.db
--------------------------------------------------------------------------------
/backend/system_loader.py:
--------------------------------------------------------------------------------
1 | from collections import namedtuple
2 |
3 | from backend.components.clock import Clock
4 | from backend.components.element import OutputElement
5 | from backend.components.element import InputElement
6 | from backend.components.element import Blind
7 | from backend.components.element import Element
8 |
9 | from backend.components.module import Module
10 | from backend.components.module import InputModule
11 | from backend.components.module import OutputModule
12 | from backend.components.module import OutputBoard
13 | from backend.components.module import LedLightBoard
14 | from backend.components.module import AmbientBoard
15 | from backend.components.module import InputBoard
16 |
17 | from backend.components.dependancy import Dependancy
18 | from backend.components.regulation import Regulation
19 |
20 | from backend.sys_database.database import create_db_object
21 |
22 |
23 | def system_loader():
24 | """Loads all necessary objects for system operation from database
25 | Configures system and returns system named tuple"""
26 |
27 | system = namedtuple("System",
28 | ["clock",
29 | "elements",
30 | "modules",
31 | "dependancies",
32 | "regulations"])
33 |
34 | system.elements = namedtuple("elements", ['all', 'input', 'output'])
35 | system.modules = namedtuple("modules", ['all', 'input', 'output'])
36 | system.modules.input = namedtuple("input_modules", ['input_board', 'ambient_board'])
37 | system.modules.output = namedtuple("input_modules", ['output_board', 'led_light_board'])
38 |
39 | system.clock = Clock() # instantiate global clock. Reference is stored in clock.py
40 |
41 | db = create_db_object()
42 | db.load_objects_from_table(InputElement)
43 | db.load_objects_from_table(OutputElement)
44 | db.load_objects_from_table(Blind)
45 |
46 | db.load_objects_from_table(OutputBoard)
47 | db.load_objects_from_table(LedLightBoard)
48 | db.load_objects_from_table(AmbientBoard)
49 | db.load_objects_from_table(InputBoard)
50 |
51 | db.load_objects_from_table(Dependancy)
52 | db.load_objects_from_table(Regulation)
53 |
54 | for element in Element.items.values():
55 | module = Module.items[element.module_id]
56 | module.elements[element.reg_id] = element # pass elements to modules registers
57 |
58 | # pair blinds so two motors of the same blinds never turn on both. It would cause shortcut!
59 | for blind in Blind.items.values():
60 | other_blind_id = blind.other_blind
61 | blind.other_blind = Blind.items[other_blind_id]
62 |
63 | for dep in Dependancy.items.values():
64 | dep._parse_cause(all_elements=Element.items)
65 |
66 | for reg in Regulation.items.values():
67 | InputElement.items[reg.feed_el_id].subscribe(reg)
68 |
69 | system.elements.all = Element.items
70 | system.elements.input = InputElement.items
71 | system.elements.output = OutputElement.items
72 | system.modules.input = InputModule.items
73 | system.modules.output = OutputModule.items
74 | system.dependancies = Dependancy.items
75 | system.regulations = Regulation.items
76 |
77 | return system
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## Intelligent home system
2 |
3 | ### List of content
4 | 1. **Preview**
5 | 2. **Purpose**
6 | 2. **Hardware**
7 | 3. **Software**
8 | 4. **System in action**
9 | 4. **Installation**
10 | 5. **Conclusion**
11 |
12 | ### Preview
13 |
14 | In this manual I will describe intelligent home system which I have to developed. It is based on Raspberry Pi which serves as web server and logic master. All system functions are provided by Atmega slave boards. I will briefly explain how the hardware works. Later on main software concepts will be shown. At the beginning I would like to emphasis that I will be extremely happy if somebody would like to cooperate with me on the project. I can supply you with the hardware needed :)
15 |
16 | | System hardware | House maquette |
17 | | :-------: | :-------: |
18 | |  |  |
19 |
20 |
21 | ### Purpose
22 |
23 | Purpose of project is to build intelligent home system which is reliable, scalable, modifiable and cheap. In my opinion reliability can by only achieved by using wired network between system nodes. Modular topology gives scalability as you can easily extend system by adding extra modules. I did my best to write simple understandable and modular Pythonic code so it would be easy to modify operation of the system. Talking about cost it is widely known that RPi is a cheap computer (about 20$). Same goes with slave boards for which production price oscilates between 30-50$ depending on the board.
24 |
25 | ### Hardware
26 |
27 | Core of the system is widely known [Raspberry Pi](https://www.raspberrypi.org/) computer (_it basically may be any other linux or windows computer_). It is connected with Atmega based boards (which I have developed) by RS485 twisted pair cable. RPI serves as web server and logic master. It is connected with the internet to handle traffic from clients to system and vice versa. It also sends and reads modbus messeges to and from slaves.
28 |
29 | For the present time i have developed 5 different slaves boards. All are based on [Atmega328](https://en.wikipedia.org/wiki/ATmega328) micro controller which can be found in [Arduino](https://www.arduino.cc/). They can be powered from 10V-24V AC/DC. All of the communicate via RS485 physical interface. All boards have top board with TX and RX leds indicating communication and pulsating RGB led indicating board status.
30 |
31 | These are:
32 | * **Output board**
33 | Purpose of output board is to turn on and off electric appliances. It is equipped with 10 relays so it can control 10 devices. In addition, the board has special top with 10 leds each indicating state of corresponding relay.
34 |
35 | | Output board | Output board top |
36 | | :-------: | :-------: |
37 | |  |  |
38 |
39 | * **Input board**
40 | Purpose of input board is to get states of on/off devices such as Reed switches, switches, PIR sensors, smoke sensors. This board is in prototype state and was made as RPI shield
41 | * Led light board
42 | Purpose of led light board is to dim DC appliances. Most often it would be LED stripes. It has got 3 channels controlled by mosfet transistors. Voltage can be regulated from 0 to 100% by the PWM.
43 |
44 | | Led light board |
45 | | :-------: |
46 | |  |
47 |
48 | * **Ambient board**
49 | Purpose of ambient board is to gather information from sensors. Currently the board supports ds18b20 temperature sensors DHT22 humidity and temperature sensors, BMP180 pressure and temperature sensor, and analog sensors such as fotoresistors for light level measure.
50 |
51 | | Ambient board |
52 | | :-------: |
53 | |  |
54 |
55 | * **Glow light board** - (_Not used in project_)
56 | The board is very similar to Led light. Rather than DC it controls AC voltage by firing Triacs. It has got 3 channels so for example 3 glow bulbs can be dimmed.
57 |
58 | | Glow light board |
59 | | :-------: |
60 | |  |
61 |
62 |
63 | ### Software
64 |
65 | Code consists of 3 main modules. These are backend, server and client. This graph explains relations between software modules and system.
66 |
67 | 
68 |
69 | There is also configuration script which populate database with all necessary data
70 |
71 | #### Language
72 | I have chosen **Python** language to implement system logic and server. Client code is implemented in **javascript/html/css**.
73 |
74 | #### Terminology.
75 | I will now explain terminology used in system. It will give you a basic glance at system operation principal.
76 |
77 | System is made from __components__. These are:
78 |
79 | * **Elements** - all input and output devices connected to system. For example, sensors, switches, lights, heaters etc.
80 | * **Modules** - Boards that are connected to RS485 net.
81 | * **Relations** - Relations between elements. I have implemented two types of relations:
82 | * Regulations. They control output element based on feed element value to maintain set value. ```Turning on and off heater to maintain desired temperature```
83 | * Dependencies. They consist of cause and effects. If the cause is evaluated to true, then effects happen in desired time after cause.
84 | ```When light level is less than 20% and soil humidity less than 30% then turn on soil irrigation (Evening irrigation)```
85 | * **Rooms** - House rooms. For now, purpose of room is only visual. They store reference to elements that are in the room. It allows to display system topology in client application.
86 | * **Groups** Also visual purpose. They aggregate elements with similar functions. For example, heating group aggregates temperature sensor, heater and temperature regulation set.
87 |
88 |
89 | #### Client application
90 |
91 | 
92 |
93 | For the present moment client view is very simple. Its purpose is to display all elements organized in groups and rooms. Client layout is generated by server. Js code is responsible for authorization, communication and system values update. In future I would like to improve client side in such way that server will send only representation of system - (elements id's, types and relations between elements). Client would then generate view by itself.
94 |
95 | #### Server application
96 | As a server i have used [Tornado webserver](http://www.tornadoweb.org/en/stable/). It is lightweight fast and easy to use server. It supports websockets which are the way to exchange information between clients and system in the real time. Server's responsibilities are:
97 | * Authorization of users
98 | * Generation of visual system structure (In the future i plan to pass this task to client code)
99 | * Exchanging information between clients and logic.
100 |
101 | #### Backend application
102 | Backend consist of three main threads. These are:
103 | * Communication thread - Holds websocket connection. It listens to server incoming transmission and sends messages to server which are generated by logic thread.
104 | * Logic thread - Takes care of evaluation of relations in system and checks messages from communication process. If any relations evaluate to true or there are commands to be done from clients it creates task list which is passed to modbus thread. It also checks element's "new_value_flag" which are set by modbus thread whenever any element has new value. If so then appropriate message is passed to communication thread buffer.
105 | * Modbus thread - It is responsible for communication with slave boards. It polls all input boards as fast as it can. It is important to achieve low response latency. If there are any tasks to be done appropriate message is sent to output boards.
106 |
107 | #### Configuration script
108 | In the script you can define system. First you should add all the modules connected to network and rooms in your apartment.
109 | Then you can add as many input and output elements as you want, specifying to which module are they connected and in which room are they placed. At the end you define regulations and dependencies between elements.
110 |
111 |
112 | ### System in action!
113 |
114 | Video presents operation of system: https://www.youtube.com/watch?v=Z7VvjViKQB0&feature=youtu.be
115 |
116 | ### Installation
117 |
118 | System can be installed on linux and windows machines. Server and client application can be run without any additional hardware. If you would like to run backend you are going to need Arduino to emulate system board. I will now write down all steps starting with Server. If you have any problems, please let me know.
119 |
120 | **Common steps:**
121 |
122 | 1. First of all you need to install [python 3.5](https://www.python.org/downloads/)
123 | 2. Then install pip for easy packages installation. On Linux just type
124 | ```sudo apt-get install pip3.5```On windows: [tut](http://stackoverflow.com/questions/4750806/how-do-i-install-pip-on-windows)
125 | 3. Install packages used in project:
126 | ```sudo pip3.5 install pyserial websocket-client coloredlogs tornado dominate```
127 | 4. All imports statements in the code are relative to system directory. To make python see this directory as environment directory you should add ```system.pth``` file in site-packages directory. you can find the directory by typing in python shell:
128 | ```import site site.getsitepackages()```
129 |
130 | **Config steps:**
131 |
132 | 1. In your favorite code editor open system/backend/config/config.py. The file contains factory class for creating system. At the bottom you can see example configuration and explanation on how to configure system
133 | 2. Run config and create database located at system/backend/sys_database/sys_database.db . Of course you can use default configuration which is already in database supplied.
134 |
135 | **Server steps:**
136 |
137 | 1. With the console open navigate to system/server_client and type python3 start.py (_insure that you have python3 in your system path_)
138 | 2. Script should run and it is going to show that rooms were loaded
139 | 3. Go to your browser and type: localhost:8888. You should see client view.
140 |
141 | **Backend steps:**
142 |
143 | 1. First of all you are going to need Arduino with modbus slave code. You can find slave modbus library [here](https://github.com/angeloc/simplemodbusng).
144 | 2. Example code for input board with id 1 that will set input states to on or off [here](http://pastebin.com/J806GX8q).
145 | 3. in ```system/backend/modbus/modbus.py``` in ```Modbus``` class change serial port so it matches your Arduino's port.
146 | 3. With the console open navigate to ```system/backend``` and type ```python3 start.py```
147 | 4. Go to the browser and you should see half of inputs on and half of them off.
148 | 5. You can turn on debugging by going to ```system/backend/start.py``` and setting loggers level to ```DEBUG```.
149 |
150 | ### Conclusion
151 | Currently project is in working but still very early stage. I have a lot of ideas how to make the system better. For example:
152 | * It would be awesome to make new gui rendered by the client.
153 | * A proper gui configuration tool would be great to have.
154 | * Graphs for ambient readings.
155 | * Module for configuration slave boards f. example dim time for led light board or types of connected device for input board so it knows how to deal with them rather than simply returning 1 or 0 corresponding to on and off.
156 |
157 | If you would like to cooperate with me on system development, I would be very pleased. I can support with the hardware needed as I have got few additional boards. Please contact me with any questions via email: _janpleszynski@gmail.com or simply on Facebook: _https://www.facebook.com/jan.pleszynski remove trailing _ .
158 |
--------------------------------------------------------------------------------
/server_client/__init__.py:
--------------------------------------------------------------------------------
1 | class __init__(object):
2 | """description of class"""
3 |
4 |
5 |
--------------------------------------------------------------------------------
/server_client/client/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/background.jpg
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/css/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.6 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | .btn-default,
7 | .btn-primary,
8 | .btn-success,
9 | .btn-info,
10 | .btn-warning,
11 | .btn-danger {
12 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
13 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
14 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
15 | }
16 | .btn-default:active,
17 | .btn-primary:active,
18 | .btn-success:active,
19 | .btn-info:active,
20 | .btn-warning:active,
21 | .btn-danger:active,
22 | .btn-default.active,
23 | .btn-primary.active,
24 | .btn-success.active,
25 | .btn-info.active,
26 | .btn-warning.active,
27 | .btn-danger.active {
28 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
29 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
30 | }
31 | .btn-default.disabled,
32 | .btn-primary.disabled,
33 | .btn-success.disabled,
34 | .btn-info.disabled,
35 | .btn-warning.disabled,
36 | .btn-danger.disabled,
37 | .btn-default[disabled],
38 | .btn-primary[disabled],
39 | .btn-success[disabled],
40 | .btn-info[disabled],
41 | .btn-warning[disabled],
42 | .btn-danger[disabled],
43 | fieldset[disabled] .btn-default,
44 | fieldset[disabled] .btn-primary,
45 | fieldset[disabled] .btn-success,
46 | fieldset[disabled] .btn-info,
47 | fieldset[disabled] .btn-warning,
48 | fieldset[disabled] .btn-danger {
49 | -webkit-box-shadow: none;
50 | box-shadow: none;
51 | }
52 | .btn-default .badge,
53 | .btn-primary .badge,
54 | .btn-success .badge,
55 | .btn-info .badge,
56 | .btn-warning .badge,
57 | .btn-danger .badge {
58 | text-shadow: none;
59 | }
60 | .btn:active,
61 | .btn.active {
62 | background-image: none;
63 | }
64 | .btn-default {
65 | text-shadow: 0 1px 0 #fff;
66 | background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
67 | background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
68 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
69 | background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
70 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
71 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
72 | background-repeat: repeat-x;
73 | border-color: #dbdbdb;
74 | border-color: #ccc;
75 | }
76 | .btn-default:hover,
77 | .btn-default:focus {
78 | background-color: #e0e0e0;
79 | background-position: 0 -15px;
80 | }
81 | .btn-default:active,
82 | .btn-default.active {
83 | background-color: #e0e0e0;
84 | border-color: #dbdbdb;
85 | }
86 | .btn-default.disabled,
87 | .btn-default[disabled],
88 | fieldset[disabled] .btn-default,
89 | .btn-default.disabled:hover,
90 | .btn-default[disabled]:hover,
91 | fieldset[disabled] .btn-default:hover,
92 | .btn-default.disabled:focus,
93 | .btn-default[disabled]:focus,
94 | fieldset[disabled] .btn-default:focus,
95 | .btn-default.disabled.focus,
96 | .btn-default[disabled].focus,
97 | fieldset[disabled] .btn-default.focus,
98 | .btn-default.disabled:active,
99 | .btn-default[disabled]:active,
100 | fieldset[disabled] .btn-default:active,
101 | .btn-default.disabled.active,
102 | .btn-default[disabled].active,
103 | fieldset[disabled] .btn-default.active {
104 | background-color: #e0e0e0;
105 | background-image: none;
106 | }
107 | .btn-primary {
108 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
109 | background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
110 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
111 | background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
112 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
113 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
114 | background-repeat: repeat-x;
115 | border-color: #245580;
116 | }
117 | .btn-primary:hover,
118 | .btn-primary:focus {
119 | background-color: #265a88;
120 | background-position: 0 -15px;
121 | }
122 | .btn-primary:active,
123 | .btn-primary.active {
124 | background-color: #265a88;
125 | border-color: #245580;
126 | }
127 | .btn-primary.disabled,
128 | .btn-primary[disabled],
129 | fieldset[disabled] .btn-primary,
130 | .btn-primary.disabled:hover,
131 | .btn-primary[disabled]:hover,
132 | fieldset[disabled] .btn-primary:hover,
133 | .btn-primary.disabled:focus,
134 | .btn-primary[disabled]:focus,
135 | fieldset[disabled] .btn-primary:focus,
136 | .btn-primary.disabled.focus,
137 | .btn-primary[disabled].focus,
138 | fieldset[disabled] .btn-primary.focus,
139 | .btn-primary.disabled:active,
140 | .btn-primary[disabled]:active,
141 | fieldset[disabled] .btn-primary:active,
142 | .btn-primary.disabled.active,
143 | .btn-primary[disabled].active,
144 | fieldset[disabled] .btn-primary.active {
145 | background-color: #265a88;
146 | background-image: none;
147 | }
148 | .btn-success {
149 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
150 | background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
151 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
152 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
153 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
154 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
155 | background-repeat: repeat-x;
156 | border-color: #3e8f3e;
157 | }
158 | .btn-success:hover,
159 | .btn-success:focus {
160 | background-color: #419641;
161 | background-position: 0 -15px;
162 | }
163 | .btn-success:active,
164 | .btn-success.active {
165 | background-color: #419641;
166 | border-color: #3e8f3e;
167 | }
168 | .btn-success.disabled,
169 | .btn-success[disabled],
170 | fieldset[disabled] .btn-success,
171 | .btn-success.disabled:hover,
172 | .btn-success[disabled]:hover,
173 | fieldset[disabled] .btn-success:hover,
174 | .btn-success.disabled:focus,
175 | .btn-success[disabled]:focus,
176 | fieldset[disabled] .btn-success:focus,
177 | .btn-success.disabled.focus,
178 | .btn-success[disabled].focus,
179 | fieldset[disabled] .btn-success.focus,
180 | .btn-success.disabled:active,
181 | .btn-success[disabled]:active,
182 | fieldset[disabled] .btn-success:active,
183 | .btn-success.disabled.active,
184 | .btn-success[disabled].active,
185 | fieldset[disabled] .btn-success.active {
186 | background-color: #419641;
187 | background-image: none;
188 | }
189 | .btn-info {
190 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
191 | background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
192 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
193 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
196 | background-repeat: repeat-x;
197 | border-color: #28a4c9;
198 | }
199 | .btn-info:hover,
200 | .btn-info:focus {
201 | background-color: #2aabd2;
202 | background-position: 0 -15px;
203 | }
204 | .btn-info:active,
205 | .btn-info.active {
206 | background-color: #2aabd2;
207 | border-color: #28a4c9;
208 | }
209 | .btn-info.disabled,
210 | .btn-info[disabled],
211 | fieldset[disabled] .btn-info,
212 | .btn-info.disabled:hover,
213 | .btn-info[disabled]:hover,
214 | fieldset[disabled] .btn-info:hover,
215 | .btn-info.disabled:focus,
216 | .btn-info[disabled]:focus,
217 | fieldset[disabled] .btn-info:focus,
218 | .btn-info.disabled.focus,
219 | .btn-info[disabled].focus,
220 | fieldset[disabled] .btn-info.focus,
221 | .btn-info.disabled:active,
222 | .btn-info[disabled]:active,
223 | fieldset[disabled] .btn-info:active,
224 | .btn-info.disabled.active,
225 | .btn-info[disabled].active,
226 | fieldset[disabled] .btn-info.active {
227 | background-color: #2aabd2;
228 | background-image: none;
229 | }
230 | .btn-warning {
231 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
232 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
233 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
234 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
235 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
236 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
237 | background-repeat: repeat-x;
238 | border-color: #e38d13;
239 | }
240 | .btn-warning:hover,
241 | .btn-warning:focus {
242 | background-color: #eb9316;
243 | background-position: 0 -15px;
244 | }
245 | .btn-warning:active,
246 | .btn-warning.active {
247 | background-color: #eb9316;
248 | border-color: #e38d13;
249 | }
250 | .btn-warning.disabled,
251 | .btn-warning[disabled],
252 | fieldset[disabled] .btn-warning,
253 | .btn-warning.disabled:hover,
254 | .btn-warning[disabled]:hover,
255 | fieldset[disabled] .btn-warning:hover,
256 | .btn-warning.disabled:focus,
257 | .btn-warning[disabled]:focus,
258 | fieldset[disabled] .btn-warning:focus,
259 | .btn-warning.disabled.focus,
260 | .btn-warning[disabled].focus,
261 | fieldset[disabled] .btn-warning.focus,
262 | .btn-warning.disabled:active,
263 | .btn-warning[disabled]:active,
264 | fieldset[disabled] .btn-warning:active,
265 | .btn-warning.disabled.active,
266 | .btn-warning[disabled].active,
267 | fieldset[disabled] .btn-warning.active {
268 | background-color: #eb9316;
269 | background-image: none;
270 | }
271 | .btn-danger {
272 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
273 | background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
274 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
275 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
276 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
277 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
278 | background-repeat: repeat-x;
279 | border-color: #b92c28;
280 | }
281 | .btn-danger:hover,
282 | .btn-danger:focus {
283 | background-color: #c12e2a;
284 | background-position: 0 -15px;
285 | }
286 | .btn-danger:active,
287 | .btn-danger.active {
288 | background-color: #c12e2a;
289 | border-color: #b92c28;
290 | }
291 | .btn-danger.disabled,
292 | .btn-danger[disabled],
293 | fieldset[disabled] .btn-danger,
294 | .btn-danger.disabled:hover,
295 | .btn-danger[disabled]:hover,
296 | fieldset[disabled] .btn-danger:hover,
297 | .btn-danger.disabled:focus,
298 | .btn-danger[disabled]:focus,
299 | fieldset[disabled] .btn-danger:focus,
300 | .btn-danger.disabled.focus,
301 | .btn-danger[disabled].focus,
302 | fieldset[disabled] .btn-danger.focus,
303 | .btn-danger.disabled:active,
304 | .btn-danger[disabled]:active,
305 | fieldset[disabled] .btn-danger:active,
306 | .btn-danger.disabled.active,
307 | .btn-danger[disabled].active,
308 | fieldset[disabled] .btn-danger.active {
309 | background-color: #c12e2a;
310 | background-image: none;
311 | }
312 | .thumbnail,
313 | .img-thumbnail {
314 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
315 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
316 | }
317 | .dropdown-menu > li > a:hover,
318 | .dropdown-menu > li > a:focus {
319 | background-color: #e8e8e8;
320 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
321 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
322 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
323 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
325 | background-repeat: repeat-x;
326 | }
327 | .dropdown-menu > .active > a,
328 | .dropdown-menu > .active > a:hover,
329 | .dropdown-menu > .active > a:focus {
330 | background-color: #2e6da4;
331 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
332 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
333 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
334 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
335 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
336 | background-repeat: repeat-x;
337 | }
338 | .navbar-default {
339 | background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
340 | background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
341 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
342 | background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
343 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
344 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
345 | background-repeat: repeat-x;
346 | border-radius: 4px;
347 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
348 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
349 | }
350 | .navbar-default .navbar-nav > .open > a,
351 | .navbar-default .navbar-nav > .active > a {
352 | background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
353 | background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
354 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
355 | background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
356 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
357 | background-repeat: repeat-x;
358 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
359 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
360 | }
361 | .navbar-brand,
362 | .navbar-nav > li > a {
363 | text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
364 | }
365 | .navbar-inverse {
366 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
367 | background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
368 | background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
369 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
370 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
371 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
372 | background-repeat: repeat-x;
373 | border-radius: 4px;
374 | }
375 | .navbar-inverse .navbar-nav > .open > a,
376 | .navbar-inverse .navbar-nav > .active > a {
377 | background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
378 | background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
379 | background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
380 | background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
381 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
382 | background-repeat: repeat-x;
383 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
384 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
385 | }
386 | .navbar-inverse .navbar-brand,
387 | .navbar-inverse .navbar-nav > li > a {
388 | text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
389 | }
390 | .navbar-static-top,
391 | .navbar-fixed-top,
392 | .navbar-fixed-bottom {
393 | border-radius: 0;
394 | }
395 | @media (max-width: 767px) {
396 | .navbar .navbar-nav .open .dropdown-menu > .active > a,
397 | .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
398 | .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
399 | color: #fff;
400 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
401 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
402 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
403 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
404 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
405 | background-repeat: repeat-x;
406 | }
407 | }
408 | .alert {
409 | text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
410 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
411 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
412 | }
413 | .alert-success {
414 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
415 | background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
416 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
417 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
418 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
419 | background-repeat: repeat-x;
420 | border-color: #b2dba1;
421 | }
422 | .alert-info {
423 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
424 | background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
425 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
426 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
427 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
428 | background-repeat: repeat-x;
429 | border-color: #9acfea;
430 | }
431 | .alert-warning {
432 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
433 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
434 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
435 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
437 | background-repeat: repeat-x;
438 | border-color: #f5e79e;
439 | }
440 | .alert-danger {
441 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
442 | background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
443 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
444 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
445 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
446 | background-repeat: repeat-x;
447 | border-color: #dca7a7;
448 | }
449 | .progress {
450 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
451 | background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
452 | background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
453 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
454 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
455 | background-repeat: repeat-x;
456 | }
457 | .progress-bar {
458 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
459 | background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
460 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
461 | background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
462 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
463 | background-repeat: repeat-x;
464 | }
465 | .progress-bar-success {
466 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
467 | background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
468 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
469 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
470 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
471 | background-repeat: repeat-x;
472 | }
473 | .progress-bar-info {
474 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
475 | background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
476 | background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
477 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
478 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
479 | background-repeat: repeat-x;
480 | }
481 | .progress-bar-warning {
482 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
483 | background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
484 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
485 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
486 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
487 | background-repeat: repeat-x;
488 | }
489 | .progress-bar-danger {
490 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
491 | background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
492 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
493 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
494 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
495 | background-repeat: repeat-x;
496 | }
497 | .progress-bar-striped {
498 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
499 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
500 | background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
501 | }
502 | .list-group {
503 | border-radius: 4px;
504 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
505 | box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
506 | }
507 | .list-group-item.active,
508 | .list-group-item.active:hover,
509 | .list-group-item.active:focus {
510 | text-shadow: 0 -1px 0 #286090;
511 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
512 | background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
513 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
514 | background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
515 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
516 | background-repeat: repeat-x;
517 | border-color: #2b669a;
518 | }
519 | .list-group-item.active .badge,
520 | .list-group-item.active:hover .badge,
521 | .list-group-item.active:focus .badge {
522 | text-shadow: none;
523 | }
524 | .panel {
525 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
526 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
527 | }
528 | .panel-default > .panel-heading {
529 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
530 | background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
531 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
532 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
533 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
534 | background-repeat: repeat-x;
535 | }
536 | .panel-primary > .panel-heading {
537 | background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
538 | background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
539 | background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
540 | background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
541 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
542 | background-repeat: repeat-x;
543 | }
544 | .panel-success > .panel-heading {
545 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
546 | background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
547 | background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
548 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
549 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
550 | background-repeat: repeat-x;
551 | }
552 | .panel-info > .panel-heading {
553 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
554 | background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
555 | background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
556 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
557 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
558 | background-repeat: repeat-x;
559 | }
560 | .panel-warning > .panel-heading {
561 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
562 | background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
563 | background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
564 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
565 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
566 | background-repeat: repeat-x;
567 | }
568 | .panel-danger > .panel-heading {
569 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
570 | background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
571 | background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
572 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
573 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
574 | background-repeat: repeat-x;
575 | }
576 | .well {
577 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
578 | background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
579 | background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
580 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
581 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
582 | background-repeat: repeat-x;
583 | border-color: #dcdcdc;
584 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
585 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
586 | }
587 | /*# sourceMappingURL=bootstrap-theme.css.map */
588 |
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/css/bootstrap-theme.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.6 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/css/bootstrap-theme.min.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"}
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/bootstrap-3.3.6/fonts/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/server_client/client/bootstrap-3.3.6/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/server_client/client/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/favicon.png
--------------------------------------------------------------------------------
/server_client/client/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/server_client/client/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/server_client/client/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/server_client/client/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/server_client/client/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/server_client/client/graphics/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/client/graphics/logo.jpg
--------------------------------------------------------------------------------
/server_client/client/init.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | System
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/server_client/client/init.js:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 |
5 | class System {
6 | constructor() {
7 | this.ws = new WebSocket("ws://" + location.host + "/websocket");
8 | }
9 |
10 | init(){
11 | window.addEventListener("hashchange", function () { scrollBy(0, -60) });
12 | $("input").on('change', $.proxy(this.send_value, this))
13 | $(".btn").on('click', $.proxy(this.send_value, this))
14 |
15 | this.ws.onmessage = this.get_data
16 | }
17 |
18 | get_data(evt){
19 | log(evt.data, 'success');
20 | var data = evt.data.split(',');
21 | var id = data[0];
22 |
23 | if (id[0] == 'r') {
24 | id = 'input' + id;
25 | }
26 |
27 | var value = data[1];
28 | var element = document.getElementById(id);
29 | var action = data[2];
30 |
31 | if (id.startsWith('input')) {
32 | element.value = value;
33 | }
34 |
35 | else if (action == undefined) {
36 | element.innerText = value;
37 | }
38 | else if (action == 's') {
39 | if (value == 1){
40 | $(element).addClass("on");
41 | $(element).removeClass("off");
42 | log('class on');
43 | }
44 | else if (value == 0) {
45 | $(element).removeClass("on");
46 | $(element).addClass("off");
47 | log('class off');
48 | }
49 | }
50 | }
51 |
52 | send_value(event) {
53 | var id = event.currentTarget.id;
54 | var val = document.getElementById(id).value;
55 | if (val == 'click') {
56 | val = 1
57 | }
58 | console.log(id, val);
59 | id = id.slice('input'.length);
60 | this.ws.send(id + ',' + val);
61 | }
62 |
63 | }
64 |
65 | class Auth {
66 | constructor() {
67 | this.user_name = getCookie('name');
68 | self = this;
69 | }
70 |
71 | is_logged_in() {
72 | console.log(this);
73 | if (this.user_name != '') {
74 | return true;
75 | }
76 | else {
77 | return false;
78 | }
79 | return false
80 |
81 | }
82 |
83 | redirect_to_system() {
84 | $(".auth").empty();
85 | $(".navigation-bar").load('ui/navigation_bar').show();
86 | $(".system-content").load('ui/system', function (resp, status, xhr) {
87 | if (status == "success") {
88 | $(document).trigger('system_redirect');
89 | }
90 | });
91 | }
92 |
93 | redirect_to_login() {
94 | document.cookie = "name="; //clear name cookie
95 | var self = this;
96 | $(".navigation-bar").empty(); //hide and empty navigation bar
97 | $(".system-content").empty();
98 | $(".auth").load('auth/login_page.html', $.proxy(function (resp, status, xhr) {
99 | if (status = "success") {
100 | $('#submit-button').on("click", $.proxy(self.submit_login, this));
101 | }
102 | },this));
103 | }
104 |
105 | submit_login() {
106 | var user_name = $("#name").val();
107 | var pwd = md5($("#pwd").val());
108 |
109 | var data = JSON.stringify({
110 | action: 'login',
111 | name: user_name,
112 | password: pwd
113 | });
114 | log("submit login", 'info');
115 |
116 | $.post("auth", data, $.proxy(function (resp, status) {
117 | if (status == "success") {
118 | if (resp == "ok") {
119 | document.cookie = "name=" + user_name;
120 | this.redirect_to_system();
121 | }
122 | }
123 | }, this))
124 | }
125 |
126 | }
127 |
128 | auth = new Auth();
129 | system = new System();
130 |
131 | $(document).ready(function () {
132 | $(document).on('system_redirect', $.proxy(system.init, system))
133 | if (auth.is_logged_in()) {
134 | auth.redirect_to_system();
135 | }
136 | else {
137 |
138 | auth.redirect_to_login();
139 | }
140 | });
--------------------------------------------------------------------------------
/server_client/client/login_page.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/server_client/client/misc.js:
--------------------------------------------------------------------------------
1 | function log(msg, color) {
2 | color = color || "black";
3 | bgc = "White";
4 | switch (color) {
5 | case "success": color = "Green"; bgc = "LimeGreen"; break;
6 | case "info": color = "DodgerBlue"; bgc = "Turquoise"; break;
7 | case "error": color = "Red"; bgc = "Black"; break;
8 | case "start": color = "OliveDrab"; bgc = "PaleGreen"; break;
9 | case "warning": color = "Tomato"; bgc = "Black"; break;
10 | case "end": color = "Orchid"; bgc = "MediumVioletRed"; break;
11 | default: color = color;
12 | }
13 |
14 | if (typeof msg == "object") {
15 | console.log(msg);
16 | } else if (typeof color == "object") {
17 | console.log("%c" + msg, "color: PowderBlue;font-weight:bold; background-color: RoyalBlue;");
18 | console.log(color);
19 | } else {
20 | console.log("%c" + msg, "color:" + color + ";font-weight:bold; background-color: " + bgc + ";");
21 | }
22 | }
23 |
24 | function getCookie(cname) {
25 | var name = cname + "=";
26 | var ca = document.cookie.split(';');
27 | for (var i = 0; i < ca.length; i++) {
28 | var c = ca[i];
29 | while (c.charAt(0) == ' ') {
30 | c = c.substring(1);
31 | }
32 | if (c.indexOf(name) == 0) {
33 | return c.substring(name.length, c.length);
34 | }
35 | }
36 | return "";
37 | }
38 |
--------------------------------------------------------------------------------
/server_client/client/navigation_bar.html:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/server_client/client/ui.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | body {
4 | background-image: url("background.jpg");
5 | }
6 |
7 |
8 | .text-center{
9 | text-align: center;
10 | }
11 |
12 |
13 |
14 | .field{
15 | vertical-align: middle;
16 | display : inline-block;
17 | margin-left: 5px;
18 | margin-right: 5px
19 | }
20 |
21 | .field > .value-text{
22 | width :50px;
23 | border: solid 2px #f5f5f5;
24 | border-radius: 5px;
25 | }
26 |
27 | .field-value{
28 | margin-left: auto;
29 | margin-right: auto;
30 | margin-top: 5px;
31 | margin-bottom:5px;
32 | }
33 |
34 | .field-value-icon{
35 | height:24px;
36 | width:24px;
37 | -webkit-border-radius:12px;
38 | -moz-border-radius:12px;
39 | }
40 |
41 | .on {
42 | background-color:#337ab7;
43 | }
44 |
45 | .off {
46 | background-color: #f5f5f5;
47 | border: 2px solid #337ab7;
48 | }
49 |
50 |
51 | .btn-default:focus,
52 | .btn-default.focus {
53 | color: #333;
54 | background-color: #fff;
55 | border-color: #8c8c8c;
56 | }
57 |
58 | .system-content{
59 | padding-top:60px;
60 | }
61 |
62 | .navbar-brand {
63 | padding: 0px;
64 | }
65 | .navbar-brand>img {
66 | height: 100%;
67 | padding: 7px 15px;
68 | width: auto;
69 | }
70 |
71 |
72 | input[type=range]{
73 | -webkit-appearance: none;
74 | width: 100%;
75 | }
76 |
77 | input[type=range]::-webkit-slider-runnable-track {
78 | width: 300px;
79 | height: 16px;
80 | background: #ddd;
81 | border: none;
82 | border-radius: 3px;
83 | }
84 |
85 | input[type=range]::-webkit-slider-thumb {
86 | -webkit-appearance: none;
87 | border: none;
88 | height: 16px;
89 | width: 16px;
90 | border-radius: 50%;
91 | background: #337ab7;
92 | margin-top: 0px;
93 | }
94 |
95 | input[type=range]:focus {
96 | outline: none;
97 | }
98 |
99 | input[type=range]:focus::-webkit-slider-runnable-track {
100 | background: #ccc;
101 | }
102 |
103 |
104 |
105 |
106 |
107 | .col-xs-auto, .col-xs1-auto, .col-sm-auto, .col-md-auto, .col-lg-auto,
108 | .col-xs-auto-right, .col-xs1-auto-right, .col-sm-auto-right, .col-md-auto-right, .col-lg-auto-right,
109 | .col-middle {
110 | position: relative;
111 | min-height: 1px;
112 | padding-left: 15px;
113 | padding-right: 15px;
114 | }
115 |
116 | .col-middle {
117 | display: table;
118 | }
119 |
120 | .col-xs-auto {
121 | float: left;
122 | }
123 | .col-xs-auto-right {
124 | float: right;
125 | }
126 |
127 | @media (min-width: 480px) {
128 | .col-xs1-auto {
129 | float: left;
130 | }
131 | .col-xs1-auto-right {
132 | float: right;
133 | }
134 | }
135 |
136 | @media (min-width: 768px) {
137 | .col-sm-auto {
138 | float: left;
139 | }
140 | .col-sm-auto-right {
141 | float: right;
142 | }
143 | }
144 |
145 | @media (min-width: 992px) {
146 | .col-md-auto {
147 | float: left;
148 | }
149 | .col-md-auto-right {
150 | float: right;
151 | }
152 | }
153 |
154 | @media (min-width: 1200px) {
155 | .col-lg-auto {
156 | float: left;
157 | }
158 | .col-lg-auto-right {
159 | float: right;
160 | }
161 | }
--------------------------------------------------------------------------------
/server_client/server/__init__.py:
--------------------------------------------------------------------------------
1 | port = 8888
2 |
--------------------------------------------------------------------------------
/server_client/server/handlers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/server/handlers/__init__.py
--------------------------------------------------------------------------------
/server_client/server/handlers/auth_handler.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import json
3 | import tornado.web
4 | from ..models.user import User
5 | from backend.sys_database.database import create_db_object
6 |
7 | paths = (r'auth/login_page.html',)
8 |
9 | actions = ['login', 'logout'] #nie zmieniac kolejnosci. mozna nazwe
10 |
11 | class AuthenticationHandler(tornado.web.RequestHandler):
12 |
13 | def __init__(self, application, request, **kwargs):
14 | self.db = create_db_object()
15 | self.logger = logging.getLogger('AUTH')
16 | self.logger.disabled = False
17 | self.logger.setLevel(logging.INFO)
18 | return super().__init__(application, request, **kwargs)
19 |
20 | def login(self, data):
21 |
22 | db_user = self.db.read(User, 'name', data['name'])
23 |
24 | if db_user:
25 | if db_user.loged_in:
26 | self.write("ok")
27 | self.logger.info("LOGGED IN: " + str(db_user))
28 | return True
29 |
30 | elif data['password'] == db_user.password:
31 | db_user.loged_in = True;
32 | self.db.update_field(db_user, 'logged_in', 1)
33 | self.write("ok")
34 | self.logger.info("LOGGED IN: " + str(db_user))
35 | return True
36 |
37 | else:
38 | self.logger.info("WRONG PASSWORD: " + str(db_user))
39 | self.write("error");
40 | return False
41 | else:
42 | self.logger.info("NOT IN DATABASE: " + str(db_user))
43 | self.write("error")
44 | return False
45 |
46 | def logout(self, data):
47 | self.db = create_db_object()
48 | db_user = self.db.read(User, 'name', data['name'])
49 | if db_user:
50 | if db_user.loged_in == True:
51 | db_user.loged_in = False
52 | self.db.update_field(db_user, 'logged_in', 0)
53 | self.write("ok")
54 | self.logger.info("LOGGED OUT: " + str(db_user))
55 |
56 | else:
57 | self.write("error")
58 | self.logger.warning("USER: " + str(db_user) + " was logged out and attempted to log out once again")
59 |
60 | else:
61 | self.write("error")
62 | self.logger.warning("USER: " + str(db_user) + " not recognized while logging out")
63 |
64 | def get(self):
65 | self.render('login_page.html')
66 |
67 | def validate_in_data(self, in_data):
68 |
69 | #sprawdz czy uzytkownik wyslal poprawna akcje
70 | in_action = in_data['action']
71 | try:
72 | assert (in_action in actions)
73 |
74 | except AssertionError as e:
75 | self.logger.warn(in_action + " is not valid action!")
76 | return False
77 |
78 | return True;
79 |
80 | def post(self):
81 | actions_map = {actions[0] : self.login, actions[1] : self.logout}
82 | in_data = json.loads(self.request.body.decode('UTF-8'))
83 |
84 | if self.validate_in_data(in_data):
85 | action = in_data['action']
86 | actions_map[action](in_data)
87 | else:
88 | self.write("error")
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/server_client/server/handlers/init_handler.py:
--------------------------------------------------------------------------------
1 | import tornado.web
2 |
3 |
4 | class InitHandler(tornado.web.RequestHandler):
5 | def get(self):
6 | self.render("init.html")
--------------------------------------------------------------------------------
/server_client/server/handlers/ui_handler.py:
--------------------------------------------------------------------------------
1 | import tornado.web
2 | from server_client.server.models.room import Room
3 |
4 |
5 | class UiHandler(tornado.web.RequestHandler):
6 | def get(self):
7 | if self.request.path == '/ui/navigation_bar':
8 | self.render('navigation_bar.html', rooms=Room.items.values())
9 |
10 | elif self.request.path == '/ui/system':
11 | for room in Room.items.values():
12 | self.write(room.get_html())
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/server_client/server/handlers/websocket.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from tornado.websocket import WebSocketHandler
3 | from server_client.server.models.visual_element import Visual_element
4 |
5 | socket_logger = logging.getLogger('WS')
6 | socket_logger.disabled = False
7 | socket_logger.setLevel("INFO")
8 |
9 |
10 | class Websocket(WebSocketHandler):
11 |
12 | clients = set()
13 | logic = None
14 |
15 | def open(self, ):
16 | """Opens websocet connection """
17 | if 'Secret' in self.request.headers._dict:
18 | secret = self.request.headers._dict['Secret']
19 | if secret == 'f59c8e3cc40bdc367d81f0c6a84b1766': # if logic password matches
20 | Websocket.logic = self
21 | self.name = 'logic'
22 | socket_logger.info("logic connected")
23 | else:
24 | socket_logger.warn("Attempted unauthorized logic connection")
25 | self.close()
26 | else:
27 | self.name = self.get_cookie('name')
28 | Websocket.clients.add(self)
29 | socket_logger.info("client: {} connected".format(self.name))
30 |
31 | def on_message(self, message):
32 | socket_logger.debug('%s send: %s',self.name, message)
33 |
34 | if self == Websocket.logic:
35 | data = message.split(',')
36 | id = data[0]
37 | value = data[1]
38 | Visual_element.items[id].value = value # Save of actual values in server memory
39 | for con in Websocket.clients: # pass message to all clients
40 | socket_logger.debug('passing to clients')
41 | con.write_message(message)
42 | else:
43 | if Websocket.logic:
44 | socket_logger.debug('sending to logic')
45 | Websocket.logic.write_message(message)# pass message to logic
46 |
47 | def on_close(self):
48 | if self != Websocket.logic:
49 | socket_logger.info("client: {} disconnected".format(self.name))
50 | Websocket.clients.remove(self)
51 | if self == Websocket.logic:
52 | socket_logger.info("logic disconected")
53 | Websocket.logic = None
54 |
55 | def check_origin(self, origin):
56 | return True
--------------------------------------------------------------------------------
/server_client/server/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/server_client/server/models/__init__.py
--------------------------------------------------------------------------------
/server_client/server/models/group.py:
--------------------------------------------------------------------------------
1 | from backend.misc.sys_types import Et, Gt, Regt
2 | from backend.components.base_component import BaseComponent
3 |
4 | from dominate.tags import div, br, b, ul, li, span
5 | class Group(BaseComponent):
6 | """Groups aggregate visual elements"""
7 |
8 | el_to_group_map = {Et.blind : Gt.blinds,
9 | Et.dht_hum : Gt.ventilation, # pierwsza grupa jesli czujnik jesli wewnatrz. druga jesli na zewnatrz
10 | Et.dht_temp: Gt.heating,
11 | Et.ds : Gt.heating,
12 | Et.heater : Gt.heating,
13 | Et.led : Gt.lights,
14 | Et.ls : Gt.ambient,
15 | Et.pir : Gt.inputs,
16 | Et.rs : Gt.inputs,
17 | Et.switch: Gt.inputs,
18 | Et.ventilator : Gt.ventilation,
19 | Regt.hum: Gt.ventilation,
20 | Regt.temp: Gt.heating,}
21 |
22 | sensors = (Et.dht_hum, Et.dht_temp, Et.ds, Et.ls)
23 |
24 |
25 |
26 | def __init__(self, type):
27 | self.type = type
28 | self.elements = []
29 |
30 | def get_html(self, ):
31 |
32 | gr_name = div(b(self.type.name.title()), cls="panel-heading text-center")
33 |
34 | group = div(cls="panel panel-primary")
35 |
36 | group_body = div(cls="panel-body")
37 | for element in self.elements:
38 | group_body.add(element.get_html())
39 |
40 | group.add(gr_name, group_body)
41 | return group
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/server_client/server/models/room.py:
--------------------------------------------------------------------------------
1 | from collections import OrderedDict
2 |
3 | from tornado import template
4 | import tornado.web
5 | from dominate.tags import div, br, h3
6 |
7 | from backend.components.base_component import BaseComponent
8 | from backend.misc.sys_types import Rt
9 |
10 |
11 | class Room(BaseComponent):
12 | """Defines room display in system"""
13 | table_name = 'rooms'
14 | column_headers_and_types = BaseComponent.column_headers_and_types + [['els', 'text'], ['regs', 'text']]
15 |
16 | COL_REGS = 4
17 | COL_ELS = 3
18 | ID = 0
19 | items = {}
20 | groups_per_row = 4
21 |
22 | def __init__(self, *args):
23 | super().__init__(args[0], Rt(args[1]), args[2]) # inicjalizuj id type, name
24 | Room.items[self.id] = self
25 | self.elements = args[Room.COL_ELS]
26 | self.regulations = args[Room.COL_REGS]
27 | self.groups = OrderedDict() # Thanks to ordered dict groups always apear in the same order
28 |
29 | def add_element(self, *elements):
30 | for element in elements:
31 | self.elements.append(element)
32 |
33 | def get_display_data(self,):
34 | """Returns data organized in bootstrap rows and columns. There are Room.groups_per_row columns per row"""
35 | rows = []
36 | row = []
37 | for group_num, group in enumerate(self.groups.values()):
38 | row.append(group)
39 | if group_num == Room.groups_per_row-1:
40 | rows.append(row)
41 | row = []
42 | if row:
43 | rows.append(row) # dla ostatniego niepelnego rzedu
44 |
45 | return rows
46 |
47 | def get_html(self, ):
48 | """Generates room html"""
49 | rows = self.get_display_data()
50 |
51 | room_container = div(cls = "well", id='room' + str(self.id))
52 | room_name = h3(self.name, cls="text-center")
53 | room_container.add(room_name)
54 | for row in rows:
55 | r = div(cls='row')
56 | with r:
57 | for group in row:
58 | div(cls="col-sm-3 group").add(group.get_html())
59 | room_container.add(r)
60 | return room_container.render()
61 |
62 |
63 | def __str__(self, ):
64 | return "".join([super().__str__(), '\tELEMENTS: ', ",".join([str(el.id) for el in self.elements])])
65 |
66 | if __name__ == "__main__":
67 | from common.sys_types import rt, et
68 | from common.elements.output_element import Blind
69 | room = Room(rt.corridor, 'Korytarz')
70 | el0 = Blind(et.blind, 'roleta', 86, 86)
71 | el1 = Blind(et.blind, 'roleta', 86, 86)
72 | room.add_element((el0, el1))
73 | print (str(room))
74 |
--------------------------------------------------------------------------------
/server_client/server/models/system_representation.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from backend.components.regulation import Regulation
4 | from backend.sys_database.database import create_db_object
5 | from server_client.server.models.group import Group
6 | from server_client.server.models.room import Room
7 | from server_client.server.models.visual_element import *
8 |
9 |
10 | def load_system_representation():
11 | db = create_db_object() #database object
12 | db.load_objects_from_table(Room)
13 | logger = logging.getLogger('SYS_REPR_CONF')
14 |
15 | for room in Room.items.values():
16 | if room.elements: # if there are elments in room
17 | elements = room.elements
18 | room.elements = []#[db.read(Visual_element, 'id', int(el_num)) for el_num in room.elements.split(',')]
19 | for element_id in elements.split(','):
20 | element_data = db.read_simple(Visual_element.table_name, 'id', int(element_id))
21 | element_data[0] = 'e'+str(element_data[0]) #dodanie litery r do id zeby bylo wiadomo ze chodzi o wartosc nastawy regulacji
22 | room.elements.append(Visual_element(*element_data[:3]))
23 | else:
24 | room.elements = [] # so the type of room.elements is always list
25 | if room.regulations: # if there are regulations in room
26 | regulations = room.regulations
27 | room.regulations = []
28 | for reg_id in regulations.split(','):
29 | reg_data = db.read_simple(Regulation.table_name, 'id', int(reg_id))
30 | reg_data[0] = 'r'+str(reg_data[0]) #dodanie litery r do id zeby bylo wiadomo ze chodzi o wartosc nastawy regulacji
31 | room.regulations.append(Visual_element(*reg_data[:3]))
32 | else:
33 | room.regulations = []
34 |
35 | for el in room.elements: # Adds elements to groups and groups to rooms
36 | group_type = Group.el_to_group_map[el.type]
37 | group = Group(group_type)
38 | if group_type not in room.groups.keys():
39 | room.groups[group_type] = group
40 | group.elements.append(el)
41 | else:
42 | room.groups[group_type].elements.append(el)
43 |
44 | for reg in room.regulations:
45 | group_type = Group.el_to_group_map[reg.type]
46 | group = Group(group_type)
47 | if group_type not in room.groups.keys():
48 | room.groups[group_type] = group
49 | group.elements.append(reg)
50 | else:
51 | room.groups[group_type].elements.append(reg)
52 | logger.info("Loaded room: {}".format(room.id))
--------------------------------------------------------------------------------
/server_client/server/models/user.py:
--------------------------------------------------------------------------------
1 | class User():
2 | """System user"""
3 | table_name = "users"
4 | column_headers_and_types = [['id', 'integer primary key'],
5 | ['name', 'text'],
6 | ['password', 'text'],
7 | ['logged_in', 'integer'],]
8 |
9 | COL_PASS = 3
10 | COL_LOGGED_IN = 4
11 |
12 | def __init__(self, *args):
13 | self.id, self.user_name, self.password, self.loged_in = args
14 |
15 | def __str__(self,):
16 | return "user name: " + self.user_name + " password: " + self.password
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/server_client/server/models/visual_element.py:
--------------------------------------------------------------------------------
1 | from backend.components.base_component import BaseComponent
2 | from backend.misc.sys_types import Et, Regt
3 | from dominate.tags import div, button, h4, span, b, p, input, label
4 | from functools import wraps
5 |
6 |
7 | def field(func):
8 | @wraps(func)
9 | def func_wrapper(*args, **kwargs):
10 | field = span(cls = "field")
11 | field_name = div(b(args[0].name), cls = "text-center")
12 | field_value = func(args[0])
13 | field.add(field_name, field_value)
14 | return field
15 | return func_wrapper
16 |
17 | class Visual_element(BaseComponent):
18 |
19 | table_name = "elements"
20 |
21 | items = {}
22 | def __init__(self, *args):
23 | Visual_element.items[args[0]] = self
24 | if args[0].startswith('r'): #jesli to regulacja. wtedy id jest stringiem i pierwsza litera to r
25 | super().__init__(args[0], Regt(args[1]), args[2]) # inicjalizuj id type, name
26 | else:
27 | super().__init__(args[0], Et(args[1]), args[2]) # inicjalizuj id type, name
28 | self.value = 0
29 |
30 | def get_html(self,):
31 | if self.type == Et.blind:
32 | return self.blind()
33 | elif self.type in (Et.dht_hum, Et.dht_temp, Et.ds, Et.ls):
34 | return self.value_field()
35 | elif self.type in (Regt.hum, Regt.temp):
36 | return self.input_field()
37 | elif self.type in (Et.heater, Et.pir, Et.rs, Et.switch):
38 | return self.state_field()
39 | elif self.type==Et.led:
40 | return self.slider()
41 | else:
42 | return self.value()
43 |
44 | @field
45 | def value_field(self, ):
46 | return div(str(self.value), cls="field-value value-text text-center", id=str(self.id))
47 |
48 | @field
49 | def input_field(self, ):
50 | return input(value=self.value, min="10", max="40", type="number", cls="field-value value-text", id="input" + str(self.id))
51 |
52 | @field
53 | def state_field(self, ):
54 | st = "off"
55 | if self.value == '1':
56 | st = "on"
57 | elif self.value == '0':
58 | st = "off"
59 | return div(cls="field-value field-value-icon "+st, id=str(self.id))
60 |
61 | @field
62 | def value(self, ):
63 | return span(str(self.value))
64 |
65 | @field
66 | def slider(self,):
67 |
68 | range = input(value=self.value, type="range", min="0", max="100", cls="field-value", id="input" + str(self.id))
69 | lab = label(str(self.value), id=str(self.id), style="margin-left:5px;")
70 | return range, lab
71 |
72 | @field
73 | def blind(self, ):
74 | btn_up = input(value='click', type="button", cls='btn btn-md btn-primary', id="input" + str(self.id))
75 | st = "off"
76 | if self.value == '1':
77 | st = "on"
78 | elif self.value == '0':
79 | st = "off"
80 | btn_state = div(cls="field-value field-value-icon "+st, id=str(self.id))
81 | return btn_up, btn_state
82 |
83 |
84 | if __name__ == "__main__":
85 | btn = get_button(1, name='dupa')
86 | circle = get_state_ind(2)
--------------------------------------------------------------------------------
/server_client/server_client.pyproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ProjectFiles
5 |
6 |
--------------------------------------------------------------------------------
/server_client/start.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from os import path
3 | import tornado
4 | from tornado.log import access_log
5 |
6 | from server.handlers.init_handler import InitHandler
7 | from server.handlers.auth_handler import AuthenticationHandler
8 | from server.handlers.ui_handler import UiHandler
9 | from server.handlers.websocket import Websocket
10 | from server import port
11 | from server.models.system_representation import load_system_representation
12 |
13 | from backend.misc.color_logs import color_logs
14 |
15 | def load_routers():
16 |
17 | routers = [(r"/", InitHandler)]
18 |
19 | routers.append((r"/auth.*", AuthenticationHandler))
20 | routers.append((r"/ui.*", UiHandler))
21 | routers.append((r"/websocket", Websocket))
22 |
23 | return routers
24 |
25 | def load_app(port, root):
26 | settings = {
27 | "socket_io_port": port,
28 | "static_path": path.join(root, "client"),
29 | "template_path": path.join(root, "client"),
30 | "debug" : True,
31 | "xsrf_cookies": False,
32 | }
33 |
34 | routers = load_routers()
35 |
36 | application = tornado.web.Application(
37 | routers,
38 | **settings
39 | )
40 | access_log.info("Starting server")
41 | application.listen(port)
42 | tornado.ioloop.IOLoop.instance().start()
43 |
44 |
45 |
46 | if __name__ == "__main__":
47 |
48 | access_log.disabled = True #Tornado access log
49 |
50 | color_logs()
51 | root = path.dirname(__file__)
52 | load_system_representation()
53 | load_app(port, root)
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/system.VC.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dzon4xx/system/be9485d5b769639307e49db471fed469c8c4ba94/system.VC.db
--------------------------------------------------------------------------------