Browse Source

first commit with base ino file

Julien Banchet 3 years ago
commit
a422c5ca90
2 changed files with 540 additions and 0 deletions
  1. 0 0
      README.md
  2. 540 0
      m5core-ovmsWSclient.ino

+ 0 - 0
README.md


+ 540 - 0
m5core-ovmsWSclient.ino

@@ -0,0 +1,540 @@
+/*
+ * WebSocketClient.ino
+ *
+ *  Created on: 24.05.2015
+ *
+ */
+ 
+//#include <Arduino.h>
+#include <M5Stack.h>
+#include <ArduinoJson.h>
+#include <WiFi.h>
+#include <WiFiMulti.h>
+#include <WiFiClientSecure.h>
+ 
+#include <HttpClient.h>
+ 
+#include <WebSocketsClient.h>
+ 
+ 
+WiFiMulti WiFiMulti;
+WebSocketsClient webSocket;
+ 
+HttpClient htclient;
+ 
+bool localTesting = true;
+bool genRandomVals = false;
+ 
+const char* OVMSUSER = "admin";
+const char* OVMSPASS = "OVMSP455w0rd"; // Change this to your password
+ 
+const int LCDBrightnessSteps[] = {60, 120, 250};
+int LCDBrightnessStep = 0;
+ 
+const int heightBarGraphSOC = 72;
+const int widthBarGraphSOC = 300;
+const int leftBarGraphSOC  = (320-widthBarGraphSOC-4)/2;
+const int topBarGraphSOC   = 0;
+const int SOCtextSize  = 7;
+const int SOCtextWidth = M5.Lcd.textWidth("99%")*SOCtextSize;
+ 
+const int heightBarGraphPWR = 32;
+const int widthBarGraphPWR = 220;
+const int PWRtextSize  = 3;
+const int PWRtextWidth = M5.Lcd.textWidth("100kW")*PWRtextSize;
+const int leftBarGraphPWR  = (320-widthBarGraphPWR-4);
+const int topBarGraphPWR   = 80;
+ 
+const int topTextLineRange = topBarGraphPWR + heightBarGraphPWR + 16;
+const int topTextLineConsumption = topTextLineRange + 32;
+const int topTextLineTemps = topTextLineConsumption + 32;
+ 
+const float MAXINPOWER  = 100;
+const float MAXOUTPOWER = 200;
+const float MAXRANGEPWR = 300;
+ 
+float vbsoc         = 90.5;
+float vbrangeideal  = 402 ;
+float vbrangefull   = 450 ;
+float vetemp        = 13  ;
+float vbtemp        = 15  ;
+float xknbinlettemp = 14  ;
+float vbpower       = 1   ;
+float vbconsumption = 160 ;
+ 
+const uint16_t DARKERCYAN = 0x0124;
+ 
+const size_t jsonCapacity = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(38) + 910;
+DynamicJsonDocument jsonDoc(jsonCapacity);
+DeserializationError payloadDecodeError;
+uint8_t M5triggerUpdate = 0;
+ 
+uint8_t dummyCounter = 0;
+ 
+unsigned long prevTimer     = millis();
+unsigned long displayTimer  = millis();
+unsigned long spentTimer       = 0;
+bool          slowRefreshTrigger = false;
+unsigned long slowRefreshTimer = 0;
+unsigned long slowRefreshMax   = 5000;
+bool          mildRefreshTrigger = false;
+unsigned long mildRefreshTimer = 0;
+unsigned long mildRefreshMax   = 2000;
+bool          fastRefreshTrigger = false;
+unsigned long fastRefreshTimer = 0;
+unsigned long fastRefreshMax   = 100;
+ 
+#define USE_SERIAL Serial
+ 
+void hexdump(const void *mem, uint32_t len, uint8_t cols = 16) {
+  const uint8_t* src = (const uint8_t*) mem;
+  USE_SERIAL.printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
+  for(uint32_t i = 0; i < len; i++) {
+    if(i % cols == 0) {
+      USE_SERIAL.printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
+    }
+    USE_SERIAL.printf("%02X ", *src);
+    src++;
+  }
+  USE_SERIAL.printf("\n");
+}
+ 
+void draw_vbsoc(float vbsoc) {
+  M5.Lcd.setTextSize(SOCtextSize);
+  uint16_t SOCcolor = GREEN;
+  uint16_t SOCtextColor = 0x0720; // Slightly Darker Green
+  uint16_t BorderColor = WHITE;
+  uint16_t BGcolor = 0x2970; //BLUE;
+  if(vbsoc<25){ SOCcolor = YELLOW; SOCtextColor = YELLOW; BorderColor = YELLOW; }
+  if(vbsoc<10){ SOCcolor = RED; SOCtextColor = RED; BorderColor = RED; BGcolor = MAROON; }
+  M5.Lcd.fillRoundRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+6, heightBarGraphSOC+4 , 6, BGcolor);
+  //M5.Lcd.fillRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+4, heightBarGraphSOC+4 , BGcolor);
+  M5.Lcd.drawRoundRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+4, heightBarGraphSOC+4 , 6, BorderColor);
+  M5.Lcd.fillRoundRect(leftBarGraphSOC+2, topBarGraphSOC+2, widthBarGraphSOC*(vbsoc/100), heightBarGraphSOC , 4, SOCcolor);
+  if(vbsoc>=50){
+    M5.Lcd.setTextColor(BLACK);
+    M5.Lcd.setCursor(leftBarGraphSOC+6+3*vbsoc-SOCtextWidth-6, topBarGraphSOC+14);
+    M5.Lcd.print(vbsoc,0); M5.Lcd.print('%');
+  }else{
+    M5.Lcd.setTextColor(SOCtextColor);
+    M5.Lcd.setCursor(leftBarGraphSOC+6+3*vbsoc+8, topBarGraphSOC+12);
+    M5.Lcd.print(vbsoc,0); M5.Lcd.print('%');
+  }
+  //M5.Lcd.printf("%*s", 5, vbsoc);
+  M5triggerUpdate++;
+}
+ 
+void draw_vbpower(float vbpower) {
+  M5.Lcd.setTextSize(PWRtextSize);
+  uint16_t PWRcolor;
+ 
+  uint16_t BorderColor = LIGHTGREY; //WHITE;
+  uint16_t BGcolor = 0x528A;
+ 
+  // Draw empty full bar
+  M5.Lcd.fillRoundRect(leftBarGraphPWR, topBarGraphPWR, widthBarGraphPWR+6, heightBarGraphPWR+4 , 6, BGcolor);
+  //M5.Lcd.fillRect(leftBarGraphSOC, topBarGraphSOC, widthBarGraphSOC+4, heightBarGraphSOC+4 , BGcolor);
+  M5.Lcd.drawRoundRect(leftBarGraphPWR, topBarGraphPWR, widthBarGraphPWR+4, heightBarGraphPWR+4 , 6, BorderColor);
+  float barOffset;
+  float barWidth;
+ 
+  if(vbpower>0){
+    PWRcolor = YELLOW; if(vbpower>25) PWRcolor = RED;
+    barWidth = (widthBarGraphPWR*vbpower)/MAXRANGEPWR;
+    // (220*140)/220
+    barOffset = (widthBarGraphPWR*(float)MAXINPOWER)/MAXRANGEPWR;
+  } else {
+    PWRcolor = GREEN;
+    barWidth = (widthBarGraphPWR*-vbpower)/MAXRANGEPWR;
+    barOffset = (widthBarGraphPWR*((float)MAXINPOWER+vbpower))/MAXRANGEPWR;
+  }
+  // USE_SERIAL.print(barOffset); USE_SERIAL.print(" "); USE_SERIAL.println(barWidth);
+ 
+  // Draw Power Bar
+  //M5.Lcd.fillRoundRect(leftBarGraphPWR+2+barOffset, topBarGraphPWR+2, barWidth, heightBarGraphPWR , 4, PWRcolor);
+  M5.Lcd.fillRect(leftBarGraphPWR+2+barOffset, topBarGraphPWR+2, barWidth, heightBarGraphPWR, PWRcolor);
+ 
+  // Clean text
+  M5.Lcd.fillRoundRect(4, topBarGraphPWR, PWRtextWidth, heightBarGraphPWR+4 , 6, BLACK);
+ 
+  // Place 0 line
+  M5.Lcd.drawLine(
+    leftBarGraphPWR+2+(widthBarGraphPWR*(float)MAXINPOWER)/MAXRANGEPWR, 
+    topBarGraphPWR, 
+    leftBarGraphPWR+2+(widthBarGraphPWR*(float)MAXINPOWER)/MAXRANGEPWR, 
+    topBarGraphPWR+heightBarGraphPWR+2, 
+    BorderColor);
+ 
+  M5.Lcd.setCursor(4, topBarGraphPWR+8);
+  if(vbpower>0){
+    M5.Lcd.setTextColor(RED);
+  }else{
+    M5.Lcd.setTextColor(GREEN);
+  }
+  if(vbpower > 0   && vbpower < 10  ) M5.Lcd.print(" ");
+  if(vbpower > -10 && vbpower < 100 ) M5.Lcd.print(" ");
+ 
+  M5.Lcd.print(vbpower,0); M5.Lcd.print("kW");
+  //M5.Lcd.drawFloat(vbpower, 0, 4, topBarGraphPWR+8);
+  //M5.Lcd.drawNumber(vbpower, 4, topBarGraphPWR+8);
+  //M5.Lcd.print('kW');
+  //M5.Lcd.printf("%*s", 5, vbpower);
+  M5triggerUpdate++;
+}
+ 
+void draw_vbrange() {
+  M5.Lcd.setTextSize(3);
+  M5.lcd.fillRect(0, topTextLineRange, 320,32, DARKERCYAN);
+  M5.Lcd.setCursor(4, topTextLineRange+4);
+  M5.Lcd.setTextColor(LIGHTGREY);
+  M5.Lcd.print("range: "); M5.Lcd.print(vbrangeideal,0); M5.Lcd.print("/");
+  M5.Lcd.print(vbrangefull,0); M5.Lcd.print(" km");
+  M5triggerUpdate++;
+}
+ 
+void draw_vbconsumption() {
+  uint16_t textColor = GREEN;
+  if(vbconsumption>150) textColor = YELLOW;
+  if(vbconsumption>200) textColor = RED;
+  M5.Lcd.setTextSize(3);
+  M5.lcd.fillRect(0, topTextLineConsumption, 320,32, DARKERCYAN);
+  M5.Lcd.setCursor(4, topTextLineConsumption+4);
+  M5.Lcd.setTextColor(LIGHTGREY);
+  M5.Lcd.print("cons: ");
+  M5.Lcd.setTextColor(textColor);
+  M5.Lcd.print(vbconsumption,0);
+  M5.Lcd.setTextColor(LIGHTGREY);
+  M5.Lcd.print(" Wh/km");
+  M5triggerUpdate++;
+}
+ 
+void draw_temps() {
+  M5.Lcd.setTextSize(2);
+  M5.lcd.fillRect(0, topTextLineTemps,320 , 24, DARKERCYAN);
+  M5.Lcd.setCursor(4, topTextLineTemps+4);
+  M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print("temps ext:");
+  M5.Lcd.print(vetemp,0);
+  M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print(" in:");
+  M5.Lcd.print(xknbinlettemp,0);
+  M5.Lcd.setTextColor(LIGHTGREY); M5.Lcd.print(" bt:");
+  M5.Lcd.print(vbtemp,0);
+  M5triggerUpdate++;
+}
+ 
+void tickerBox(uint16_t tickerColor) {
+  M5.lcd.fillRect(0,240-6 ,4 ,4 , tickerColor);
+  M5triggerUpdate++;
+}
+ 
+void changeLCDBrightness() {
+  LCDBrightnessStep++;
+  if(LCDBrightnessStep==3) LCDBrightnessStep=0;
+  M5.Lcd.setBrightness(LCDBrightnessSteps[LCDBrightnessStep]);
+  if(localTesting) USE_SERIAL.printf("[LCD] Setting Brightness to : %u (step %u)\n", LCDBrightnessSteps[LCDBrightnessStep], LCDBrightnessStep);
+}
+ 
+void sendButtonA() {
+  sendEvent("event+raise+M5.button.A");
+}
+ 
+void sendButtonB() {
+  sendEvent("event+raise+M5.button.B");
+}
+ 
+void sendEvent(char* event){
+  M5.Lcd.fillRect(0, 220, 320, 32, BLUE);
+  M5.Lcd.setCursor(6, 222);
+  M5.Lcd.setTextColor(YELLOW);
+  M5.Lcd.setTextSize(2);
+  M5.Lcd.print(event);
+  M5.update();
+  M5triggerUpdate = 0;
+ 
+  WiFiClient client;
+  if (!client.connect("192.168.4.1", 80 )) {
+    Serial.println("connection failed");
+    return;
+  }
+  String url = "/api/execute?apikey=";
+    url += OVMSPASS;
+    url += "&command=";
+    url += event;
+  USE_SERIAL.print("Requesting URL: ");
+  USE_SERIAL.println(url);
+ 
+  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
+    "Host: 192.168.4.1\r\n" +
+    "Connection: close\r\n\r\n");
+    unsigned long timeout = millis();
+    while (client.available() == 0) {
+      if (millis() - timeout > 5000) {
+          USE_SERIAL.println(">>> Client Timeout !");
+          client.stop();
+          return;
+      }
+  }
+  while(client.available()) {
+    String line = client.readStringUntil('\r');
+    USE_SERIAL.print(line);
+  }
+ 
+  Serial.println();
+  Serial.println("closing connection");
+ 
+  M5.Lcd.fillRect(0, 220, 320, 32, BLACK);
+  M5.update();
+  M5triggerUpdate = 0;
+}
+ 
+void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
+ 
+  // Timer control
+  prevTimer=displayTimer;
+  displayTimer=millis();
+  spentTimer=displayTimer-prevTimer;
+  slowRefreshTimer+=spentTimer;
+  mildRefreshTimer+=spentTimer;
+  fastRefreshTimer+=spentTimer;
+  slowRefreshTrigger = (slowRefreshTimer>=slowRefreshMax) ? true : false;
+  mildRefreshTrigger = (mildRefreshTimer>=mildRefreshMax) ? true : false;
+  fastRefreshTrigger = (fastRefreshTimer>=fastRefreshMax) ? true : false;
+ 
+  switch(type) {
+    case WStype_DISCONNECTED:
+      USE_SERIAL.printf("[WSc] Disconnected!\n");
+      tickerBox(RED); M5.update();
+      break;
+    case WStype_CONNECTED:
+      tickerBox(YELLOW); M5.update();
+      if(localTesting) USE_SERIAL.printf("[WSc] Connected to url: %s\n", payload);
+      // send message to server when Connected
+      //webSocket.sendTXT("M5 Stack Connected");
+      sendEvent("event+raise+M5.websocket.connected");
+      break;
+    case WStype_TEXT:
+      //if(localTesting) USE_SERIAL.printf("[WSc] get text: %s\n", payload);
+ 
+      payloadDecodeError = deserializeJson(jsonDoc, payload);
+      // Test if parsing succeeds.
+      if (payloadDecodeError) {
+        USE_SERIAL.printf("deserializeJson() failed: \n");
+        USE_SERIAL.println(payloadDecodeError.c_str());
+      }else{
+        //USE_SERIAL.printf("parseObject() succeeded\n");
+        JsonObject metrics = jsonDoc["metrics"];
+        if (!metrics.isNull()) {
+ 
+          if(localTesting && genRandomVals){
+            metrics["v.b.soc"] = random(0,200)/2;
+            metrics["v.b.power"] = random(-MAXINPOWER*10,MAXOUTPOWER*10+1)/10;
+            metrics["v.b.range.ideal"] = random(360*10,400*10)/10;
+            metrics["v.b.range.ideal"] = random(360*10,400*10)/10;
+            metrics["v.e.temp"] = random(12*10,17*10)/10;
+            metrics["v.b.temp"] = random(18*10,21*10)/10;
+            metrics["xkn.b.inlet.temp"] = random(14*10,19*10)/10;
+            metrics["v.b.consumption"] = random(130,240);
+          }
+ 
+          // v.b.soc
+          if(metrics.containsKey("v.b.soc") && metrics["v.b.soc"]!=vbsoc){
+            vbsoc = metrics["v.b.soc"];
+            //USE_SERIAL.printf("[json] v.b.soc: %f\n", vbsoc);
+          }
+          if(slowRefreshTrigger) draw_vbsoc(vbsoc);
+ 
+          // v.b.range.ideal
+          if(metrics.containsKey("v.b.range.ideal") && metrics["v.b.range.ideal"]!=vbrangeideal){
+            vbrangeideal = metrics["v.b.range.ideal"];
+          }
+          // v.b.range.full
+          if(metrics.containsKey("v.b.range.full") && metrics["v.b.range.full"]!=vbrangefull){
+            vbrangefull = metrics["v.b.range.full"];
+          }
+          if(slowRefreshTrigger) draw_vbrange();
+ 
+          // v.e.temp
+          if(metrics.containsKey("v.e.temp") && metrics["v.e.temp"]!=vetemp){
+            vetemp = metrics["v.e.temp"];
+          }
+          // v.b.temp
+          if(metrics.containsKey("v.b.temp") && metrics["v.b.temp"]!=vbtemp){
+            vbtemp = metrics["v.b.temp"];
+          }
+          // xkn.b.inlet.temp
+          if(metrics.containsKey("xkn.b.inlet.temp") && metrics["xkn.b.inlet.temp"]!=xknbinlettemp){
+            xknbinlettemp = metrics["xkn.b.inlet.temp"];
+          }
+          if(mildRefreshTrigger) draw_temps();
+ 
+          // xkn.b.inlet.temp
+          if(metrics.containsKey("v.b.consumption") && metrics["v.b.consumption"]!=vbconsumption){
+            vbconsumption = metrics["v.b.consumption"];
+          }
+          if(mildRefreshTrigger) draw_vbconsumption();
+ 
+          // v.b.power
+          if(metrics.containsKey("v.b.power")){
+            vbpower = metrics["v.b.power"];
+            //USE_SERIAL.printf("[json] v.b.power: %f\n", vbpower);
+          }
+ 
+          if(fastRefreshTrigger) draw_vbpower(vbpower);
+ 
+          if(metrics.containsKey("m.monotonic")){
+            //JsonVariant _mmonotonic=metrics["m.monotonic"];
+            //int mmonotonic=_mmonotonic.as<int>();
+            int mmonotonic=metrics["m.monotonic"].as<int>();
+            if(mmonotonic % 2){
+              tickerBox(DARKGREEN);
+            }else{
+              tickerBox(BLUE);
+            }
+          }
+ 
+          if(M5.BtnA.wasReleased()) sendButtonA();
+          if(M5.BtnB.wasReleased()) sendButtonB();
+          if(M5.BtnC.wasReleased()) changeLCDBrightness();
+ 
+        }
+ 
+        if(M5triggerUpdate){
+          M5.update();
+          M5triggerUpdate = 0;
+        }
+        if(slowRefreshTrigger){
+          slowRefreshTimer=0;
+          //USE_SERIAL.printf("slowRefreshTimer reset\n");
+        }
+        if(mildRefreshTrigger){
+          mildRefreshTimer=0;
+          //USE_SERIAL.printf("mildRefreshTimer reset\n");
+        }
+        if(fastRefreshTrigger){
+          fastRefreshTimer=0;
+          //USE_SERIAL.printf("fastRefreshTimer reset\n");
+        }
+      }
+      // send message to server
+      // webSocket.sendTXT("message here");
+      break;
+    case WStype_BIN:
+      USE_SERIAL.printf("[WSc] get binary length: %u\n", length);
+      hexdump(payload, length);
+ 
+      // send data to server
+      // webSocket.sendBIN(payload, length);
+      break;
+    case WStype_ERROR:      
+    case WStype_FRAGMENT_TEXT_START:
+    case WStype_FRAGMENT_BIN_START:
+    case WStype_FRAGMENT:
+    case WStype_FRAGMENT_FIN:
+      break;
+  }
+ 
+}
+ 
+void setup() {
+ 
+  M5.begin();
+  M5.Power.begin();
+  M5.Lcd.setBrightness(LCDBrightnessSteps[LCDBrightnessStep+1]);
+  // USE_SERIAL.begin(921600);
+  // USE_SERIAL.begin(115200);
+  USE_SERIAL.begin(230400);
+ 
+  //Serial.setDebugOutput(true);
+  USE_SERIAL.setDebugOutput(true);
+ 
+  USE_SERIAL.println();
+ 
+  //M5.Lcd.fillScreen(BLACK);
+ 
+  ledcDetachPin(SPEAKER_PIN);
+  pinMode(SPEAKER_PIN, INPUT);
+ 
+  for(uint8_t t = 5; t > 0; t--) {
+    USE_SERIAL.printf("[SETUP] BOOT WAIT %d...\n", t);
+    USE_SERIAL.flush();
+    delay(500);
+  }
+ 
+  WiFiMulti.addAP("OVMSWifi", "W1f1P4ssW0rd"); // Change This to your OVMS
+ 
+  USE_SERIAL.printf("[Wifi] Added APs...\n");
+ 
+  WiFi.disconnect();
+  if(WiFiMulti.run() != WL_CONNECTED){
+    M5.Lcd.fillRect(0, 220, 320, 32, BLUE);
+    M5.Lcd.setCursor(6, 222);
+    M5.Lcd.setTextColor(WHITE);
+    M5.Lcd.setTextSize(2);
+    M5.Lcd.print("[Wifi] Connecting...");
+    USE_SERIAL.printf("[Wifi] Connecting...\n");
+    dummyCounter = 1;
+    tickerBox(RED);
+    while(WiFiMulti.run() != WL_CONNECTED) {
+      USE_SERIAL.printf("[Wifi] Connecting... count %d\n",dummyCounter++);
+      M5.Lcd.print(".");
+      M5.update();
+      delay(500);
+      if (M5.BtnC.wasReleased()) changeLCDBrightness();
+    }
+ 
+    //hexdump(WiFi.SSID().c_str(), sizeof(WiFi.SSID().c_str()));
+    USE_SERIAL.printf("[Wifi] Connected: %s\n",WiFi.SSID().c_str());
+ 
+    M5.Lcd.fillRect(0, 220, 320, 32, BLACK);
+    M5.update();
+  }
+ 
+ 
+  // server address, port and URL
+  webSocket.begin("192.168.4.1", 80, "/");
+ 
+  // event handler
+  webSocket.onEvent(webSocketEvent);
+ 
+  // use HTTP Basic Authorization this is optional remove if not needed
+  webSocket.setAuthorization(OVMSUSER, OVMSPASS);
+ 
+  // try ever 5000 again if connection has failed
+  webSocket.setReconnectInterval(2000);
+ 
+}
+ 
+void loop() {
+  webSocket.loop();
+  if(WiFiMulti.run() != WL_CONNECTED){
+    M5.Lcd.fillRect(0, 220, 320, 32, BLUE);
+    M5.Lcd.setCursor(6, 222);
+    M5.Lcd.setTextColor(WHITE);
+    M5.Lcd.setTextSize(2);
+    M5.Lcd.print("[Wifi] Reconnecting...");
+     while(WiFiMulti.run() != WL_CONNECTED) {
+      USE_SERIAL.printf("[Wifi] Connecting...\n");
+      M5.Lcd.print(".");
+      M5.update();
+      delay(500);
+      if (M5.BtnC.wasReleased()) changeLCDBrightness();
+    }
+ 
+    USE_SERIAL.printf("Wifi: %s\n",WiFi.SSID().c_str());
+ 
+    M5.Lcd.fillRect(0, 220, 320, 32, BLUE);
+    M5.Lcd.setCursor(6, 222);
+    M5.Lcd.print("[Wifi] : ");
+    M5.Lcd.print(WiFi.SSID().c_str());
+    M5.update();
+    M5.Speaker.mute();
+    delay(500);
+    M5.Lcd.fillRect(0, 220, 320, 32, BLACK);
+    M5.update();
+  }
+  //M5.Lcd.fillScreen(BLACK);
+  if(M5triggerUpdate){
+    M5.update();
+    M5triggerUpdate = 0;
+  }
+  //USE_SERIAL.printf("end of loop\n");
+}