Skip to content

Commit bb280c9

Browse files
committed
[feat] Add cloud functions to ingest cloud iot data
1 parent 84ba5ae commit bb280c9

File tree

13 files changed

+5124
-21
lines changed

13 files changed

+5124
-21
lines changed

.DS_Store

0 Bytes
Binary file not shown.

.firebaserc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projects": {
3+
"production": "gcloud-iot-edge"
4+
}
5+
}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*.pem
2-
.DS_Store
2+
.DS_Store
3+
node_modules

database.rules.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"rules": {
3+
".read": "auth != null",
4+
".write": "auth != null"
5+
}
6+
}

edge-server/CloudIoTCoreGateway.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,21 @@ class CloudIoTCoreGateway {
5555

5656
// Create a client, and connect to the Google MQTT bridge
5757
this.iatTime = parseInt( Date.now() / 1000 )
58-
this.client = mqtt.connect( connectionArgs )
58+
this.client = mqtt.connect( connectionArgs )
59+
this.client.on( 'connect', ( success ) => {
60+
if ( success ) {
61+
logger.info( 'Client connected.' )
62+
63+
} else {
64+
logger.info( 'Client not connected.' )
65+
}
66+
} )
5967
}
6068

61-
checkConnection() {
69+
checkConnection() {
6270
const secsFromIssue = parseInt( Date.now() / 1000 ) - this.iatTime
6371
if ( secsFromIssue > this.tokenExpMins * 60 ) {
64-
logger.info( `\tRefreshing token after ${secsFromIssue} seconds.` )
72+
logger.info( `Refreshing token after ${secsFromIssue} seconds.` )
6573
this.connect()
6674
}
6775
}

edge-server/EdgeServer.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class EdgeServer {
1717
this.classifier = new ImageClassifier( classifier )
1818
this.gateway = new CloudIoTCoreGateway( gateway )
1919
this.web = new WebInterface( web )
20-
this.limiter = new RateLimiter( 2, 'minute' )
20+
this.limiter = new RateLimiter( 1, 'minute' )
2121

2222
this.deviceQueue = {}
2323
}
@@ -27,16 +27,16 @@ class EdgeServer {
2727
await this.classifier.load()
2828
this.deviceListener.start()
2929
this.web.start()
30-
this.gateway.start()
30+
await this.gateway.start()
3131

32-
this.deviceListener.onDeviceAdded( ( deviceId ) => {
33-
this.gateway.attachDevice( deviceId )
32+
this.deviceListener.onDeviceAdded( async ( deviceId ) => {
33+
await this.gateway.attachDevice( deviceId )
3434
this.gateway.publishDeviceState( deviceId, { status : 'online' } )
3535
} )
3636

37-
this.deviceListener.onDeviceRemoved( ( deviceId ) => {
38-
this.gateway.detachDevice( deviceId )
37+
this.deviceListener.onDeviceRemoved( async ( deviceId ) => {
3938
this.gateway.publishDeviceState( deviceId, { status : 'offline' } )
39+
this.gateway.detachDevice( deviceId )
4040
} )
4141

4242
this.run()
@@ -49,11 +49,11 @@ class EdgeServer {
4949
return Object.keys( this.deviceQueue ).length > 0
5050
}
5151

52-
queueData( device, { classes, trackedClasses, countClasses } ) {
53-
if ( classes.length === 0 ) {
54-
return
52+
queueData( device, { classes, trackedClasses, countClasses } ) {
53+
if ( classes.length === 0 ) {
54+
classes = [ 'empty' ]
55+
countClasses = { empty : 1 }
5556
}
56-
5757
const { name } = device
5858
const deviceData = this.deviceQueue[name]
5959
if ( !deviceData ) {
@@ -134,12 +134,18 @@ class EdgeServer {
134134
logger.info( '[PublishData] Sending data to cloud iot core.' )
135135
const publishPromises = Object.keys( this.deviceQueue ).map( ( deviceId ) => {
136136
const res = this.deviceQueue[deviceId]
137-
return this.gateway.publishDeviceTelemetry( deviceId, res.countClasses )
138-
} )
139-
logger.info( `Promises ${publishPromises}` )
140-
this.clearQueue()
137+
// Check if is just empty
138+
if ( res.countClasses.empty && Object.keys( res.countClasses ).length === 1 ) {
139+
logger.info( '[Empty] Message ' )
140+
return this.gateway.publishDeviceTelemetry( deviceId, { classes : {} } )
141+
}
142+
delete res.countClasses.empty
143+
return this.gateway.publishDeviceTelemetry( deviceId, { classes : res.countClasses } )
144+
} )
141145
await Promise.all( publishPromises )
146+
this.clearQueue()
142147
} else {
148+
143149
logger.info( '[PublishData] Publishing throttled.' )
144150
}
145151
}
@@ -166,14 +172,15 @@ class EdgeServer {
166172

167173
logger.info( 'Sending offline events' )
168174
try {
175+
logger.info( 'Sending gateway offline event' )
176+
await this.gateway.publishGatewayState( { status : 'offline' } )
169177
const publishPromises = devices.map( ( device ) => {
170178
logger.info( `Sending offline event for device ${device.name}` )
171179
return this.gateway.publishDeviceState( device.name, { status : 'offline' } )
172180
} )
173181
await Promise.all( publishPromises )
174-
logger.info( 'Sending gateway offline event' )
175-
await this.gateway.publishGatewayState( { status : 'offline' } )
176182
logger.info( 'All offline events sent' )
183+
await new Promise( resolve => setTimeout( resolve, 1000 ) )
177184
} catch ( err ) {
178185
logger.error( `Error sending data to cloud iot core ${err}`, err )
179186
}

esp32-camera-firmware/.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"sdkconfig.h": "c",
88
"qr_recoginize.h": "c",
99
"ov2640.h": "c",
10-
"sensor.h": "c"
10+
"sensor.h": "c",
11+
"esp_wifi.h": "c"
1112
}
1213
}

firebase.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"database": {
3+
"rules": "database.rules.json"
4+
},
5+
"functions": {
6+
"predeploy": [
7+
"npm --prefix \"$RESOURCE_DIR\" run lint"
8+
]
9+
},
10+
"hosting": {
11+
"public": "public",
12+
"ignore": [
13+
"firebase.json",
14+
"**/.*",
15+
"**/node_modules/**"
16+
],
17+
"rewrites": [
18+
{
19+
"source": "**",
20+
"destination": "/index.html"
21+
}
22+
]
23+
}
24+
}

functions/.eslintrc.json

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{
2+
"parserOptions": {
3+
// Required for certain syntax usages
4+
"ecmaVersion": 6
5+
},
6+
"plugins": [
7+
"promise"
8+
],
9+
"extends": "eslint:recommended",
10+
"rules": {
11+
// Removed rule "disallow the use of console" from recommended eslint rules
12+
"no-console": "off",
13+
14+
// Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules
15+
"no-regex-spaces": "off",
16+
17+
// Removed rule "disallow the use of debugger" from recommended eslint rules
18+
"no-debugger": "off",
19+
20+
// Removed rule "disallow unused variables" from recommended eslint rules
21+
"no-unused-vars": "off",
22+
23+
// Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules
24+
"no-mixed-spaces-and-tabs": "off",
25+
26+
// Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules
27+
"no-undef": "off",
28+
29+
// Warn against template literal placeholder syntax in regular strings
30+
"no-template-curly-in-string": 1,
31+
32+
// Warn if return statements do not either always or never specify values
33+
"consistent-return": 1,
34+
35+
// Warn if no return statements in callbacks of array methods
36+
"array-callback-return": 1,
37+
38+
// Require the use of === and !==
39+
"eqeqeq": 2,
40+
41+
// Disallow the use of alert, confirm, and prompt
42+
"no-alert": 2,
43+
44+
// Disallow the use of arguments.caller or arguments.callee
45+
"no-caller": 2,
46+
47+
// Disallow null comparisons without type-checking operators
48+
"no-eq-null": 2,
49+
50+
// Disallow the use of eval()
51+
"no-eval": 2,
52+
53+
// Warn against extending native types
54+
"no-extend-native": 1,
55+
56+
// Warn against unnecessary calls to .bind()
57+
"no-extra-bind": 1,
58+
59+
// Warn against unnecessary labels
60+
"no-extra-label": 1,
61+
62+
// Disallow leading or trailing decimal points in numeric literals
63+
"no-floating-decimal": 2,
64+
65+
// Warn against shorthand type conversions
66+
"no-implicit-coercion": 1,
67+
68+
// Warn against function declarations and expressions inside loop statements
69+
"no-loop-func": 1,
70+
71+
// Disallow new operators with the Function object
72+
"no-new-func": 2,
73+
74+
// Warn against new operators with the String, Number, and Boolean objects
75+
"no-new-wrappers": 1,
76+
77+
// Disallow throwing literals as exceptions
78+
"no-throw-literal": 2,
79+
80+
// Require using Error objects as Promise rejection reasons
81+
"prefer-promise-reject-errors": 2,
82+
83+
// Enforce “for” loop update clause moving the counter in the right direction
84+
"for-direction": 2,
85+
86+
// Enforce return statements in getters
87+
"getter-return": 2,
88+
89+
// Disallow await inside of loops
90+
"no-await-in-loop": 2,
91+
92+
// Disallow comparing against -0
93+
"no-compare-neg-zero": 2,
94+
95+
// Warn against catch clause parameters from shadowing variables in the outer scope
96+
"no-catch-shadow": 1,
97+
98+
// Disallow identifiers from shadowing restricted names
99+
"no-shadow-restricted-names": 2,
100+
101+
// Enforce return statements in callbacks of array methods
102+
"callback-return": 2,
103+
104+
// Require error handling in callbacks
105+
"handle-callback-err": 2,
106+
107+
// Warn against string concatenation with __dirname and __filename
108+
"no-path-concat": 1,
109+
110+
// Prefer using arrow functions for callbacks
111+
"prefer-arrow-callback": 1,
112+
113+
// Return inside each then() to create readable and reusable Promise chains.
114+
// Forces developers to return console logs and http calls in promises.
115+
"promise/always-return": 2,
116+
117+
//Enforces the use of catch() on un-returned promises
118+
"promise/catch-or-return": 2,
119+
120+
// Warn against nested then() or catch() statements
121+
"promise/no-nesting": 1
122+
}
123+
}

functions/index.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const functions = require('firebase-functions')
2+
const admin = require('firebase-admin')
3+
4+
admin.initializeApp()
5+
6+
const db = admin.database()
7+
8+
exports.processTelemetry = functions
9+
.pubsub
10+
.topic('telemetry')
11+
.onPublish( handleMessage('data') )
12+
13+
exports.processState = functions
14+
.pubsub
15+
.topic('state')
16+
.onPublish( handleMessage('meta') )
17+
18+
function handleMessage( topic ) {
19+
return ( message, context ) => {
20+
const attributes = message.attributes
21+
const payload = message.json
22+
23+
const deviceId = attributes['deviceId']
24+
25+
const data = Object.assign({}, payload, {
26+
updated: context.timestamp
27+
})
28+
29+
return db.ref(`/devices/${deviceId}/${topic}`).update(data)
30+
}
31+
}

0 commit comments

Comments
 (0)