This is a SLIP interface for the STM32F446RE Nucleo Board. It is designed to work specifically with the esp-link software for the ESP8266. The program is an example of a rest command.

Dependencies:   mbed DHT Matrix

Committer:
ShaneKirkbride
Date:
Mon Oct 10 04:41:04 2016 +0000
Revision:
12:0df73cbe5cbf
Parent:
8:6a3b7c5d9ba7
the latest;

Who changed what in which revision?

UserRevisionLine numberNew contents of line
ShaneKirkbride 0:70a6082c1bf7 1 #include "STMClient.h"
ShaneKirkbride 0:70a6082c1bf7 2 #include "millis.h"
ShaneKirkbride 0:70a6082c1bf7 3
ShaneKirkbride 0:70a6082c1bf7 4 #define xstr(s) str(s)
ShaneKirkbride 0:70a6082c1bf7 5 #define str(s) #s
ShaneKirkbride 0:70a6082c1bf7 6
ShaneKirkbride 0:70a6082c1bf7 7 #define SLIP_END 0300 // indicates end of packet
ShaneKirkbride 0:70a6082c1bf7 8 #define SLIP_ESC 0333 // indicates byte stuffing
ShaneKirkbride 0:70a6082c1bf7 9 #define SLIP_ESC_END 0334 // ESC ESC_END means END data byte
ShaneKirkbride 0:70a6082c1bf7 10 #define SLIP_ESC_ESC 0335 // ESC ESC_ESC means ESC data byte
ShaneKirkbride 0:70a6082c1bf7 11
ShaneKirkbride 0:70a6082c1bf7 12 //===== Input
ShaneKirkbride 0:70a6082c1bf7 13
ShaneKirkbride 0:70a6082c1bf7 14 // Process a received SLIP message
ShaneKirkbride 0:70a6082c1bf7 15 STMClientPacket* STMClient::protoCompletedCb(void) {
ShaneKirkbride 0:70a6082c1bf7 16 // the packet starts with a STMClientPacket
ShaneKirkbride 0:70a6082c1bf7 17 STMClientPacket* packet = (STMClientPacket*)_proto.buf;
ShaneKirkbride 0:70a6082c1bf7 18 if (_debugEn) {
ShaneKirkbride 0:70a6082c1bf7 19 // _debug->printf("STMC: got %i @ %i:\n\r 0x%x\n\r 0x%x\n\r 0x%x\n\r", _proto.dataLen,(uint32_t)_proto.buf, packet->cmd, packet->value, packet->argc);
ShaneKirkbride 0:70a6082c1bf7 20
ShaneKirkbride 0:70a6082c1bf7 21 for (uint16_t i=8; i<_proto.dataLen; i++)
ShaneKirkbride 0:70a6082c1bf7 22 {
ShaneKirkbride 0:70a6082c1bf7 23 // _debug->printf("%x", *(uint8_t*)(_proto.buf+i));
ShaneKirkbride 0:70a6082c1bf7 24 }
ShaneKirkbride 0:70a6082c1bf7 25 //_debug->printf("\n\r");
ShaneKirkbride 0:70a6082c1bf7 26 }
ShaneKirkbride 0:70a6082c1bf7 27
ShaneKirkbride 0:70a6082c1bf7 28 // verify CRC
ShaneKirkbride 0:70a6082c1bf7 29 uint16_t crc = crc16Data(_proto.buf, _proto.dataLen-2, 0);
ShaneKirkbride 0:70a6082c1bf7 30 //_debug->printf("CRC: %i\n\r",crc);
ShaneKirkbride 0:70a6082c1bf7 31 //wait(0.5);
ShaneKirkbride 0:70a6082c1bf7 32 uint16_t resp_crc = *(uint16_t*)(_proto.buf+_proto.dataLen-2);
ShaneKirkbride 0:70a6082c1bf7 33 //_debug->printf("resp_crc: %i\n\r",resp_crc);
ShaneKirkbride 0:70a6082c1bf7 34 if (crc != resp_crc) {
ShaneKirkbride 0:70a6082c1bf7 35 DBG("STMC: Invalid CRC\n\r");
ShaneKirkbride 8:6a3b7c5d9ba7 36 wait(0.25);
ShaneKirkbride 8:6a3b7c5d9ba7 37 return NULL; //maybe the CRC isn't getting calculated correctly...
ShaneKirkbride 0:70a6082c1bf7 38 }
ShaneKirkbride 0:70a6082c1bf7 39
ShaneKirkbride 0:70a6082c1bf7 40 // dispatch based on command
ShaneKirkbride 0:70a6082c1bf7 41 if (packet->cmd == CMD_RESP_V) {
ShaneKirkbride 0:70a6082c1bf7 42 // value response
ShaneKirkbride 0:70a6082c1bf7 43 _debug->printf("RESP_V: 0x%x \n\r",packet->value);
ShaneKirkbride 0:70a6082c1bf7 44
ShaneKirkbride 0:70a6082c1bf7 45 return packet;
ShaneKirkbride 0:70a6082c1bf7 46 } else if (packet->cmd == CMD_RESP_CB) {
ShaneKirkbride 0:70a6082c1bf7 47 FP<void, void*> *fp;
ShaneKirkbride 0:70a6082c1bf7 48 // callback reponse
ShaneKirkbride 0:70a6082c1bf7 49 _debug->printf("RESP_CB: 0x%x 0x%x \n\r", packet->value, packet->argc);
ShaneKirkbride 0:70a6082c1bf7 50
ShaneKirkbride 0:70a6082c1bf7 51 fp = (FP<void, void*>*)packet->value;
ShaneKirkbride 0:70a6082c1bf7 52 if (fp->attached()) {
ShaneKirkbride 0:70a6082c1bf7 53 STMClientResponse resp(packet);
ShaneKirkbride 0:70a6082c1bf7 54 (*fp)(&resp);
ShaneKirkbride 0:70a6082c1bf7 55 }
ShaneKirkbride 0:70a6082c1bf7 56 return NULL;
ShaneKirkbride 0:70a6082c1bf7 57 } else {
ShaneKirkbride 0:70a6082c1bf7 58 // command (NOT IMPLEMENTED)
ShaneKirkbride 0:70a6082c1bf7 59 _debug->printf("CMD 0x%x Value 0x%x ??\n\r", packet->cmd, packet->value);
ShaneKirkbride 0:70a6082c1bf7 60 return NULL;
ShaneKirkbride 0:70a6082c1bf7 61 }
ShaneKirkbride 0:70a6082c1bf7 62 }
ShaneKirkbride 0:70a6082c1bf7 63
ShaneKirkbride 0:70a6082c1bf7 64 // Read all characters available on the serial input and process any messages that arrive, but
ShaneKirkbride 0:70a6082c1bf7 65 // stop if a non-callback response comes in
ShaneKirkbride 0:70a6082c1bf7 66 STMClientPacket *STMClient::Process() {
ShaneKirkbride 4:31bed73a0d08 67 int value;
ShaneKirkbride 0:70a6082c1bf7 68 while (_serial->readable()) {
ShaneKirkbride 4:31bed73a0d08 69 value = _serial->getc();
ShaneKirkbride 4:31bed73a0d08 70 if (value == SLIP_ESC) {
ShaneKirkbride 0:70a6082c1bf7 71 _proto.isEsc = 1;
ShaneKirkbride 4:31bed73a0d08 72 } else if (value == SLIP_END) {
ShaneKirkbride 0:70a6082c1bf7 73 STMClientPacket *packet = _proto.dataLen >= 8 ? protoCompletedCb() : 0;
ShaneKirkbride 0:70a6082c1bf7 74 _proto.dataLen = 0;
ShaneKirkbride 0:70a6082c1bf7 75 _proto.isEsc = 0;
ShaneKirkbride 0:70a6082c1bf7 76 if (packet != NULL) return packet;
ShaneKirkbride 0:70a6082c1bf7 77 } else {
ShaneKirkbride 0:70a6082c1bf7 78 if (_proto.isEsc) {
ShaneKirkbride 4:31bed73a0d08 79 if (value == SLIP_ESC_END) value = SLIP_END;
ShaneKirkbride 4:31bed73a0d08 80 if (value == SLIP_ESC_ESC) value = SLIP_ESC;
ShaneKirkbride 0:70a6082c1bf7 81 _proto.isEsc = 0;
ShaneKirkbride 0:70a6082c1bf7 82 }
ShaneKirkbride 0:70a6082c1bf7 83 if (_proto.dataLen < _proto.bufSize) {
ShaneKirkbride 4:31bed73a0d08 84 _proto.buf[_proto.dataLen++] = value;
ShaneKirkbride 0:70a6082c1bf7 85 }
ShaneKirkbride 0:70a6082c1bf7 86 }
ShaneKirkbride 0:70a6082c1bf7 87 }
ShaneKirkbride 0:70a6082c1bf7 88 return NULL;
ShaneKirkbride 0:70a6082c1bf7 89 }
ShaneKirkbride 0:70a6082c1bf7 90
ShaneKirkbride 0:70a6082c1bf7 91 //===== Output
ShaneKirkbride 0:70a6082c1bf7 92
ShaneKirkbride 0:70a6082c1bf7 93 // Write a byte to the output stream and perform SLIP escaping
ShaneKirkbride 0:70a6082c1bf7 94 void STMClient::write(uint8_t data) {
ShaneKirkbride 0:70a6082c1bf7 95 switch (data) {
ShaneKirkbride 0:70a6082c1bf7 96 case SLIP_END:
ShaneKirkbride 0:70a6082c1bf7 97 _serial->putc(SLIP_ESC);
ShaneKirkbride 0:70a6082c1bf7 98 _serial->putc(SLIP_ESC_END);
ShaneKirkbride 0:70a6082c1bf7 99 break;
ShaneKirkbride 0:70a6082c1bf7 100 case SLIP_ESC:
ShaneKirkbride 0:70a6082c1bf7 101 _serial->putc(SLIP_ESC);
ShaneKirkbride 0:70a6082c1bf7 102 _serial->putc(SLIP_ESC_ESC);
ShaneKirkbride 0:70a6082c1bf7 103 break;
ShaneKirkbride 0:70a6082c1bf7 104 default:
ShaneKirkbride 0:70a6082c1bf7 105 _serial->putc(data);
ShaneKirkbride 0:70a6082c1bf7 106 }
ShaneKirkbride 0:70a6082c1bf7 107 }
ShaneKirkbride 0:70a6082c1bf7 108
ShaneKirkbride 0:70a6082c1bf7 109 // Write some bytes to the output stream
ShaneKirkbride 0:70a6082c1bf7 110 void STMClient::write(void* data, uint16_t len) {
ShaneKirkbride 0:70a6082c1bf7 111 uint8_t *d = (uint8_t*)data;
ShaneKirkbride 3:8ed85d940c4c 112 //_debug->printf("Writing: 0x%x\n\r", (const char*)data);
ShaneKirkbride 0:70a6082c1bf7 113 while (len--)
ShaneKirkbride 2:20ea1be14e4b 114 {
ShaneKirkbride 0:70a6082c1bf7 115 write(*d++);
ShaneKirkbride 2:20ea1be14e4b 116 }
ShaneKirkbride 0:70a6082c1bf7 117 }
ShaneKirkbride 0:70a6082c1bf7 118
ShaneKirkbride 0:70a6082c1bf7 119 // Start a request. cmd=command, value=address of callback pointer or first arg,
ShaneKirkbride 0:70a6082c1bf7 120 // argc=additional argument count
ShaneKirkbride 0:70a6082c1bf7 121 void STMClient::Request(uint16_t cmd, uint32_t value, uint16_t argc) {
ShaneKirkbride 3:8ed85d940c4c 122 //_debug->printf("Starting a request...\n\r");
ShaneKirkbride 3:8ed85d940c4c 123 wait(0.25);
ShaneKirkbride 0:70a6082c1bf7 124 crc = 0;
ShaneKirkbride 0:70a6082c1bf7 125 _serial->putc(SLIP_END);
ShaneKirkbride 0:70a6082c1bf7 126
ShaneKirkbride 0:70a6082c1bf7 127 write(&cmd, 2);
ShaneKirkbride 0:70a6082c1bf7 128 crc = crc16Data((unsigned const char*)&cmd, 2, crc);
ShaneKirkbride 0:70a6082c1bf7 129
ShaneKirkbride 0:70a6082c1bf7 130 write(&argc, 2);
ShaneKirkbride 0:70a6082c1bf7 131 crc = crc16Data((unsigned const char*)&argc, 2, crc);
ShaneKirkbride 0:70a6082c1bf7 132
ShaneKirkbride 0:70a6082c1bf7 133 write(&value, 4);
ShaneKirkbride 0:70a6082c1bf7 134 crc = crc16Data((unsigned const char*)&value, 4, crc);
ShaneKirkbride 0:70a6082c1bf7 135 }
ShaneKirkbride 0:70a6082c1bf7 136
ShaneKirkbride 0:70a6082c1bf7 137 // Append a block of data as an argument to the request
ShaneKirkbride 0:70a6082c1bf7 138 void STMClient::Request(const void* data, uint16_t len) {
ShaneKirkbride 0:70a6082c1bf7 139 uint8_t *d = (uint8_t*)data;
ShaneKirkbride 0:70a6082c1bf7 140
ShaneKirkbride 0:70a6082c1bf7 141 // write the length
ShaneKirkbride 0:70a6082c1bf7 142 write(&len, 2);
ShaneKirkbride 0:70a6082c1bf7 143 crc = crc16Data((unsigned const char*)&len, 2, crc);
ShaneKirkbride 0:70a6082c1bf7 144
ShaneKirkbride 0:70a6082c1bf7 145 // output the data
ShaneKirkbride 0:70a6082c1bf7 146 for (uint16_t l=len; l>0; l--) {
ShaneKirkbride 0:70a6082c1bf7 147 write(*d);
ShaneKirkbride 0:70a6082c1bf7 148 crc = crc16Add(*d, crc);
ShaneKirkbride 0:70a6082c1bf7 149 d++;
ShaneKirkbride 0:70a6082c1bf7 150 }
ShaneKirkbride 0:70a6082c1bf7 151
ShaneKirkbride 0:70a6082c1bf7 152 // output padding
ShaneKirkbride 0:70a6082c1bf7 153 uint16_t pad = (4-(len&3))&3;
ShaneKirkbride 0:70a6082c1bf7 154 uint8_t temp = 0;
ShaneKirkbride 0:70a6082c1bf7 155 while (pad--) {
ShaneKirkbride 0:70a6082c1bf7 156 write(temp);
ShaneKirkbride 0:70a6082c1bf7 157 crc = crc16Add(temp, crc);
ShaneKirkbride 0:70a6082c1bf7 158 }
ShaneKirkbride 0:70a6082c1bf7 159 }
ShaneKirkbride 2:20ea1be14e4b 160 /*
ShaneKirkbride 0:70a6082c1bf7 161 // Append a block of data located in flash as an argument to the request
ShaneKirkbride 2:20ea1be14e4b 162 void STMClient::Request(const __FlashStringHelper* data, uint16_t len) {
ShaneKirkbride 0:70a6082c1bf7 163 // write the length
ShaneKirkbride 0:70a6082c1bf7 164 write(&len, 2);
ShaneKirkbride 0:70a6082c1bf7 165 crc = crc16Data((unsigned const char*)&len, 2, crc);
ShaneKirkbride 0:70a6082c1bf7 166
ShaneKirkbride 0:70a6082c1bf7 167 // output the data
ShaneKirkbride 0:70a6082c1bf7 168 PGM_P p = reinterpret_cast<PGM_P>(data);
ShaneKirkbride 0:70a6082c1bf7 169 for (uint16_t l=len; l>0; l--) {
ShaneKirkbride 0:70a6082c1bf7 170 uint8_t c = pgm_read_byte(p++);
ShaneKirkbride 0:70a6082c1bf7 171 write(c);
ShaneKirkbride 0:70a6082c1bf7 172 crc = crc16Add(c, crc);
ShaneKirkbride 0:70a6082c1bf7 173 }
ShaneKirkbride 0:70a6082c1bf7 174
ShaneKirkbride 0:70a6082c1bf7 175 // output padding
ShaneKirkbride 0:70a6082c1bf7 176 uint16_t pad = (4-(len&3))&3;
ShaneKirkbride 0:70a6082c1bf7 177 uint8_t temp = 0;
ShaneKirkbride 0:70a6082c1bf7 178 while (pad--) {
ShaneKirkbride 0:70a6082c1bf7 179 write(temp);
ShaneKirkbride 0:70a6082c1bf7 180 crc = crc16Add(temp, crc);
ShaneKirkbride 0:70a6082c1bf7 181 }
ShaneKirkbride 0:70a6082c1bf7 182 }
ShaneKirkbride 0:70a6082c1bf7 183 */
ShaneKirkbride 0:70a6082c1bf7 184
ShaneKirkbride 0:70a6082c1bf7 185 // Append the final CRC to the request and finish the request
ShaneKirkbride 0:70a6082c1bf7 186 void STMClient::Request(void) {
ShaneKirkbride 0:70a6082c1bf7 187 write((uint8_t*)&crc, 2);
ShaneKirkbride 2:20ea1be14e4b 188
ShaneKirkbride 0:70a6082c1bf7 189 _serial->putc(SLIP_END);
ShaneKirkbride 0:70a6082c1bf7 190 }
ShaneKirkbride 0:70a6082c1bf7 191
ShaneKirkbride 0:70a6082c1bf7 192 //===== Initialization
ShaneKirkbride 0:70a6082c1bf7 193
ShaneKirkbride 0:70a6082c1bf7 194 void STMClient::init() {
ShaneKirkbride 0:70a6082c1bf7 195 _proto.buf = _protoBuf;
ShaneKirkbride 0:70a6082c1bf7 196 _proto.bufSize = sizeof(_protoBuf);
ShaneKirkbride 0:70a6082c1bf7 197 _proto.dataLen = 0;
ShaneKirkbride 0:70a6082c1bf7 198 _proto.isEsc = 0;
ShaneKirkbride 0:70a6082c1bf7 199 }
ShaneKirkbride 0:70a6082c1bf7 200
ShaneKirkbride 0:70a6082c1bf7 201 STMClient::STMClient(Serial* serial) :
ShaneKirkbride 0:70a6082c1bf7 202 _serial(serial) {
ShaneKirkbride 0:70a6082c1bf7 203 _debugEn = false;
ShaneKirkbride 0:70a6082c1bf7 204 init();
ShaneKirkbride 0:70a6082c1bf7 205 }
ShaneKirkbride 0:70a6082c1bf7 206
ShaneKirkbride 0:70a6082c1bf7 207 STMClient::STMClient(Serial* serial, Serial* debug) :
ShaneKirkbride 0:70a6082c1bf7 208 _debug(debug), _serial(serial) {
ShaneKirkbride 0:70a6082c1bf7 209 _debugEn = true;
ShaneKirkbride 0:70a6082c1bf7 210 init();
ShaneKirkbride 0:70a6082c1bf7 211 }
ShaneKirkbride 0:70a6082c1bf7 212
ShaneKirkbride 0:70a6082c1bf7 213 void STMClient::DBG(const char* info) {
ShaneKirkbride 0:70a6082c1bf7 214 if (_debugEn) _debug->printf(info);
ShaneKirkbride 0:70a6082c1bf7 215 }
ShaneKirkbride 0:70a6082c1bf7 216
ShaneKirkbride 0:70a6082c1bf7 217 //===== Responses
ShaneKirkbride 0:70a6082c1bf7 218
ShaneKirkbride 0:70a6082c1bf7 219 // Wait for a response for a given timeout
ShaneKirkbride 0:70a6082c1bf7 220 STMClientPacket *STMClient::WaitReturn(uint32_t timeout) {
ShaneKirkbride 0:70a6082c1bf7 221 uint32_t wait = millis();
ShaneKirkbride 0:70a6082c1bf7 222 while (millis() - wait < timeout) {
ShaneKirkbride 0:70a6082c1bf7 223 STMClientPacket *packet = Process();
ShaneKirkbride 0:70a6082c1bf7 224 if (packet != NULL) return packet;
ShaneKirkbride 0:70a6082c1bf7 225 }
ShaneKirkbride 0:70a6082c1bf7 226 return NULL;
ShaneKirkbride 0:70a6082c1bf7 227 }
ShaneKirkbride 0:70a6082c1bf7 228
ShaneKirkbride 0:70a6082c1bf7 229 //===== CRC hSTMper functions
ShaneKirkbride 0:70a6082c1bf7 230
ShaneKirkbride 0:70a6082c1bf7 231 uint16_t STMClient::crc16Add(unsigned char b, uint16_t acc)
ShaneKirkbride 0:70a6082c1bf7 232 {
ShaneKirkbride 0:70a6082c1bf7 233 acc ^= b;
ShaneKirkbride 0:70a6082c1bf7 234 acc = (acc >> 8) | (acc << 8);
ShaneKirkbride 0:70a6082c1bf7 235 acc ^= (acc & 0xff00) << 4;
ShaneKirkbride 0:70a6082c1bf7 236 acc ^= (acc >> 8) >> 4;
ShaneKirkbride 0:70a6082c1bf7 237 acc ^= (acc & 0xff00) >> 5;
ShaneKirkbride 0:70a6082c1bf7 238 return acc;
ShaneKirkbride 0:70a6082c1bf7 239 }
ShaneKirkbride 0:70a6082c1bf7 240
ShaneKirkbride 0:70a6082c1bf7 241 uint16_t STMClient::crc16Data(const unsigned char *data, uint16_t len, uint16_t acc)
ShaneKirkbride 0:70a6082c1bf7 242 {
ShaneKirkbride 0:70a6082c1bf7 243 for (uint16_t i=0; i<len; i++)
ShaneKirkbride 0:70a6082c1bf7 244 acc = crc16Add(*data++, acc);
ShaneKirkbride 0:70a6082c1bf7 245 return acc;
ShaneKirkbride 0:70a6082c1bf7 246 }
ShaneKirkbride 0:70a6082c1bf7 247
ShaneKirkbride 0:70a6082c1bf7 248 //===== Basic requests built into STMClient
ShaneKirkbride 0:70a6082c1bf7 249
ShaneKirkbride 0:70a6082c1bf7 250 bool STMClient::Sync(uint32_t timeout) {
ShaneKirkbride 0:70a6082c1bf7 251 //_debug->printf("syncing...");
ShaneKirkbride 0:70a6082c1bf7 252 wait(0.5);
ShaneKirkbride 0:70a6082c1bf7 253 // send sync request
ShaneKirkbride 0:70a6082c1bf7 254 Request(CMD_SYNC, (uint32_t)&wifiCb, 0);
ShaneKirkbride 0:70a6082c1bf7 255 Request();
ShaneKirkbride 0:70a6082c1bf7 256
ShaneKirkbride 0:70a6082c1bf7 257 // empty the response queue hoping to find the wifiCb address
ShaneKirkbride 0:70a6082c1bf7 258 STMClientPacket *packet;
ShaneKirkbride 0:70a6082c1bf7 259 while ((packet = WaitReturn(timeout)) != NULL) {
ShaneKirkbride 0:70a6082c1bf7 260 if (packet->value == (uint32_t)&wifiCb)
ShaneKirkbride 0:70a6082c1bf7 261 {
ShaneKirkbride 0:70a6082c1bf7 262 // _debug->printf("SYNC!");
ShaneKirkbride 2:20ea1be14e4b 263 wait(0.5);
ShaneKirkbride 0:70a6082c1bf7 264 return true;
ShaneKirkbride 0:70a6082c1bf7 265 }
ShaneKirkbride 0:70a6082c1bf7 266 _debug->printf("BAD: %s /n/r", packet->value);
ShaneKirkbride 0:70a6082c1bf7 267 }
ShaneKirkbride 0:70a6082c1bf7 268
ShaneKirkbride 0:70a6082c1bf7 269 // doesn't look like we got a real response
ShaneKirkbride 0:70a6082c1bf7 270 return false;
ShaneKirkbride 0:70a6082c1bf7 271 }
ShaneKirkbride 8:6a3b7c5d9ba7 272 //look in cmd.c for this. Puts ESP to sleep until a fall edge triggers on RST for it to reset.
ShaneKirkbride 8:6a3b7c5d9ba7 273
ShaneKirkbride 8:6a3b7c5d9ba7 274 void STMClient::Sleep(void){
ShaneKirkbride 8:6a3b7c5d9ba7 275 Request(CMD_SLEEP, 0, 0);
ShaneKirkbride 8:6a3b7c5d9ba7 276 Request();
ShaneKirkbride 8:6a3b7c5d9ba7 277 }
ShaneKirkbride 0:70a6082c1bf7 278
ShaneKirkbride 0:70a6082c1bf7 279 void STMClient::GetWifiStatus(void) {
ShaneKirkbride 0:70a6082c1bf7 280 Request(CMD_WIFI_STATUS, 0, 0);
ShaneKirkbride 0:70a6082c1bf7 281 Request();
ShaneKirkbride 0:70a6082c1bf7 282 }