IoT sensor/controller using STM32, W5500 ethernet, MQTT

Dependencies:   mbed WIZnet_Library Watchdog DHT MQTT DS1820

Committer:
Geekshow
Date:
Wed Feb 26 00:07:17 2020 +0000
Revision:
6:4d30bc5a8076
Parent:
5:b2ae1ed8a30e
Child:
7:3c2222251deb
inputs and outputs working!!

Who changed what in which revision?

UserRevisionLine numberNew contents of line
zhangyx 0:1170747a672f 1 #include "mbed.h"
Geekshow 3:de9611d75590 2 //#include "rtos.h"
Geekshow 3:de9611d75590 3 //#include "pins.h"
Geekshow 4:ebaf1973d008 4 #include "WIZnetInterface.h"
Geekshow 4:ebaf1973d008 5 #include "MQTTSocket.h"
Geekshow 4:ebaf1973d008 6 #include "MQTTClient.h"
Geekshow 3:de9611d75590 7
Geekshow 3:de9611d75590 8 // ========== PIN DEFINITIONS ============
Geekshow 6:4d30bc5a8076 9 // TODO move pin definitions into separate file
Geekshow 4:ebaf1973d008 10 #define LED_GREEN PA_5
Geekshow 4:ebaf1973d008 11 #define LED_ORANGE PA_1 // Don't use! Shared with D3
Geekshow 3:de9611d75590 12 #define BUTTON PC_9
Geekshow 3:de9611d75590 13
Geekshow 6:4d30bc5a8076 14 #define A_0 PC_0 // Analogue Input 0
Geekshow 6:4d30bc5a8076 15 #define A_1 PC_1 // Analogue Input 1
Geekshow 6:4d30bc5a8076 16 #define A_2 PC_2 // Analogue Input 2
Geekshow 6:4d30bc5a8076 17 #define A_3 PC_3 // Analogue Input 3
Geekshow 6:4d30bc5a8076 18 #define A_4 PC_4 // Analogue Input 4
Geekshow 6:4d30bc5a8076 19 #define A_5 PC_5 // Analogue Input 5
Geekshow 3:de9611d75590 20
Geekshow 6:4d30bc5a8076 21 #define D_0 PA_3 // digital output D0
Geekshow 6:4d30bc5a8076 22 #define D_1 PA_2 // digital output D1
Geekshow 6:4d30bc5a8076 23 #define D_2 PA_0 // digital output D2
Geekshow 6:4d30bc5a8076 24 #define D_3 PA_1 // digital output D3
Geekshow 6:4d30bc5a8076 25 #define D_4 PB_5 // digital output D4
Geekshow 6:4d30bc5a8076 26 #define D_5 PB_6 // digital output D5
Geekshow 6:4d30bc5a8076 27 #define D_6 PA_8 // digital output D6
Geekshow 6:4d30bc5a8076 28 #define D_7 PA_9 // digital output D7
Geekshow 6:4d30bc5a8076 29 #define D_8 PA_10 // digital output D8
Geekshow 6:4d30bc5a8076 30 #define D_9 PB_7 // digital output D9
Geekshow 6:4d30bc5a8076 31 #define D_10 PA_4 // digital output D10 - SPI1 SS
Geekshow 6:4d30bc5a8076 32 #define D_11 PA_4 // digital output D11 - SPI1 MOSI
Geekshow 6:4d30bc5a8076 33 #define D_12 PA_4 // digital output D12 - SPI1 MISO
Geekshow 6:4d30bc5a8076 34 //#define D_13 PA_4 // digital output D13 - SPI1 CLK - GREEN LED
Geekshow 6:4d30bc5a8076 35 #define D_14 PB_8 // digital output D14
Geekshow 3:de9611d75590 36 // ================= *************** ==================
Geekshow 6:4d30bc5a8076 37 #define USART3_TX PC_10 // D26 - pin 4 on Extension
Geekshow 6:4d30bc5a8076 38 // ================= *************** ==================
Geekshow 6:4d30bc5a8076 39 // serial output? for uLCD
Geekshow 6:4d30bc5a8076 40 // ================= *************** ==================
Geekshow 6:4d30bc5a8076 41 // sensor inputs?
Geekshow 3:de9611d75590 42 // ================= *************** ==================
Geekshow 4:ebaf1973d008 43 #define NODE_NAME "controller03" // TODO just define node number
Geekshow 3:de9611d75590 44
Geekshow 6:4d30bc5a8076 45 #define NUM_OUTPUTS 8
Geekshow 6:4d30bc5a8076 46 DigitalOut outputs[NUM_OUTPUTS] = {D_0, D_1, D_2, D_3, D_4, D_5, D_6, D_7};
Geekshow 6:4d30bc5a8076 47 #define NUM_INPUTS 6
Geekshow 6:4d30bc5a8076 48 DigitalIn inputs[NUM_INPUTS] = {PC_0, PC_1, PC_2, PC_3, PC_4, PC_5};
Geekshow 6:4d30bc5a8076 49 bool input_state[NUM_INPUTS];
Geekshow 6:4d30bc5a8076 50
Geekshow 6:4d30bc5a8076 51 Serial pc(USART3_TX, NC); // serial debug output on D26 (pin 4 of Extension)
Geekshow 6:4d30bc5a8076 52 //Serial pc(PA_9, NC); // serial debug output on D7
Geekshow 6:4d30bc5a8076 53 //Serial xxxxxx // find a serial port for Amp/uLCD connection
Geekshow 6:4d30bc5a8076 54
Geekshow 6:4d30bc5a8076 55 DigitalIn button(BUTTON);
Geekshow 6:4d30bc5a8076 56 DigitalOut led(LED_GREEN);
Geekshow 6:4d30bc5a8076 57
Geekshow 6:4d30bc5a8076 58 Ticker tick_60sec;
Geekshow 5:b2ae1ed8a30e 59 Ticker tick_5sec;
Geekshow 5:b2ae1ed8a30e 60 Ticker tick_1sec;
Geekshow 6:4d30bc5a8076 61 Ticker tick_500ms;
Geekshow 6:4d30bc5a8076 62
Geekshow 6:4d30bc5a8076 63 bool flag_publish;
Geekshow 4:ebaf1973d008 64
Geekshow 4:ebaf1973d008 65 typedef MQTT::Client<MQTTSocket,Countdown> MClient;
Geekshow 4:ebaf1973d008 66
Geekshow 6:4d30bc5a8076 67 const char* ONOFF[] = {"ON", "OFF"};
Geekshow 4:ebaf1973d008 68 const char* OPENCLOSED[] = {"CLOSED", "OPEN"};
Geekshow 6:4d30bc5a8076 69 enum IO_STATE{IO_ON, IO_OFF};
Geekshow 4:ebaf1973d008 70
Geekshow 5:b2ae1ed8a30e 71 uint8_t mac_addr[6]={0x00, 0x00, 0x00, 0xBE, 0xEF, 0x03}; // TODO make last byte dynamic
Geekshow 5:b2ae1ed8a30e 72 const char* mqtt_broker = "192.168.1.99";
Geekshow 5:b2ae1ed8a30e 73 const int mqtt_port = 1883;
Geekshow 5:b2ae1ed8a30e 74 unsigned long uptime_sec = 0;
Geekshow 6:4d30bc5a8076 75 int connected = -1;
Geekshow 5:b2ae1ed8a30e 76
Geekshow 5:b2ae1ed8a30e 77
Geekshow 4:ebaf1973d008 78
Geekshow 6:4d30bc5a8076 79 void on_control_cmd(const char* topic, const char* message)
zhangyx 0:1170747a672f 80 {
Geekshow 4:ebaf1973d008 81 int new_state = 0;
Geekshow 6:4d30bc5a8076 82 pc.printf("Received CMD %s %s\r\n", topic, message);
Geekshow 6:4d30bc5a8076 83 // find out command first
Geekshow 6:4d30bc5a8076 84 if(strcmp(message, "ON") == 0) {
Geekshow 4:ebaf1973d008 85 pc.printf("ON value requested!\r\n");
Geekshow 6:4d30bc5a8076 86 new_state = IO_ON;
Geekshow 4:ebaf1973d008 87 }
Geekshow 6:4d30bc5a8076 88 else if(strcmp(message, "OFF") == 0) {
Geekshow 4:ebaf1973d008 89 pc.printf("OFF value requested!\r\n");
Geekshow 6:4d30bc5a8076 90 new_state = IO_OFF;
Geekshow 4:ebaf1973d008 91 }
Geekshow 4:ebaf1973d008 92 else {
Geekshow 6:4d30bc5a8076 93 pc.printf("Unknown command value specified!\r\n"); // TODO return current value on no message
Geekshow 4:ebaf1973d008 94 return;
Geekshow 4:ebaf1973d008 95 }
Geekshow 6:4d30bc5a8076 96 // are we updating an output?
Geekshow 6:4d30bc5a8076 97 if(strncmp(topic, "output", 6) == 0) {
Geekshow 6:4d30bc5a8076 98 // find out which output to apply it to
Geekshow 6:4d30bc5a8076 99 int output_num = int(topic[6])-48;
Geekshow 6:4d30bc5a8076 100 if(output_num >= NUM_OUTPUTS) {
Geekshow 6:4d30bc5a8076 101 pc.printf("ERROR: unknown output num %d\r\n", output_num);
Geekshow 6:4d30bc5a8076 102 }
Geekshow 6:4d30bc5a8076 103 else {
Geekshow 6:4d30bc5a8076 104 // turn something on/off!
Geekshow 6:4d30bc5a8076 105 pc.printf("Output: %d updated to %s\r\n", output_num, ONOFF[new_state]);
Geekshow 6:4d30bc5a8076 106 outputs[output_num] = new_state;
Geekshow 6:4d30bc5a8076 107 // publish_value(client, topic, ONOFF[new_state]); // needs to access client :-/
Geekshow 6:4d30bc5a8076 108 }
Geekshow 6:4d30bc5a8076 109 }
Geekshow 6:4d30bc5a8076 110 else {
Geekshow 6:4d30bc5a8076 111 pc.printf("ERROR: Couldn't parse topic: %s\r\n", topic);
Geekshow 4:ebaf1973d008 112 }
Geekshow 4:ebaf1973d008 113 }
Geekshow 4:ebaf1973d008 114
Geekshow 4:ebaf1973d008 115 int publish(MClient& client, const char* msg_type, const char* point,
Geekshow 4:ebaf1973d008 116 const char* payload = NULL, size_t payload_len = 0,
Geekshow 4:ebaf1973d008 117 bool retain = false, MQTT::QoS qos = MQTT::QOS1){
Geekshow 4:ebaf1973d008 118 char topic[64];
Geekshow 4:ebaf1973d008 119 sprintf(topic, "%s/" NODE_NAME "/%s", msg_type, point);
Geekshow 4:ebaf1973d008 120 int ret = client.publish(topic, (void*)payload, payload_len, qos, retain);
Geekshow 4:ebaf1973d008 121 if(ret == -1) {
Geekshow 4:ebaf1973d008 122 pc.printf("ERROR during client.publish() = %d\r\n",ret);
Geekshow 4:ebaf1973d008 123 }
Geekshow 4:ebaf1973d008 124 return ret;
zhangyx 0:1170747a672f 125 }
zhangyx 2:a50b794b8ede 126
Geekshow 4:ebaf1973d008 127
Geekshow 4:ebaf1973d008 128 void messageArrived(MQTT::MessageData& md)
Geekshow 3:de9611d75590 129 {
Geekshow 6:4d30bc5a8076 130 // MQTT callback function
Geekshow 4:ebaf1973d008 131 MQTT::Message &message = md.message;
Geekshow 4:ebaf1973d008 132
Geekshow 4:ebaf1973d008 133 // copy message payload into local char array IMPROVE ME!
Geekshow 4:ebaf1973d008 134 char* payload = new char[message.payloadlen+1];
Geekshow 6:4d30bc5a8076 135 if(!payload) // will this ever happen?
Geekshow 4:ebaf1973d008 136 return;
Geekshow 4:ebaf1973d008 137 memcpy(payload, message.payload, message.payloadlen);
Geekshow 4:ebaf1973d008 138 payload[message.payloadlen]='\0';
Geekshow 4:ebaf1973d008 139
Geekshow 4:ebaf1973d008 140 // copy topic payload into local char array IMPROVE ME!
Geekshow 4:ebaf1973d008 141 char* topic = new char[md.topicName.lenstring.len+1];
Geekshow 6:4d30bc5a8076 142 if(!topic){ // will this ever happen?
Geekshow 4:ebaf1973d008 143 delete[] payload;
Geekshow 4:ebaf1973d008 144 return;
Geekshow 4:ebaf1973d008 145 }
Geekshow 4:ebaf1973d008 146 memcpy(topic, md.topicName.lenstring.data, md.topicName.lenstring.len);
Geekshow 4:ebaf1973d008 147 topic[md.topicName.lenstring.len]='\0';
Geekshow 4:ebaf1973d008 148
Geekshow 4:ebaf1973d008 149 pc.printf("Rcvd: %s : %s\r\n", topic, payload);
Geekshow 4:ebaf1973d008 150
Geekshow 6:4d30bc5a8076 151 // find first delimiter in topic string
Geekshow 4:ebaf1973d008 152 char *topics = strtok (topic,"/");
Geekshow 4:ebaf1973d008 153 for (int tok=0; tok<2 && topics != NULL; tok++) // WARNING! hard coded 2 layer topic!
Geekshow 4:ebaf1973d008 154 {
Geekshow 6:4d30bc5a8076 155 // pc.printf ("Topics %d: %s\r\n",tok, topics);
Geekshow 4:ebaf1973d008 156 topics = strtok (NULL, "/");
Geekshow 4:ebaf1973d008 157 }
Geekshow 4:ebaf1973d008 158 on_control_cmd(topics, payload);
Geekshow 4:ebaf1973d008 159 delete[] topic;
Geekshow 4:ebaf1973d008 160 delete[] payload;
Geekshow 3:de9611d75590 161 }
Geekshow 3:de9611d75590 162
Geekshow 4:ebaf1973d008 163
Geekshow 4:ebaf1973d008 164 int publish_value(MClient &client, const char *topic, const char *buf)
Geekshow 3:de9611d75590 165 {
Geekshow 4:ebaf1973d008 166 return publish(client, "stat", topic, buf, strlen(buf), true);
Geekshow 4:ebaf1973d008 167 }
Geekshow 4:ebaf1973d008 168
Geekshow 6:4d30bc5a8076 169
Geekshow 6:4d30bc5a8076 170 void publish_outputs(MClient &client) {
Geekshow 6:4d30bc5a8076 171 for(int i=0; i<NUM_OUTPUTS; i++) {
Geekshow 6:4d30bc5a8076 172 bool output_state = outputs[i];
Geekshow 6:4d30bc5a8076 173 char topic[] = "outputx";
Geekshow 6:4d30bc5a8076 174 topic[6] = i+48;
Geekshow 6:4d30bc5a8076 175 pc.printf("Output: %s is %s\r\n", topic, ONOFF[output_state]);
Geekshow 6:4d30bc5a8076 176 connected = publish_value(client, topic, ONOFF[output_state]);
Geekshow 6:4d30bc5a8076 177 }
Geekshow 6:4d30bc5a8076 178 }
Geekshow 6:4d30bc5a8076 179
Geekshow 6:4d30bc5a8076 180
Geekshow 6:4d30bc5a8076 181 void publish_info(MClient &client) {
Geekshow 6:4d30bc5a8076 182 // uptime
Geekshow 6:4d30bc5a8076 183 pc.printf("Uptime %d\r\n", uptime_sec);
Geekshow 6:4d30bc5a8076 184 char uptime_sec_str[12]; // long enough string for a long int
Geekshow 6:4d30bc5a8076 185 sprintf(uptime_sec_str, "%d", uptime_sec);
Geekshow 6:4d30bc5a8076 186 connected = publish_value(client,"uptime",uptime_sec_str);
Geekshow 6:4d30bc5a8076 187 }
Geekshow 6:4d30bc5a8076 188
Geekshow 6:4d30bc5a8076 189
Geekshow 6:4d30bc5a8076 190 void read_inputs(MClient &client)
Geekshow 6:4d30bc5a8076 191 {
Geekshow 6:4d30bc5a8076 192 for(int i=0; i<NUM_INPUTS; i++) {
Geekshow 6:4d30bc5a8076 193 bool old_state = input_state[i]; // save old state
Geekshow 6:4d30bc5a8076 194 input_state[i] = inputs[i]; // read new value
Geekshow 6:4d30bc5a8076 195 // pc.printf("Input %d is %d\r\n", i, input_state[i]);
Geekshow 6:4d30bc5a8076 196 if(input_state[i] != old_state) {
Geekshow 6:4d30bc5a8076 197 // input has changed state
Geekshow 6:4d30bc5a8076 198 pc.printf("Input %d changed to %s\r\n", i, OPENCLOSED[input_state[i]]);
Geekshow 6:4d30bc5a8076 199 char topic_str[8]; // long enough string for inputx
Geekshow 6:4d30bc5a8076 200 sprintf(topic_str, "input%d", i);
Geekshow 6:4d30bc5a8076 201 connected = publish_value(client,topic_str,OPENCLOSED[input_state[i]]);
Geekshow 6:4d30bc5a8076 202 }
Geekshow 6:4d30bc5a8076 203 }
Geekshow 6:4d30bc5a8076 204 }
Geekshow 6:4d30bc5a8076 205
Geekshow 5:b2ae1ed8a30e 206
Geekshow 5:b2ae1ed8a30e 207 int networking_init(MQTTSocket &sock, MClient &client, WIZnetInterface &wiz) {
Geekshow 4:ebaf1973d008 208 int ret = 0;
Geekshow 4:ebaf1973d008 209 pc.printf("\n\nNode: %s\r\n", NODE_NAME);
Geekshow 4:ebaf1973d008 210 pc.printf("%s attempting ethernet connection...\r\n", NODE_NAME);
Geekshow 4:ebaf1973d008 211 wiz.init(mac_addr); // resets the w5500
Geekshow 4:ebaf1973d008 212 if (wiz.connect() == (-1)) {
Geekshow 4:ebaf1973d008 213 pc.printf("Error getting DHCP address!!\r\n");
Geekshow 4:ebaf1973d008 214 }
Geekshow 4:ebaf1973d008 215
Geekshow 4:ebaf1973d008 216 pc.printf("IP: %s\r\n", wiz.getIPAddress());
Geekshow 5:b2ae1ed8a30e 217
Geekshow 4:ebaf1973d008 218 ret = sock.connect((char*)mqtt_broker,mqtt_port);
Geekshow 4:ebaf1973d008 219 if(ret != 0){
Geekshow 4:ebaf1973d008 220 pc.printf("failed to connect to TCP server\r\n");
Geekshow 4:ebaf1973d008 221 return 1;
Geekshow 4:ebaf1973d008 222 }
Geekshow 4:ebaf1973d008 223 pc.printf("sock.connect()=%d\r\n",ret);
Geekshow 5:b2ae1ed8a30e 224
Geekshow 4:ebaf1973d008 225 if(client.connect() != 0){
Geekshow 4:ebaf1973d008 226 pc.printf("MQTT connect failed\r\n");
Geekshow 4:ebaf1973d008 227 return -1;
Geekshow 4:ebaf1973d008 228 }
Geekshow 4:ebaf1973d008 229 pc.printf("client.connect()=%d\r\n",ret);
Geekshow 4:ebaf1973d008 230
Geekshow 4:ebaf1973d008 231 ret = client.subscribe("cmnd/" NODE_NAME "/+", MQTT::QOS1, messageArrived);
Geekshow 4:ebaf1973d008 232 pc.printf("client.subscribe()=%d\r\n", ret);
Geekshow 6:4d30bc5a8076 233 // TODO add client ID when subscribing
Geekshow 4:ebaf1973d008 234
Geekshow 4:ebaf1973d008 235 // Node online message
Geekshow 5:b2ae1ed8a30e 236 publish_value(client, "alive","ON");
Geekshow 5:b2ae1ed8a30e 237 publish_value(client, "IPAddress", wiz.getIPAddress());
Geekshow 4:ebaf1973d008 238 pc.printf("Initialization done.\r\n");
Geekshow 4:ebaf1973d008 239
Geekshow 4:ebaf1973d008 240 return 0;
Geekshow 5:b2ae1ed8a30e 241 }
Geekshow 5:b2ae1ed8a30e 242
Geekshow 6:4d30bc5a8076 243 void every_60sec() {
Geekshow 6:4d30bc5a8076 244 // no waits or blocking routines here please!
Geekshow 6:4d30bc5a8076 245 }
Geekshow 6:4d30bc5a8076 246
Geekshow 6:4d30bc5a8076 247 void every_5sec() {
Geekshow 6:4d30bc5a8076 248 // no waits or blocking routines here please!
Geekshow 6:4d30bc5a8076 249 flag_publish = 1;
Geekshow 3:de9611d75590 250 }
Geekshow 3:de9611d75590 251
Geekshow 5:b2ae1ed8a30e 252 void every_second() {
Geekshow 6:4d30bc5a8076 253 // no waits or blocking routines here please!
Geekshow 5:b2ae1ed8a30e 254 uptime_sec++;
Geekshow 6:4d30bc5a8076 255 if(connected == 0) {
Geekshow 6:4d30bc5a8076 256 led = !led;
Geekshow 6:4d30bc5a8076 257 }}
Geekshow 6:4d30bc5a8076 258
Geekshow 6:4d30bc5a8076 259 void every_500ms() {
Geekshow 6:4d30bc5a8076 260 // no waits or blocking routines here please!
Geekshow 6:4d30bc5a8076 261 if(connected != 0) {
Geekshow 6:4d30bc5a8076 262 led = !led;
Geekshow 6:4d30bc5a8076 263 }
Geekshow 5:b2ae1ed8a30e 264 }
Geekshow 3:de9611d75590 265
Geekshow 3:de9611d75590 266 int main()
Geekshow 3:de9611d75590 267 {
Geekshow 6:4d30bc5a8076 268 //WIZnetInterface wiz(PA_7, PA_6, PA_5, PA_4, PB_10); // SPI1 with D29 (reset)
Geekshow 6:4d30bc5a8076 269 WIZnetInterface wiz(PB_15, PB_14, PB_13, PB_12, NC); // SPI2 with no reset
Geekshow 6:4d30bc5a8076 270 MQTTSocket sock;
Geekshow 6:4d30bc5a8076 271 MClient client(sock);
Geekshow 6:4d30bc5a8076 272
Geekshow 6:4d30bc5a8076 273 tick_500ms.attach(&every_500ms, 0.5);
Geekshow 5:b2ae1ed8a30e 274 tick_1sec.attach(&every_second, 1.0);
Geekshow 6:4d30bc5a8076 275 tick_5sec.attach(&every_5sec, 5.0);
Geekshow 6:4d30bc5a8076 276 tick_60sec.attach(&every_60sec, 60);
Geekshow 6:4d30bc5a8076 277
Geekshow 6:4d30bc5a8076 278 //pulse all outputs
Geekshow 6:4d30bc5a8076 279 for(int i=0; i<NUM_OUTPUTS; i++) {
Geekshow 6:4d30bc5a8076 280 outputs[i] = IO_OFF;
Geekshow 6:4d30bc5a8076 281 wait(0.2);
Geekshow 6:4d30bc5a8076 282 }
Geekshow 6:4d30bc5a8076 283
Geekshow 5:b2ae1ed8a30e 284 pc.printf("\n\nNode: %s\r\n", NODE_NAME);
Geekshow 6:4d30bc5a8076 285
Geekshow 5:b2ae1ed8a30e 286 connected = networking_init(sock, client, wiz);
Geekshow 5:b2ae1ed8a30e 287
Geekshow 3:de9611d75590 288 while(1) {
Geekshow 6:4d30bc5a8076 289 read_inputs(client);
Geekshow 5:b2ae1ed8a30e 290
Geekshow 5:b2ae1ed8a30e 291 if(connected != 0) {
Geekshow 5:b2ae1ed8a30e 292 pc.printf("Restarting network....\r\n");
Geekshow 6:4d30bc5a8076 293 connected = networking_init(sock, client, wiz);
Geekshow 6:4d30bc5a8076 294 }
Geekshow 6:4d30bc5a8076 295 else {
Geekshow 6:4d30bc5a8076 296 // we're connected, do stuff!
Geekshow 6:4d30bc5a8076 297 if(flag_publish) {
Geekshow 6:4d30bc5a8076 298 publish_outputs(client);
Geekshow 6:4d30bc5a8076 299 publish_info(client);
Geekshow 6:4d30bc5a8076 300 flag_publish = 0;
Geekshow 6:4d30bc5a8076 301 }
zhangyx 0:1170747a672f 302 }
Geekshow 5:b2ae1ed8a30e 303 // pause a while, yawn......
Geekshow 6:4d30bc5a8076 304 client.yield(500);
zhangyx 0:1170747a672f 305 }
zhangyx 0:1170747a672f 306 }