├── .gitignore
├── README.md
├── mobile
├── .gitignore
├── .idea
│ ├── codeStyles
│ │ └── codeStyleConfig.xml
│ ├── misc.xml
│ ├── mobile.iml
│ ├── modules.xml
│ └── vcs.xml
├── .watchmanconfig
├── App.js
├── __tests__
│ └── App-test.js
├── app.json
├── assets
│ ├── fonts
│ │ └── SpaceMono-Regular.ttf
│ └── images
│ │ ├── accessory
│ │ ├── air-conditioner.svg
│ │ └── ceiling-lamp.svg
│ │ ├── avatar.jpeg
│ │ ├── bedroom.jpeg
│ │ ├── icon.png
│ │ ├── index.js
│ │ ├── living-room-unity.jpg
│ │ ├── robot-dev.png
│ │ ├── robot-prod.png
│ │ ├── room
│ │ ├── humidity.svg
│ │ ├── living-room.svg
│ │ └── thermometer.svg
│ │ └── splash.png
├── babel.config.js
├── components
│ ├── Grid.js
│ ├── StyledButton.js
│ ├── StyledText.js
│ ├── SvgUri
│ │ ├── index.js
│ │ └── utils.js
│ ├── TabBarIcon.js
│ └── __tests__
│ │ └── StyledText-test.js
├── constants
│ ├── Colors.js
│ └── Layout.js
├── navigation
│ ├── AppNavigator.js
│ └── MainTabNavigator.js
├── package-lock.json
├── package.json
└── screens
│ ├── HomeScreen
│ ├── Accessory.js
│ ├── HomeHeader.js
│ ├── Room.js
│ └── index.js
│ ├── LinksScreen.js
│ └── SettingsScreen.js
├── package.json
├── screenshots
└── desktop1.png
├── services
└── gateway
│ ├── config
│ ├── config.ts
│ └── express.ts
│ ├── controllers
│ └── accessory.controller.ts
│ ├── index.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── routes
│ ├── accessory.route.ts
│ └── index.route.ts
│ ├── services
│ └── debug.service.ts
│ ├── tsconfig.json
│ ├── tslint.json
│ ├── types
│ └── hap-types.ts
│ └── yarn.lock
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | /dist
3 | /services/data
4 | /services/mqtt
5 |
6 | **/.env
7 | **/.DS_Store
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | .vscode
15 | frontend/.env
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Homify
2 |
3 | Homify is a home automation platform which allows you to control and track all internet devices at home.
4 |
5 | The goal of Homify is providing a platform that supports all kind of things from different brands and protocols, enable smart devices to talk to each other and make corresponding interactions.
6 |
7 |
8 |
9 | ### Installation
10 |
11 | In root folder, execute following command:
12 |
13 | ```
14 | cd frontend && npm install && cd ../services/gateway && npm install
15 | ```
16 |
17 | ### Get Started
18 |
19 | In root folder, execute following command:
20 |
21 | ```
22 | npm run docker
23 | npm run web
24 | npm run gateway
25 | ```
26 |
27 | Todos: moving eveything to docker
28 |
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/**/*
2 | .expo/*
3 | npm-debug.*
4 | *.jks
5 | *.p12
6 | *.key
7 | *.mobileprovision
8 |
--------------------------------------------------------------------------------
/mobile/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/mobile/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/mobile/.idea/mobile.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/mobile/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/mobile/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/mobile/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/mobile/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native';
3 | import { AppLoading, Asset, Font, Icon } from 'expo';
4 | import AppNavigator from './navigation/AppNavigator';
5 |
6 | export default class App extends React.Component {
7 | state = {
8 | isLoadingComplete: false,
9 | };
10 |
11 | render() {
12 | if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
13 | return (
14 |
19 | );
20 | } else {
21 | return (
22 |
23 | {Platform.OS === 'ios' && }
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | _loadResourcesAsync = async () => {
31 | return Promise.all([
32 | Asset.loadAsync([
33 | require('./assets/images/robot-dev.png'),
34 | require('./assets/images/robot-prod.png'),
35 | ]),
36 | Font.loadAsync({
37 | // This is the font that we are using for our tab bar
38 | ...Icon.Ionicons.font,
39 | // We include SpaceMono because we use it in index.jsl free
40 | // to remove this if you are not using it in your app
41 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
42 | }),
43 | ]);
44 | };
45 |
46 | _handleLoadingError = error => {
47 | // In this case, you might want to report the error to your error
48 | // reporting service, for example Sentry
49 | console.warn(error);
50 | };
51 |
52 | _handleFinishLoading = () => {
53 | this.setState({ isLoadingComplete: true });
54 | };
55 | }
56 |
57 | const styles = StyleSheet.create({
58 | container: {
59 | flex: 1,
60 | backgroundColor: '#fff',
61 | },
62 | });
63 |
--------------------------------------------------------------------------------
/mobile/__tests__/App-test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import App from '../App';
4 | import renderer from 'react-test-renderer';
5 | import NavigationTestUtils from 'react-navigation/NavigationTestUtils';
6 |
7 | describe('App snapshot', () => {
8 | jest.useFakeTimers();
9 | beforeEach(() => {
10 | NavigationTestUtils.resetInternalState();
11 | });
12 |
13 | it('renders the loading screen', async () => {
14 | const tree = renderer.create().toJSON();
15 | expect(tree).toMatchSnapshot();
16 | });
17 |
18 | it('renders the root without loading screen', async () => {
19 | const tree = renderer.create().toJSON();
20 | expect(tree).toMatchSnapshot();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/mobile/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "mobile",
4 | "slug": "mobile",
5 | "privacy": "public",
6 | "sdkVersion": "31.0.0",
7 | "platforms": [
8 | "ios",
9 | "android"
10 | ],
11 | "version": "1.0.0",
12 | "orientation": "portrait",
13 | "icon": "./assets/images/icon.png",
14 | "splash": {
15 | "image": "./assets/images/splash.png",
16 | "resizeMode": "contain",
17 | "backgroundColor": "#ffffff"
18 | },
19 | "updates": {
20 | "fallbackToCacheTimeout": 0
21 | },
22 | "assetBundlePatterns": [
23 | "**/*"
24 | ],
25 | "ios": {
26 | "supportsTablet": true
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/mobile/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/mobile/assets/images/accessory/air-conditioner.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
--------------------------------------------------------------------------------
/mobile/assets/images/accessory/ceiling-lamp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mobile/assets/images/avatar.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/avatar.jpeg
--------------------------------------------------------------------------------
/mobile/assets/images/bedroom.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/bedroom.jpeg
--------------------------------------------------------------------------------
/mobile/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/icon.png
--------------------------------------------------------------------------------
/mobile/assets/images/index.js:
--------------------------------------------------------------------------------
1 | const images = {
2 | 'air-conditioner': require("./accessory/air-conditioner.svg"),
3 | 'ceiling-lamp': require("./accessory/ceiling-lamp.svg")
4 | };
5 | export default images;
--------------------------------------------------------------------------------
/mobile/assets/images/living-room-unity.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/living-room-unity.jpg
--------------------------------------------------------------------------------
/mobile/assets/images/robot-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/robot-dev.png
--------------------------------------------------------------------------------
/mobile/assets/images/robot-prod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/robot-prod.png
--------------------------------------------------------------------------------
/mobile/assets/images/room/humidity.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mobile/assets/images/room/living-room.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/mobile/assets/images/room/thermometer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
49 |
--------------------------------------------------------------------------------
/mobile/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/homify-iot/homify/5eca40b849164605c44a9a0328beffe0700e5665/mobile/assets/images/splash.png
--------------------------------------------------------------------------------
/mobile/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/mobile/components/Grid.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | Text,
4 | StyleSheet,
5 | View,
6 | } from "react-native";
7 |
8 | export class Grid extends React.Component {
9 | render() {
10 | return (
11 |
12 |
13 | 24C
14 | avg house temp
15 |
16 |
17 | 69%
18 | humidity
19 |
20 |
21 | 36C
22 | outside temp
23 |
24 |
25 | 8
26 | devices on
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | const styles = StyleSheet.create({
34 | gridContainer: {
35 | flexWrap: 'wrap',
36 | flexDirection: 'row',
37 | margin: 20,
38 | backgroundColor: 'white',
39 | borderRadius: 20,
40 | shadowColor: '#444',
41 | shadowOpacity: 0.2,
42 | shadowRadius: 8,
43 | },
44 | gridItem: {
45 | width: '50%',
46 | borderColor: '#bbb',
47 | alignItems: 'center',
48 | padding: 16
49 | },
50 | itemValue: {
51 | fontSize: 22,
52 | fontWeight: '400',
53 | lineHeight: 36
54 | },
55 | itemTitle: {
56 | color: 'rgba(0,0,0,0.4)'
57 | }
58 | });
59 |
--------------------------------------------------------------------------------
/mobile/components/StyledButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {
3 | TouchableOpacity,
4 | StyleSheet,
5 | } from "react-native";
6 |
7 | export class StyledButton extends React.Component {
8 | render() {
9 | const {
10 | style,
11 | onPress,
12 | disabled,
13 | children
14 | } = this.props;
15 | const childrenWithProps = React.Children.map(children, child =>
16 | React.cloneElement(child, { ...this.props })
17 | );
18 | return (
19 |
23 | {childrenWithProps}
24 |
25 | );
26 | }
27 | }
28 |
29 | const styles = StyleSheet.create({
30 | container: {
31 | margin: 4,
32 | borderRadius: 10,
33 | shadowColor: '#000',
34 | shadowOffset: { width: 0, height: 2 },
35 | shadowOpacity: 0.25,
36 | shadowRadius: 2,
37 | elevation: 1,
38 | padding: 10,
39 | backgroundColor: 'white',
40 | }
41 | });
42 |
--------------------------------------------------------------------------------
/mobile/components/StyledText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Text } from 'react-native';
3 |
4 | export class MonoText extends React.Component {
5 | render() {
6 | return ;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/mobile/components/SvgUri/index.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from "react";
2 | import { View } from 'react-native';
3 | import { Svg } from 'expo';
4 | import PropTypes from 'prop-types'
5 | import xmldom from 'xmldom';
6 | import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
7 |
8 | const {
9 | Circle,
10 | Ellipse,
11 | G ,
12 | LinearGradient,
13 | RadialGradient,
14 | Line,
15 | Path,
16 | Polygon,
17 | Polyline,
18 | Rect,
19 | Text,
20 | TSpan,
21 | Defs,
22 | Stop
23 | } = Svg;
24 |
25 | import * as utils from './utils';
26 |
27 | const ACCEPTED_SVG_ELEMENTS = [
28 | 'svg',
29 | 'g',
30 | 'circle',
31 | 'path',
32 | 'rect',
33 | 'defs',
34 | 'line',
35 | 'linearGradient',
36 | 'radialGradient',
37 | 'stop',
38 | 'ellipse',
39 | 'polygon',
40 | 'polyline',
41 | 'text',
42 | 'tspan'
43 | ];
44 |
45 | // Attributes from SVG elements that are mapped directly.
46 | const SVG_ATTS = ['viewBox', 'width', 'height'];
47 | const G_ATTS = ['id'];
48 |
49 | const CIRCLE_ATTS = ['cx', 'cy', 'r'];
50 | const PATH_ATTS = ['d'];
51 | const RECT_ATTS = ['width', 'height'];
52 | const LINE_ATTS = ['x1', 'y1', 'x2', 'y2'];
53 | const LINEARG_ATTS = LINE_ATTS.concat(['id', 'gradientUnits']);
54 | const RADIALG_ATTS = CIRCLE_ATTS.concat(['id', 'gradientUnits']);
55 | const STOP_ATTS = ['offset'];
56 | const ELLIPSE_ATTS = ['cx', 'cy', 'rx', 'ry'];
57 |
58 | const TEXT_ATTS = ['fontFamily', 'fontSize', 'fontWeight', 'textAnchor']
59 |
60 | const POLYGON_ATTS = ['points'];
61 | const POLYLINE_ATTS = ['points'];
62 |
63 | const COMMON_ATTS = ['fill', 'fillOpacity', 'stroke', 'strokeWidth', 'strokeOpacity', 'opacity',
64 | 'strokeLinecap', 'strokeLinejoin',
65 | 'strokeDasharray', 'strokeDashoffset', 'x', 'y', 'rotate', 'scale', 'origin', 'originX', 'originY', 'transform', 'clipPath'];
66 |
67 | let ind = 0;
68 |
69 | function fixYPosition (y, node) {
70 | if (node.attributes) {
71 | const fontSizeAttr = Object.keys(node.attributes).find(a => node.attributes[a].name === 'font-size');
72 | if (fontSizeAttr) {
73 | return '' + (parseFloat(y) - parseFloat(node.attributes[fontSizeAttr].value));
74 | }
75 | }
76 | if (!node.parentNode) {
77 | return y;
78 | }
79 | return fixYPosition(y, node.parentNode)
80 | }
81 |
82 | class SvgUri extends Component{
83 |
84 | constructor(props){
85 | super(props);
86 |
87 | this.state = {fill: props.fill, svgXmlData: props.svgXmlData};
88 |
89 | this.createSVGElement = this.createSVGElement.bind(this);
90 | this.obtainComponentAtts = this.obtainComponentAtts.bind(this);
91 | this.inspectNode = this.inspectNode.bind(this);
92 | this.fetchSVGData = this.fetchSVGData.bind(this);
93 |
94 | this.isComponentMounted = false;
95 |
96 | // Gets the image data from an URL or a static file
97 | if (props.source) {
98 | const source = resolveAssetSource(props.source) || {};
99 | this.fetchSVGData(source.uri);
100 | }
101 | }
102 |
103 | componentWillMount() {
104 | this.isComponentMounted = true;
105 | }
106 |
107 | componentWillReceiveProps (nextProps){
108 | if (nextProps.source) {
109 | const source = resolveAssetSource(nextProps.source) || {};
110 | const oldSource = resolveAssetSource(this.props.source) || {};
111 | if(source.uri !== oldSource.uri){
112 | this.fetchSVGData(source.uri);
113 | }
114 | }
115 |
116 | if (nextProps.svgXmlData !== this.props.svgXmlData) {
117 | this.setState({ svgXmlData: nextProps.svgXmlData });
118 | }
119 |
120 | if (nextProps.fill !== this.props.fill) {
121 | this.setState({ fill: nextProps.fill });
122 | }
123 | }
124 |
125 | componentWillUnmount() {
126 | this.isComponentMounted = false
127 | }
128 |
129 | async fetchSVGData(uri) {
130 | let responseXML = null, error = null;
131 | try {
132 | const response = await fetch(uri);
133 | responseXML = await response.text();
134 | } catch(e) {
135 | error = e;
136 | console.error("ERROR SVG", e);
137 | } finally {
138 | if (this.isComponentMounted) {
139 | this.setState({ svgXmlData: responseXML }, () => {
140 | const { onLoad } = this.props;
141 | if (onLoad && !error) {
142 | onLoad();
143 | }
144 | });
145 | }
146 | }
147 |
148 | return responseXML;
149 | }
150 |
151 | // Remove empty strings from children array
152 | trimElementChilden(children) {
153 | for (child of children) {
154 | if (typeof child === 'string') {
155 | if (child.trim().length === 0)
156 | children.splice(children.indexOf(child), 1);
157 | }
158 | }
159 | }
160 |
161 | createSVGElement(node, childs){
162 | this.trimElementChilden(childs);
163 | let componentAtts = {};
164 | const i = ind++;
165 | switch (node.nodeName) {
166 | case 'svg':
167 | componentAtts = this.obtainComponentAtts(node, SVG_ATTS);
168 | if (this.props.width) {
169 | componentAtts.width = this.props.width;
170 | }
171 | if (this.props.height) {
172 | componentAtts.height = this.props.height;
173 | }
174 |
175 | if (this.props.fill) {
176 | componentAtts.fill = this.props.fill;
177 | }
178 |
179 | return ;
180 | case 'g':
181 | componentAtts = this.obtainComponentAtts(node, G_ATTS);
182 | return {childs};
183 | case 'path':
184 | componentAtts = this.obtainComponentAtts(node, PATH_ATTS);
185 | return {childs};
186 | case 'circle':
187 | componentAtts = this.obtainComponentAtts(node, CIRCLE_ATTS);
188 | return {childs};
189 | case 'rect':
190 | componentAtts = this.obtainComponentAtts(node, RECT_ATTS);
191 | return {childs};
192 | case 'line':
193 | componentAtts = this.obtainComponentAtts(node, LINE_ATTS);
194 | return {childs};
195 | case 'defs':
196 | return {childs};
197 | case 'linearGradient':
198 | componentAtts = this.obtainComponentAtts(node, LINEARG_ATTS);
199 | return {childs};
200 | case 'radialGradient':
201 | componentAtts = this.obtainComponentAtts(node, RADIALG_ATTS);
202 | return {childs};
203 | case 'stop':
204 | componentAtts = this.obtainComponentAtts(node, STOP_ATTS);
205 | return {childs};
206 | case 'ellipse':
207 | componentAtts = this.obtainComponentAtts(node, ELLIPSE_ATTS);
208 | return {childs};
209 | case 'polygon':
210 | componentAtts = this.obtainComponentAtts(node, POLYGON_ATTS);
211 | return {childs};
212 | case 'polyline':
213 | componentAtts = this.obtainComponentAtts(node, POLYLINE_ATTS);
214 | return {childs};
215 | case 'text':
216 | componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
217 | return {childs};
218 | case 'tspan':
219 | componentAtts = this.obtainComponentAtts(node, TEXT_ATTS);
220 | if (componentAtts.y) {
221 | componentAtts.y = fixYPosition(componentAtts.y, node)
222 | }
223 | return {childs};
224 | default:
225 | return null;
226 | }
227 | }
228 |
229 | obtainComponentAtts({attributes}, enabledAttributes) {
230 | const styleAtts = {};
231 |
232 | if (this.state.fill && this.props.fillAll) {
233 | styleAtts.fill = this.state.fill;
234 | }
235 |
236 | Array.from(attributes).forEach(({nodeName, nodeValue}) => {
237 | Object.assign(styleAtts, utils.transformStyle({
238 | nodeName,
239 | nodeValue,
240 | fillProp: this.state.fill
241 | }));
242 | });
243 |
244 | const componentAtts = Array.from(attributes)
245 | .map(utils.camelCaseNodeName)
246 | .map(utils.removePixelsFromNodeValue)
247 | .filter(utils.getEnabledAttributes(enabledAttributes.concat(COMMON_ATTS)))
248 | .reduce((acc, {nodeName, nodeValue}) => {
249 | acc[nodeName] = (this.state.fill && nodeName === 'fill' && nodeValue !== 'none') ? this.state.fill : nodeValue
250 | return acc
251 | }, {});
252 | Object.assign(componentAtts, styleAtts);
253 |
254 | return componentAtts;
255 | }
256 |
257 | inspectNode(node){
258 | // Only process accepted elements
259 | if (!ACCEPTED_SVG_ELEMENTS.includes(node.nodeName)) {
260 | return ();
261 | }
262 |
263 | // Process the xml node
264 | const arrayElements = [];
265 |
266 | // if have children process them.
267 | // Recursive function.
268 | if (node.childNodes && node.childNodes.length > 0){
269 | for (let i = 0; i < node.childNodes.length; i++){
270 | const isTextValue = node.childNodes[i].nodeValue
271 | if (isTextValue) {
272 | arrayElements.push(node.childNodes[i].nodeValue)
273 | } else {
274 | const nodo = this.inspectNode(node.childNodes[i]);
275 | if (nodo != null) {
276 | arrayElements.push(nodo);
277 | }
278 | }
279 | }
280 | }
281 |
282 | return this.createSVGElement(node, arrayElements);
283 | }
284 |
285 | render () {
286 | try {
287 | if (this.state.svgXmlData == null) {
288 | return null;
289 | }
290 |
291 | const inputSVG = this.state.svgXmlData.substring(
292 | this.state.svgXmlData.indexOf("