ME910 changes
Dependencies: Cayenne-MQTT-mbed-M1 X_NUCLEO_IKS01A1 mbed mtsas-m1
Fork of 5_Dragonfly_Cayenne_Sprint_IKS01A1 by
main.cpp@6:d70ed27847fe, 2017-09-07 (annotated)
- Committer:
- pferland
- Date:
- Thu Sep 07 16:11:41 2017 +0000
- Revision:
- 6:d70ed27847fe
- Parent:
- 5:a2d72fe4d7bd
Preliminary support for ME910C1
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
pferland | 0:5107fce16490 | 1 | #include "mbed.h" |
pferland | 0:5107fce16490 | 2 | |
pferland | 0:5107fce16490 | 3 | #include "mtsas.h" |
pferland | 0:5107fce16490 | 4 | |
pferland | 0:5107fce16490 | 5 | #include "MQTTTimer.h" |
pferland | 0:5107fce16490 | 6 | #include "CayenneMQTTClient.h" |
pferland | 0:5107fce16490 | 7 | #include "MQTTNetwork.h" |
pferland | 0:5107fce16490 | 8 | |
pferland | 0:5107fce16490 | 9 | #include "x_nucleo_iks01a1.h" |
pferland | 0:5107fce16490 | 10 | |
pferland | 0:5107fce16490 | 11 | #include <string> |
pferland | 0:5107fce16490 | 12 | #include <sstream> |
pferland | 0:5107fce16490 | 13 | |
pferland | 0:5107fce16490 | 14 | using std::string; |
pferland | 0:5107fce16490 | 15 | typedef CayenneMQTT::MQTTClient<MQTTNetwork<Cellular>, MQTTTimer> MQTTClient; |
pferland | 0:5107fce16490 | 16 | |
pferland | 0:5107fce16490 | 17 | // Cayenne authentication info. This should be obtained from the Cayenne Dashboard. |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 18 | string username = "3f842710-8e51-11e7-a5d9-9de9b49680ec"; |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 19 | string password = "46f0e2add1dbcd1d7cab6fde2a2b93c4423a377a"; |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 20 | string clientID = "b0358dd0-8f43-11e7-b153-197ebdab87be"; |
pferland | 0:5107fce16490 | 21 | |
pferland | 0:5107fce16490 | 22 | DigitalOut Led1Out(LED1); |
pferland | 0:5107fce16490 | 23 | |
pferland | 0:5107fce16490 | 24 | // Debug serial port |
pferland | 0:5107fce16490 | 25 | //static Serial debug(USBTX, USBRX); |
pferland | 0:5107fce16490 | 26 | Serial pc(USBTX, USBRX); |
pferland | 0:5107fce16490 | 27 | // MTSSerialFlowControl - serial link between processor and radio |
pferland | 0:5107fce16490 | 28 | static MTSSerialFlowControl* io; |
pferland | 0:5107fce16490 | 29 | |
pferland | 0:5107fce16490 | 30 | // Cellular - radio object for cellular operations (SMS, TCP, etc) |
pferland | 0:5107fce16490 | 31 | Cellular* radio; |
pferland | 0:5107fce16490 | 32 | |
pferland | 0:5107fce16490 | 33 | /* Instantiate the expansion board */ |
pferland | 0:5107fce16490 | 34 | static X_NUCLEO_IKS01A1 *mems_expansion_board = X_NUCLEO_IKS01A1::Instance(I2C_SDA, I2C_SCL); |
pferland | 0:5107fce16490 | 35 | |
pferland | 0:5107fce16490 | 36 | /* Retrieve the composing elements of the expansion board */ |
pferland | 0:5107fce16490 | 37 | static GyroSensor *gyroscope = mems_expansion_board->GetGyroscope(); |
pferland | 0:5107fce16490 | 38 | static MotionSensor *accelerometer = mems_expansion_board->GetAccelerometer(); |
pferland | 0:5107fce16490 | 39 | static MagneticSensor *magnetometer = mems_expansion_board->magnetometer; |
pferland | 0:5107fce16490 | 40 | static HumiditySensor *humidity_sensor = mems_expansion_board->ht_sensor; |
pferland | 0:5107fce16490 | 41 | static PressureSensor *pressure_sensor = mems_expansion_board->pt_sensor; |
pferland | 0:5107fce16490 | 42 | static TempSensor *temp_sensor1 = mems_expansion_board->ht_sensor; |
pferland | 0:5107fce16490 | 43 | static TempSensor *temp_sensor2 = mems_expansion_board->pt_sensor; |
pferland | 0:5107fce16490 | 44 | |
pferland | 0:5107fce16490 | 45 | |
pferland | 0:5107fce16490 | 46 | CayenneMQTT::MessageData lastMessage; |
pferland | 0:5107fce16490 | 47 | bool messageReady; |
pferland | 0:5107fce16490 | 48 | |
pferland | 0:5107fce16490 | 49 | /* |
pferland | 0:5107fce16490 | 50 | * Initialize cellular radio. |
pferland | 0:5107fce16490 | 51 | */ |
pferland | 0:5107fce16490 | 52 | bool init_mtsas() |
pferland | 0:5107fce16490 | 53 | { |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 54 | |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 55 | io = new MTSSerialFlowControl(D8,D2,D3,D6);//This is the only thing that I have changed! |
pferland | 0:5107fce16490 | 56 | if (! io) |
pferland | 0:5107fce16490 | 57 | return false; |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 58 | |
pferland | 0:5107fce16490 | 59 | io->baud(115200); |
pferland | 0:5107fce16490 | 60 | radio = CellularFactory::create(io); |
pferland | 0:5107fce16490 | 61 | if (! radio) |
pferland | 0:5107fce16490 | 62 | return false; |
pferland | 0:5107fce16490 | 63 | |
pferland | 6:d70ed27847fe | 64 | //radio->setApn("wireless.twilio.com"); |
pferland | 0:5107fce16490 | 65 | Transport::setTransport(radio); |
pferland | 0:5107fce16490 | 66 | while (! radio->connect()) { |
pferland | 0:5107fce16490 | 67 | logError("failed to bring up PPP link"); |
pferland | 0:5107fce16490 | 68 | wait(2); |
pferland | 0:5107fce16490 | 69 | } |
pferland | 0:5107fce16490 | 70 | |
pferland | 0:5107fce16490 | 71 | printf("Signal Strength: %d\n\r", radio->getSignalStrength()); |
pferland | 0:5107fce16490 | 72 | return true; |
pferland | 0:5107fce16490 | 73 | } |
pferland | 0:5107fce16490 | 74 | |
pferland | 0:5107fce16490 | 75 | /** |
pferland | 0:5107fce16490 | 76 | * Print the message info. |
pferland | 0:5107fce16490 | 77 | * @param[in] message The message received from the Cayenne server. |
pferland | 0:5107fce16490 | 78 | */ |
pferland | 0:5107fce16490 | 79 | void outputMessage(CayenneMQTT::MessageData& message) |
pferland | 0:5107fce16490 | 80 | { |
pferland | 0:5107fce16490 | 81 | switch (message.topic) { |
pferland | 0:5107fce16490 | 82 | case COMMAND_TOPIC: |
ScottHoppeMultitech | 5:a2d72fe4d7bd | 83 | pc.printf("topic=Command"); |
pferland | 0:5107fce16490 | 84 | break; |
pferland | 0:5107fce16490 | 85 | case CONFIG_TOPIC: |
pferland | 0:5107fce16490 | 86 | printf("topic=Config"); |
pferland | 0:5107fce16490 | 87 | break; |
pferland | 0:5107fce16490 | 88 | default: |
pferland | 0:5107fce16490 | 89 | printf("topic=%d", message.topic); |
pferland | 0:5107fce16490 | 90 | break; |
pferland | 0:5107fce16490 | 91 | } |
pferland | 0:5107fce16490 | 92 | printf(" channel=%d", message.channel); |
pferland | 0:5107fce16490 | 93 | if (message.clientID) { |
pferland | 0:5107fce16490 | 94 | printf(" clientID=%s", message.clientID); |
pferland | 0:5107fce16490 | 95 | } |
pferland | 0:5107fce16490 | 96 | if (message.type) { |
pferland | 0:5107fce16490 | 97 | printf(" type=%s", message.type); |
pferland | 0:5107fce16490 | 98 | } |
pferland | 0:5107fce16490 | 99 | for (size_t i = 0; i < message.valueCount; ++i) { |
pferland | 0:5107fce16490 | 100 | if (message.getValue(i)) { |
pferland | 0:5107fce16490 | 101 | printf(" value=%s", message.getValue(i)); |
pferland | 0:5107fce16490 | 102 | } |
pferland | 0:5107fce16490 | 103 | if (message.getUnit(i)) { |
pferland | 0:5107fce16490 | 104 | printf(" unit=%s", message.getUnit(i)); |
pferland | 0:5107fce16490 | 105 | } |
pferland | 0:5107fce16490 | 106 | } |
pferland | 0:5107fce16490 | 107 | if (message.id) { |
pferland | 0:5107fce16490 | 108 | printf(" id=%s", message.id); |
pferland | 0:5107fce16490 | 109 | } |
pferland | 2:abc89d2aede3 | 110 | printf("\r\n"); |
pferland | 0:5107fce16490 | 111 | } |
pferland | 0:5107fce16490 | 112 | |
pferland | 0:5107fce16490 | 113 | /** |
pferland | 0:5107fce16490 | 114 | * Handle messages received from the Cayenne server. |
pferland | 0:5107fce16490 | 115 | * @param[in] message The message received from the Cayenne server. |
pferland | 0:5107fce16490 | 116 | */ |
pferland | 0:5107fce16490 | 117 | void messageArrived(CayenneMQTT::MessageData& message) |
pferland | 0:5107fce16490 | 118 | { |
pferland | 0:5107fce16490 | 119 | int error = 0; |
pferland | 0:5107fce16490 | 120 | //note: if you change this example to use mbed-os you will need a mutex |
pferland | 0:5107fce16490 | 121 | lastMessage = message; |
pferland | 0:5107fce16490 | 122 | messageReady = true; |
pferland | 0:5107fce16490 | 123 | |
pferland | 0:5107fce16490 | 124 | } |
pferland | 0:5107fce16490 | 125 | |
pferland | 0:5107fce16490 | 126 | /** |
pferland | 0:5107fce16490 | 127 | * Connect to the Cayenne server. |
pferland | 0:5107fce16490 | 128 | * @return Returns CAYENNE_SUCCESS if the connection succeeds, or an error code otherwise. |
pferland | 0:5107fce16490 | 129 | */ |
pferland | 0:5107fce16490 | 130 | int connectClient(MQTTClient &mqttClient, MQTTNetwork<Cellular> &network) |
pferland | 0:5107fce16490 | 131 | { |
pferland | 0:5107fce16490 | 132 | int error = 0; |
pferland | 0:5107fce16490 | 133 | // Connect to the server. |
pferland | 2:abc89d2aede3 | 134 | printf("Connecting to %s:%d\r\n", CAYENNE_DOMAIN, CAYENNE_PORT); |
pferland | 0:5107fce16490 | 135 | while ((error = network.connect(CAYENNE_DOMAIN, CAYENNE_PORT)) != 0) { |
pferland | 2:abc89d2aede3 | 136 | printf("TCP connect failed, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 137 | wait(2); |
pferland | 0:5107fce16490 | 138 | } |
pferland | 0:5107fce16490 | 139 | |
pferland | 0:5107fce16490 | 140 | if ((error = mqttClient.connect()) != MQTT::SUCCESS) { |
pferland | 2:abc89d2aede3 | 141 | printf("MQTT connect failed, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 142 | return error; |
pferland | 0:5107fce16490 | 143 | } |
pferland | 2:abc89d2aede3 | 144 | printf("Connected\r\n"); |
pferland | 0:5107fce16490 | 145 | |
pferland | 0:5107fce16490 | 146 | // Subscribe to required topics. |
pferland | 0:5107fce16490 | 147 | if ((error = mqttClient.subscribe(COMMAND_TOPIC, CAYENNE_ALL_CHANNELS)) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 148 | printf("Subscription to Command topic failed, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 149 | } |
pferland | 0:5107fce16490 | 150 | if ((error = mqttClient.subscribe(CONFIG_TOPIC, CAYENNE_ALL_CHANNELS)) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 151 | printf("Subscription to Config topic failed, error:%d\r\n", error); |
pferland | 0:5107fce16490 | 152 | } |
pferland | 0:5107fce16490 | 153 | |
pferland | 0:5107fce16490 | 154 | // Send device info. Here we just send some example values for the system info. These should be changed to use actual system data, or removed if not needed. |
pferland | 0:5107fce16490 | 155 | mqttClient.publishData(SYS_VERSION_TOPIC, CAYENNE_NO_CHANNEL, NULL, NULL, CAYENNE_VERSION); |
pferland | 0:5107fce16490 | 156 | mqttClient.publishData(SYS_MODEL_TOPIC, CAYENNE_NO_CHANNEL, NULL, NULL, "mbedDevice"); |
pferland | 0:5107fce16490 | 157 | //mqttClient.publishData(SYS_CPU_MODEL_TOPIC, CAYENNE_NO_CHANNEL, NULL, NULL, "CPU Model"); |
pferland | 0:5107fce16490 | 158 | //mqttClient.publishData(SYS_CPU_SPEED_TOPIC, CAYENNE_NO_CHANNEL, NULL, NULL, "1000000000"); |
pferland | 0:5107fce16490 | 159 | |
pferland | 0:5107fce16490 | 160 | return CAYENNE_SUCCESS; |
pferland | 0:5107fce16490 | 161 | } |
pferland | 0:5107fce16490 | 162 | |
pferland | 0:5107fce16490 | 163 | /** |
pferland | 0:5107fce16490 | 164 | * Main loop where MQTT code is run. |
pferland | 0:5107fce16490 | 165 | */ |
pferland | 0:5107fce16490 | 166 | void loop(MQTTClient &mqttClient, MQTTNetwork<Cellular> &network) |
pferland | 0:5107fce16490 | 167 | { |
pferland | 0:5107fce16490 | 168 | // Start the countdown timer for publishing data every 5 seconds. Change the timeout parameter to publish at a different interval. |
pferland | 2:abc89d2aede3 | 169 | MQTTTimer timer(1000); |
pferland | 2:abc89d2aede3 | 170 | printf("Starting loop.\r\n"); |
pferland | 0:5107fce16490 | 171 | while (true) { |
pferland | 0:5107fce16490 | 172 | // Yield to allow MQTT message processing. |
pferland | 2:abc89d2aede3 | 173 | mqttClient.yield(10); |
pferland | 0:5107fce16490 | 174 | if(messageReady){ |
pferland | 0:5107fce16490 | 175 | int error = 0; |
pferland | 0:5107fce16490 | 176 | messageReady = false; |
pferland | 0:5107fce16490 | 177 | // Add code to process the message. Here we just ouput the message data. |
pferland | 0:5107fce16490 | 178 | outputMessage(lastMessage); |
pferland | 0:5107fce16490 | 179 | |
pferland | 0:5107fce16490 | 180 | if (lastMessage.topic == COMMAND_TOPIC) { |
pferland | 0:5107fce16490 | 181 | switch(lastMessage.channel) { |
pferland | 0:5107fce16490 | 182 | case 0: |
pferland | 0:5107fce16490 | 183 | // Set the onboard LED state |
pferland | 0:5107fce16490 | 184 | Led1Out = atoi(lastMessage.getValue()); |
pferland | 0:5107fce16490 | 185 | // Publish the updated LED state |
pferland | 3:a40321269c7f | 186 | if ((error = mqttClient.publishData(DATA_TOPIC, lastMessage.channel, NULL, NULL, Led1Out.read())) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 187 | printf("Publish LED state failure, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 188 | } |
pferland | 0:5107fce16490 | 189 | break; |
pferland | 0:5107fce16490 | 190 | } |
pferland | 0:5107fce16490 | 191 | |
pferland | 0:5107fce16490 | 192 | // If this is a command message we publish a response. Here we are just sending a default 'OK' response. |
pferland | 0:5107fce16490 | 193 | // An error response should be sent if there are issues processing the message. |
pferland | 0:5107fce16490 | 194 | if ((error = mqttClient.publishResponse(lastMessage.id, NULL, lastMessage.clientID)) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 195 | printf("Response failure, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 196 | } |
pferland | 0:5107fce16490 | 197 | } |
pferland | 0:5107fce16490 | 198 | } |
pferland | 0:5107fce16490 | 199 | |
pferland | 0:5107fce16490 | 200 | // Check that we are still connected, if not, reconnect. |
pferland | 0:5107fce16490 | 201 | if (!network.connected() || !mqttClient.connected()) { |
pferland | 0:5107fce16490 | 202 | network.disconnect(); |
pferland | 0:5107fce16490 | 203 | mqttClient.disconnect(); |
pferland | 2:abc89d2aede3 | 204 | printf("Reconnecting\r\n"); |
pferland | 0:5107fce16490 | 205 | while (connectClient(mqttClient, network) != CAYENNE_SUCCESS) { |
pferland | 0:5107fce16490 | 206 | wait(2); |
pferland | 2:abc89d2aede3 | 207 | printf("Reconnect failed, retrying\r\n"); |
pferland | 0:5107fce16490 | 208 | } |
pferland | 0:5107fce16490 | 209 | } |
pferland | 0:5107fce16490 | 210 | |
pferland | 0:5107fce16490 | 211 | // Publish some example data every few seconds. This should be changed to send your actual data to Cayenne. |
pferland | 0:5107fce16490 | 212 | if (timer.expired()) { |
pferland | 0:5107fce16490 | 213 | int error = 0; |
pferland | 0:5107fce16490 | 214 | float temp_data; |
pferland | 0:5107fce16490 | 215 | temp_sensor1->get_temperature(&temp_data); |
pferland | 2:abc89d2aede3 | 216 | printf("Temperature was: %f \r\n", temp_data); |
pferland | 0:5107fce16490 | 217 | if ((error = mqttClient.publishData(DATA_TOPIC, 1, TYPE_TEMPERATURE, UNIT_CELSIUS, temp_data)) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 218 | printf("Publish temperature failed, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 219 | } |
pferland | 0:5107fce16490 | 220 | humidity_sensor->get_humidity(&temp_data); |
pferland | 2:abc89d2aede3 | 221 | printf("Humidity was: %f \r\n", temp_data); |
pferland | 0:5107fce16490 | 222 | if ((error = mqttClient.publishData(DATA_TOPIC, 2, TYPE_RELATIVE_HUMIDITY, UNIT_PERCENT, temp_data)) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 223 | printf("Publish luminosity failed, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 224 | } |
pferland | 0:5107fce16490 | 225 | pressure_sensor->get_pressure(&temp_data); |
pferland | 2:abc89d2aede3 | 226 | printf("Pressure was: %f \r\n", temp_data); |
pferland | 0:5107fce16490 | 227 | if ((error = mqttClient.publishData(DATA_TOPIC, 3, TYPE_BAROMETRIC_PRESSURE, UNIT_HECTOPASCAL, temp_data)) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 228 | printf("Publish barometric pressure failed, error: %d\r\n", error); |
pferland | 0:5107fce16490 | 229 | } |
pferland | 2:abc89d2aede3 | 230 | printf("Led is: %s\r\n", Led1Out.read() ? "on" : "off"); |
pferland | 3:a40321269c7f | 231 | if ((error = mqttClient.publishData(DATA_TOPIC, 0, "digital_actuator", UNIT_DIGITAL, Led1Out.read())) != CAYENNE_SUCCESS) { |
pferland | 2:abc89d2aede3 | 232 | printf("Publish LED status failed, error: %d\r\n", error); |
pferland | 2:abc89d2aede3 | 233 | } |
pferland | 0:5107fce16490 | 234 | // Restart the countdown timer for publishing data every 5 seconds. Change the timeout parameter to publish at a different interval. |
pferland | 0:5107fce16490 | 235 | timer.countdown_ms(5000); |
pferland | 0:5107fce16490 | 236 | } else { |
pferland | 2:abc89d2aede3 | 237 | // debug |
pferland | 2:abc89d2aede3 | 238 | // printf("Timer: %d", timer.left_ms()); |
pferland | 0:5107fce16490 | 239 | } |
pferland | 0:5107fce16490 | 240 | } |
pferland | 0:5107fce16490 | 241 | } |
pferland | 0:5107fce16490 | 242 | |
pferland | 0:5107fce16490 | 243 | int main() |
pferland | 0:5107fce16490 | 244 | { |
pferland | 0:5107fce16490 | 245 | pc.baud(115200); |
pferland | 0:5107fce16490 | 246 | Led1Out = 0; |
pferland | 0:5107fce16490 | 247 | mts::MTSLog::setLogLevel(mts::MTSLog::TRACE_LEVEL); |
pferland | 0:5107fce16490 | 248 | // init radio, setup Cayenne connection |
pferland | 0:5107fce16490 | 249 | if (!init_mtsas()) { |
pferland | 0:5107fce16490 | 250 | while (true) { |
pferland | 0:5107fce16490 | 251 | logError("failed to initialize cellular radio"); |
pferland | 0:5107fce16490 | 252 | wait(1); |
pferland | 0:5107fce16490 | 253 | } |
pferland | 0:5107fce16490 | 254 | } |
pferland | 0:5107fce16490 | 255 | // Test with a ping |
pferland | 0:5107fce16490 | 256 | if(radio->ping("www.google.com")){ |
pferland | 2:abc89d2aede3 | 257 | printf("Ping test succeeded!\r\n"); |
pferland | 0:5107fce16490 | 258 | } else { |
pferland | 2:abc89d2aede3 | 259 | printf("Failed ping test!\r\n"); |
pferland | 0:5107fce16490 | 260 | } |
pferland | 0:5107fce16490 | 261 | MQTTNetwork<Cellular> network(*radio); |
pferland | 0:5107fce16490 | 262 | messageReady = false; |
pferland | 0:5107fce16490 | 263 | MQTTClient mqttClient(network, username.c_str(), password.c_str(), clientID.c_str()); |
pferland | 0:5107fce16490 | 264 | |
pferland | 0:5107fce16490 | 265 | // Set the default function that receives Cayenne messages. |
pferland | 0:5107fce16490 | 266 | mqttClient.setDefaultMessageHandler(messageArrived); |
pferland | 0:5107fce16490 | 267 | |
pferland | 0:5107fce16490 | 268 | // Connect to Cayenne. |
pferland | 0:5107fce16490 | 269 | if (connectClient(mqttClient, network) == CAYENNE_SUCCESS) { |
pferland | 0:5107fce16490 | 270 | // Run main loop. |
pferland | 0:5107fce16490 | 271 | loop(mqttClient, network); |
pferland | 0:5107fce16490 | 272 | } |
pferland | 0:5107fce16490 | 273 | else { |
pferland | 2:abc89d2aede3 | 274 | printf("Connection failed, exiting\r\n"); |
pferland | 0:5107fce16490 | 275 | } |
pferland | 0:5107fce16490 | 276 | |
pferland | 0:5107fce16490 | 277 | if (mqttClient.connected()) |
pferland | 0:5107fce16490 | 278 | mqttClient.disconnect(); |
pferland | 0:5107fce16490 | 279 | if (network.connected()) |
pferland | 0:5107fce16490 | 280 | network.disconnect(); |
pferland | 0:5107fce16490 | 281 | |
pferland | 0:5107fce16490 | 282 | return 0; |
pferland | 0:5107fce16490 | 283 | } |