{"id":3713,"date":"2021-10-05T14:00:29","date_gmt":"2021-10-05T21:00:29","guid":{"rendered":"https:\/\/www.cloudacm.com\/?p=3713"},"modified":"2021-09-10T19:03:50","modified_gmt":"2021-09-11T02:03:50","slug":"esp-32-flight-datalogger-field-validation","status":"publish","type":"post","link":"https:\/\/www.cloudacm.com\/?p=3713","title":{"rendered":"ESP-32 Flight Datalogger &#8211; Field Validation"},"content":{"rendered":"<p>With all of the final code validated on the workbench, it was time to test it in the field. The project deadline was 3 weeks away and the field testing was a formality to ensure nothing was missing before assembly. With all of the bench testing done, it seemed that this would take a single day to complete and the next phase would begin.<\/p>\n<p>Field testing involves taking the hardware outside and testing it in a state that closely matches its final form. The hardware is rather raw and not complete, so care must be taken to not disturb or mishandle it. This was the case with this project, the hardware and wiring were fastened down and secure. The process for activating and controlling the hardware was well rehearsed. It behaved with high reliability on the bench, so it was the best it would be for field testing.<\/p>\n<p>When the tests began, it became apparent early on that something wasn&#8217;t working. The GPS module was failing to get a reliable lock or it was taking a longer than expected time to get a lock. The first thing to suspect were loose wiring. But all of these checked fine. The field testing took a sour turn, the unreliable GPS lock issue had to be identified before clearing the firmware for the hardware build phase.<\/p>\n<p>Out came the test equipment. The GPS was connected to a serial monitor, however it operated without issue. The GPS unit by itself acquired a lock quickly and consistently. The GPS mockup was tested again and the system operated as expected. However, the GPS lock issue continued to plague the project when the GPS was connected to the ESP32-Cam module.<\/p>\n<p>This went on for a couple of days, the pressure was on to get a fix, pun intended. Identifying the problem was the first hurdle, the next would be correcting it, while the last would be validated field testing. This meant going back to the firmware development stage and bench hardware. Each interface of the ESP32-Cam module was scrutinized. The project had originally used hardware UART pins as serial debug for development. The GPS serial was received on a software serial pin that was assigned to another GPIO pin. The mockup worked without issue in this configuration. However, the GPS module had issues with that setup. The GPIO pin assigned as a software serial input exhibited a noticeable voltage drop, changing the logic recognized by the ESP32-Cam module. No voltage drop occurred when the GPS was connected to the hardware serial pin of the ESP32-Cam module. This meant that the firmware would need to be reworked to accommodate this requirement.<\/p>\n<p>See notes &#8220;Supported Interfaces_Ver5.txt&#8221;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">ESP-32 Cam\r\nGPIO0 CSI_MCLK, I2C SLC, this can't be grounded at startup, it's the program pin\r\nGPIO1 U0TXD, WRITE INT, used for DEBUG console during DEV\r\nGPIO3 U0RXD, GPS RXD\r\nGPIO16 U2RXD, I2C SDA\r\n\r\nGPS GP-20U7\r\nUART Transmit - This will connect to ESP-32 GPIO3 (U0RXD)\r\n\r\n\r\nVolt\/Amp Sensor - INA219\r\nThis uses I2C, a bit of a challenge, requires 2 data pins\r\nSLC - This will connect to ESP-32 GPIO0\r\nSDA - This will connect to ESP-32 GPIO16\r\n\r\n\r\nI also need a file write interupt to prevent data corruption at power down.\r\nGPIO1 - Set Pin HIGH for data write, LOW to stop data write\r\n    Set this option when all debuging is done\r\n\r\n\r\nNotes\r\n\r\nField tests were spotty using software serial or U2RXD (GPIO16) for input from the GPS.\r\nOnly U0RXD (GPIO3) reliably recieved and processed GPS data promptly and consistantly.\r\n\r\nEarlier dev noted that the GPS logic levels dropped from 3.3v to 1.5v when connected to any GPIO other than GPIO3.\r\n\r\nAlso noted that the bootup message on DEBUG had system memory errors, but cleared on subsequent reboots.\r\n\r\nWrite interupt works fine using the U0TXD pin if the following are followed.\r\n\r\nWhen hardware is initialized,\r\nStart both GPIO1 and GPIO3 as hardware serial ports\r\nThen set GPIO1 pin as an input, followed by defining its initial state<\/pre>\n<p>Back to the bench, the code changes were made. The revision involved a global change to all of the GPIOs used on the ESP32-Cam module to all of the sensors. Fortunately, the code was organized in a way that the time involved for a new version was minimal. The I2C data bus, write interrupt, GPS input, and serial debug were tested one by one and passed. The hardware was once again ready for field testing.<\/p>\n<p>See code set &#8220;FlightData_Final_Ver9.ino&#8221;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/** \r\n\r\nFlightData_Final_Ver9.ino\r\nPressure and Temperature Sensor Merge\r\nJuly 22nd, 2021 - 04:21\r\n\r\n**\/\r\n\r\n\r\n\/\/ Libraries\r\n#include \"FS.h\" \r\n#include \"SD_MMC.h\"\r\n#include &lt;TinyGPS++.h&gt;\r\n#include &lt;Wire.h&gt;\r\n#include &lt;Adafruit_INA219.h&gt;\r\n#include \"WiFi.h\"\r\n#include &lt;Adafruit_BMP085.h&gt;\r\n\r\n\r\n\r\n\/\/ Variables and Constants\r\n\r\n\/\/ Set these based on your needs\r\n\/\/ GPS Home, https:\/\/www.google.com\/maps\r\nstatic const double SixtyAcres_LAT = 47.702875, SixtyAcres_LON = -122.137535;\r\n\/\/ Current baromentric pressure, https:\/\/tides4fishing.com\/\r\nuint32_t inHg = 30.03; \r\n\r\n\r\nTinyGPSPlus gps;\t\/\/ The TinyGPS++ object\r\nunsigned long last = 0UL;\t\/\/ For stats that happen every 5 seconds\r\nint PadSeconds;\r\nint PadMinutes;\r\nint PadDays;\r\nint PadMonths;\r\n\r\nAdafruit_INA219 ina219(0x40);\r\n    \/\/ Initializes I2C communication with the Adafruit_INA219 device address 0x40\r\nuint32_t currentFrequency;\r\nfloat shuntvoltage = 0;\r\nfloat busvoltage = 0;\r\nfloat current_mA = 0;\r\nfloat loadvoltage = 0;\r\nfloat power_mW = 0;\r\n\r\nint WifiNetworks = 0;\r\nint NetworkCount = 0;\r\n\r\nAdafruit_BMP085 bmp; \/\/ can this be addressed?\r\nuint32_t mbar = inHg \/ .02953; \/\/ convert inHg to mbar\r\nuint32_t Pa = mbar \/ .01; \/\/ convert mbar to Pa\r\n\r\nint InteruptState = 1;\r\nint FaultState = 0;\r\nint WarmUpState = 1;\r\n\r\nconst char* FileName = \"\/FLTDATA.CSV\";\r\n\r\n#define FlashLED 4 \r\n#define WriteInt 1\r\n#define StautsLED 33 \/\/ REMEMBER - The ESP32-CAM has Reverse logic, HIGH is off, LOW is on.\r\n#define SCL 0 \r\n#define SDA 16\r\n\r\n\r\n\/\/ Routines and Subroutines\r\n\r\n\r\n\/\/ Hardware Initialization Routine\r\nvoid HardwareInit()\r\n{\r\n  Serial.begin(9600);\r\n  Wire.begin(SDA, SCL);\r\n  WiFi.mode(WIFI_STA);\r\n  WiFi.disconnect();\r\n  pinMode(StautsLED, OUTPUT);\r\n  pinMode(FlashLED, OUTPUT);\r\n  pinMode(WriteInt, INPUT);\r\n  digitalWrite(FlashLED, LOW);\r\n  digitalWrite(StautsLED, HIGH); \/\/ The ESP32-Cam Module uses reverse logic, so HIGH = off and LOW = on\r\n  delay(5000);  \/\/ give GPS time to settle \r\n} \r\n\r\n    \r\nvoid StartupMicrSD()\r\n{\r\n   if(!SD_MMC.begin()){\r\n        SystemHalt();\r\n        return;\r\n    }\r\n    uint8_t cardType = SD_MMC.cardType();\r\n\r\n    if(cardType == CARD_NONE){\r\n        SystemHalt();\r\n        return;\r\n    }\r\n\r\n}  \r\n\r\n\/\/Append to the end of file in SD card\r\nvoid appendFile(fs::FS &amp;fs, const char * path, const char * message){\r\n\r\n    File file = fs.open(path, FILE_APPEND);\r\n    if(!file){\r\n        return;\r\n    }\r\n    if(file.print(message)){\r\n    } else {\r\n    }\r\n}\r\n\r\n\r\n\/\/Sample Data Write for Main Loop\r\nvoid WriteInterupt()\r\n{\r\n     InteruptState = digitalRead(WriteInt);\r\n     if (InteruptState == 0)\r\n     {\r\n     \twhile (InteruptState == 0){\r\n     \t        InteruptState = digitalRead(WriteInt);\r\n     \t\tdigitalWrite(StautsLED, LOW);\r\n     \t\tdelay(50);\r\n     \t\tdigitalWrite(StautsLED, HIGH);\r\n     \t\tdelay(950);\r\n       }\r\n     }\r\n}\r\n\r\n\r\n\/\/Sample Data Write for Main Loop\r\nvoid SystemHalt()\r\n{\r\n     FaultState = 1;\r\n     while (FaultState == 1){\r\n     \tdigitalWrite(StautsLED, LOW);\r\n     \tdelay(50);\r\n     \tdigitalWrite(StautsLED, HIGH);\r\n     \tdelay(50);\r\n     }\r\n}\r\n  \r\n  \r\n\/\/ Fault Detetection and Alerting Routine\r\nvoid FaultDetect()\r\n{\r\n   if (! bmp.begin ()) {\r\n    digitalWrite(StautsLED, LOW);\r\n   while (1) { delay(10); }\r\n  }\r\n  \r\n  if (! ina219.begin()) {\r\n    digitalWrite(StautsLED, LOW);\r\n    while (1) { delay(10); }\r\n  }\r\n} \r\n\r\n\r\n\/\/ Clear Fault Alert Routine\r\nvoid ClearFaultAlert()\r\n{\r\n  digitalWrite(StautsLED, HIGH);\r\n}\r\n\r\n  \r\n\/\/ Sensor Calibraion Scaling Routine\r\nvoid SensorCalibration()\r\n{\r\n  ina219.setCalibration_16V_400mA();\r\n}\r\n\r\n\r\n\/\/ Get Voltage Sensor Value Routine\r\nvoid GetVoltSensorValues()\r\n{\r\n  shuntvoltage = ina219.getShuntVoltage_mV();\r\n  busvoltage = ina219.getBusVoltage_V();\r\n  current_mA = ina219.getCurrent_mA();\r\n  power_mW = ina219.getPower_mW();\r\n  loadvoltage = busvoltage + (shuntvoltage \/ 1000);\r\n}\r\n\r\n\r\n\/\/ WiFi Scanning Routine\r\nvoid WifiScanning()\r\n{\r\n    \/\/ WiFi.scanNetworks will return the number of networks found\r\n    WifiNetworks = WiFi.scanNetworks();\r\n    \r\n    if (WifiNetworks == 0) \r\n       {\r\n    \r\n       } \r\n    \r\n    else \r\n       {\r\n        String StringWifiNetworks = String(WifiNetworks);\r\n        appendFile(SD_MMC, FileName, StringWifiNetworks.c_str()); \r\n        appendFile(SD_MMC, FileName, \",\");\r\n        \r\n        for (NetworkCount = 0; NetworkCount &lt; 3; ++NetworkCount) \r\n           {\r\n           \r\n            String StringSSID = String(WiFi.SSID(NetworkCount));\r\n            appendFile(SD_MMC, FileName, StringSSID.c_str()); \r\n            appendFile(SD_MMC, FileName, \",\");\r\n           \r\n            String StringRSSI = String(WiFi.RSSI(NetworkCount));\r\n            appendFile(SD_MMC, FileName, StringRSSI.c_str()); \r\n            appendFile(SD_MMC, FileName, \",\");\r\n           \r\n            String StringEncryptionType = String((WiFi.encryptionType(NetworkCount) == WIFI_AUTH_OPEN)?\" \":\"*\");\r\n            appendFile(SD_MMC, FileName, StringEncryptionType.c_str()); \r\n            appendFile(SD_MMC, FileName, \",\");\r\n            \r\n           }\r\n       }\r\n} \r\n\r\n\r\n\/\/ Printout Header Routine\r\nvoid PrintHeader()\r\n{\r\n    appendFile(SD_MMC, FileName, \"Lat,Long,Date,Time,MPH,Course,Altitude,Home,Bearing,Cardinal,Bus V,Shunt mV,Load V,mA,mW,Temp C,Temp F,Def Alt M,Cal Alt M, Cal Alt F,Networks,SSID-1,RSSID-1,Enc-1,SSID-2,RSSID-2,Enc-2,SSID-3,RSSID-3,Enc-3\\n\");\r\n}\r\n\r\n\r\n\/\/ Printout Location Routine\r\nvoid PrintOutLocation()\r\n{\r\n    String StringLat = String(gps.location.lat(), 6);\r\n    String StringLong = String(gps.location.lng(), 6);\r\n    String Location = String(StringLat + \",\" + StringLong + \",\");\r\n    appendFile(SD_MMC, FileName, Location.c_str());\r\n}\r\n\r\n\r\n\/\/ Printout Date Routine\r\nvoid PrintOutDate()\r\n{\r\n    PadMonths = gps.date.month();\r\n      if (PadMonths &lt; 10)\r\n        {        \r\n         appendFile(SD_MMC, FileName, \"0\");\r\n        }        \r\n    String StringMonth = String(gps.date.month());\r\n    appendFile(SD_MMC, FileName, StringMonth.c_str());\r\n    appendFile(SD_MMC, FileName, \"\/\");\r\n    PadDays = gps.date.day();\r\n      if (PadDays &lt; 10)\r\n        {        \r\n          appendFile(SD_MMC, FileName, \"0\");\r\n        }        \r\n    String StringDay = String(gps.date.day());\r\n    appendFile(SD_MMC, FileName, StringDay.c_str());     \r\n    appendFile(SD_MMC, FileName, \"\/\");    \r\n    String StringYear = String(gps.date.year());\r\n    appendFile(SD_MMC, FileName, StringYear.c_str());   \r\n    appendFile(SD_MMC, FileName, \",\");\r\n}\r\n\r\n\r\n\/\/ Printout Time Routine\r\nvoid PrintOutTime()\r\n{\r\n    String StringHour = String(gps.time.hour());\r\n    appendFile(SD_MMC, FileName, StringHour.c_str());    \r\n    appendFile(SD_MMC, FileName, \":\");      \r\n    PadMinutes = gps.time.minute();\r\n      if (PadMinutes &lt; 10)\r\n        {\r\n          appendFile(SD_MMC, FileName, \"0\");\r\n        }\r\n    String StringMinutes = String(gps.time.minute());\r\n    appendFile(SD_MMC, FileName, StringMinutes.c_str());    \r\n    appendFile(SD_MMC, FileName, \":\");    \r\n    PadSeconds = gps.time.second();\r\n      if (PadSeconds &lt; 10)\r\n        {\r\n          appendFile(SD_MMC, FileName, \"0\");\r\n        }\r\n    String StringSeconds = String(gps.time.second());\r\n    appendFile(SD_MMC, FileName, StringSeconds.c_str());       \r\n    appendFile(SD_MMC, FileName, \",\");    \r\n}\r\n\r\n\r\n\/\/ Printout Speed Routine\r\nvoid PrintOutSpeed()\r\n{\r\n    String StringMph = String(gps.speed.mph());\r\n    appendFile(SD_MMC, FileName, StringMph.c_str()); \r\n    appendFile(SD_MMC, FileName, \",\"); \r\n}\r\n\r\n\r\n\/\/ Printout Course Routine\r\nvoid PrintOutCourse()\r\n{\r\n    String StringCourse = String(gps.course.deg());\r\n    appendFile(SD_MMC, FileName, StringCourse.c_str()); \r\n    appendFile(SD_MMC, FileName, \",\"); \r\n}\r\n\r\n\r\n\/\/ Printout Altitude Routine\r\nvoid PrintOutAltitude()\r\n{\r\n    String StringAltitude = String(gps.altitude.feet());\r\n    appendFile(SD_MMC, FileName, StringAltitude.c_str()); \r\n    appendFile(SD_MMC, FileName, \",\"); \r\n}\r\n\r\n\r\n\/\/ Reference Bearing Routine\r\nvoid BearingReference()\r\n{\r\n      \r\n      double distanceToSixtyAcres =\r\n        TinyGPSPlus::distanceBetween(\r\n          gps.location.lat(),\r\n          gps.location.lng(),\r\n          SixtyAcres_LAT, \r\n          SixtyAcres_LON);\r\n      double courseToSixtyAcres =\r\n        TinyGPSPlus::courseTo(\r\n          gps.location.lat(),\r\n          gps.location.lng(),\r\n          SixtyAcres_LAT, \r\n          SixtyAcres_LON);\r\n          \r\n      String StringDistToHome = String(distanceToSixtyAcres\/1609, 6);\r\n      appendFile(SD_MMC, FileName, StringDistToHome.c_str()); \r\n      appendFile(SD_MMC, FileName, \",\"); \r\n      String StringCourseToHome = String(courseToSixtyAcres, 6);\r\n      appendFile(SD_MMC, FileName, StringCourseToHome.c_str()); \r\n      appendFile(SD_MMC, FileName, \",\"); \r\n      String StringCardinal = String(TinyGPSPlus::cardinal(courseToSixtyAcres));\r\n      appendFile(SD_MMC, FileName, StringCardinal.c_str()); \r\n      appendFile(SD_MMC, FileName, \",\"); \r\n}\r\n\r\n\r\n\r\n\/\/ Printout Results Routine\r\nvoid PrintOutINA219Readings()\r\n{\r\n  String StringBusVoltage = String(busvoltage);\r\n  appendFile(SD_MMC, FileName, StringBusVoltage.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringShuntVoltage = String(shuntvoltage);\r\n  appendFile(SD_MMC, FileName, StringShuntVoltage.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringLoadVoltage = String(loadvoltage);\r\n  appendFile(SD_MMC, FileName, StringLoadVoltage.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringCurrentmA = String(current_mA);\r\n  appendFile(SD_MMC, FileName, StringCurrentmA.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringPowermA = String(power_mW);\r\n  appendFile(SD_MMC, FileName, StringPowermA.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n}\r\n\r\n\r\n\r\n\/\/ Printout Results Routine\r\nvoid PrintOutGY65Readings()\r\n{\r\n  String StringTemperatureC = String(bmp.readTemperature ());\r\n  appendFile(SD_MMC, FileName, StringTemperatureC.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringTemperatureF = String(bmp.readTemperature () * 9 \/ 5 + 32);\r\n  appendFile(SD_MMC, FileName, StringTemperatureF.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringDefAltitudeM = String(bmp.readAltitude ());\r\n  appendFile(SD_MMC, FileName, StringDefAltitudeM.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringCalAltitudeM = String(bmp.readAltitude (Pa));\r\n  appendFile(SD_MMC, FileName, StringCalAltitudeM.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n  String StringCalAltitudeF = String(bmp.readAltitude (Pa) * 3.2808);\r\n  appendFile(SD_MMC, FileName, StringCalAltitudeF.c_str()); \r\n  appendFile(SD_MMC, FileName, \",\"); \r\n}\r\n\r\n\r\n\/\/ Inbound Serial Data Processing\r\nvoid InboundSerialData()\r\n{\r\n  while (Serial.available() &gt; 0)\r\n    gps.encode(Serial.read());\r\n\r\n  if (gps.altitude.isUpdated())\r\n  {\r\n    PrintOutGlobalReadings();\r\n  }\r\n\r\n  else if (millis() - last &gt; 5000)\r\n  {\r\n    if (gps.location.isValid())\r\n    last = millis();\r\n  }\r\n}\r\n\r\n\r\n\/\/ Printout Global Results Routine\r\nvoid PrintOutGlobalReadings()\r\n{\r\n    PrintOutLocation();\r\n    PrintOutDate();\r\n    PrintOutTime();\r\n    PrintOutSpeed();\r\n    PrintOutCourse();\r\n    PrintOutAltitude();\r\n    BearingReference();\r\n    GetVoltSensorValues();\r\n    PrintOutINA219Readings();\r\n    PrintOutGY65Readings();\r\n    WifiScanning();\r\n    appendFile(SD_MMC, FileName, \"\\n\"); \r\n}\r\n\r\n\r\nvoid setup()\r\n{\r\n  HardwareInit();  \r\n  StartupMicrSD();\r\n  FaultDetect();\r\n  SensorCalibration();\r\n  ClearFaultAlert();\r\n  PrintHeader();\r\n}\r\n\r\n\r\nvoid loop()\r\n{\r\n  WriteInterupt();\r\n  InboundSerialData();\r\n}\r\n\r\n<\/pre>\n<p>Now the deadline was 2 weeks away, so the pressure for a passing result was at its highest. The first test had the GPS lock take within 30 seconds. The following day, three additional tests were done in varying manners with all passing results. The firmware was ready for the hardware build phase.<\/p>\n<p>Now with the project ready for building, hardware used in the development would need to be sourced for the final builds. The next section will cover a supply chain challenge and late phase changes to firmware.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With all of the final code validated on the workbench, it was time to test it in the field. The project deadline was 3 weeks away and the field testing was a formality to ensure nothing was missing before assembly. With all of the bench testing done, it seemed that this would take a single day to complete and the next phase would begin. Field testing involves taking the hardware outside and testing it in a state that closely matches&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.cloudacm.com\/?p=3713\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[12,3],"tags":[],"class_list":["post-3713","post","type-post","status-publish","format-standard","hentry","category-esp32-cam","category-rd"],"_links":{"self":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3713","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3713"}],"version-history":[{"count":8,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3713\/revisions"}],"predecessor-version":[{"id":3814,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3713\/revisions\/3814"}],"wp:attachment":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3713"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3713"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3713"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}