489 lines
15 KiB
Arduino
489 lines
15 KiB
Arduino
|
|
/******************************************************************************************************************************
|
||
|
|
* Date: 28/02/2023
|
||
|
|
* Filename: LabSage_v1.1.5
|
||
|
|
* Description: ESP Multitasking + EMS
|
||
|
|
* Update - Changing thresholds, re-timing tasks - Resetting GAS threshold as constant value due to large difference
|
||
|
|
*****************************************************************************************************************************/
|
||
|
|
// INCLUDE LIBRARIES
|
||
|
|
#include <TaskScheduler.h> // Install TaskScheduler Arduino Library
|
||
|
|
#include <WiFiManager.h> // Install WiFiManager Library
|
||
|
|
#include <FirebaseESP8266.h> // Install Firebase ESP8266 library (Mobitz?)
|
||
|
|
#include <MB_NTP.h>
|
||
|
|
#include <ESP8266WiFi.h>
|
||
|
|
#include <DHT.h> // Install DHT11 Library and Adafruit Unified Sensor Library
|
||
|
|
#include <Wire.h>
|
||
|
|
#include <Adafruit_Sensor.h>
|
||
|
|
#include <Adafruit_ADXL345_U.h> // Install Adafruit ADXL345 accelerometer library
|
||
|
|
|
||
|
|
// FIREBASE RTDB PATH AND KEY
|
||
|
|
#define FIREBASE_HOST "https://smartlab-9b83e-default-rtdb.firebaseio.com/"
|
||
|
|
#define FIREBASE_AUTH "AIzaSyCiO5owTmUgosDyjL0_SqpxRoAwgKtn5-o"
|
||
|
|
|
||
|
|
// SETTING PINS FOR SENSORS
|
||
|
|
#define LED D1
|
||
|
|
#define DHTPIN D2 // Connect Data pin of DHT to D2
|
||
|
|
#define GASPIN A0 // Gas sensor analog pin
|
||
|
|
#define BUZZERPIN D7 // Connect LED to D7
|
||
|
|
#define SDA D5
|
||
|
|
#define SCL D6
|
||
|
|
|
||
|
|
// DHT VARIABLE SET
|
||
|
|
#define DHTTYPE DHT11
|
||
|
|
|
||
|
|
// OBJECT DECLARATIONS
|
||
|
|
Scheduler runner;
|
||
|
|
WiFiManager wm;
|
||
|
|
DHT dht(DHTPIN, DHTTYPE);
|
||
|
|
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
|
||
|
|
|
||
|
|
//MQ2 parameter for LPG/Smoke sensing
|
||
|
|
float gasSensor_volt;
|
||
|
|
float RS;
|
||
|
|
const float R0 = 2.12;
|
||
|
|
const float b = 1.5120222;
|
||
|
|
const float m = -0.33975668;
|
||
|
|
float RS_R0_ratio;
|
||
|
|
float ppm_log;
|
||
|
|
float ppm;
|
||
|
|
|
||
|
|
// VARIABLE DEFINITION
|
||
|
|
float t, h, g;
|
||
|
|
String buzz = "0";
|
||
|
|
String coll = "0";
|
||
|
|
float x, y, z;
|
||
|
|
String PATH = "Devices/";
|
||
|
|
String UID = "";
|
||
|
|
float sum = 0;
|
||
|
|
int k = 0;
|
||
|
|
// THRESHOLD VALUES
|
||
|
|
const float COLLISION_THRESHOLD = 10.0; // Too sensitive perhaps?
|
||
|
|
const float SMOKE_THRESHOLD = 15000; // NEEDS TO BE UPLOADED TO THE BOARD YET!!!
|
||
|
|
|
||
|
|
// DEFINE FIREBASE DATA OBJECT
|
||
|
|
FirebaseData firebaseData;
|
||
|
|
FirebaseData buzzerData;
|
||
|
|
|
||
|
|
FirebaseJson json;
|
||
|
|
|
||
|
|
// FUNCTION DECLARATION
|
||
|
|
void dhtSetup(void);
|
||
|
|
void gasSetup(void);
|
||
|
|
void ambiRead(void);
|
||
|
|
void accelSetup(void);
|
||
|
|
void accelRead(void);
|
||
|
|
void sensorUpdate(void);
|
||
|
|
void upload(void);
|
||
|
|
void uploadAlert(void);
|
||
|
|
void sensorReset(void);
|
||
|
|
void reconnection(void);
|
||
|
|
|
||
|
|
// TASK SCHEDULE FUNCTIONS
|
||
|
|
void task1Callback() { // Sensor update
|
||
|
|
// code for task 1
|
||
|
|
sensorUpdate();
|
||
|
|
}
|
||
|
|
|
||
|
|
void task2Callback() { // Sensor value upload
|
||
|
|
// code for task 2
|
||
|
|
if (WiFi.status() == WL_CONNECTED) {
|
||
|
|
upload();
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
Serial.println("Connection Lost!");
|
||
|
|
digitalWrite(LED, !digitalRead(LED));
|
||
|
|
Serial.println("Please Reconnect to a different WiFi.\nStarting Soft API...");
|
||
|
|
reconnection();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void task3Callback(){ // Reset receive and execute
|
||
|
|
if (WiFi.status() == WL_CONNECTED){
|
||
|
|
if (Firebase.getString(buzzerData, PATH + "/reset") && buzzerData.stringData() == "1") { //Read from firebase for application
|
||
|
|
Serial.println("Call to reset");
|
||
|
|
sensorReset();
|
||
|
|
if (Firebase.setString(firebaseData, PATH + "/reset", "0"))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
uploadAlert();
|
||
|
|
}
|
||
|
|
if (buzz == "1" || coll== "1"){
|
||
|
|
uploadAlert();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
Serial.println("Connection Lost!");
|
||
|
|
digitalWrite(LED, !digitalRead(LED));
|
||
|
|
Serial.println("Please Reconnect to a different WiFi.\nStarting Soft API...");
|
||
|
|
reconnection();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Task t1(1000, TASK_FOREVER, &task1Callback); // Task rate: Once every second
|
||
|
|
Task t2(60000, TASK_FOREVER, &task2Callback); // Task rate: Once every minute
|
||
|
|
Task t3(3000, TASK_FOREVER, &task3Callback); // Task rate: Once every 3 seconds
|
||
|
|
|
||
|
|
// SETUP
|
||
|
|
void setup(){
|
||
|
|
pinMode(LED, OUTPUT);
|
||
|
|
digitalWrite(LED, LOW);
|
||
|
|
pinMode(BUZZERPIN, OUTPUT);
|
||
|
|
Serial.begin(9600);
|
||
|
|
dhtSetup();
|
||
|
|
gasSetup();
|
||
|
|
accelSetup();
|
||
|
|
UID = ESP.getChipId(); // Extract Unique Chip ID for authentication and identity purposes
|
||
|
|
Serial.printf("ESP8266 Unique Chip id = %s\n", UID); // For hex representation, use %08X
|
||
|
|
Serial.println();
|
||
|
|
Serial.println("Opening WiFi AP");
|
||
|
|
// wm.resetSettings(); // Comment out before deployment
|
||
|
|
bool res;
|
||
|
|
wm.setConnectTimeout(60);
|
||
|
|
wm.setConfigPortalTimeoutCallback(reconnection);
|
||
|
|
wm.setWiFiAutoReconnect(true);
|
||
|
|
res = wm.autoConnect("SMI_LBS_ESP"); // auto generated AP name from chipid
|
||
|
|
if (!res) {
|
||
|
|
Serial.println("Failed to connect");
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
//if you get here you have connected to the WiFi
|
||
|
|
Serial.println("Connected");
|
||
|
|
}
|
||
|
|
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH); // Begin Firebase Server
|
||
|
|
Firebase.reconnectWiFi(true);
|
||
|
|
|
||
|
|
Serial.println(PATH.concat(UID)); // Dynamic database path allocation
|
||
|
|
digitalWrite(LED, HIGH);
|
||
|
|
runner.init(); // Initialise Scheduler, add tasks to it, and then enable the tasks for multitasking
|
||
|
|
runner.addTask(t1);
|
||
|
|
runner.addTask(t2);
|
||
|
|
runner.addTask(t3);
|
||
|
|
t1.enable();
|
||
|
|
t2.enable();
|
||
|
|
t3.enable();
|
||
|
|
}
|
||
|
|
|
||
|
|
// MAIN LOOP CODE
|
||
|
|
void loop() {
|
||
|
|
runner.execute(); // Run the tasks simultaneously
|
||
|
|
}
|
||
|
|
|
||
|
|
void reconnection(void) { // Re-establishing network with new Wi-Fi using Soft AP
|
||
|
|
bool res = wm.autoConnect();
|
||
|
|
if (!res) {
|
||
|
|
Serial.println("Failed to connect");
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
Serial.println("Connected");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void accelSetup(void) { // Setup Accelerometer
|
||
|
|
Serial.println("Accelerometer Test"); Serial.println("");
|
||
|
|
|
||
|
|
// Initialise I2C communication as MASTER
|
||
|
|
Wire.begin(SDA, SCL);
|
||
|
|
|
||
|
|
if (!accel.begin()) {
|
||
|
|
Serial.println("Failed to initialize ADXL345 sensor.");
|
||
|
|
while (1);
|
||
|
|
}
|
||
|
|
accel.setRange(ADXL345_RANGE_16_G); // Setting Max Range
|
||
|
|
// Get accelerometer data
|
||
|
|
sensors_event_t event;
|
||
|
|
accel.getEvent(&event);
|
||
|
|
// Assign current values to states
|
||
|
|
x = event.acceleration.x;
|
||
|
|
y = event.acceleration.y;
|
||
|
|
z = event.acceleration.z;
|
||
|
|
Serial.println("Accelerometer Ready!"); Serial.println("");
|
||
|
|
}
|
||
|
|
|
||
|
|
void accelRead(void) { // Read Accelerometer
|
||
|
|
// Get accelerometer data
|
||
|
|
sensors_event_t event;
|
||
|
|
accel.getEvent(&event);
|
||
|
|
|
||
|
|
// Print the accelerometer data to serial monitor
|
||
|
|
Serial.print("Acceleration: ");
|
||
|
|
Serial.print(event.acceleration.x);
|
||
|
|
Serial.print(" m/s^2 ");
|
||
|
|
Serial.print(event.acceleration.y);
|
||
|
|
Serial.print(" m/s^2 ");
|
||
|
|
Serial.print(event.acceleration.z);
|
||
|
|
Serial.println(" m/s^2 ");
|
||
|
|
if (isnan(event.acceleration.x) || isnan(event.acceleration.y) || isnan(event.acceleration.z)) {
|
||
|
|
Serial.println(F("Failed to read from Accelerometer!"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Check if there is a collision
|
||
|
|
if ((abs(event.acceleration.x - x)) > COLLISION_THRESHOLD ||
|
||
|
|
(abs(event.acceleration.y - y)) > COLLISION_THRESHOLD ||
|
||
|
|
(abs(event.acceleration.z - z)) > COLLISION_THRESHOLD) {
|
||
|
|
Serial.println("Collision detected!");
|
||
|
|
coll = "1";
|
||
|
|
tone(BUZZERPIN, 100);
|
||
|
|
}
|
||
|
|
// Assign current values to states
|
||
|
|
x = event.acceleration.x;
|
||
|
|
y = event.acceleration.y;
|
||
|
|
z = event.acceleration.z;
|
||
|
|
}
|
||
|
|
|
||
|
|
void dhtSetup(void) { // Setup DHT11
|
||
|
|
Serial.println(F("DHT11 setting up..."));
|
||
|
|
dht.begin();
|
||
|
|
Serial.println("DHT11 ready!");
|
||
|
|
}
|
||
|
|
|
||
|
|
void gasSetup(void) { // Setup MQ-2 Smoke Sensor
|
||
|
|
Serial.println("Gas Sensor setting up...");
|
||
|
|
pinMode(GASPIN, INPUT_PULLUP);
|
||
|
|
Serial.println("Gas Sensor ready!");
|
||
|
|
Serial.printf("\nSmoke Threshold: %f ppm\n", SMOKE_THRESHOLD);
|
||
|
|
Serial.println("");
|
||
|
|
}
|
||
|
|
|
||
|
|
void ambiRead(void) { // Temp, Humidity, Gas reading
|
||
|
|
// Read relative humidity in % (the default)
|
||
|
|
h = dht.readHumidity();
|
||
|
|
|
||
|
|
// Read temperature as Celsius (the default)
|
||
|
|
t = dht.readTemperature();
|
||
|
|
// Read gas sensor analog data
|
||
|
|
float gasSensorValue = analogRead(GASPIN);
|
||
|
|
gasSensor_volt = gasSensorValue / 1024 * 5.0;
|
||
|
|
RS = ((5 - gasSensor_volt) / gasSensor_volt);
|
||
|
|
RS_R0_ratio = RS / R0;
|
||
|
|
ppm_log = ((log (RS_R0_ratio) - b) / m);
|
||
|
|
ppm = pow (10, ppm_log);
|
||
|
|
g = ppm;
|
||
|
|
|
||
|
|
// Check if any reads failed and exit early (to try again).
|
||
|
|
if (isnan(h) || isnan(t)) {
|
||
|
|
Serial.println(F("Failed to read from DHT sensor!"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if(isnan(g)){
|
||
|
|
Serial.println(F("Failed to read from Gas sensor!"));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Check if there is a fire/smoke
|
||
|
|
if (g > SMOKE_THRESHOLD) {
|
||
|
|
sum += g;
|
||
|
|
k++;
|
||
|
|
Serial.println("Smoke detected!");
|
||
|
|
buzz = "1";
|
||
|
|
tone(BUZZERPIN, 400);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if(k<=10){
|
||
|
|
buzz = "0";
|
||
|
|
if (coll == "0") {
|
||
|
|
noTone(BUZZERPIN);
|
||
|
|
}}
|
||
|
|
}
|
||
|
|
Serial.print(F("Humidity: "));
|
||
|
|
Serial.print(h);
|
||
|
|
Serial.print(F("% Temperature: "));
|
||
|
|
Serial.print(t);
|
||
|
|
Serial.print(F("C PPM:"));
|
||
|
|
Serial.print(g);
|
||
|
|
Serial.println();
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void sensorUpdate(void) {
|
||
|
|
ambiRead();
|
||
|
|
accelRead();
|
||
|
|
}
|
||
|
|
|
||
|
|
void sensorReset(void) {
|
||
|
|
coll = "0";
|
||
|
|
buzz = "0";
|
||
|
|
noTone(BUZZERPIN);
|
||
|
|
sum = 0;
|
||
|
|
k = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void upload(void) {
|
||
|
|
if (Firebase.setFloat(firebaseData, PATH + "/temperature", t))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Firebase.setFloat(firebaseData, PATH + "/humidity", h))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
if (Firebase.setFloat(firebaseData, PATH + "/ppm", g))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (Firebase.setFloat(firebaseData, PATH + "/x", x))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
if (Firebase.setFloat(firebaseData, PATH + "/y", y))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
if (Firebase.setFloat(firebaseData, PATH + "/z", z))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void uploadAlert(void){
|
||
|
|
if (Firebase.setString(firebaseData, PATH + "/buzzer", buzz))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
if (Firebase.setString(firebaseData, PATH + "/collision", coll))
|
||
|
|
{
|
||
|
|
Serial.println("PASSED");
|
||
|
|
Serial.println("PATH: " + firebaseData.dataPath());
|
||
|
|
Serial.println("TYPE: " + firebaseData.dataType());
|
||
|
|
Serial.println("ETag: " + firebaseData.ETag());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
Serial.println("FAILED");
|
||
|
|
Serial.println("REASON: " + firebaseData.errorReason());
|
||
|
|
Serial.println("------------------------------------");
|
||
|
|
Serial.println();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/*
|
||
|
|
void startCustomConfigPortal() {
|
||
|
|
WiFiManager wifiManager;
|
||
|
|
wifiManager.setConfigPortalTimeout(120);
|
||
|
|
wifiManager.setMinimumSignalQuality(-1);
|
||
|
|
const char * pass = "SM1ota#W"; //"46EAD57A89F1D240CBD7704B6FAD544C"; //"46ead57a89f1d240cbd7704b6fad544c";
|
||
|
|
WiFiManagerParameter password("", "Developer Options Password", "", 40);
|
||
|
|
wifiManager.addParameter(&password);
|
||
|
|
|
||
|
|
WiFiManagerParameter customHtmlOptions("<br><a href='/info'>Info</a> <a href='/update'>Update</a>");
|
||
|
|
const char * c1 = customHtmlOptions.getValue();
|
||
|
|
wifiManager.setCustomHeadElement(c1);
|
||
|
|
|
||
|
|
if (!wifiManager.startConfigPortal("Custom Config Portal")) {
|
||
|
|
Serial.println("Failed to connect and hit timeout");
|
||
|
|
delay(3000);
|
||
|
|
ESP.reset();
|
||
|
|
delay(5000);
|
||
|
|
}
|
||
|
|
|
||
|
|
const char * uPass = password.getValue();
|
||
|
|
Serial.println();
|
||
|
|
if (strcmp(uPass, pass) == 0) {
|
||
|
|
WiFiManagerParameter customHtmlDeveloperOptions("<br><a href='/info'>Info</a> <a href='/update'>Update</a>");
|
||
|
|
const char * c = customHtmlDeveloperOptions.getValue();
|
||
|
|
wifiManager.setCustomHeadElement(c);
|
||
|
|
} else {
|
||
|
|
wifiManager.setCustomHeadElement("");
|
||
|
|
wifiManager.startConfigPortal("Custom Config Portal");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
*/
|