├── .DS_Store ├── .gitignore ├── Blockchain ├── .DS_Store ├── IoT-Perishable-Network │ ├── README.md │ ├── lib │ │ └── logic.js │ ├── logic.js │ ├── models │ │ └── perishable.cto │ ├── package.json │ ├── perishable.cto │ └── permissions.acl ├── README.md ├── iot-asset-tracker-network.bna └── screenshots │ ├── .DS_Store │ ├── API.png │ ├── Authorize.png │ ├── AuthorizeCloud.png │ ├── Breadcrumbs.png │ ├── ConnectNow.png │ ├── Create.png │ ├── DeliveryPipeline.png │ ├── DeployMarbles.png │ ├── DeploymentProgress.png │ ├── DevOps.png │ ├── Guided.png │ ├── MarblesToolchain.png │ ├── MarblesUI.png │ ├── Passed.png │ ├── Perishable-Network-BNA-ConnectNow.png │ ├── Perishable-Network-BNA-Model-update-annotated.png │ ├── Perishable-Network-BNA-annotated.png │ ├── Perishable-Network-BNA-creds-annotated.png │ ├── Perishable-Network-REST-API-swagger.png │ ├── RepoName.png │ ├── SeeApp.png │ ├── SelectGitHub.png │ ├── SetupDemo.png │ ├── SubmitTransaction.png │ ├── Success.png │ ├── Test.png │ ├── ToolChainName.png │ ├── TrySamples.png │ ├── Update.png │ ├── ViewLogs.png │ ├── VisitAppURL.png │ ├── clonegithub.png │ ├── completetoolchain.png │ ├── composer-rest-server.png │ ├── createkey.png │ ├── deploynew.png │ ├── deploypassed.png │ ├── developcode.png │ ├── example.png │ ├── export.png │ ├── gitcommit.png │ ├── gotogithub.png │ ├── launchnow.png │ ├── menubar.png │ ├── movecontents.png │ ├── npmsample.png │ ├── repositorycreate.png │ ├── restserverdetails.png │ ├── savebna.png │ ├── starterkittoolchain.png │ ├── stopanddeleteapp.png │ ├── toolchainlog.png │ ├── webplayground.png │ └── yo.png ├── LICENSE ├── Node-RED ├── README.md ├── SIMULATOR.md ├── flows │ ├── IoTAssetTracker-AllFlows.json │ └── IoTAssetTracker-SimulatedRoute.json └── screenshots │ ├── IBMCloud-Catalog-newstarter-annotated.png │ ├── IBMCloud-NodeRED-CFappcreate.png │ ├── IBMCloud-NodeRED-import.png │ ├── IBMCloud-NodeRED-launch.png │ ├── IBMCloud-NodeRED-nodeinstall.png │ ├── IBMCloud-NodeRED-palette.png │ ├── IBMCloud-NodeRED-pastefromclipboard.png │ ├── Node-RED-dashboard-AssetTracker-NJ.png │ ├── Node-RED-dashboard-AssetTracker-NYC.png │ ├── Node-RED-dashboard-AssetTracker-PR.png │ ├── Node-RED-dashboard-ControlParticleDevice.png │ ├── Node-RED-flow-AssetTrackerDashboardControls-fixup.png │ ├── Node-RED-flow-AssetTrackerDashboardControls.png │ ├── Node-RED-flow-AssetTrackerMap.png │ ├── Node-RED-flow-ControlParticleDevice.png │ ├── Node-RED-flow-InitPerishableBlockchain.png │ ├── Node-RED-flow-LoadBlockchainTransactionHistory.png │ ├── Node-RED-flow-ReceiveParticleEvents.png │ ├── Node-RED-flow-SimulateRoute.png │ ├── Node-RED-flow-WriteParticleEvents2Blockchain.png │ └── onthegomap-route.png ├── ParticleElectron ├── README.md ├── WatsonIoTAssetTracker.ino ├── project.properties └── screenshots │ ├── ParticleConsoleDeviceEvents.png │ ├── ParticleElectronAssetTracker-IoT.jpg │ ├── ParticleElectronAssetTracker-Kit.jpg │ └── ParticleElectronAssetTracker-in-Case.jpg ├── README.md └── Workshop └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Blockchain/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/.DS_Store -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/README.md: -------------------------------------------------------------------------------- 1 | # IoT Asset Tracking Perishable Goods Network 2 | 3 | > Example business network that shows growers, shippers and importers defining contracts for the price of perishable goods, based on temperature readings from IoT sensors in the shipping containers. 4 | 5 | The business network defines a contract between growers and importers. The contract stipulates that: On receipt of the shipment the importer pays the grower the unit price x the number of units in the shipment. Shipments that arrive late are free. Shipments that have breached the low temperate threshold have a penalty applied proportional to the magnitude of the breach x a penalty factor. Shipments that have breached the high temperate threshold have a penalty applied proportional to the magnitude of the breach x a penalty factor. 6 | 7 | This business network defines: 8 | 9 | **Participants** 10 | `Grower` `Importer` `Shipper` 11 | 12 | **Assets** 13 | `Contract` `Shipment` 14 | 15 | **Transactions** 16 | `TemperatureReading``AccelReading` `GpsReading` `ShipmentReceived` `SetupDemo` 17 | 18 | **Events** 19 | `TemperatureThresholdEvent` `AccelerationThresholdEvent` `ShipmentInPortEvent` 20 | 21 | To test this Business Network Definition in the **Test** tab: 22 | 23 | Submit a `SetupDemo` transaction: 24 | 25 | ``` 26 | { 27 | "$class": "org.acme.shipping.perishable.SetupDemo" 28 | } 29 | ``` 30 | 31 | This transaction populates the Participant Registries with a `Grower`, an `Importer` and a `Shipper`. The Asset Registries will have a `Contract` asset and a `Shipment` asset. 32 | 33 | Submit a `TemperatureReading` transaction: 34 | 35 | ``` 36 | { 37 | "$class": "org.acme.shipping.perishable.TemperatureReading", 38 | "celsius": 8, 39 | "latitude": "40.6840", 40 | "longitude":"74.0062", 41 | "readingTime": "2018-03-22T17:31:36.229Z", 42 | "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001" 43 | } 44 | ``` 45 | 46 | If the temperature reading falls outside the min/max range of the contract, the price received by the grower will be reduced, and a `TemperatureThresholdEvent` is emitted. You may submit several readings if you wish. Each reading will be aggregated within `SHIP_001` Shipment Asset Registry. 47 | 48 | Submit a `AccelReading` transaction: 49 | 50 | ``` 51 | { 52 | "$class": "org.acme.shipping.perishable.AccelReading", 53 | "accel_x": -96, 54 | "accel_y": 18368, 55 | "accel_z": -12032, 56 | "latitude": "40.6840", 57 | "longitude":"74.0062", 58 | "readingTime": "2018-03-22T17:31:36.229Z", 59 | "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001" 60 | } 61 | ``` 62 | 63 | If the acceleration reading falls outside the min/max range of the contract, the price received by the grower will be reduced, and a `AccelerationThresholdEvent` is emitted. You may submit several readings if you wish. Each reading will be aggregated within `SHIP_001` Shipment Asset Registry. 64 | 65 | Submit a `ShipmentReceived` transaction for `SHIP_001` to trigger the payout to the grower, based on the parameters of the `CON_001` contract: 66 | 67 | ``` 68 | { 69 | "$class": "org.acme.shipping.perishable.ShipmentReceived", 70 | "shipment": "resource:org.acme.shipping.perishable.Shipment#SHIP_001" 71 | } 72 | ``` 73 | 74 | If the date-time of the `ShipmentReceived` transaction is after the `arrivalDateTime` on `CON_001` then the grower will no receive any payment for the shipment. 75 | 76 | Submit a `GpsReading` transaction: 77 | 78 | ``` 79 | { 80 | "$class": "org.acme.shipping.perishable.GpsReading", 81 | "readingTime": "120000", 82 | "readingDate": "20171024", 83 | "latitude":"40.6840", 84 | "latitudeDir":"N", 85 | "longitude":"74.0062", 86 | "laongitudeDir":"W", 87 | } 88 | ``` 89 | 90 | If the GPS reading indicates the ship's location is the Port of New Jersey/New York (40.6840,-74.0062) then a `ShipmentInPortEvent` is emitted. 91 | 92 | Enjoy! 93 | -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/lib/logic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 'use strict'; 16 | /** 17 | * A shipment has been received by an importer 18 | * @param {org.acme.shipping.perishable.ShipmentReceived} shipmentReceived - the ShipmentReceived transaction 19 | * @transaction 20 | */ 21 | function payOut(shipmentReceived) { 22 | 23 | var contract = shipmentReceived.shipment.contract; 24 | var shipment = shipmentReceived.shipment; 25 | var payOut = contract.unitPrice * shipment.unitCount; 26 | 27 | //console.log('Received at: ' + shipmentReceived.timestamp); 28 | //console.log('Contract arrivalDateTime: ' + contract.arrivalDateTime); 29 | 30 | // set the status of the shipment 31 | shipment.status = 'ARRIVED'; 32 | 33 | // if the shipment did not arrive on time the payout is zero 34 | if (shipmentReceived.timestamp > contract.arrivalDateTime) { 35 | payOut = 0; 36 | //console.log('Late shipment'); 37 | } else { 38 | // find the lowest temperature reading 39 | if (shipment.temperatureReadings) { 40 | // sort the temperatureReadings by celsius 41 | shipment.temperatureReadings.sort(function (a, b) { 42 | return (a.celsius - b.celsius); 43 | }); 44 | var lowestReading = shipment.temperatureReadings[0]; 45 | var highestReading = shipment.temperatureReadings[shipment.temperatureReadings.length - 1]; 46 | var penalty = 0; 47 | //console.log('Lowest temp reading: ' + lowestReading.celsius); 48 | //console.log('Highest temp reading: ' + highestReading.celsius); 49 | 50 | // does the lowest temperature violate the contract? 51 | if (lowestReading.celsius < contract.minTemperature) { 52 | penalty += (contract.minTemperature - lowestReading.celsius) * contract.minPenaltyFactor; 53 | //console.log('Min temp penalty: ' + penalty); 54 | } 55 | 56 | // does the highest temperature violate the contract? 57 | if (highestReading.celsius > contract.maxTemperature) { 58 | penalty += (highestReading.celsius - contract.maxTemperature) * contract.maxPenaltyFactor; 59 | //console.log('Max temp penalty: ' + penalty); 60 | } 61 | 62 | // apply any penalities 63 | payOut -= (penalty * shipment.unitCount); 64 | 65 | if (payOut < 0) { 66 | payOut = 0; 67 | } 68 | } 69 | } 70 | 71 | //console.log('Payout: ' + payOut); 72 | contract.grower.accountBalance += payOut; 73 | contract.importer.accountBalance -= payOut; 74 | 75 | //console.log('Grower: ' + contract.grower.$identifier + ' new balance: ' + contract.grower.accountBalance); 76 | //console.log('Importer: ' + contract.importer.$identifier + ' new balance: ' + contract.importer.accountBalance); 77 | 78 | return getParticipantRegistry('org.acme.shipping.perishable.Grower') 79 | .then(function (growerRegistry) { 80 | // update the grower's balance 81 | return growerRegistry.update(contract.grower); 82 | }) 83 | .then(function () { 84 | return getParticipantRegistry('org.acme.shipping.perishable.Importer'); 85 | }) 86 | .then(function (importerRegistry) { 87 | // update the importer's balance 88 | return importerRegistry.update(contract.importer); 89 | }) 90 | .then(function () { 91 | return getAssetRegistry('org.acme.shipping.perishable.Shipment'); 92 | }) 93 | .then(function (shipmentRegistry) { 94 | // update the state of the shipment 95 | return shipmentRegistry.update(shipment); 96 | }); 97 | } 98 | 99 | /** 100 | * A temperature reading has been received for a shipment 101 | * @param {org.acme.shipping.perishable.TemperatureReading} temperatureReading - the TemperatureReading transaction 102 | * @transaction 103 | */ 104 | function temperatureReading(temperatureReading) { 105 | 106 | var shipment = temperatureReading.shipment; 107 | var NS = 'org.acme.shipping.perishable'; 108 | var contract = shipment.contract; 109 | var factory = getFactory(); 110 | 111 | //console.log('Adding temperature ' + temperatureReading.celsius + ' to shipment ' + shipment.$identifier); 112 | 113 | if (shipment.temperatureReadings) { 114 | shipment.temperatureReadings.push(temperatureReading); 115 | } else { 116 | shipment.temperatureReadings = [temperatureReading]; 117 | } 118 | 119 | if (temperatureReading.celsius < contract.minTemperature || 120 | temperatureReading.celsius > contract.maxTemperature) { 121 | var temperatureEvent = factory.newEvent(NS, 'TemperatureThresholdEvent'); 122 | temperatureEvent.shipment = shipment; 123 | temperatureEvent.temperature = temperatureReading.celsius; 124 | temperatureEvent.latitude = temperatureReading.latitude; 125 | temperatureEvent.longitude = temperatureReading.longitude; 126 | temperatureEvent.readingTime = temperatureReading.readingTime; 127 | temperatureEvent.message = 'Temperature threshold violated! Emitting TemperatureEvent for shipment: ' + shipment.$identifier; 128 | emit(temperatureEvent); 129 | } 130 | 131 | return getAssetRegistry(NS + '.Shipment') 132 | .then(function (shipmentRegistry) { 133 | // add the temp reading to the shipment 134 | return shipmentRegistry.update(shipment); 135 | }); 136 | } 137 | 138 | /** 139 | * An Acceleration reading has been received for a shipment 140 | * @param {org.acme.shipping.perishable.AccelReading} AccelReading - the AccelReading transaction 141 | * @transaction 142 | */ 143 | function AccelReading(AccelReading) { 144 | var shipment = AccelReading.shipment; 145 | var NS = 'org.acme.shipping.perishable'; 146 | var contract = shipment.contract; 147 | var factory = getFactory(); 148 | 149 | //console.log('Adding acceleration ' + AccelReading.accel_x + ' to shipment ' + shipment.$identifier); 150 | 151 | if (shipment.AccelReadings) { 152 | shipment.AccelReadings.push(AccelReading); 153 | } else { 154 | shipment.AccelReadings = [AccelReading]; 155 | } 156 | 157 | // Also test for accel_y / accel_z 158 | if (AccelReading.accel_x < contract.maxAccel ) { 159 | var AccelerationEvent = factory.newEvent(NS, 'AccelerationThresholdEvent'); 160 | AccelerationEvent.shipment = shipment; 161 | AccelerationEvent.accel_x = AccelReading.accel_x; 162 | AccelerationEvent.accel_y = AccelReading.accel_y; 163 | AccelerationEvent.accel_z = AccelReading.accel_z; 164 | AccelerationEvent.latitude = AccelReading.latitude; 165 | AccelerationEvent.longitude = AccelReading.longitude; 166 | AccelerationEvent.readingTime = AccelReading.readingTime; 167 | AccelerationEvent.message = 'Acceleration threshold violated! Emitting AccelerationEvent for shipment: ' + shipment.$identifier; 168 | emit(AccelerationEvent); 169 | } 170 | 171 | return getAssetRegistry(NS + '.Shipment') 172 | .then(function (shipmentRegistry) { 173 | // add the temp reading to the shipment 174 | return shipmentRegistry.update(shipment); 175 | }); 176 | } 177 | 178 | /** 179 | * A GPS reading has been received for a shipment 180 | * @param {org.acme.shipping.perishable.GpsReading} gpsReading - the GpsReading transaction 181 | * @transaction 182 | */ 183 | function gpsReading(gpsReading) { 184 | 185 | var factory = getFactory(); 186 | var NS = 'org.acme.shipping.perishable'; 187 | var shipment = gpsReading.shipment; 188 | var PORT_OF_NEW_YORK = '/LAT:40.6840N/LONG:74.0062W'; 189 | 190 | if (shipment.gpsReadings) { 191 | shipment.gpsReadings.push(gpsReading); 192 | } else { 193 | shipment.gpsReadings = [gpsReading]; 194 | } 195 | 196 | var latLong = '/LAT:' + gpsReading.latitude + gpsReading.latitudeDir + '/LONG:' + 197 | gpsReading.longitude + gpsReading.longitudeDir; 198 | 199 | if (latLong === PORT_OF_NEW_YORK) { 200 | var shipmentInPortEvent = factory.newEvent(NS, 'ShipmentInPortEvent'); 201 | shipmentInPortEvent.shipment = shipment; 202 | var message = 'Shipment has reached the destination port of ' + PORT_OF_NEW_YORK; 203 | shipmentInPortEvent.message = message; 204 | emit(shipmentInPortEvent); 205 | } 206 | 207 | return getAssetRegistry(NS + '.Shipment') 208 | .then(function (shipmentRegistry) { 209 | // add the temp reading to the shipment 210 | return shipmentRegistry.update(shipment); 211 | }); 212 | } 213 | 214 | /** 215 | * Initialize some test assets and participants useful for running a demo. 216 | * @param {org.acme.shipping.perishable.SetupDemo} setupDemo - the SetupDemo transaction 217 | * @transaction 218 | */ 219 | function setupDemo(setupDemo) { 220 | 221 | var factory = getFactory(); 222 | var NS = 'org.acme.shipping.perishable'; 223 | 224 | // create the grower 225 | var grower = factory.newResource(NS, 'Grower', 'farmer@email.com'); 226 | var growerAddress = factory.newConcept(NS, 'Address'); 227 | growerAddress.country = 'USA'; 228 | grower.address = growerAddress; 229 | grower.accountBalance = 0; 230 | 231 | // create the importer 232 | var importer = factory.newResource(NS, 'Importer', 'supermarket@email.com'); 233 | var importerAddress = factory.newConcept(NS, 'Address'); 234 | importerAddress.country = 'UK'; 235 | importer.address = importerAddress; 236 | importer.accountBalance = 0; 237 | 238 | // create the shipper 239 | var shipper = factory.newResource(NS, 'Shipper', 'shipper@email.com'); 240 | var shipperAddress = factory.newConcept(NS, 'Address'); 241 | shipperAddress.country = 'Panama'; 242 | shipper.address = shipperAddress; 243 | shipper.accountBalance = 0; 244 | 245 | // create the contract 246 | var contract = factory.newResource(NS, 'Contract', 'CON_002'); 247 | contract.grower = factory.newRelationship(NS, 'Grower', 'farmer@email.com'); 248 | contract.importer = factory.newRelationship(NS, 'Importer', 'supermarket@email.com'); 249 | contract.shipper = factory.newRelationship(NS, 'Shipper', 'shipper@email.com'); 250 | var tomorrow = setupDemo.timestamp; 251 | tomorrow.setDate(tomorrow.getDate() + 1); 252 | contract.arrivalDateTime = tomorrow; // the shipment has to arrive tomorrow 253 | contract.unitPrice = 0.5; // pay 50 cents per unit 254 | contract.minTemperature = 2; // min temperature for the cargo 255 | contract.maxTemperature = 10; // max temperature for the cargo 256 | contract.maxAccel = 15000; // max acceleration for the cargo 257 | contract.minPenaltyFactor = 0.2; // we reduce the price by 20 cents for every degree below the min temp 258 | contract.maxPenaltyFactor = 0.1; // we reduce the price by 10 cents for every degree above the max temp 259 | 260 | // create the shipment 261 | var shipment = factory.newResource(NS, 'Shipment', '320022000251363131363432'); 262 | shipment.type = 'MEDICINE'; 263 | shipment.status = 'IN_TRANSIT'; 264 | shipment.unitCount = 5000; 265 | shipment.contract = factory.newRelationship(NS, 'Contract', 'CON_002'); 266 | return getParticipantRegistry(NS + '.Grower') 267 | .then(function (growerRegistry) { 268 | // add the growers 269 | return growerRegistry.addAll([grower]); 270 | }) 271 | .then(function() { 272 | return getParticipantRegistry(NS + '.Importer'); 273 | }) 274 | .then(function(importerRegistry) { 275 | // add the importers 276 | return importerRegistry.addAll([importer]); 277 | }) 278 | .then(function() { 279 | return getParticipantRegistry(NS + '.Shipper'); 280 | }) 281 | .then(function(shipperRegistry) { 282 | // add the shippers 283 | return shipperRegistry.addAll([shipper]); 284 | }) 285 | .then(function() { 286 | return getAssetRegistry(NS + '.Contract'); 287 | }) 288 | .then(function(contractRegistry) { 289 | // add the contracts 290 | return contractRegistry.addAll([contract]); 291 | }) 292 | .then(function() { 293 | return getAssetRegistry(NS + '.Shipment'); 294 | }) 295 | .then(function(shipmentRegistry) { 296 | // add the shipments 297 | return shipmentRegistry.addAll([shipment]); 298 | }); 299 | } -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/logic.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | 'use strict'; 16 | /** 17 | * A shipment has been received by an importer 18 | * @param {org.acme.shipping.perishable.ShipmentReceived} shipmentReceived - the ShipmentReceived transaction 19 | * @transaction 20 | */ 21 | function payOut(shipmentReceived) { 22 | 23 | var contract = shipmentReceived.shipment.contract; 24 | var shipment = shipmentReceived.shipment; 25 | var payOut = contract.unitPrice * shipment.unitCount; 26 | 27 | //console.log('Received at: ' + shipmentReceived.timestamp); 28 | //console.log('Contract arrivalDateTime: ' + contract.arrivalDateTime); 29 | 30 | // set the status of the shipment 31 | shipment.status = 'ARRIVED'; 32 | 33 | // if the shipment did not arrive on time the payout is zero 34 | if (shipmentReceived.timestamp > contract.arrivalDateTime) { 35 | payOut = 0; 36 | //console.log('Late shipment'); 37 | } else { 38 | // find the lowest temperature reading 39 | if (shipment.temperatureReadings) { 40 | // sort the temperatureReadings by celsius 41 | shipment.temperatureReadings.sort(function (a, b) { 42 | return (a.celsius - b.celsius); 43 | }); 44 | var lowestReading = shipment.temperatureReadings[0]; 45 | var highestReading = shipment.temperatureReadings[shipment.temperatureReadings.length - 1]; 46 | var penalty = 0; 47 | //console.log('Lowest temp reading: ' + lowestReading.celsius); 48 | //console.log('Highest temp reading: ' + highestReading.celsius); 49 | 50 | // does the lowest temperature violate the contract? 51 | if (lowestReading.celsius < contract.minTemperature) { 52 | penalty += (contract.minTemperature - lowestReading.celsius) * contract.minPenaltyFactor; 53 | //console.log('Min temp penalty: ' + penalty); 54 | } 55 | 56 | // does the highest temperature violate the contract? 57 | if (highestReading.celsius > contract.maxTemperature) { 58 | penalty += (highestReading.celsius - contract.maxTemperature) * contract.maxPenaltyFactor; 59 | //console.log('Max temp penalty: ' + penalty); 60 | } 61 | 62 | // apply any penalities 63 | payOut -= (penalty * shipment.unitCount); 64 | 65 | if (payOut < 0) { 66 | payOut = 0; 67 | } 68 | } 69 | } 70 | 71 | //console.log('Payout: ' + payOut); 72 | contract.grower.accountBalance += payOut; 73 | contract.importer.accountBalance -= payOut; 74 | 75 | //console.log('Grower: ' + contract.grower.$identifier + ' new balance: ' + contract.grower.accountBalance); 76 | //console.log('Importer: ' + contract.importer.$identifier + ' new balance: ' + contract.importer.accountBalance); 77 | 78 | return getParticipantRegistry('org.acme.shipping.perishable.Grower') 79 | .then(function (growerRegistry) { 80 | // update the grower's balance 81 | return growerRegistry.update(contract.grower); 82 | }) 83 | .then(function () { 84 | return getParticipantRegistry('org.acme.shipping.perishable.Importer'); 85 | }) 86 | .then(function (importerRegistry) { 87 | // update the importer's balance 88 | return importerRegistry.update(contract.importer); 89 | }) 90 | .then(function () { 91 | return getAssetRegistry('org.acme.shipping.perishable.Shipment'); 92 | }) 93 | .then(function (shipmentRegistry) { 94 | // update the state of the shipment 95 | return shipmentRegistry.update(shipment); 96 | }); 97 | } 98 | 99 | /** 100 | * A temperature reading has been received for a shipment 101 | * @param {org.acme.shipping.perishable.TemperatureReading} temperatureReading - the TemperatureReading transaction 102 | * @transaction 103 | */ 104 | function temperatureReading(temperatureReading) { 105 | 106 | var shipment = temperatureReading.shipment; 107 | var NS = 'org.acme.shipping.perishable'; 108 | var contract = shipment.contract; 109 | var factory = getFactory(); 110 | 111 | //console.log('Adding temperature ' + temperatureReading.celsius + ' to shipment ' + shipment.$identifier); 112 | 113 | if (shipment.temperatureReadings) { 114 | shipment.temperatureReadings.push(temperatureReading); 115 | } else { 116 | shipment.temperatureReadings = [temperatureReading]; 117 | } 118 | 119 | if (temperatureReading.celsius < contract.minTemperature || 120 | temperatureReading.celsius > contract.maxTemperature) { 121 | var temperatureEvent = factory.newEvent(NS, 'TemperatureThresholdEvent'); 122 | temperatureEvent.shipment = shipment; 123 | temperatureEvent.temperature = temperatureReading.celsius; 124 | temperatureEvent.latitude = temperatureReading.latitude; 125 | temperatureEvent.longitude = temperatureReading.longitude; 126 | temperatureEvent.readingTime = temperatureReading.readingTime; 127 | temperatureEvent.message = 'Temperature threshold violated! Emitting TemperatureEvent for shipment: ' + shipment.$identifier; 128 | emit(temperatureEvent); 129 | } 130 | 131 | return getAssetRegistry(NS + '.Shipment') 132 | .then(function (shipmentRegistry) { 133 | // add the temp reading to the shipment 134 | return shipmentRegistry.update(shipment); 135 | }); 136 | } 137 | 138 | /** 139 | * An Acceleration reading has been received for a shipment 140 | * @param {org.acme.shipping.perishable.AccelReading} AccelReading - the AccelReading transaction 141 | * @transaction 142 | */ 143 | function AccelReading(AccelReading) { 144 | var shipment = AccelReading.shipment; 145 | var NS = 'org.acme.shipping.perishable'; 146 | var contract = shipment.contract; 147 | var factory = getFactory(); 148 | 149 | //console.log('Adding acceleration ' + AccelReading.accel_x + ' to shipment ' + shipment.$identifier); 150 | 151 | if (shipment.AccelReadings) { 152 | shipment.AccelReadings.push(AccelReading); 153 | } else { 154 | shipment.AccelReadings = [AccelReading]; 155 | } 156 | 157 | // Also test for accel_y / accel_z 158 | if (AccelReading.accel_x < contract.maxAccel ) { 159 | var AccelerationEvent = factory.newEvent(NS, 'AccelerationThresholdEvent'); 160 | AccelerationEvent.shipment = shipment; 161 | AccelerationEvent.accel_x = AccelReading.accel_x; 162 | AccelerationEvent.accel_y = AccelReading.accel_y; 163 | AccelerationEvent.accel_z = AccelReading.accel_z; 164 | AccelerationEvent.latitude = AccelReading.latitude; 165 | AccelerationEvent.longitude = AccelReading.longitude; 166 | AccelerationEvent.readingTime = AccelReading.readingTime; 167 | AccelerationEvent.message = 'Acceleration threshold violated! Emitting AccelerationEvent for shipment: ' + shipment.$identifier; 168 | emit(AccelerationEvent); 169 | } 170 | 171 | return getAssetRegistry(NS + '.Shipment') 172 | .then(function (shipmentRegistry) { 173 | // add the temp reading to the shipment 174 | return shipmentRegistry.update(shipment); 175 | }); 176 | } 177 | 178 | /** 179 | * A GPS reading has been received for a shipment 180 | * @param {org.acme.shipping.perishable.GpsReading} gpsReading - the GpsReading transaction 181 | * @transaction 182 | */ 183 | function gpsReading(gpsReading) { 184 | 185 | var factory = getFactory(); 186 | var NS = 'org.acme.shipping.perishable'; 187 | var shipment = gpsReading.shipment; 188 | var PORT_OF_NEW_YORK = '/LAT:40.6840N/LONG:74.0062W'; 189 | 190 | if (shipment.gpsReadings) { 191 | shipment.gpsReadings.push(gpsReading); 192 | } else { 193 | shipment.gpsReadings = [gpsReading]; 194 | } 195 | 196 | var latLong = '/LAT:' + gpsReading.latitude + gpsReading.latitudeDir + '/LONG:' + 197 | gpsReading.longitude + gpsReading.longitudeDir; 198 | 199 | if (latLong === PORT_OF_NEW_YORK) { 200 | var shipmentInPortEvent = factory.newEvent(NS, 'ShipmentInPortEvent'); 201 | shipmentInPortEvent.shipment = shipment; 202 | var message = 'Shipment has reached the destination port of ' + PORT_OF_NEW_YORK; 203 | shipmentInPortEvent.message = message; 204 | emit(shipmentInPortEvent); 205 | } 206 | 207 | return getAssetRegistry(NS + '.Shipment') 208 | .then(function (shipmentRegistry) { 209 | // add the temp reading to the shipment 210 | return shipmentRegistry.update(shipment); 211 | }); 212 | } 213 | 214 | /** 215 | * Initialize some test assets and participants useful for running a demo. 216 | * @param {org.acme.shipping.perishable.SetupDemo} setupDemo - the SetupDemo transaction 217 | * @transaction 218 | */ 219 | function setupDemo(setupDemo) { 220 | 221 | var factory = getFactory(); 222 | var NS = 'org.acme.shipping.perishable'; 223 | 224 | // create the grower 225 | var grower = factory.newResource(NS, 'Grower', 'farmer@email.com'); 226 | var growerAddress = factory.newConcept(NS, 'Address'); 227 | growerAddress.country = 'USA'; 228 | grower.address = growerAddress; 229 | grower.accountBalance = 0; 230 | 231 | // create the importer 232 | var importer = factory.newResource(NS, 'Importer', 'supermarket@email.com'); 233 | var importerAddress = factory.newConcept(NS, 'Address'); 234 | importerAddress.country = 'UK'; 235 | importer.address = importerAddress; 236 | importer.accountBalance = 0; 237 | 238 | // create the shipper 239 | var shipper = factory.newResource(NS, 'Shipper', 'shipper@email.com'); 240 | var shipperAddress = factory.newConcept(NS, 'Address'); 241 | shipperAddress.country = 'Panama'; 242 | shipper.address = shipperAddress; 243 | shipper.accountBalance = 0; 244 | 245 | // create the contract 246 | var contract = factory.newResource(NS, 'Contract', 'CON_002'); 247 | contract.grower = factory.newRelationship(NS, 'Grower', 'farmer@email.com'); 248 | contract.importer = factory.newRelationship(NS, 'Importer', 'supermarket@email.com'); 249 | contract.shipper = factory.newRelationship(NS, 'Shipper', 'shipper@email.com'); 250 | var tomorrow = setupDemo.timestamp; 251 | tomorrow.setDate(tomorrow.getDate() + 1); 252 | contract.arrivalDateTime = tomorrow; // the shipment has to arrive tomorrow 253 | contract.unitPrice = 0.5; // pay 50 cents per unit 254 | contract.minTemperature = 2; // min temperature for the cargo 255 | contract.maxTemperature = 10; // max temperature for the cargo 256 | contract.maxAccel = 15000; // max acceleration for the cargo 257 | contract.minPenaltyFactor = 0.2; // we reduce the price by 20 cents for every degree below the min temp 258 | contract.maxPenaltyFactor = 0.1; // we reduce the price by 10 cents for every degree above the max temp 259 | 260 | // create the shipment 261 | var shipment = factory.newResource(NS, 'Shipment', '320022000251363131363432'); 262 | shipment.type = 'MEDICINE'; 263 | shipment.status = 'IN_TRANSIT'; 264 | shipment.unitCount = 5000; 265 | shipment.contract = factory.newRelationship(NS, 'Contract', 'CON_002'); 266 | return getParticipantRegistry(NS + '.Grower') 267 | .then(function (growerRegistry) { 268 | // add the growers 269 | return growerRegistry.addAll([grower]); 270 | }) 271 | .then(function() { 272 | return getParticipantRegistry(NS + '.Importer'); 273 | }) 274 | .then(function(importerRegistry) { 275 | // add the importers 276 | return importerRegistry.addAll([importer]); 277 | }) 278 | .then(function() { 279 | return getParticipantRegistry(NS + '.Shipper'); 280 | }) 281 | .then(function(shipperRegistry) { 282 | // add the shippers 283 | return shipperRegistry.addAll([shipper]); 284 | }) 285 | .then(function() { 286 | return getAssetRegistry(NS + '.Contract'); 287 | }) 288 | .then(function(contractRegistry) { 289 | // add the contracts 290 | return contractRegistry.addAll([contract]); 291 | }) 292 | .then(function() { 293 | return getAssetRegistry(NS + '.Shipment'); 294 | }) 295 | .then(function(shipmentRegistry) { 296 | // add the shipments 297 | return shipmentRegistry.addAll([shipment]); 298 | }); 299 | } -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/models/perishable.cto: -------------------------------------------------------------------------------- 1 | /** 2 | * A business network for shipping perishable goods 3 | * The cargo is temperature controlled and contracts 4 | * can be negociated based on the temperature 5 | * readings received for the cargo 6 | */ 7 | 8 | namespace org.acme.shipping.perishable 9 | 10 | /** 11 | * The type of perishable product being shipped 12 | */ 13 | enum ProductType { 14 | o BANANAS 15 | o APPLES 16 | o PEARS 17 | o PEACHES 18 | o COFFEE 19 | o MEDICINE 20 | } 21 | 22 | /** 23 | * The status of a shipment 24 | */ 25 | enum ShipmentStatus { 26 | o CREATED 27 | o IN_TRANSIT 28 | o ARRIVED 29 | } 30 | 31 | /** 32 | * Directions of the compass 33 | */ 34 | enum CompassDirection { 35 | o N 36 | o S 37 | o E 38 | o W 39 | } 40 | 41 | /** 42 | * An abstract transaction that is related to a Shipment 43 | */ 44 | abstract transaction ShipmentTransaction { 45 | --> Shipment shipment 46 | } 47 | 48 | /** 49 | * An Accelerometer reading for a shipment. E.g. received from a 50 | * device within an accelerometer controlled shipping container 51 | * 52 | * The combination of the accelerometer environment reading, 53 | * PLUS the GPS location, PLUS the timestamp is what is interesting 54 | * Just knowing temperature without knowing where or when is 55 | * not sufficient. 56 | */ 57 | transaction AccelReading extends ShipmentTransaction { 58 | o Double accel_x 59 | o Double accel_y 60 | o Double accel_z 61 | o String latitude 62 | o String longitude 63 | o String readingTime 64 | } 65 | 66 | /** 67 | * An temperature reading for a shipment. E.g. received from a 68 | * device within a temperature controlled shipping container 69 | * 70 | * The combination of the temperature environment reading, 71 | * PLUS the GPS location, PLUS the timestamp is what is interesting 72 | * Just knowing temperature without knowing where or when is 73 | * not sufficient. 74 | */ 75 | transaction TemperatureReading extends ShipmentTransaction { 76 | o Double celsius 77 | o String latitude 78 | o String longitude 79 | o String readingTime 80 | } 81 | 82 | /** 83 | * A GPS reading for a shipment. E.g. received from a device 84 | * within a shipping container 85 | */ 86 | transaction GpsReading extends ShipmentTransaction { 87 | o String readingTime 88 | o String readingDate 89 | o String latitude 90 | o CompassDirection latitudeDir 91 | o String longitude 92 | o CompassDirection longitudeDir 93 | } 94 | 95 | /** 96 | * A notification that a shipment has been received by the 97 | * importer and that funds should be transferred from the importer 98 | * to the grower to pay for the shipment. 99 | */ 100 | transaction ShipmentReceived extends ShipmentTransaction { 101 | } 102 | 103 | /** 104 | * A shipment being tracked as an asset on the ledger 105 | */ 106 | asset Shipment identified by shipmentId { 107 | o String shipmentId 108 | o ProductType type 109 | o ShipmentStatus status 110 | o Long unitCount 111 | --> Contract contract 112 | o TemperatureReading[] temperatureReadings optional 113 | o AccelReading[] AccelReadings optional 114 | o GpsReading[] gpsReadings optional 115 | } 116 | 117 | /** 118 | * Defines a contract between a Grower and an Importer to ship using 119 | * a Shipper, paying a set unit price. The unit price is multiplied by 120 | * a penality factor proportional to the deviation from the min and max 121 | * negociated temperatures for the shipment. 122 | */ 123 | asset Contract identified by contractId { 124 | o String contractId 125 | --> Grower grower 126 | --> Shipper shipper 127 | --> Importer importer 128 | o DateTime arrivalDateTime 129 | o Double unitPrice 130 | o Double minTemperature 131 | o Double maxTemperature 132 | o Double minPenaltyFactor 133 | o Double maxPenaltyFactor 134 | o Double maxAccel 135 | } 136 | 137 | /** 138 | * A concept for a simple street address 139 | */ 140 | concept Address { 141 | o String city optional 142 | o String country 143 | o String street optional 144 | o String zip optional 145 | } 146 | 147 | /** 148 | * An abstract participant type in this business network 149 | */ 150 | abstract participant Business identified by email { 151 | o String email 152 | o Address address 153 | o Double accountBalance 154 | } 155 | 156 | /** 157 | * A Grower is a type of participant in the network 158 | */ 159 | participant Grower extends Business { 160 | } 161 | 162 | /** 163 | * A Shipper is a type of participant in the network 164 | */ 165 | participant Shipper extends Business { 166 | } 167 | 168 | /** 169 | * An Importer is a type of participant in the network 170 | */ 171 | participant Importer extends Business { 172 | } 173 | 174 | /** 175 | * JUST FOR INITIALIZING A DEMO 176 | */ 177 | transaction SetupDemo { 178 | } 179 | 180 | /** 181 | * An event - when the temperature goes outside the agreed-upon boundaries 182 | */ 183 | event TemperatureThresholdEvent { 184 | o String message 185 | o Double temperature 186 | o String latitude 187 | o String longitude 188 | o String readingTime 189 | --> Shipment shipment 190 | } 191 | 192 | /** 193 | * An event - when the acceleration event has been detected 194 | */ 195 | event AccelerationThresholdEvent { 196 | o String message 197 | o Double accel_x 198 | o Double accel_y 199 | o Double accel_z 200 | o String latitude 201 | o String longitude 202 | o String readingTime 203 | --> Shipment shipment 204 | } 205 | 206 | /** 207 | * An event - when the ship arrives at the port 208 | */ 209 | event ShipmentInPortEvent { 210 | o String message 211 | --> Shipment shipment 212 | } 213 | -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "composer": "^0.15.0" 4 | }, 5 | "name": "iot-asset-tracker-network", 6 | "version": "0.1.0", 7 | "description": "IoT Asset Tracker Perishable Goods Business Network", 8 | "scripts": { 9 | "clean": "rm -Rf ./node_modules ./dist ./composer-logs ./out", 10 | "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/iot-asset-tracker-network.bna", 11 | "pretest": "npm run lint", 12 | "lint": "eslint .", 13 | "postlint": "npm run licchk", 14 | "licchk": "license-check", 15 | "postlicchk": "npm run doc", 16 | "doc": "jsdoc --pedantic --recurse -c jsdoc.json", 17 | "test": "mocha -t 0 --recursive && cucumber-js", 18 | "deploy": "./scripts/deploy.sh" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/IBM/IoT-AssetTracking-Perishable-Network-Blockchain.git" 23 | }, 24 | "keywords": [ 25 | "shipping", 26 | "goods", 27 | "perishable", 28 | "asset-tracking", 29 | "asset-tracker", 30 | "composer", 31 | "composer-network", 32 | "iot" 33 | ], 34 | "author": "Hyperledger Composer", 35 | "license": "Apache-2.0", 36 | "devDependencies": { 37 | "browserfs": "^1.2.0", 38 | "chai": "latest", 39 | "chai-as-promised": "latest", 40 | "composer-admin": "^0.19.1", 41 | "composer-cli": "^0.19.1", 42 | "composer-client": "^0.19.1", 43 | "composer-common": "^0.19.1", 44 | "composer-connector-embedded": "^0.19.1", 45 | "composer-cucumber-steps": "^0.19.1", 46 | "cucumber": "^2.2.0", 47 | "eslint": "latest", 48 | "istanbul": "^0.4.5", 49 | "jsdoc": "^3.5.5", 50 | "license-check": "^1.1.5", 51 | "mkdirp": "latest", 52 | "mocha": "latest", 53 | "moment": "^2.17.1", 54 | "nyc": "latest" 55 | }, 56 | "license-check-config": { 57 | "src": [ 58 | "**/*.js", 59 | "!./coverage/**/*", 60 | "!./node_modules/**/*", 61 | "!./out/**/*", 62 | "!./scripts/**/*" 63 | ], 64 | "path": "header.txt", 65 | "blocking": true, 66 | "logInfo": false, 67 | "logError": true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/perishable.cto: -------------------------------------------------------------------------------- 1 | /** 2 | * A business network for shipping perishable goods 3 | * The cargo is temperature controlled and contracts 4 | * can be negociated based on the temperature 5 | * readings received for the cargo 6 | */ 7 | 8 | namespace org.acme.shipping.perishable 9 | 10 | /** 11 | * The type of perishable product being shipped 12 | */ 13 | enum ProductType { 14 | o BANANAS 15 | o APPLES 16 | o PEARS 17 | o PEACHES 18 | o COFFEE 19 | o MEDICINE 20 | } 21 | 22 | /** 23 | * The status of a shipment 24 | */ 25 | enum ShipmentStatus { 26 | o CREATED 27 | o IN_TRANSIT 28 | o ARRIVED 29 | } 30 | 31 | /** 32 | * Directions of the compass 33 | */ 34 | enum CompassDirection { 35 | o N 36 | o S 37 | o E 38 | o W 39 | } 40 | 41 | /** 42 | * An abstract transaction that is related to a Shipment 43 | */ 44 | abstract transaction ShipmentTransaction { 45 | --> Shipment shipment 46 | } 47 | 48 | /** 49 | * An Accelerometer reading for a shipment. E.g. received from a 50 | * device within an accelerometer controlled shipping container 51 | * 52 | * The combination of the accelerometer environment reading, 53 | * PLUS the GPS location, PLUS the timestamp is what is interesting 54 | * Just knowing temperature without knowing where or when is 55 | * not sufficient. 56 | */ 57 | transaction AccelReading extends ShipmentTransaction { 58 | o Double accel_x 59 | o Double accel_y 60 | o Double accel_z 61 | o String latitude 62 | o String longitude 63 | o String readingTime 64 | } 65 | 66 | /** 67 | * An temperature reading for a shipment. E.g. received from a 68 | * device within a temperature controlled shipping container 69 | * 70 | * The combination of the temperature environment reading, 71 | * PLUS the GPS location, PLUS the timestamp is what is interesting 72 | * Just knowing temperature without knowing where or when is 73 | * not sufficient. 74 | */ 75 | transaction TemperatureReading extends ShipmentTransaction { 76 | o Double celsius 77 | o String latitude 78 | o String longitude 79 | o String readingTime 80 | } 81 | 82 | /** 83 | * A GPS reading for a shipment. E.g. received from a device 84 | * within a shipping container 85 | */ 86 | transaction GpsReading extends ShipmentTransaction { 87 | o String readingTime 88 | o String readingDate 89 | o String latitude 90 | o CompassDirection latitudeDir 91 | o String longitude 92 | o CompassDirection longitudeDir 93 | } 94 | 95 | /** 96 | * A notification that a shipment has been received by the 97 | * importer and that funds should be transferred from the importer 98 | * to the grower to pay for the shipment. 99 | */ 100 | transaction ShipmentReceived extends ShipmentTransaction { 101 | } 102 | 103 | /** 104 | * A shipment being tracked as an asset on the ledger 105 | */ 106 | asset Shipment identified by shipmentId { 107 | o String shipmentId 108 | o ProductType type 109 | o ShipmentStatus status 110 | o Long unitCount 111 | --> Contract contract 112 | o TemperatureReading[] temperatureReadings optional 113 | o AccelReading[] AccelReadings optional 114 | o GpsReading[] gpsReadings optional 115 | } 116 | 117 | /** 118 | * Defines a contract between a Grower and an Importer to ship using 119 | * a Shipper, paying a set unit price. The unit price is multiplied by 120 | * a penality factor proportional to the deviation from the min and max 121 | * negociated temperatures for the shipment. 122 | */ 123 | asset Contract identified by contractId { 124 | o String contractId 125 | --> Grower grower 126 | --> Shipper shipper 127 | --> Importer importer 128 | o DateTime arrivalDateTime 129 | o Double unitPrice 130 | o Double minTemperature 131 | o Double maxTemperature 132 | o Double minPenaltyFactor 133 | o Double maxPenaltyFactor 134 | o Double maxAccel 135 | } 136 | 137 | /** 138 | * A concept for a simple street address 139 | */ 140 | concept Address { 141 | o String city optional 142 | o String country 143 | o String street optional 144 | o String zip optional 145 | } 146 | 147 | /** 148 | * An abstract participant type in this business network 149 | */ 150 | abstract participant Business identified by email { 151 | o String email 152 | o Address address 153 | o Double accountBalance 154 | } 155 | 156 | /** 157 | * A Grower is a type of participant in the network 158 | */ 159 | participant Grower extends Business { 160 | } 161 | 162 | /** 163 | * A Shipper is a type of participant in the network 164 | */ 165 | participant Shipper extends Business { 166 | } 167 | 168 | /** 169 | * An Importer is a type of participant in the network 170 | */ 171 | participant Importer extends Business { 172 | } 173 | 174 | /** 175 | * JUST FOR INITIALIZING A DEMO 176 | */ 177 | transaction SetupDemo { 178 | } 179 | 180 | /** 181 | * An event - when the temperature goes outside the agreed-upon boundaries 182 | */ 183 | event TemperatureThresholdEvent { 184 | o String message 185 | o Double temperature 186 | o String latitude 187 | o String longitude 188 | o String readingTime 189 | --> Shipment shipment 190 | } 191 | 192 | /** 193 | * An event - when the acceleration event has been detected 194 | */ 195 | event AccelerationThresholdEvent { 196 | o String message 197 | o Double accel_x 198 | o Double accel_y 199 | o Double accel_z 200 | o String latitude 201 | o String longitude 202 | o String readingTime 203 | --> Shipment shipment 204 | } 205 | 206 | /** 207 | * An event - when the ship arrives at the port 208 | */ 209 | event ShipmentInPortEvent { 210 | o String message 211 | --> Shipment shipment 212 | } 213 | -------------------------------------------------------------------------------- /Blockchain/IoT-Perishable-Network/permissions.acl: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample access control list. 3 | */ 4 | rule Default { 5 | description: "Allow all participants access to all resources" 6 | participant: "ANY" 7 | operation: ALL 8 | resource: "org.acme.shipping.perishable.*" 9 | action: ALLOW 10 | } 11 | 12 | rule SystemACL { 13 | description: "System ACL to permit all access" 14 | participant: "org.hyperledger.composer.system.Participant" 15 | operation: ALL 16 | resource: "org.hyperledger.composer.system.**" 17 | action: ALLOW 18 | } 19 | 20 | rule NetworkAdminUser { 21 | description: "Grant business network administrators full access to user resources" 22 | participant: "org.hyperledger.composer.system.NetworkAdmin" 23 | operation: ALL 24 | resource: "**" 25 | action: ALLOW 26 | } 27 | 28 | rule NetworkAdminSystem { 29 | description: "Grant business network administrators full access to system resources" 30 | participant: "org.hyperledger.composer.system.NetworkAdmin" 31 | operation: ALL 32 | resource: "org.hyperledger.composer.system.**" 33 | action: ALLOW 34 | } -------------------------------------------------------------------------------- /Blockchain/README.md: -------------------------------------------------------------------------------- 1 | # IoT Asset Tracking on a Hyperledger Blockchain 2 | 3 | This section of the IoT Asset tracking workshop is really split into two parts. The first part, which we will call **Blockchain Part A**, follows the tutorial to deploy a [Hyperledger](https://www.hyperledger.org/) Fabric and Hyperledger Composer running in the [IBM Blockchain Starter Plan](https://www.ibm.com/blockchain/getting-started.html) in the IBM Cloud. 4 | 5 | **Note:** User could incur charges! 6 | 7 | * The IBM Blockchain Starter Plan is free for only 30 days through IBM Cloud credits and could change at any time. You may need to enter your credit card information to be able to deploy the Blockchain Starter Plan. There will be notes at the end of the code pattern to remove the instances to help eliminate the potential risk of charges. 8 | 9 | There are quite a few IBM Blockchain tutorials that are excellent and we won't try to repeat them here. We will be using a couple of them along the way. In **Blockchain Part A** we are going to create our blockchain business network of perishable goods. In **Blockchain Part B** we will convert it into smart contracts to deploy to the IBM Blockchain Starter Plan. We will use part of the IBM Cloud called DevOps to build our code and deploy it to an instance of the IBM Blockchain Starter Plan. The build process will also deploy a Hyperledger Composer Rest Server running as a Cloud Foundry service in the IBM Cloud. The Hyperledger Composer REST APIs will be used by Node-RED to talk to the blockchain perishable network. Finally, you'll get to work with Node-RED to interact and visually see the tracking of the asset. 10 | 11 | ## Blockchain Part A - Build your perishable network 12 | 13 | We will be using Hyperledger Composer Playground to build our perishable network. When we are done, we will export the code to our local system to use in Part B. 14 | 15 | ### Import the sample perishable network into Hyperledger Composer Playground 16 | 1. Access the [IBM Hyperledger Composer Playground](http://composer-playground.mybluemix.net/). 17 | 2. Click on **Deploy a new business network** 18 | ![Select Deploy a new business network.](screenshots/deploynew.png) 19 | 3. Scroll down and choose **perishable-network** from the samples on npm. 20 | ![Select perishable-network from the *Samples on npm*.](screenshots/npmsample.png) 21 | 4. Scrolling back to the top, you should now have a business network name of **xxx-perishable-network**, where xxx is a unique identifer. 22 | * Note: The unique identifier becomes very important later on in this code pattern because this business network name is used to create a rest server on the IBM Cloud. If the name is not unique then it will fail during the build process. 23 | 5. Give the network admin card that will be created a name **admin@xxx-perishable-network**. 24 | ![Perishable Network BNA screenshot](screenshots/Perishable-Network-BNA-annotated.png "Hyperledger Composer") 25 | 6. On the right sidebar, click on **Deploy**. 26 | 7. Press **Connect now ->** 27 | 28 | ![Select Connect now](screenshots/ConnectNow.png) 29 | 30 | ### Customize the perishable network for IoT tracking 31 | Let's pause for a moment to review the perishable-network you just deployed. It tracks temperature but not geolocation information. There is an excellent three part Hyperledger series of articles in developerWorks that introduce the perishable-network. 32 | * [Hyperledger Composer basics, Part 1 - Model and test your blockchain network](https://www.ibm.com/developerworks/cloud/library/cl-refine-deploy-your-blockchain-network-with-hyperledger-composer-playground/index.html) 33 | * [Hyperledger Composer basics, Part 2 - Refine and deploy your blockchain network](https://www.ibm.com/developerworks/cloud/library/cl-refine-deploy-your-blockchain-network-with-hyperledger-composer-playground/index.html) 34 | * [Hyperledger Composer basics, Part 3 - Deploy locally, interact with, and extend your blockchain network](https://www.ibm.com/developerworks/cloud/library/cl-deploy-interact-extend-local-blockchain-network-with-hyperledger-composer/index.html) 35 | 36 | Part 2 includes instructions 37 | 38 | > Then you'll make changes to the sample Perishable Goods network that you worked with in Part 1. Specifically, you'll model an IoT GPS sensor in the shipping container by adding GPS readings to the Shipment asset, and modify the smart contract (chaincode) to send an alert when the Shipment reaches its destination port. 39 | 40 | Well, duh. THAT'S WHAT WE WANT TO DO.... With full step by step instructions. Triple word score. Following all the links finds Steve Perry's [perishable-network git repository]( 41 | https://github.com/makotogo/developerWorks) that contains the variants he details in the dW articles. 42 | 43 | Of course, those samples only got me so far because I also want to send accelerometer data from the Particle Electron Asset Tracker to the cloud. 44 | 45 | We're going to need to learn a little bit about the Hyperledger Blockchain modeling language CTO files and chaincode. Part 1 of the dW Series (link above) is a good primer. 46 | 47 | In the following steps, we will make changes to the model file to add in accelerometer data, environmental data, geolocation and a timestamp. Adding in the IoT data will also require additional transactions. To save time after we complete the model file updates, we will import the transactions from a cloned repository. 48 | 49 | 1. In the **enum ProductType**, add a variable to the enum for **medicine**. 50 | ``` 51 | enum ProductType{ 52 | 53 | o BANANAS 54 | 55 | o APPLES 56 | 57 | o PEARS 58 | 59 | o PEACHES 60 | 61 | o COFFEE 62 | 63 | o MEDICINE 64 | 65 | } 66 | ``` 67 | 68 | 2. In your model file, create **enum CompassDirection**. Enter the following values to enumerate the four cardinal directions: 69 | ``` 70 | /** 71 | * Directions of the compass 72 | */ 73 | enum CompassDirection { 74 | o N 75 | o S 76 | o E 77 | o W 78 | } 79 | ``` 80 | 2. Now we need some transaction models to be able to get data from our sensors. Add the following **transaction AccelReading extends ShipmentTransaction**: 81 | ``` 82 | /** 83 | * An Accelerometer reading for a shipment. E.g. received from a 84 | * device within an accelerometer controlled shipping container 85 | * 86 | * The combination of the accelerometer environment reading, 87 | * PLUS the GPS location, PLUS the timestamp is what is interesting 88 | * Just knowing temperature without knowing where or when is 89 | * not sufficient. 90 | */ 91 | transaction AccelReading extends ShipmentTransaction { 92 | o Double accel_x 93 | o Double accel_y 94 | o Double accel_z 95 | o String latitude 96 | o String longitude 97 | o String readingTime 98 | } 99 | ``` 100 | 3. For **transaction TemperatureReading extends ShipmentTransaction** modify the transaction to match the following variables: 101 | ``` 102 | transaction TemperatureReading extends ShipmentTransaction { 103 | o Double celsius 104 | o String latitude 105 | o String longitude 106 | o String readingTime 107 | } 108 | ``` 109 | 4. It's time to setup the **transaction GpsReading extends ShipmentTransaction**. Add the following: 110 | ``` 111 | /** 112 | * A GPS reading for a shipment. E.g. received from a device 113 | * within a shipping container 114 | */ 115 | transaction GpsReading extends ShipmentTransaction { 116 | o String readingTime 117 | o String readingDate 118 | o String latitude 119 | o CompassDirection latitudeDir 120 | o String longitude 121 | o CompassDirection longitudeDir 122 | } 123 | ``` 124 | 125 | 5. For the IoT data to be associated with the asset, shipment, we will need to add some IoT related variables to the asset model for shipment. Make the following additions to **asset Shipment identified by shipmentId**. 126 | * AccelReading[] AccelReadings optional 127 | * GpsReading[] gpsReadings optional 128 | ``` 129 | asset Shipment identified by shipmentId { 130 | o String shipmentId 131 | o ProductType type 132 | o ShipmentStatus status 133 | o Long unitCount 134 | --> Contract contract 135 | o TemperatureReading[] temperatureReadings optional 136 | o AccelReading[] AccelReadings optional 137 | o GpsReading[] gpsReadings optional 138 | } 139 | ``` 140 | 141 | 6. Our contract will also now be dependent on the shipment arriving without any incidents captured by the accelerometer. We will need to add a field for the accelerometer value to the contract asset model. This will allow us to specify conditions for a crash, a hard jolt or other incidents based on the accelerometer data in the logic.js file. 142 | * Add **Double maxAccel** to the Contract asset model. 143 | ``` 144 | asset Contract identified by contractId { 145 | o String contractId 146 | --> Grower grower 147 | --> Shipper shipper 148 | --> Importer importer 149 | o DateTime arrivalDateTime 150 | o Double unitPrice 151 | o Double minTemperature 152 | o Double maxTemperature 153 | o Double minPenaltyFactor 154 | o Double maxPenaltyFactor 155 | o Double maxAccel 156 | } 157 | ``` 158 | 7. Now we need to create some events so we can alert the appropriate participants when agreed upon thresholds are exceeded. Scroll down to the bottom of the model and add in the following information for the **TemperatureThresholdEvent**. 159 | ``` 160 | /** 161 | * An event - when the temperature goes outside the agreed-upon boundaries 162 | */ 163 | event TemperatureThresholdEvent { 164 | o String message 165 | o Double temperature 166 | o String latitude 167 | o String longitude 168 | o String readingTime 169 | --> Shipment shipment 170 | } 171 | ``` 172 | 173 | 8. Create an event for the **AccelerationThresholdEvent**. 174 | ``` 175 | /** 176 | * An event - when the acceleration event has been detected 177 | */ 178 | event AccelerationThresholdEvent { 179 | o String message 180 | o Double accel_x 181 | o Double accel_y 182 | o Double accel_z 183 | o String latitude 184 | o String longitude 185 | o String readingTime 186 | --> Shipment shipment 187 | } 188 | ``` 189 | 9. Add in the event **ShipmentInPort**. 190 | ``` 191 | /** 192 | * An event - when the ship arrives at the port 193 | */ 194 | event ShipmentInPortEvent { 195 | o String message 196 | --> Shipment shipment 197 | } 198 | ``` 199 | 200 | 10. Our model file is now complete. Select **Deploy changes** to save the changes in the Hyperledger Composer Playground. 201 | ![Click Update.](screenshots/Update.png) 202 | 203 | 11. Now it is time to **copy** (CTRL+C) our new logic.js file from our [repository](IoT-Perishable-Network/lib/logic.js). 204 | 205 | 12. Back in the Hyperledger Composer Playground: 206 | * **Remove all of the content** (CTRL+A) in the logic.js file 207 | * **Paste** (CTRL+V) in the content copied from the logic.js file in our repository. 208 | * Select **Deploy Changes** to save the changes. 209 | ![Perishable Network BNA model update screenshot](screenshots/Perishable-Network-BNA-Model-update-annotated.png "Hyperledger Composer Model") 210 | 211 | 13. Now let's test our work! Click on the **Test** tab at the top of the page. 212 | ![Click Test.](screenshots/Test.png) 213 | 214 | 14. First, select **Submit Transaction** so we can run our *setupDemo* transaction to give us some default values. 215 | ![Select Submit Transaction.](screenshots/SubmitTransaction.png) 216 | 217 | 15. From the *drop down*, select **SetupDemo** and then **Submit**. 218 | ![Run setupDemo.](screenshots/SetupDemo.png) 219 | 220 | 16. Look through your three partipants and two assets. You should now have a defined Grower, Importer, Shipper, Shipment and Contract. 221 | 222 | 17. Play with the other transactions to make sure that they update your assets. You should see fields added to your shipment in particular like the example below. You can enter any data. It doesn't need to be realistic. 223 | ![Test your transactions and verify it updates the asset.](screenshots/example.png) 224 | 225 | 18. Click on the **Define** tab to**Export** the code to your local system. We will use it during the deployment process. 226 | ![Click Export.](screenshots/export.png) 227 | 228 | 19. Save the business network archive, **perishable-network.bna**, somewhere you can easily find it. 229 | ![Save your business network archive locally.](screenshots/savebna.png) 230 | 231 | ## Blockchain Part B - Implement a Perishable Business Network 232 | Now it's time for the fun to begin! We are going to break this down into two sections: 233 | 234 | * [Deploying your blockchain network to your IBM Blockchain Starter Plan](#deploy-your-network) 235 | * [Generating your API for your deployed blockchain network with Hyperledger Composer Rest Server](#working-with-the-rest-api) 236 | 237 | 238 | ### Deploy your network 239 | Now that you've created your blockchain application, it's time to make it run on the IBM Blockchain Starter Plan. To do that, we are going to use the DevOps service in the IBM Cloud to deploy our code and start a REST server. This entire process is documented [here](https://github.com/sstone1/blockchain-starter-kit/blob/master/README.md) if you are interested in doing something similar outside of this exercise. This process will create the IBM Blockchain Starter Plan for you. 240 | 241 | **NOTE:** You may have to upgrade your IBM Cloud account to a pay-as-you-go account to use the IBM Blockchain Starter Plan. It is free for up to 30 days. It is your responsibility to monitor usage to avoid fees. 242 | 243 | This breaks down into the following steps: 244 | * [Create a DevOps toolchain](#create-a-devops-toolchain) 245 | * [Installing Hyperledger Composer locally](#install-hyperledger-composer-locally) 246 | * [Moving your code into your repository](#prepare-your-code-for-deployment) 247 | * [Verifying deployment of code](#verify-deployment) 248 | 249 | #### Create a DevOps toolchain 250 | 1. Start [here](https://console.bluemix.net/devops/setup/deploy/?repository=https%3A//github.com/sstone1/blockchain-starter-kit&branch=master&env_id=ibm%3Ayp%3Aus-south) to create your DevOps toolchain. 251 | 252 | 2. Enter a name for your toolchain. Make it unique! 253 | 254 | **Note:** If you haven't authenticated with GitHub in IBM Cloud before, you will need to do this before you will be able to create the Blockchain Starter Kit. You can do this now by scrolling down on this screen and selecting the authenticate button. 255 | 256 | ![Enter a name for your toolchain.](screenshots/starterkittoolchain.png) 257 | 258 | 3. Scroll down and create a unique repository name where your artifacts will be stored on GitHub, **XXX-blockchain-toolkit** where XXX are your initials. 259 | 260 | 4. Click **Create**. 261 | ![Create a uniquire repository name and select Create.](screenshots/repositorycreate.png) 262 | 263 | 5. If you've used the Delivery Pipeline before, then skip this step. Otherwise, complete the required fields to setup your delivery pipeline. 264 | 265 | * Click on **Create +** to generate an IBM Cloud API Key 266 | 267 | * Enter a unique name for your blockchain service, e.g. xxx-iot-asset-blockchain-service. 268 | 269 | * Click **Create** to begin working with your toolchain. 270 | 271 | ![Create your IBM Cloud API Key.](screenshots/createkey.png) 272 | 273 | 6. Congratulations! You have a complete toolchain that can be used to deploy your code. 274 | ![Complete blockchain-starter-kit toolchain.](screenshots/completetoolchain.png) 275 | 276 | The "GitHub" button in the middle will take you to your newly created GitHub repository. You will clone this GitHub repository into your local development environment, so you can work on your blockchain application. 277 | 278 | The "Delivery Pipeline" button on the right will take you to the delivery pipeline for your DevOps toolchain. From here, you can inspect the output from the latest automated build and deployment of your blockchain application. 279 | 280 | #### Install Hyperledger Composer locally 281 | To deploy our code, we'll need to work with some of the Hyperledger Composer commands on our system. 282 | 283 | 1. Follow the [directions](https://hyperledger.github.io/composer/latest/installing/installing-index) for installing the prerequisites and installing Hyperledger Composer. 284 | * Only complete Step 1 and Step 2 of installing Hyperledger Composer for this exercise. 285 | * If you've previously had Hyperledger Composer installed, follow these directions for [updating Hyperledger Composer](https://hyperledger.github.io/composer/v0.16/managing/updating-composer). Complete both steps. 286 | 287 | #### Prepare your code for deployment 288 | 1. In your toolchain, select the **GitHub** icon to open your newly created repository. 289 | ![Select GitHub.](screenshots/gotogithub.png) 290 | 291 | 2. In GitHub, click **Clone or download** and then the **copy** button to get the URL to use to clone your repository to your local system. 292 | ![Select Clone and then copy.](screenshots/clonegithub.png) 293 | 294 | 3. In a terminal on your local system, enter `git clone ` where \ is the value you copied in the previous step. 295 | ``` 296 | $ git clone https://github.com/SweetJenn23/XXX-blockchain-starter-kit.git 297 | Cloning into 'XXX-blockchain-starter-kit'... 298 | remote: Counting objects: 40, done. 299 | remote: Compressing objects: 100% (35/35), done. 300 | remote: Total 40 (delta 2), reused 40 (delta 2), pack-reused 0 301 | Unpacking objects: 100% (40/40), done. 302 | ``` 303 | 304 | 4. Move into the contracts directory, `cd XXX-blockchain-starter-kit/contracts` where XXX are your initials. 305 | 306 | 5. We need to make a smart contract from our blockchain network (.bna). To do this we will use one of the tools installed with Hyperledger Composer called Yeoman. This will create a smart contract skeleton we can deploy to Hyperledger Fabric. We will have to copy our work into this skeleton. 307 | 308 | To make the skeleton, type `yo` into a terminal on your local system and create a business network named **XXX-perishable-network** where XXX are your initials. Complete the rest of the information in the prompts. 309 | ![Enter the information for Yeoman to create a skeleton smart contract.](screenshots/yo.png) 310 | 311 | 6. To copy our code into the skeleton you'll need to expand the business network archive. 312 | * In your terminal, change directory to where your **perishable-network.bna** is saved. 313 | * Change the extension on the file. `mv perishable-network.bna perishable-network.zip` 314 | * Unzip the file. `unzip perishable-network.zip` 315 | ``` 316 | > unzip perishable-network.zip 317 | Archive: perishable-network.zip 318 | extracting: package.json 319 | extracting: README.md 320 | extracting: permissions.acl 321 | creating: models/ 322 | extracting: models/perishable.cto 323 | creating: lib/ 324 | extracting: lib/logic.js 325 | ``` 326 | 7. Copy the extracted files into your cloned GitHub directory, */XXX-blockchain-starter-kit/contracts/xxx-perishable-network*. Replace the files already in the *xxx-perishable-network* directory with the same name. Do this for all of the following files: 327 | * remove `/xxx-blockchain-starter-kit/contracts/xxx-perisable-network/models/org.acme.biznet.perishable.cto` 328 | * copy `perishable-network/README.md` to `XXX-blockchain-starter-kit/contracts/xxx-perishable-network` 329 | * copy `perishable-network/permissions.acl` to `XXX-blockchain-starter-kit/contracts/xxx-perishable-network` 330 | * copy `perishable-network/models/perishable.cto` to `XXX-blockchain-starter-kit/contracts/xxx-perishable-network/models/` 331 | * copy `perishable-network/lib/logic.js` to `XXX-blockchain-starter-kit/contracts/xxx-perishable-network/lib/` 332 | ![Move the contents of your perishable-network.bna into the generated skeleton.](screenshots/movecontents.png) 333 | 334 | 8. In your repository edit the file, **~/XXX-blockchain-starter-kit/.bluemix/pipeline-BUILD.sh** 335 | 336 | * Find **function test_composer_contract** 337 | 338 | * In the function comment out the line **npm test**, `#npm test` 339 | ``` 340 | function test_composer_contract { 341 | CONTRACT=$1 342 | echo testing composer contract ${CONTRACT} 343 | pushd contracts/${CONTRACT} 344 | npm install 345 | #npm test 346 | rm -rf node_modules 347 | popd 348 | } 349 | ``` 350 | 351 | 9. To commit the code to your repository on GitHub for the toolchain you'll need to use the following in a terminal in your **XXX-blockchain-starter-kit** directory: 352 | * `git add -A` 353 | * `git commit -m "Update files"` 354 | * `git push` 355 | ![Run these git commands.](screenshots/gitcommit.png) 356 | 357 | 358 | #### Verify deployment 359 | When you committed your code to GitHub, the DevOps toolchain automatically picked up the changes. The toolchain will immediately begin deploying those changes. 360 | 361 | 1. Navigate to the DevOps toolchain page, and click on the "**Delivery Pipeline**" button. You should see the following page, giving you an overview of the current status of your delivery pipeline: 362 | ![Successful deployment.](screenshots/deploypassed.png) 363 | 364 | 2. The delivery pipeline is made up of two phases, "BUILD" and "DEPLOY". 365 | 366 | The "BUILD" phase of the delivery pipeline clones your GitHub repository, installs any dependencies, and runs all of the automated unit tests for all of your smart contracts. If any unit tests fail, then the delivery pipeline will fail and your changes will not be deployed. 367 | 368 | The "DEPLOY" phase of the delivery pipeline deploys your smart contracts into the IBM Cloud. It is reponsible for provisioning and configuring an instance of the IBM Blockchain Platform: Starter Plan (the blockchain network), an instance of Cloudant (the wallet for blockchain credentials), deploying the smart contracts, and deploying RESTful API servers for each deployed smart contract. 369 | 370 | If you click "View logs and history", you can see the latest logs for your build: 371 | ![View your logs.](screenshots/toolchainlog.png) 372 | 373 | Both "BUILD" and "DELIVERY" phases should be green and showing that no errors have occurred. If this is not the case, you must use the logs to investigate the cause of the errors. 374 | 375 | ### Working with the REST API 376 | 377 | To manipulate the blockchain from Node-RED, we will expose the perishable-network business network using the Hyperledger Composer REST API. Currently, this starter kit does not deploy a RESTful API server for smart contracts developed using Hyperledger Fabric. Since we used Hyperledger Composer, the DevOps toolchain has automatically deployed a RESTful API server for each deployed smart contract. You can use these RESTful APIs to build end user applications that interact with a smart contract. 378 | 379 | 1. The URLs for the deployed RESTful API servers are available in the logs for the "DELIVERY" phase, but you can also find them in the [IBM Cloud Dashboard](https://console.bluemix.net/dashboard/apps). The RESTful API server is deployed as an application, with a name made up of "composer-rest-server-" and the name of the smart contract. Ours are called **composer-rest-server-xxx-perishable-network**. 380 | ![Find your composer-rest-server in the IBM Cloud Dashboard.](screenshots/composer-rest-server.png) 381 | 382 | 2. Click on the rest server to navigate to the application details page. 383 | ![View the rest server application details.](screenshots/restserverdetails.png) 384 | 385 | 3. Select the **Visit App URL** to view your API. 386 | ![Select Visit App URL.](screenshots/VisitAppURL.png) 387 | 388 | 4. These APIs are how Node-RED will communicate with blockchain. 389 | ![View your APIs.](screenshots/API.png) 390 | 391 | Congratulations! You have completed the Blockchain section of the workshop. Proceed to the [Node-RED section](../Node-RED/README.md) which will leverage the REST API you just enabled to write / read / visualize IoT Asset environmental sensor data to the transaction history. 392 | 393 | 394 | 395 | ### Removing instances from IBM Cloud 396 | 397 | 1. From the [IBM Cloud Dashboard](https://console.bluemix.net/dashboard/apps) **stop** and **delete** the following services. 398 | 399 | * Under Cloud Foundry Applications: **composer-rest-server-xxx-perishable-network** 400 | 401 | ![Stop your app and then delete your app.](screenshots/stopanddeleteapp.png) 402 | 403 | * Under Cloud Foundry Services: 404 | 405 | * Your toolchain: **iot-asset-blockchain-starter-kit** (you were told to give this a unique name, so your name may differ.) 406 | * Your blockchain starter plan: **xxx-iot-asset-blockchain-service** 407 | 408 | -------------------------------------------------------------------------------- /Blockchain/iot-asset-tracker-network.bna: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/iot-asset-tracker-network.bna -------------------------------------------------------------------------------- /Blockchain/screenshots/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/.DS_Store -------------------------------------------------------------------------------- /Blockchain/screenshots/API.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/API.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Authorize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Authorize.png -------------------------------------------------------------------------------- /Blockchain/screenshots/AuthorizeCloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/AuthorizeCloud.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Breadcrumbs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Breadcrumbs.png -------------------------------------------------------------------------------- /Blockchain/screenshots/ConnectNow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/ConnectNow.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Create.png -------------------------------------------------------------------------------- /Blockchain/screenshots/DeliveryPipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/DeliveryPipeline.png -------------------------------------------------------------------------------- /Blockchain/screenshots/DeployMarbles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/DeployMarbles.png -------------------------------------------------------------------------------- /Blockchain/screenshots/DeploymentProgress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/DeploymentProgress.png -------------------------------------------------------------------------------- /Blockchain/screenshots/DevOps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/DevOps.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Guided.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Guided.png -------------------------------------------------------------------------------- /Blockchain/screenshots/MarblesToolchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/MarblesToolchain.png -------------------------------------------------------------------------------- /Blockchain/screenshots/MarblesUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/MarblesUI.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Passed.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Perishable-Network-BNA-ConnectNow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Perishable-Network-BNA-ConnectNow.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Perishable-Network-BNA-Model-update-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Perishable-Network-BNA-Model-update-annotated.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Perishable-Network-BNA-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Perishable-Network-BNA-annotated.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Perishable-Network-BNA-creds-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Perishable-Network-BNA-creds-annotated.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Perishable-Network-REST-API-swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Perishable-Network-REST-API-swagger.png -------------------------------------------------------------------------------- /Blockchain/screenshots/RepoName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/RepoName.png -------------------------------------------------------------------------------- /Blockchain/screenshots/SeeApp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/SeeApp.png -------------------------------------------------------------------------------- /Blockchain/screenshots/SelectGitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/SelectGitHub.png -------------------------------------------------------------------------------- /Blockchain/screenshots/SetupDemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/SetupDemo.png -------------------------------------------------------------------------------- /Blockchain/screenshots/SubmitTransaction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/SubmitTransaction.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Success.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Test.png -------------------------------------------------------------------------------- /Blockchain/screenshots/ToolChainName.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/ToolChainName.png -------------------------------------------------------------------------------- /Blockchain/screenshots/TrySamples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/TrySamples.png -------------------------------------------------------------------------------- /Blockchain/screenshots/Update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/Update.png -------------------------------------------------------------------------------- /Blockchain/screenshots/ViewLogs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/ViewLogs.png -------------------------------------------------------------------------------- /Blockchain/screenshots/VisitAppURL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/VisitAppURL.png -------------------------------------------------------------------------------- /Blockchain/screenshots/clonegithub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/clonegithub.png -------------------------------------------------------------------------------- /Blockchain/screenshots/completetoolchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/completetoolchain.png -------------------------------------------------------------------------------- /Blockchain/screenshots/composer-rest-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/composer-rest-server.png -------------------------------------------------------------------------------- /Blockchain/screenshots/createkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/createkey.png -------------------------------------------------------------------------------- /Blockchain/screenshots/deploynew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/deploynew.png -------------------------------------------------------------------------------- /Blockchain/screenshots/deploypassed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/deploypassed.png -------------------------------------------------------------------------------- /Blockchain/screenshots/developcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/developcode.png -------------------------------------------------------------------------------- /Blockchain/screenshots/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/example.png -------------------------------------------------------------------------------- /Blockchain/screenshots/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/export.png -------------------------------------------------------------------------------- /Blockchain/screenshots/gitcommit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/gitcommit.png -------------------------------------------------------------------------------- /Blockchain/screenshots/gotogithub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/gotogithub.png -------------------------------------------------------------------------------- /Blockchain/screenshots/launchnow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/launchnow.png -------------------------------------------------------------------------------- /Blockchain/screenshots/menubar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/menubar.png -------------------------------------------------------------------------------- /Blockchain/screenshots/movecontents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/movecontents.png -------------------------------------------------------------------------------- /Blockchain/screenshots/npmsample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/npmsample.png -------------------------------------------------------------------------------- /Blockchain/screenshots/repositorycreate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/repositorycreate.png -------------------------------------------------------------------------------- /Blockchain/screenshots/restserverdetails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/restserverdetails.png -------------------------------------------------------------------------------- /Blockchain/screenshots/savebna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/savebna.png -------------------------------------------------------------------------------- /Blockchain/screenshots/starterkittoolchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/starterkittoolchain.png -------------------------------------------------------------------------------- /Blockchain/screenshots/stopanddeleteapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/stopanddeleteapp.png -------------------------------------------------------------------------------- /Blockchain/screenshots/toolchainlog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/toolchainlog.png -------------------------------------------------------------------------------- /Blockchain/screenshots/webplayground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/webplayground.png -------------------------------------------------------------------------------- /Blockchain/screenshots/yo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Blockchain/screenshots/yo.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Node-RED/README.md: -------------------------------------------------------------------------------- 1 | # Node-RED - IoT Asset Tracker 2 | ## Node-RED - IoT Asset Tracker Introduction 3 | 4 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-dashboard-AssetTracker-PR.png) 5 | 6 | These Node-RED program flows implement an IoT Asset Tracker that receives geolocation and environmental sensor data from a Particle Electron, stores that information in a Hyperledger Fabric blockchain and visualizes the routes of the IoT devices on a map / dashboard. It triggers alerts when environmental thresholds are exceeded. 7 | 8 | During this workshop you will copy the flow from github and deploy it into your to Watson IoT / Node-RED Starter application running on IBM Cloud. 9 | 10 | These seven flows perform the following functions: 11 | 12 | * **Initialize Blockchain and Node-RED variables** - Set some globals that drive all of the flows. 13 | * **Control a Particle Electron** - A Node-RED Dashboard which enables / disables / configures a Particle Electron. 14 | * **Receive Particle Electron events** - subscribe to Particle Electron event handlers. 15 | * **Write Particle Electron data** to a Hyperledger Fabric Blockchain. 16 | * **Load Blockchain Transaction History** so the IoT device routes can be plotted 17 | * **Build a Dashboard** that controls the visualization of devices. 18 | * **Move a IoT asset along its recorded history** by visualizing the route on a map. 19 | 20 | The dashboards are not intended to be a fancy user experience for end users. These dashboards are demonstrations for a developer of what an IoT Asset Tracker might be capable of. End users are not likely to be interested in geolocation coordinates. 21 | 22 | ## Getting started with Node-RED in the IBM Cloud 23 | Before you can deploy the IoT Asset Tracker dashboard, you need to create an IoT Starter application in the IBM Cloud. This section walks you through those steps. 24 | ### Create an Internet of Things Starter App 25 | * Create an account and log into [IBM Cloud](http://bluemix.net) 26 | * Click on the Catalog **(1)** and search for 'internet of things' **(2)** 27 | * The Internet of Things Platform Starter **(3)** boilerplate is a pattern with pre-assembled services that work together. The Internet of Things Platform Starter includes a Node-RED Node.js web server, Cloudant database to store the flow, and the IoT platform service so you can connect devices. 28 | ![IBM Cloud Node-RED IoT Starter screenshot](screenshots/IBMCloud-Catalog-newstarter-annotated.png) 29 | * Name your application something unique. If you choose myapp, your application will be located at http://myapp.mybluemix.net There can only be one “myapp” application and URL registered in IBM Cloud. 30 | * Give the application a unique name **(4)** - eg. IoTAssetTracker-yourname 31 | * Press the Create button **(5)**. 32 | ![IBM Cloud Node-RED Starter screenshot](screenshots/IBMCloud-NodeRED-CFappcreate.png) 33 | * IBM Cloud will create an application in your account based on the services in the boilerplate. This is called staging an application. It can take a few minutes for this process to complete. While you wait, you can click on the Logs tab and see activity logs from the platform and Node.js runtime. 34 | 35 | ### Launch the IoT Starter Application 36 | Once the Green “Running” icon appears, Click the Visit App URL link **(6)** 37 | ![IBM Cloud Node-RED Starter screenshot](screenshots/IBMCloud-NodeRED-launch.png) 38 | 39 | ### Open the Node-RED visual programming editor 40 | A new browser tab will open to the Node-RED start page. Node-RED is an open-source Node.js application that provides a visual programming editor that makes it easy to wire together flows. Select a username / password to access the Node-RED editor. Remember your username / password. Click the red button. Go to your Node-RED flow editor to launch the editor. 41 | * The Node-RED Visual Programming Editor will open with a default flow. 42 | * On the left side is a palette of nodes that you can drag onto the flow. 43 | * You can wire nodes together to create a program. 44 | * The sample IoT Starter flow is not applicable to this workshop and can be deleted. 45 | * We will import the flows discussed above. 46 | 47 | ### Install Additional Node-RED nodes 48 | The IoT Starter Application deployed into IBM Cloud includes just a small subset of Node-RED nodes. The Node-RED palette can be extended with over one thousand additional nodes for different devices and functionality. These NPM nodes can be browsed at http://flows.nodered.org 49 | 50 | In this Step, you will add the Node-RED Dashboard nodes to your Internet of Things Starter Application. 51 | * Click on the Node-RED Menu **(1)** in the upper right corner, then Manage palette **(2)** 52 | ![IBM Cloud Node-RED Starter screenshot](screenshots/IBMCloud-NodeRED-palette.png) 53 | * Turn to the Install tab **(3)**, type node-red-dashboard **(4)** and press the Install button **(5)**. 54 | ![IBM Cloud Node-RED Starter screenshot](screenshots/IBMCloud-NodeRED-nodeinstall.png) 55 | * Press the Install button in the next dialog. 56 | * Repeat **(4)** to install **node-red-contrib-particle** and **node-red-contrib-web-worldmap** nodes. 57 | 58 | ### Import a prebuilt flow from GitHub 59 | Since configuring Node-RED nodes and wiring them together requires many steps to document in screenshots, there is an easier way to build a flow by importing a prebuilt flow into your IoT Starter Application. 60 | 61 | * Some of the sections below will have a **Get the Code** link. 62 | 63 | * When instructed, open the **Get the Code** github URL, mark or Ctrl-A to select all of the text, and copy the text for the flow to your Clipboard. 64 | * Click on the Node-RED Menu **(6)**, then Import **(7)**, then Clipboard **(8)**. 65 | ![IBM Cloud Node-RED Starter screenshot](screenshots/IBMCloud-NodeRED-import.png) 66 | * Paste the text of the flow into the **Import nodes** dialog and press the red **Import** button. 67 | ![IBM Cloud Node-RED Starter screenshot](screenshots/IBMCloud-NodeRED-pastefromclipboard.png) 68 | * The new flow will be imported into **new tabs** in the Node-RED Editor. 69 | 70 | ## Initialize the Perishable Blockchain / Node-RED flow 71 | ### Introduction 72 | This flow sets several global variables that drive all of the other flows. Instead of hunting through the other flows to modify the Hyperledger Fabric IP address or your Particle Electron Device ID and access token, this flow simply pulls the globals forward and initializes the remaining flows. 73 | 74 | * If you have set up your [Hyperledger Fabric](../Blockchain/README.md), edit the **Set HyperLedgerFabricIP** change node and insert the public IP address. 75 | * If you purchased your own Particle Electron, you will need to know the Particle Device ID and Access Token and insert those details into the **Particle Electron to Monitor** change node. 76 | * If you are participating in a workshop, the instructor will share the Particle Device ID and Access Token in a separate slide (not part of GitHub) 77 | 78 | The Initialization flow then uses Link nodes to most all of the other flows described below to drive the demo and workshop. 79 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-InitPerishableBlockchain.png) 80 | 81 | As a first step, copy the code from GitHub to your Clipboard and import it into your Node-RED editor. 82 | 83 | Get the Code [IoT Asset Tracker Node-RED flows](flows/IoTAssetTracker-AllFlows.json) 84 | 85 | ## Control Particle Electron events 86 | ### Introduction 87 | This flow controls the Particle Electron device configuration. This flow can send a command to enable / disable the device geolocation reporting. It can change the interval of the report. The default is 60 seconds. It also exercises the two query Particle Functions - GetRecentXYZ() and GetCurrTemp(). The acceleration threshold can also be remotely configured through a call to SetXYZThresh(). 88 | 89 | The Particle.io URL function calls use a AccessToken to control a particular device. The AccessToken is set in the InitPerishableBlockchain flow (on the first tab) The instructor will reset this access token after the workshop. 90 | 91 | The inspiration for this dashboard came from Hovig Ohannessian. He wrote a [Particle Core Bluemix article](https://www.ibm.com/blogs/bluemix/2015/05/led-hello-world-with-spark-core-android-bluemix/) 92 | that sets up the params and uses a **http request node** to post the Particle function command. 93 | 94 | I also experimented with the Node-RED Particle Function nodes but I found them inflexible in this use case. The flow could not dynamically set the Particle Device ID or AccessToken. When there are multiple Particle devices selectable from the drop down, the Node-RED Particle Function nodes are hard coded to specific devices. While you might only have one Particle Electron for prototyping an IoT Asset Tracker, when you start a deployment, you might have dozens / hundreds. I would recommend switching to MQTT for production deployments. 95 | 96 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-dashboard-ControlParticleDevice.png) 97 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-ControlParticleDevice.png) 98 | 99 | ## Receive Particle Electron events 100 | ## Introduction 101 | The Particle Receiver flow uses the **node-red-contrib-particle** nodes. The ParticleSSE node lets you subscribe to incoming server-sent events (SSE) on a Particle cloud via a persistent connection. 102 | 103 | There are four ParticleSSE() nodes on this flow. Each needs to be configured with a Device ID and an AccessToken - set by the InitPerishableBlockchain flow. 104 | * The first ParticleSSE() node just confirms that there is a Google Maps geolocation Event enabled and subscribes to the deviceLocator event on a particular Electron. The flow doesn't do anything with this information. It's just a sanity check. 105 | * The bottom ParticleSSE() node experiments with parsing the geolocation event message. The flow doesn't do anything with this information. 106 | * The middle ParticleSSE() nodes subscribe to the AssetTrackerAccelerationEvent and AssetTrackerTemperatureEvent functions of our Particle Electron - review the [WatsonIoTAssetTracker program here](../ParticleElectron/README.md). This information is converted from a string to a JSON object, parsed and reformatted into a msg.payload that is expected for a Blockchain event. If the device GPS coordinates have not moved, discard the event by using a Report By Exception node. This decision was simply because I only care about the environmental sensor conditions of the device in motion (and it was filling my blockchain while sitting on my desk). A real implementation might care about temperature and acceleration events while the device sits in a distribution center parking lot or port. Often the AssetTrackerAccelerationEvent and AssetTrackerTemperatureEvent functions are triggered nearly simultaneously by the deviceLocator callback. The flow staggers the blockchain writes a little to avoid overwhelming the Hyperledger Fabric (which could cause a write failure). 107 | 108 | In summary, this flow takes the arriving data, reformats it and calls the next flow to write the Particle Events to the Hyperledger Perishable Network blockchain transaction history. 109 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-ReceiveParticleEvents.png) 110 | 111 | ## Write Particle Events to Hyperledger Perishable Network Blockchain 112 | ### Introduction 113 | This flow sets up the http parameters to call the Hyperledger Perishable Network REST APIs. Learn about the Hyperledger Perishable Network model in the [Blockchain README](../Blockchain/README.md) section of this workshop / IBM Code pattern. There are six REST API examples in this flow. 114 | * The first REST API calls the SetupDemo API. This only needs to be called once. Buried deep in the Blockchain Model [chaincode logic.js](../Blockchain/IoT-Perishable-Network/logic.js), there is a setupDemo() function where you will need to insert your Particle Device ID as the Shipment ID 115 | * The second section sets up the POST command required to write a Temperature event into the blockchain as a transaction. 116 | * The third section sets up the GET command required to query all of the Temperature transactions on the blockchain. 117 | * The fourth section sets up the POST command required to write an Acceleration event into the blockchain as a transaction. 118 | * The fifth section sets up the GET command required to query all of the Acceleration transactions on the blockchain. 119 | * The sixth section sets up the POST command required to write a geolocation event into the blockchain as a transaction. The flow does not use this transaction history because the Temperature and Acceleration events have geolocation coordinates associated with them. 120 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-WriteParticleEvents2Blockchain.png) 121 | 122 | ## Load Perishable Network Blockchain Transaction History 123 | ### Introduction 124 | The Load Perishable Network Blockchain Transaction History flow starts to become specific to the Asset Tracker dashboard implementation of this program and workshop. It queries the Temperature blockchain transaction history and then merges the Acceleration blockchain transaction history into an **array** that will drive the map. 125 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-LoadBlockchainTransactionHistory.png) 126 | 127 | 128 | ## Build an Asset Tracking Dashboard 129 | ### Introduction 130 | This flow constructs a variety of Node-RED Dashboard UI elements to select and control the movement of the routes that the device shipment (via trucks, cars, ships) took while collecting IoT environmental sensor readings. A Particle Electron asset tracking device might take trips on different days so there is a date picker to narrow the map paths. There is a bunch of flow logic to display pins and geo fences on the map. 131 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-AssetTrackerDashboardControls.png) 132 | 133 | 134 | ## Move the Tracked Device on a Map 135 | ### Introduction 136 | This final flow drives the movement of a selected Particle Electron IoT AssetTracker device during its journey. Every fraction of a second, it advances the device on a map. It filters the array down to the selected device and time frame. There might be better ways to manage these arrays. After a few dozen routes, it probably doesn't scale. It makes for a great demo and workshop. There's always room for enterprise scale improvements. Enjoy! 137 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-flow-AssetTrackerMap.png) 138 | 139 | ## Congratulations! You have completed the Workshop / IBM Code Pattern 140 | You've built an IoT Asset Tracker with environmental sensors that store data in a Hyperledger Blockchain. 141 | ![IoT Asset Tracker Node-RED flow screenshot](screenshots/Node-RED-dashboard-AssetTracker-NJ.png) 142 | -------------------------------------------------------------------------------- /Node-RED/SIMULATOR.md: -------------------------------------------------------------------------------- 1 | # Route Simulator for the Blockchain IoT Asset Tracker 2 | 3 | ## Introduction 4 | 5 | If you have not purchased a Particle Electron but are interested in completing 6 | the [IoT Asset Tracking using Hyperledger Blockchain IBM Code Pattern](https://developer.ibm.com/code/patterns/develop-an-iot-asset-tracking-app-using-blockchain/), you can 7 | use the instructions on this page to create a simulated route and generate 8 | some temperature transactions that will be written to the 9 | IoT Asset Tracker Blockchain history. 10 | 11 | This Node-RED flow replaces the physical Particle Electron. You can 12 | optionally use the technique described below to create your own route and 13 | temperature event thresholds. 14 | 15 | ## Use Case - Simulated Bicycle Ride through New York City Central Park 16 | In the below Node-RED flow, the simulator plots a course around Central Park 17 | New York City. The story might be that you want to use the IoT Asset Tracker 18 | to track a bicycle ride through the park on a nice day. The rough outline is to generate the GPS coordinates, fake timestamps, fake temperature, fake vibration data then play it all into your Blockchain transaction history. 19 | 20 | You can create your own use case stories with simulated routes / simulated data and then play that data into the Blockchain. 21 | 22 | The next section describes the general technique of how you can build your own unique route. If you want to skip creating your own route, jump straight to the subsequent section to import the Bicycle ride through New York Central Park simulated route. 23 | 24 | ### General Technique - Build your own unique route 25 | - Visit the [OnTheGoMap website](https://onthegomap.com/#/create) that allows you to plot a route on a world map. 26 | - Create your route **(1)** using [OnTheGoMap](https://onthegomap.com/#/create) 27 | - Click on the **Menu** in the upper right corner **(2)** 28 | - Select **Export as GPX (3)** 29 | - **Save** the file to your local system. 30 | ![OnTheGoMap Route](screenshots/onthegomap-route.png) 31 | - **Inspect** this xml file. Depending on the length of your route, it will likely contain thousands of geocode waypoints. 32 | - That is way too much accuracy, you don't really want to load thousands of geolocation transactions into the Blockchain. You probably only need a few hundred for demo purposes. 33 | - The Node-RED dashboard that you will build in the final section of the workshop advances the little icon at one geolocation transaction per second. No one wants to watch your demo truck icon creep along a route for dozens of minutes to complete. Slice the data down to maybe 2-3 minutes per route. Determine the appropriate modulo to prune the data. Unix is awesome for this and there are several command line tools that can help you slice the data: 34 | ``` 35 | $ awk '0 == NR % 14'  onthegomap-6.4-mi-route-route.gps 36 | $ sed -n '0-14p'   onthegomap-6.4-mi-route-route.gps 37 | ``` 38 | - Now, we can load those abbreviated geolocation coordinates into the blockchain. 39 | 40 | ## Node-RED Simulated Route flow 41 | 42 | - Copy the code from the GitHub link below into your Clipboard and import it into your Node-RED editor. 43 | 44 | Get the Code [Simulated Route Node-RED flow for IoT Asset Tracker](flows/IoTAssetTracker-SimulatedRoute.json) 45 | 46 | ![Simulated Route flow](screenshots/Node-RED-flow-SimulateRoute.png) 47 | 48 | - Wire the **Link** node **(3)** on this flow to the Blockchain REST API flow **New Tempeature Reading** link node 49 | - Deploy the flow 50 | - Press the **One Time Setup to create the Blockchain Bicycle Shipment** inject (true) node. **(1)** 51 | - The one time setup is required to create the shipment asset 52 | - Either accept the default NYC Central Park route or insert your custom route into the **Paste GPX file into this Template Node** **(2)** 53 | - Last, press the **Inject** true button **(4)**. 54 | - The simulated route geolocation / temperature readings will start to play into your Blockchain transaction history. The rate limit node will slow it down so that transactions are successfully written to the blockchain. 55 | 56 | ## Node-RED Dashboard Asset Tracking 57 | - Turn to the **IoT Asset Dashboard** flow 58 | - Double click on the **Asset IDs to be Tracked** function node **(1)** 59 | - Add the following **(2)** to the msg.options array 60 | ``` 61 | {"Bicycle #1":"34304"}, 62 | ``` 63 | - Deploy the flow 64 | 65 | ![Node-RED flow AssetTrackerDashboardControls fixup](screenshots/Node-RED-flow-AssetTrackerDashboardControls-fixup.png) 66 | 67 | ## Node-RED Dashboard 68 | - Launch the Node-RED dashboard 69 | - Select **Bicycle #1** from the **Select Truck Route** dropdown. 70 | - Turn on the **Track Truck Route** switch 71 | ![Node-RED Dashboard Central Park Route](screenshots/Node-RED-dashboard-AssetTracker-NYC.png) 72 | -------------------------------------------------------------------------------- /Node-RED/flows/IoTAssetTracker-SimulatedRoute.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "24aab329.a0d324", 4 | "type": "tab", 5 | "label": "Simulated Route", 6 | "disabled": false, 7 | "info": "" 8 | }, 9 | { 10 | "id": "40d7609d.85a22", 11 | "type": "inject", 12 | "z": "24aab329.a0d324", 13 | "name": "", 14 | "topic": "", 15 | "payload": "true", 16 | "payloadType": "bool", 17 | "repeat": "", 18 | "crontab": "", 19 | "once": false, 20 | "onceDelay": 0.1, 21 | "x": 130, 22 | "y": 400, 23 | "wires": [ 24 | [ 25 | "7c8d6acb.b9d81c" 26 | ] 27 | ] 28 | }, 29 | { 30 | "id": "7c8d6acb.b9d81c", 31 | "type": "template", 32 | "z": "24aab329.a0d324", 33 | "name": "Paste GPX file into this Template node", 34 | "field": "payload", 35 | "fieldType": "msg", 36 | "format": "handlebars", 37 | "syntax": "plain", 38 | "template": "\n\n \n 6.43 mi route\n On The Go Map\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n", 39 | "output": "str", 40 | "x": 370, 41 | "y": 400, 42 | "wires": [ 43 | [ 44 | "39b8d718.876a7" 45 | ] 46 | ] 47 | }, 48 | { 49 | "id": "eac5bef1.7dfc9", 50 | "type": "debug", 51 | "z": "24aab329.a0d324", 52 | "name": "", 53 | "active": true, 54 | "tosidebar": true, 55 | "console": false, 56 | "tostatus": false, 57 | "complete": "false", 58 | "x": 630, 59 | "y": 540, 60 | "wires": [] 61 | }, 62 | { 63 | "id": "8b794514.171258", 64 | "type": "comment", 65 | "z": "24aab329.a0d324", 66 | "name": "#1 - Visit https://onthegomap.com to map a simulated course", 67 | "info": "", 68 | "x": 440, 69 | "y": 240, 70 | "wires": [] 71 | }, 72 | { 73 | "id": "b663982e.2d6258", 74 | "type": "comment", 75 | "z": "24aab329.a0d324", 76 | "name": "#2 - Export route to a GPX file", 77 | "info": "After plotting an interesting route,\nclick on the menu in the upper right corner \nof the https://onthegomap.com/#/create\nSelect \"Export as GPX\"\nSave as a text file\n\nYou might want to open the txt file and inspect\nthe lat / lon geocoordinates.\n\nIf your route is many thousands of waypoint entries,\nyou will fill a very big blockchain transaction\nhistory. Keep the route to < 500 waypoints", 78 | "x": 340, 79 | "y": 280, 80 | "wires": [] 81 | }, 82 | { 83 | "id": "f5ef19a5.8ae7b", 84 | "type": "comment", 85 | "z": "24aab329.a0d324", 86 | "name": "#3 - Paste the entire GPX text file into the Template node below", 87 | "info": "", 88 | "x": 450, 89 | "y": 320, 90 | "wires": [] 91 | }, 92 | { 93 | "id": "39b8d718.876a7", 94 | "type": "function", 95 | "z": "24aab329.a0d324", 96 | "name": "Extract Route", 97 | "func": "var waypoints = msg.payload.split(/\\r?\\n/) ;\n// the GPX file will contain many\n// \"\n\ngeocoordinates = waypoints.filter(path => path.match(/rtept/));\nvar route = \"\";\n\n//var lat, lon;\n//var geo = [];\n\nfor( x=0; x< geocoordinates.length; x++ ) {\n fields = geocoordinates[x].split('\"');\n// lat = parseFloat(fields[1]);\n// lon = parseFloat(fields[3]);\n// geo.push({lat,lon});\n route = route + fields[1] + \",\"+ fields[3] + \"\\n\";\n}\n//msg.payload = geo; // Array of geocoords\n\n// Send a big string to the CSV node so that\n// the CSV node splits each geocoord into an individual msg\nmsg.payload = route;\n\nreturn msg;", 98 | "outputs": 1, 99 | "noerr": 0, 100 | "x": 660, 101 | "y": 400, 102 | "wires": [ 103 | [ 104 | "5b1d8ecb.804d" 105 | ] 106 | ] 107 | }, 108 | { 109 | "id": "5b1d8ecb.804d", 110 | "type": "csv", 111 | "z": "24aab329.a0d324", 112 | "name": "Split Route", 113 | "sep": ",", 114 | "hdrin": "", 115 | "hdrout": "", 116 | "multi": "one", 117 | "ret": "\\n", 118 | "temp": "lat,lon", 119 | "skip": "0", 120 | "x": 170, 121 | "y": 500, 122 | "wires": [ 123 | [ 124 | "a822eff2.4412c8" 125 | ] 126 | ] 127 | }, 128 | { 129 | "id": "d89e55f8.3b9768", 130 | "type": "function", 131 | "z": "24aab329.a0d324", 132 | "name": "Simulate a Blockchain Temperature Transaction", 133 | "func": "var now = Date.now();\nvar d = new Date(now);\nvar ts = d.getFullYear() + \"-\" + ('0' + (d.getMonth()+1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2)+ \"T\" + d.getHours() + \":\" + d.getMinutes() + \":\" + d.getSeconds()+\"Z\";\n\n\nmsg.payload = { \"AssetID\":\"34304\",\n \"timestamp\": ts ,\n \"Temperature\": {\"Celsius\":Math.floor(Math.round(Math.random()*11)+1) },\n \"gps\": {\"lat\":msg.payload.lat.toString(),\"lon\":msg.payload.lon.toString()}};\n\nreturn msg;", 134 | "outputs": 1, 135 | "noerr": 0, 136 | "x": 280, 137 | "y": 580, 138 | "wires": [ 139 | [ 140 | "eac5bef1.7dfc9", 141 | "e44c8973.1492e8" 142 | ] 143 | ] 144 | }, 145 | { 146 | "id": "a822eff2.4412c8", 147 | "type": "delay", 148 | "z": "24aab329.a0d324", 149 | "name": "", 150 | "pauseType": "rate", 151 | "timeout": "1", 152 | "timeoutUnits": "seconds", 153 | "rate": "1", 154 | "nbRateUnits": "5", 155 | "rateUnits": "second", 156 | "randomFirst": "1", 157 | "randomLast": "5", 158 | "randomUnits": "seconds", 159 | "drop": false, 160 | "x": 380, 161 | "y": 500, 162 | "wires": [ 163 | [ 164 | "d89e55f8.3b9768" 165 | ] 166 | ] 167 | }, 168 | { 169 | "id": "307fb704.7e2078", 170 | "type": "comment", 171 | "z": "24aab329.a0d324", 172 | "name": "This example is a bike route through New York City Central Park", 173 | "info": "", 174 | "x": 450, 175 | "y": 360, 176 | "wires": [] 177 | }, 178 | { 179 | "id": "22ea9728.739a6", 180 | "type": "comment", 181 | "z": "24aab329.a0d324", 182 | "name": "Configure this link node to the \"Blockchain REST API\" flow tab and \"Build a TemperatureReading Transaction\" link node", 183 | "info": "", 184 | "x": 520, 185 | "y": 620, 186 | "wires": [] 187 | }, 188 | { 189 | "id": "e44c8973.1492e8", 190 | "type": "link out", 191 | "z": "24aab329.a0d324", 192 | "name": "", 193 | "links": [ 194 | "ae534d7a.bba95" 195 | ], 196 | "x": 580, 197 | "y": 580, 198 | "wires": [] 199 | }, 200 | { 201 | "id": "3f2b2e52.781ba2", 202 | "type": "debug", 203 | "z": "24aab329.a0d324", 204 | "name": "Blockchain Asset response", 205 | "active": true, 206 | "console": "false", 207 | "complete": "true", 208 | "x": 940, 209 | "y": 160, 210 | "wires": [] 211 | }, 212 | { 213 | "id": "e05835b2.d9e828", 214 | "type": "debug", 215 | "z": "24aab329.a0d324", 216 | "name": "Add Bicycle Asset", 217 | "active": true, 218 | "tosidebar": true, 219 | "console": false, 220 | "tostatus": false, 221 | "complete": "true", 222 | "x": 590, 223 | "y": 120, 224 | "wires": [] 225 | }, 226 | { 227 | "id": "b97ba68b.6aa838", 228 | "type": "http request", 229 | "z": "24aab329.a0d324", 230 | "name": "hyperledger - Create Bicycle Asset", 231 | "method": "POST", 232 | "ret": "obj", 233 | "url": "", 234 | "tls": "", 235 | "x": 640, 236 | "y": 160, 237 | "wires": [ 238 | [ 239 | "3f2b2e52.781ba2" 240 | ] 241 | ] 242 | }, 243 | { 244 | "id": "8201843c.bd7f9", 245 | "type": "comment", 246 | "z": "24aab329.a0d324", 247 | "name": "One Time Setup to create the Blockchain Bicycle Shipment", 248 | "info": "Remember to edit the Blockchain Model chaincode (logic.js) \nIn the setupDemo() function, insert your Particle Device ID\nas the Shipment ID", 249 | "x": 270, 250 | "y": 100, 251 | "wires": [] 252 | }, 253 | { 254 | "id": "92dccf1.dbc19b", 255 | "type": "inject", 256 | "z": "24aab329.a0d324", 257 | "name": "", 258 | "topic": "", 259 | "payload": "true", 260 | "payloadType": "bool", 261 | "repeat": "", 262 | "crontab": "", 263 | "once": false, 264 | "onceDelay": 0.1, 265 | "x": 130, 266 | "y": 160, 267 | "wires": [ 268 | [ 269 | "c06feddc.e111" 270 | ] 271 | ] 272 | }, 273 | { 274 | "id": "c06feddc.e111", 275 | "type": "function", 276 | "z": "24aab329.a0d324", 277 | "name": "Create Bicycle Shipment Asset", 278 | "func": "// A Shipment Blockchain transaction looks like this:\n// {\n// \"$class\": \"org.acme.shipping.perishable.Shipment\",\n// \"shipmentId\": \"34304\",\n// \"type\": \"MEDICINE\",\n// \"status\": \"IN_TRANSIT\",\n// \"unitCount\": 100,\n// \"contract\": \"resource:org.acme.shipping.perishable.Contract#CON_002\"\n// }\n\nvar FabricIP=global.get(\"HyperLedgerFabricIP\");\n\nmsg.payload = {\n \"$class\": \"org.acme.shipping.perishable.Shipment\",\n \"shipmentId\": \"34304\",\n \"type\": \"MEDICINE\",\n \"status\": \"IN_TRANSIT\",\n \"unitCount\": 100,\n \"contract\": \"resource:org.acme.shipping.perishable.Contract#CON_002\"\n};\nShipmentMsg = JSON.stringify(msg.payload);\n\nmsg.url = \"http://\"+FabricIP+\"/api/Shipment?data=\"+ShipmentMsg;\nmsg.headers = {\n \"Content-Type\":\"application/json\",\n \"Accept\":\"application/json\"\n};\n\nreturn msg;", 279 | "outputs": 1, 280 | "noerr": 0, 281 | "x": 330, 282 | "y": 160, 283 | "wires": [ 284 | [ 285 | "b97ba68b.6aa838", 286 | "e05835b2.d9e828" 287 | ] 288 | ] 289 | } 290 | ] 291 | -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-Catalog-newstarter-annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-Catalog-newstarter-annotated.png -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-NodeRED-CFappcreate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-NodeRED-CFappcreate.png -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-NodeRED-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-NodeRED-import.png -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-NodeRED-launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-NodeRED-launch.png -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-NodeRED-nodeinstall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-NodeRED-nodeinstall.png -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-NodeRED-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-NodeRED-palette.png -------------------------------------------------------------------------------- /Node-RED/screenshots/IBMCloud-NodeRED-pastefromclipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/IBMCloud-NodeRED-pastefromclipboard.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-dashboard-AssetTracker-NJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-dashboard-AssetTracker-NJ.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-dashboard-AssetTracker-NYC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-dashboard-AssetTracker-NYC.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-dashboard-AssetTracker-PR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-dashboard-AssetTracker-PR.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-dashboard-ControlParticleDevice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-dashboard-ControlParticleDevice.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-AssetTrackerDashboardControls-fixup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-AssetTrackerDashboardControls-fixup.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-AssetTrackerDashboardControls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-AssetTrackerDashboardControls.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-AssetTrackerMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-AssetTrackerMap.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-ControlParticleDevice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-ControlParticleDevice.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-InitPerishableBlockchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-InitPerishableBlockchain.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-LoadBlockchainTransactionHistory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-LoadBlockchainTransactionHistory.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-ReceiveParticleEvents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-ReceiveParticleEvents.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-SimulateRoute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-SimulateRoute.png -------------------------------------------------------------------------------- /Node-RED/screenshots/Node-RED-flow-WriteParticleEvents2Blockchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/Node-RED-flow-WriteParticleEvents2Blockchain.png -------------------------------------------------------------------------------- /Node-RED/screenshots/onthegomap-route.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/Node-RED/screenshots/onthegomap-route.png -------------------------------------------------------------------------------- /ParticleElectron/README.md: -------------------------------------------------------------------------------- 1 | # Particle Electron Asset Tracker 2 | The Particle Electron Asset Tracker can be used to collect environmental sensor data, calculate its location using GPS or Cellular triangulation and send both data events to the Particle.io console using Particle Functions. The power of knowing **Where, What and When** physical things were subjected to harsh environmental conditions can improve many business processes. 3 | 4 | In this section we will review : 5 | * Particle Electron Asset Tracker hardware configuration 6 | * Particle software toolchain 7 | * A Watson IoT Asset Tracker program to query and report temperature and accelerometer data to the cloud. 8 | 9 | ## Particle Electron Asset Tracker v2 10 | ![Electron Asset Tracker](https://docs.particle.io/assets/images/shields/asset-tracker-shield-v2/asset.png "Particle Electron picture") 11 | 12 | The [Particle Electron Asset Tracker v2](https://store.particle.io/products/asset-tracker) board allows you to connect a Particle Electron with its uBlox M8 GNSS GPS receiver and Adafruit LIS3DH Triple Axis Accelerometer. You can connect Grove Sensors to it as well. 13 | 14 | ## Particle Electron software toolchain 15 | 1. Set up your Particle.io Account at http://login.particle.io/ 16 | 2. Activate your Particle Electron and give it a name. I called my Electron - blockchain-iot-asset-tracker1 17 | 3. Activate the SIM 18 | 4. After I registered my Particle Electron, it appeared in the [Particle Devices Console](https://console.particle.io/devices) 19 | 5. Particle.io provides a [Particle Build WebIDE](https://docs.particle.io/guide/getting-started/build/core/) or a CLI command line toolchain. 20 | 6. I spent some time learning the [WebIDE](https://build.particle.io/) by playing with the samples: 21 | * Wire a LED to the breadboard using the blink-led program 22 | * Web Connected LED sample app 23 | * I modified the Cloud2LED.bin to send LED on/off from Particle cloud down to Electron. I modified it to support case insensitive "on/off" and "ON/OFF" - baby steps! 24 | 7. It's worth reading about [Particle Functions](https://docs.particle.io/reference/firmware/core/#particle-function-) so that you can publish and subscribe to events. 25 | 8. Since I prefer a command line interface over a Web IDE, I installed the [Particle CLI]( https://docs.particle.io/guide/tools-and-features/cli/electron/) by following the guide. 26 | ``` 27 | $ particle login 28 | $ particle update 29 | ``` 30 | The [firmware update](https://docs.particle.io/guide/tools-and-features/firmware-manager/electron/) was important to get the Google Maps geolocation device locator working. My Particle Electron was factory installed with v0.4.9, once I upgraded to v0.6.4, the Google Maps function finally worked. 31 | 32 | 9. The next step was to learn about the [$ particle compile]( https://docs.particle.io/reference/cli/#particle-compile) command 33 | ``` 34 | $ particle compile 35 | ``` 36 | In my case: 37 | ``` 38 | $ particle compile electron WatsonIoTAssetTracker --saveTo WatsonIoTAssetTracker.bin 39 | ``` 40 | 10. There are lots of Arduino / [Particle libraries](https://docs.particle.io/guide/tools-and-features/libraries/) which you can include in your Particle projects. 41 | ``` 42 | $ particle library list 43 | $ particle library view AssetTracker 44 | $ particle library view OneWire 45 | ``` 46 | 11. To flash your Particle Electron, you need to install [dfu-util](https://docs.particle.io/faq/particle-tools/installing-dfu-util/core/). 47 | I grabbed a copy of *dfu-util-0.9-1.fc25.x86_64.rpm* from the Fedora 25 repo. 48 | 12. Hold down both the Reset and Mode buttons on the Electron 49 | * Release the Reset button 50 | * Wait for the LED to turn Yellow 51 | * Release the Mode button 52 | 13. Finally flash your program to the board using the next command 53 | ``` 54 | $ particle flash --usb firmware.bin 55 | ``` 56 | In my case: 57 | ``` 58 | $ particle flash --usb WatsonIoTAssetTracker.bin 59 | ``` 60 | 14. To watch what the program is doing, you can set up the USB cable to monitor the [Particle serial](https://docs.particle.io/reference/firmware/photon/#serial) output. I found that rickkas7 wrote a great [Particle serial tutorial](https://github.com/rickkas7/serial_tutorial) 61 | ``` 62 | $ particle serial monitor 63 | ``` 64 | 65 | ## Watson IoT Asset Tracker program 66 | Let's review the Watson IoT Asset Tracker program implementation found in this repository. It queries and reports temperature and accelerometer data to the cloud along with its location. 67 | 68 | Grab the [WatsonIoTAssetTracker.ino](WatsonIoTAssetTracker.ino) code in this repo to follow along in the review. This is a [Particle Simple project](https://docs.particle.io/guide/tools-and-features/libraries/#project-file-structure) so you will also need to download the [project.properties](project.properties) file from the repo. It includes a list of libraries necessary for the program to compile. 69 | 70 | ### ```void setup()``` 71 | 72 | The most interesting aspect of this Arduino program is that in ```setup()``` I declare four Particle functions to send and query temperature and accelerometer sensor data. 73 | ``` C 74 | // Declare a Particle.function so that we can adjust the Asset Tracking on and off reporting interval from the cloud. 75 | Particle.function("SetInterval",AssetTrackerSetReportInterval); 76 | 77 | // Declare a Particle.function so that we can query the current temperature from the cloud. 78 | Particle.function("GetCurrTemp",AssetTrackerGetCurrentTemp); 79 | 80 | // Declare a Particle.function so that we can adjust the accelerometer threshold from the cloud. 81 | Particle.function("SetXYZThresh",AssetTrackerSetAccelThresh); 82 | 83 | // Declare a Particle.function so that we can query recent accelerometer data from the cloud. 84 | Particle.function("GetRecentXYZ",AssetTrackerGetRecentAccel); 85 | ``` 86 | 87 | The next most interesting thing about the program is that it uses the [Google Maps Locator API](https://docs.particle.io/tutorials/integrations/google-maps/) to triangulate its geo location based on Cellular tower signal strength. Google knows where the cell towers are (hint - they don't move and are cemented to the ground) and the RSSI strength of the signals from the towers to your thing, its a math calculation to approximate your things' location. Nice that the Particle Electron has a SIM card and communicates over the cellular network. While it's great to use the GPS on the Asset Tracker board, often the thing you want to track is deep in a truck or a car or ship without clear line of sight to the Global Navigation Satellite System - rendering GPS useless. The GPS chipset also draws substantial battery power that would be better used to increase the tracker's time between charges. In truth, cellular triangulation is not as accurate as GPS. For most cases of answering "where's the thing" as it drives down the highway, a few hundred meter radius is plenty good enough. I got myself a [Google Maps API Key](https://developers.google.com/maps/documentation/geolocation/get-api-key) It uses the CellularHelper library. 88 | ``` 89 | $ particle library view CellularHelper 90 | $ particle library view google-maps-device-locator 91 | ``` 92 | ### Functions 93 | The program proceeds to implement the four Particle Function callbacks and the helper functions to query the accelerometer and temperature sensors. 94 | 95 | Of note, while it would have been a much cleaner implementation, I could not get the [Grove Temperature sensor](http://wiki.seeed.cc/Grove-Temperature_Sensor_V1.2/) to work with the Grove connectors on the AssetTracker board. I ordered some good old Dallas DS18B20 temperature sensors. I wired one of them (and a resistor) to my Particle Electron following these [tutorial instructions](https://docs.particle.io/tutorials/projects/maker-kit/#tutorial-4-temperature-logger). It uses the OneWire library. 96 | 97 | ### Program Logic 98 | The program wakes up periodically on an interval you set to determine its location. That triggers a DeviceLocator event. After querying cellular tower signal strengths and sending the RSSI data to the Google Maps API, it responds back to the board with geolocation latitude / longitude / uncertainty coordinates. When the program gets the geolocation, it reads the temperature and checks if the accelerometer has exceeded a motion threshold. It then sends three datapoints - **WHERE, WHAT and WHEN** - to the cloud via the Particle callback function *AssetTrackerLocationCallback()* The combination of that information is the basis of an **IoT Asset Tracker**. 99 | 100 | Put it all together and it looks like this. 101 | ![WatsonIoTAssetTracker board](screenshots/ParticleElectronAssetTracker-IoT.jpg) 102 | 103 | It also fits nicely into the provided Particle Asset Tracker case: 104 | ![WatsonIoTAssetTracker case](screenshots/ParticleElectronAssetTracker-in-Case.jpg) 105 | 106 | 107 | ### Enabling your Cloud Programs to intercept your Particle Function callbacks 108 | You'll be all giddy the first time data arrives in the Particle Console. Here's a screenshot of temperature data, accelerometer data and geolocation data arriving in the [Particle.io](https://console.particle.io/devices) cloud. 109 | ![ParticleConsoleDeviceEvents screenshot](screenshots/ParticleConsoleDeviceEvents.png "Particle Console Device Event screenshot") 110 | 111 | What you really want to do is send that data somewhere else. In my case, I want to route the IoT data to the IBM Cloud and Node-RED for storage in a Hyperledger Fabric blockchain. Jump to the next [section](../Node-RED/README.md). 112 | 113 | Before you go, you'll need a Particle token to authorize your Node-RED program to observe / subscribe to these sensor events. 114 | ``` 115 | $ particle token list 116 | $ particle token create 117 | ``` 118 | You can see the data in your terminal 119 | ``` 120 | $ curl https://api.particle.io/v1/devices/events?access_token= 121 | ``` 122 | -------------------------------------------------------------------------------- /ParticleElectron/WatsonIoTAssetTracker.ino: -------------------------------------------------------------------------------- 1 | // Library dependencies to be added to the Particle Simple project.properties 2 | // dependencies.Adafruit_LIS3DH=1.0.3 3 | // dependencies.Adafruit_GPS=1.0.3 4 | // dependencies.AssetTracker=0.0.10 5 | // dependencies.CellularHelper=0.0.4 6 | // dependencies.OneWire=2.0.1 7 | // dependencies.Adafruit_Sensor=1.0.2 8 | // dependencies.google-maps-device-locator=0.0.4 9 | #include 10 | #include 11 | #include 12 | 13 | // Dallas DS18B20 initialization 14 | OneWire ds = OneWire(D4); // 1-wire signal on pin D4 15 | float lastTemp; 16 | 17 | // Adafruit LIS3DH Triple Axis Accelerometer on the Particle Asset Tracker v2 PCB 18 | // Accelerometer Threshold to trigger a publish 19 | // 9000 is very sensitive, 12000 will detect small bumps 20 | int AccelThreshold = 12000; 21 | String MaxAccelJSON; 22 | int MaxAccelThisInterval = 0; 23 | 24 | // Creating an AssetTracker named 't' 25 | GoogleMapsDeviceLocator locator; 26 | AssetTracker t = AssetTracker(); 27 | int ledonboard = D7; // Light the onboard Particle LED when in Tracking mode 28 | 29 | void setup() { 30 | // Sets up all the necessary AssetTracker bits 31 | t.begin(); 32 | 33 | Serial.begin(9600); 34 | 35 | // Declare a Particle.function so that we can adjust the Asset Tracking on and off reporting interval from the cloud. 36 | Particle.function("SetInterval",AssetTrackerSetReportInterval); 37 | 38 | // Declare a Particle.function so that we can query the current temperature from the cloud. 39 | Particle.function("GetCurrTemp",AssetTrackerGetCurrentTemp); 40 | 41 | // Declare a Particle.function so that we can adjust the accelerometer threshold from the cloud. 42 | Particle.function("SetXYZThresh",AssetTrackerSetAccelThresh); 43 | 44 | // Declare a Particle.function so that we can query recent accelerometer data from the cloud. 45 | Particle.function("GetRecentXYZ",AssetTrackerGetRecentAccel); 46 | 47 | 48 | // Set the returned location handler function by the locationCallback() method 49 | // Initialize the Asset Tracker to check in once a Day 60*60*24 50 | locator.withSubscribe(AssetTrackerLocationCallback).withLocatePeriodic(86400); 51 | 52 | // Give this Electron a name so we can identify it 53 | // locator.withEventName("blockchain-iot-asset-tracker1"); 54 | // locator.withLocatePeriodic(60); 55 | 56 | // LED pin configuration 57 | pinMode(ledonboard, OUTPUT); 58 | 59 | // Turn off LED on the Particle board when the application starts 60 | digitalWrite(ledonboard, LOW); 61 | } 62 | 63 | 64 | // Remotely change the accelerometer trigger threshold 65 | int AssetTrackerSetAccelThresh( String command ) { 66 | // Try to convert Srting to an integer 67 | int NewAccelThreshold = command.toInt(); 68 | // Disgard any non-integer command strings sent from the cloud 69 | if ( NewAccelThreshold > 0) { 70 | AccelThreshold = NewAccelThreshold; 71 | Serial.print("Accelerometer Threshold now set to : "); 72 | Serial.println(command); 73 | return 1; 74 | } 75 | // Keep the predefined Threshold if function received garbage 76 | return 0; 77 | } 78 | 79 | 80 | int AssetTrackerSetReportInterval( String command ) { 81 | /* Particle.functions always take a string as an argument and return an integer. 82 | Since we can pass a string, it means that we can give the program commands on how the function should be used. 83 | In this case, telling the function a string that contains a Number will set the AssetTracker delay 84 | and telling it "off", 0 or some bogus command will turn the AssetTracker off. 85 | Then, the function returns a value to us to let us know what happened. 86 | In this case, it will return : 87 | New Delay value - when the Cellular Asset Tracker is turning on 88 | 0 when the Cellular Asset Tracker is turning off, 89 | */ 90 | int Delay = command.toInt(); 91 | if( Delay > 0 ) { 92 | locator.withSubscribe(AssetTrackerLocationCallback).withLocatePeriodic(Delay); 93 | digitalWrite(ledonboard,HIGH); 94 | Serial.print("Enabling Asset Location reporting interval:"); 95 | Serial.println(command); 96 | return Delay; 97 | } else { 98 | // Any invalid string that doesn't convert to a number or 0 should be considered "Off" 99 | // "once a day" to be sufficiently large to be OFF 100 | locator.withSubscribe(AssetTrackerLocationCallback).withLocatePeriodic(86400); 101 | digitalWrite(ledonboard,LOW); 102 | Serial.println("Disabling Asset Location reporting"); 103 | return 0; 104 | } 105 | 106 | } 107 | 108 | 109 | int AssetTrackerGetCurrentTemp(String Coordinates ) { 110 | byte i; 111 | byte present = 0; 112 | byte type_s; 113 | byte data[12]; 114 | byte addr[8]; 115 | float celsius, fahrenheit; 116 | int init = 0; 117 | 118 | // wait to initialize / find the chip 119 | while( init < 10 ) { 120 | if ( !ds.search(addr)) { 121 | Serial.println("Searching for Temperature Sensor..."); 122 | ds.reset_search(); 123 | delay(250); 124 | init++; 125 | } else { 126 | init = 10; // found 127 | } 128 | } 129 | 130 | // The order is changed a bit in this example 131 | // first the returned address is printed 132 | Serial.print("ROM ="); 133 | for( i = 0; i < 8; i++) { 134 | Serial.write(' '); 135 | Serial.print(addr[i], HEX); 136 | } 137 | 138 | // second the CRC is checked, on fail, 139 | // print error and just return to try again 140 | if (OneWire::crc8(addr, 7) != addr[7]) { 141 | Serial.println("CRC is not valid!"); 142 | return 0; 143 | } 144 | Serial.println(); 145 | 146 | // we have a good address at this point 147 | // what kind of chip do we have? 148 | // we will set a type_s value for known types or just return 149 | 150 | // the first ROM byte indicates which chip 151 | switch (addr[0]) { 152 | case 0x10: 153 | Serial.println(" Chip = DS1820/DS18S20"); 154 | type_s = 1; 155 | break; 156 | case 0x28: 157 | Serial.println(" Chip = DS18B20"); 158 | type_s = 0; 159 | break; 160 | case 0x22: 161 | Serial.println(" Chip = DS1822"); 162 | type_s = 0; 163 | break; 164 | case 0x26: 165 | Serial.println(" Chip = DS2438"); 166 | type_s = 2; 167 | break; 168 | default: 169 | Serial.println("Unknown device type."); 170 | return 0; 171 | } 172 | 173 | // this device has temp so let's read it 174 | ds.reset(); // first clear the 1-wire bus 175 | ds.select(addr); // now select the device we just found 176 | // ds.write(0x44, 1); // tell it to start a conversion, with parasite power on at the end 177 | ds.write(0x44, 0); // or start conversion in powered mode (bus finishes low) 178 | 179 | // just wait a second while the conversion takes place 180 | // different chips have different conversion times, check the specs, 1 sec is worse case + 250ms 181 | // you could also communicate with other devices if you like but you would need 182 | // to already know their address to select them. 183 | delay(1000); // maybe 750ms is enough, maybe not, wait 1 sec for conversion 184 | 185 | // we might do a ds.depower() (parasite) here, but the reset will take care of it. 186 | 187 | // first make sure current values are in the scratch pad 188 | present = ds.reset(); 189 | ds.select(addr); 190 | ds.write(0xB8,0); // Recall Memory 0 191 | ds.write(0x00,0); // Recall Memory 0 192 | 193 | // now read the scratch pad 194 | present = ds.reset(); 195 | ds.select(addr); 196 | ds.write(0xBE,0); // Read Scratchpad 197 | if (type_s == 2) { 198 | ds.write(0x00,0); // The DS2438 needs a page# to read 199 | } 200 | 201 | // transfer and print the values 202 | Serial.print(" Data = "); 203 | Serial.print(present, HEX); 204 | Serial.print(" "); 205 | for ( i = 0; i < 9; i++) { // we need 9 bytes 206 | data[i] = ds.read(); 207 | Serial.print(data[i], HEX); 208 | Serial.print(" "); 209 | } 210 | Serial.print(" CRC="); 211 | Serial.print(OneWire::crc8(data, 8), HEX); 212 | Serial.println(); 213 | 214 | // Convert the data to actual temperature 215 | // because the result is a 16 bit signed integer, it should 216 | // be stored to an "int16_t" type, which is always 16 bits 217 | // even when compiled on a 32 bit processor. 218 | int16_t raw = (data[1] << 8) | data[0]; 219 | if (type_s == 2) raw = (data[2] << 8) | data[1]; 220 | byte cfg = (data[4] & 0x60); 221 | 222 | switch (type_s) { 223 | case 1: 224 | raw = raw << 3; // 9 bit resolution default 225 | if (data[7] == 0x10) { 226 | // "count remain" gives full 12 bit resolution 227 | raw = (raw & 0xFFF0) + 12 - data[6]; 228 | } 229 | celsius = (float)raw * 0.0625; 230 | break; 231 | case 0: 232 | // at lower res, the low bits are undefined, so let's zero them 233 | if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms 234 | if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms 235 | if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms 236 | // default is 12 bit resolution, 750 ms conversion time 237 | celsius = (float)raw * 0.0625; 238 | break; 239 | 240 | case 2: 241 | data[1] = (data[1] >> 3) & 0x1f; 242 | if (data[2] > 127) { 243 | celsius = (float)data[2] - ((float)data[1] * .03125); 244 | } else { 245 | celsius = (float)data[2] + ((float)data[1] * .03125); 246 | } 247 | } 248 | 249 | // remove random errors 250 | if((((celsius <= 0 && celsius > -1) && lastTemp > 5)) || celsius > 125) { 251 | celsius = lastTemp; 252 | } 253 | 254 | fahrenheit = celsius * 1.8 + 32.0; 255 | lastTemp = celsius; 256 | 257 | // now that we have the readings, we can publish them to the cloud 258 | // store celsius and fahrenheit temp readings in "temperature" stringified JSON 259 | String temperature = String::format("{\"Celsius\":%f,\"Fahrenheit\":%f}", celsius, fahrenheit ); 260 | String TempPlusLocation; 261 | if( Coordinates.length() != 0) { 262 | TempPlusLocation = String("{\"Temp\":"+temperature+",\"gps\":"+Coordinates+"}"); 263 | } else { 264 | TempPlusLocation = String("{\"Temp\":"+temperature+"}"); // This was a web query, location unknown 265 | } 266 | Particle.publish("AssetTrackerTemperatureEvent", TempPlusLocation, PRIVATE); // publish to cloud 267 | Serial.print( "Sending AssetTrackerTemperatureEvent : " ); 268 | Serial.println( TempPlusLocation ); 269 | return (int)fahrenheit ; 270 | } 271 | 272 | 273 | int AssetTrackerGetRecentAccel( String Coordinates ) { 274 | // Check if there's been a big acceleration 275 | // Report the largest acceleration detected during the prior time interval 276 | if ( MaxAccelThisInterval ) { 277 | String AccelPlusLocation; 278 | if( Coordinates.length() != 0) { 279 | AccelPlusLocation = String("{\"Accel\":"+MaxAccelJSON+",\"gps\":"+Coordinates+"}"); 280 | } else { 281 | AccelPlusLocation = String("{\"Accel\":"+MaxAccelJSON+"}"); // This was a web query, location unknown 282 | } 283 | Particle.publish("AssetTrackerAccelerationEvent", AccelPlusLocation, 60, PRIVATE); 284 | Serial.print( "Sending AssetTrackerAccelerationEvent : " ); 285 | Serial.println( AccelPlusLocation ); 286 | } 287 | 288 | // Report the Max Acceleration back to Particle Function return code 289 | // Reset the Max Acceleration for the next time interval to zero 290 | int reportMaxAccel = MaxAccelThisInterval; 291 | MaxAccelThisInterval = 0; 292 | return reportMaxAccel; 293 | } 294 | 295 | 296 | void AssetTrackerLocationCallback(float lat, float lon, float accuracy) { 297 | // Handle the returned location data for the device. This method is passed three arguments: 298 | // - Latitude 299 | // - Longitude 300 | // - Accuracy of estimated location (in meters) 301 | String Coordinates; 302 | Coordinates = String::format("{\"lat\":%f,\"lon\":%f,\"accuracy\":%d}", lat, lon, accuracy); 303 | 304 | // Check here if the Particle Asset Tracker v2 built-in GPS can get a more accurate geolocation 305 | // than the Cellular Tower Triangulation. Consumes more power but improves accuracy. 306 | 307 | // Report the temperture at this location. 308 | AssetTrackerGetCurrentTemp( Coordinates ); 309 | 310 | // Determine if there has been a big acceleration event in the past interval 311 | AssetTrackerGetRecentAccel( Coordinates ); 312 | } 313 | 314 | 315 | void loop() { 316 | locator.loop(); 317 | 318 | // Check if there's been a big acceleration 319 | // Save only the largest acceleration detected during the prior time period 320 | int CurrentAccelmagnitude = t.readXYZmagnitude(); 321 | if (CurrentAccelmagnitude > AccelThreshold && CurrentAccelmagnitude > MaxAccelThisInterval) { 322 | // Format a JSON Object to be parsed in the cloud {x:X,y:Y,z:Z} 323 | // Save a stringified JSON object 324 | MaxAccelJSON = String::format("{\"x\":%d,\"y\":%d,\"z\":%d}", t.readX(), t.readY(), t.readZ()); 325 | Serial.print( "AccelerationEvent triggered: " ); 326 | Serial.println( MaxAccelJSON ); 327 | MaxAccelThisInterval = CurrentAccelmagnitude; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /ParticleElectron/project.properties: -------------------------------------------------------------------------------- 1 | dependencies.Adafruit_LIS3DH=1.0.3 2 | dependencies.Adafruit_GPS=1.0.3 3 | dependencies.AssetTracker=0.0.10 4 | dependencies.CellularHelper=0.0.4 5 | dependencies.OneWire=2.0.1 6 | dependencies.Adafruit_Sensor=1.0.2 7 | dependencies.google-maps-device-locator=0.0.4 8 | -------------------------------------------------------------------------------- /ParticleElectron/screenshots/ParticleConsoleDeviceEvents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/ParticleElectron/screenshots/ParticleConsoleDeviceEvents.png -------------------------------------------------------------------------------- /ParticleElectron/screenshots/ParticleElectronAssetTracker-IoT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/ParticleElectron/screenshots/ParticleElectronAssetTracker-IoT.jpg -------------------------------------------------------------------------------- /ParticleElectron/screenshots/ParticleElectronAssetTracker-Kit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/ParticleElectron/screenshots/ParticleElectronAssetTracker-Kit.jpg -------------------------------------------------------------------------------- /ParticleElectron/screenshots/ParticleElectronAssetTracker-in-Case.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain/3ef51e93d0adc1766c81f423ecac395bc4468a1c/ParticleElectron/screenshots/ParticleElectronAssetTracker-in-Case.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Read this in other languages: [日本語](README-ja.md).* 2 | 3 | # IoT Asset Tracking using a Hyperledger Blockchain 4 | 5 | ## Introduction 6 | This repository contains three sections which assemble an **IoT Asset Tracking device**, **Hyperledger Blockchain** and a **Node-RED Dashboard** to implement a perishable network supply chain. This example can be used to track environmental conditions for a food safety supply chain, refrigerated medical supplies, garden plant shipments or any perishable shipment that are temperature / humidity / vibration / time sensitive. If a cargo needs to be delivered within safe environmental parameters and time, the use of an IoT Asset Tracking device that combines environmental sensors, calculates its location via GPS, triangulation or beacons, and then reports its location via Cellular, 5G, Sub1GHz, SigFox, WiFi networks is extremely valuable. When multiple participants - farms, manufacturers, processing plants, trucks, ports, ships, distribution centers, consumer retail outlets - are involved in the safe shipment and payment of the cargo, a Hyperledger Blockchain can be used to record immutable transactions as the cargo shipment progresses through its delivery journey. 7 | 8 | ## Workshop 9 | I've arranged this git repository to be read as an **[IBM Code Pattern](https://developer.ibm.com/code/)** [workshop tutorial](Workshop/README.md). Follow the steps in the [Workshop directory](Workshop/README.md) to learn how to build one yourself! 10 | 11 | ## Section Overviews 12 | The first [section](ParticleElectron/README.md) details how to set up a **Particle Electron Asset Tracker v2** to send environmental sensor data and location to the cloud. This implementation uses a [Particle Electron](https://docs.particle.io/datasheets/kits-and-accessories/particle-shields/#electron-asset-tracker-v2) but many other IoT Asset Tracking devices that can transmit location and data can be substituted with similar results. Subsequent revisions of this workshop tutorial will add other IoT Asset Tracking boards so check back in the future. 13 | 14 | The second [section](Blockchain/README.md) implements a **Perishable Business Network** using [Hyperledger](https://www.hyperledger.org/) Fabric, Hyperledger Composer, Hyperledger Composer REST APIs running in the [IBM Cloud Container Service](https://www.ibm.com/cloud/container-service) managed by a [Kubernetes cluster](https://console.bluemix.net/docs/tutorials/scalable-webapp-kubernetes.html#deploy-a-scalable-web-application-on-kubernetes) in the IBM Cloud. 15 | 16 | In the third [section](Node-RED/README.md), the power of **Where, What and When** is best visualized in a dashboard that plots the geo location path, the environmental sensor data and can control triggers and alerts. I use **[Node-RED](https://nodered.org/)** and a Node.js server running in an IBM Cloud hosted Cloud Foundry application to receive the IoT Asset Tracking data and write it to the Hyperledger Fabric using Hyperledger Composer REST APIs. I also use a **Node-RED Dashboard** to plot the shipment on a map. 17 | 18 | Enjoy! Give me feedback if you have suggestions on how to improve this tutorial. 19 | 20 | ## License 21 | This code pattern is licensed under the Apache Software License, Version 2. Separate third party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the Developer [Certificate of Origin, Version 1.1 (“DCO”)] (https://developercertificate.org/) and the [Apache Software License, Version 2]( (http://www.apache.org/licenses/LICENSE-2.0.txt). 22 | 23 | ASL FAQ link: http://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN 24 | -------------------------------------------------------------------------------- /Workshop/README.md: -------------------------------------------------------------------------------- 1 | # IoT Asset Tracking Perishable Network Blockchain Workshop 2 | Author: [@johnwalicki](https://twitter.com/johnwalicki) 3 | 4 | # Follow this workshop at **https://github.com/johnwalicki/IoT-AssetTracking-Perishable-Network-Blockchain** 5 | 6 | I've arranged this git repository to be read as an **[IBM Code Pattern](https://developer.ibm.com/code/)** [workshop tutorial](README.md). 7 | 8 | ## Section Overviews 9 | The first [section](../ParticleElectron/README.md) details how to set up a **Particle Electron Asset Tracker v2** to send environmental sensor data and location to the cloud. This implementation uses a [Particle Electron](https://docs.particle.io/datasheets/kits-and-accessories/particle-shields/#electron-asset-tracker-v2). Since we don't have dozens of Particle Electrons, I will demo mine from the podium. By the end of the workshop you will be reading environmental data off this tracker. 10 | 11 | The second [section](../Blockchain/README.md) implements a **Perishable Business Network** using [Hyperledger](https://www.hyperledger.org/) Fabric, Hyperledger Composer, Hyperledger Composer REST APIs running in the [IBM Cloud Container Service](https://www.ibm.com/cloud/container-service) managed by a [Kubernetes cluster](https://console.bluemix.net/docs/tutorials/scalable-webapp-kubernetes.html#deploy-a-scalable-web-application-on-kubernetes) in the IBM Cloud. 12 | 13 | In the third [section](../Node-RED/README.md), the power of **Where, What and When** is best visualized in a dashboard that plots the geo location path, the environmental sensor data and can control triggers and alerts. I use **[Node-RED](https://nodered.org/)** running in the IBM Cloud to receive the IoT Asset Tracking data and write it to the Hyperledger Fabric using Hyperledger Composer REST APIs. I also use a **Node-RED Dashboard** to plot the shipment on a map. 14 | 15 | * If you purchased your own Particle Electron, you will need to know the Particle Device ID and Access Token and insert it into Node-RED initialization flow. 16 | * If your participating in a workshop, the instructor will share the Particle Device ID and Access Token in a separate slide (not part of GitHub) 17 | 18 | At the end of the workshop, you will have a complete Hyperledger Blockchain with a food safety supply chain implemented and a Node-RED Map Dashboard that tracks the routes of my Particle. 19 | --------------------------------------------------------------------------------