{"id":2974,"date":"2016-06-12T11:17:56","date_gmt":"2016-06-12T17:17:56","guid":{"rendered":"http:\/\/dev.iachieved.it\/iachievedit\/?p=2974"},"modified":"2020-04-18T13:31:35","modified_gmt":"2020-04-18T18:31:35","slug":"mqtt-with-swift-on-linux","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/mqtt-with-swift-on-linux\/","title":{"rendered":"MQTT Clients With Swift on Linux"},"content":{"rendered":"<p><img decoding=\"async\" src=\"https:\/\/img.shields.io\/badge\/Swift-3.0-orange.svg?style=flat\" alt=\"Swift 3.0\" \/> <img decoding=\"async\" src=\"https:\/\/img.shields.io\/badge\/OS-Linux-blue.svg?style=flat\" alt=\"Swift 3.0\" \/><\/p>\n<p>Over the past few years I&#8217;ve made a living managing <a href=\"https:\/\/en.wikipedia.org\/wiki\/Internet_of_things\">Internet of Things<\/a> (IoT) software development projects.  In that time I&#8217;ve come to learn a number of protocols for communicating sensor and telemetry information back to &#8220;the cloud&#8221;.  Among the most popular in the IoT space is <a href=\"http:\/\/mqtt.org\">MQTT<\/a>, a lightweight protocol that allows for publishing messages to <i>topics<\/i>, as well as the ability to <i>subscribe<\/i> to topics.  This model is frequently refer to as &#8220;<a href=\"https:\/\/en.wikipedia.org\/wiki\/Publish\u2013subscribe_pattern\">pub-sub<\/a>&#8220;.<\/p>\n<p>In addition to working with IoT and MQTT, I am passionate about <a href=\"https:\/\/swift.org\"><b>Swift<\/b><\/a> and the inroads it is making into the server space since being open-sourced and ported to Linux.  Naturally it made sense to combine the two areas and start working on an MQTT client implementation in Swift!  We&#8217;ve taken the liberty to port an <a href=\"https:\/\/github.com\/emqtt\/CocoaMQTT\">iOS implementation of an MQTT client<\/a> over to Swift 3.0 on a Linux platform.  In general this is an example that, indeed, Swift will be making inroads into both the server and IoT domains.<\/p>\n<p>A disclaimer before we get started:  the code below is based upon Swift 3.0 which is going through developer previews on Linux right now.  To get started with Swift 3.0 on your Ubuntu 14.04 or 15.10 system go <a href=\"https:\/\/dev.iachieved.it\/iachievedit\/introducing-swift-3-0\/\">here<\/a>.  Or, if you have an armv7 device such as BeagleBone Black, try the <a href=\"https:\/\/dev.iachieved.it\/iachievedit\/swift-3-0-on-a-beaglebone-black\/\">ARM port<\/a> of Swift 3.0!<\/p>\n<h2>Sample Application<\/h2>\n<p>My first idea was to put together a cool BeagleBone MQTT client that was reading one of the ADC inputs and sending it up to the broker.  The input was <i>going<\/i> to be from a Microchip <a href=\"http:\/\/ww1.microchip.com\/downloads\/en\/DeviceDoc\/21942e.pdf\">MCP9700<\/a> temperature sensor IC.  The sensor IC maximum output voltage is 5.5V so I knew a voltage divider would be involved to keep the voltage input to the BeagleBone under 1.8V.  I had the voltage divider all sketched out and everything!<\/p>\n<figure id=\"attachment_2985\" aria-describedby=\"caption-attachment-2985\" style=\"width: 400px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2016\/06\/FullSizeRender-2-1.jpg\" rel=\"attachment wp-att-2985\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2016\/06\/FullSizeRender-2-1.jpg\" alt=\"Don&#039;t Want to Blow Up the BeagleBone!\" width=\"400\" height=\"576\" class=\"size-full wp-image-2985\" \/><\/a><figcaption id=\"caption-attachment-2985\" class=\"wp-caption-text\">Don&#8217;t Want to Blow Up the BeagleBone!<\/figcaption><\/figure>\n<p>Unfortunately while testing the sensor IC with a standard match I got the flame a little too close to the IC package and well, Project <a href=\"https:\/\/en.wikipedia.org\/wiki\/Icarus\">Icarus<\/a> came to an end.  Our replacement example is a bit less ambitious, but serves the role of learning how to build an MQTT client with Swift.  And it doesn&#8217;t involve matches.<\/p>\n<h2>MQTT<\/h2>\n<p>Our application is built around a port of a client MQTT library (yay open source!).  We&#8217;ve named it simply MQTT and posted it to <a href=\"https:\/\/github.com\/iachievedit\/MQTT\">GitHub<\/a>.  The library is meant to be used in a Swift application that you can create as follows:<\/p>\n<pre>\nmkdir PubSysTemp\ncd PubSysTemp\nswift package init --type executable\n<\/pre>\n<p>Running <code>swift package init --type executable<\/code> is going to give you a Swift project shell (think <code>npm init<\/code>) that you can begin customizing for your purposes.  We&#8217;re going to edit <code>Package.swift<\/code> and add our MQTT library dependency:<\/p>\n<pre class=\"lang:swift\">\nimport PackageDescription\nlet package = Package(\n  name: \"PubSysTemp\",\n  dependencies:[\n    .Package(url:\"https:\/\/github.com\/iachievedit\/MQTT\", majorVersion:0, minor:1)\n  ]\n)\n<\/pre>\n<h3>MQTT Client Delegate<\/h3>\n<p>The design of the MQTT library is such that you will create a client class that inherits from <code>MQTT<\/code> and <code>MQTTDelegate<\/code>.  A very basic implementation looks like this:<\/p>\n<pre class=\"lang:swift\">\nimport Foundation\nimport MQTT\n\nclass Client:MQTT, MQTTDelegate {\n  \n  init(clientId:String) {\n    super.init(clientId:clientId)\n    super.delegate = self\n  }\n  \n  func mqtt(mqtt: MQTT, didConnect host: String, port: Int) {\n  }\n  \n  func mqtt(mqtt: MQTT, didConnectAck ack: MQTTConnAck) {\n  }\n  \n  func mqtt(mqtt: MQTT, didPublishMessage message: MQTTMessage, id: UInt16) {\n  }\n  \n  func mqtt(mqtt: MQTT, didPublishAck id: UInt16) {\n  }\n  \n  func mqtt(mqtt: MQTT, didReceiveMessage message: MQTTMessage, id: UInt16 ) {\n  }\n  \n  func mqtt(mqtt: MQTT, didSubscribeTopic topic: String) {\n  }\n  \n  func mqtt(mqtt: MQTT, didUnsubscribeTopic topic: String) {\n  }\n  \n  func mqttDidPing(mqtt: MQTT) {\n  }\n  \n  func mqttDidReceivePong(mqtt: MQTT) {\n  }\n  \n  func mqttDidDisconnect(mqtt: MQTT, withError err: NSError?) {\n  }\n}\n<\/pre>\n<p>As you can probably guess the functions above are delegate methods that are called when the underlying MQTT client connects, publishes a message, subscribes to a topic, etc.  It is up to you, the client writer, to fill out the implementation to suit your needs.  In our case we&#8217;re going to implement the <code>mqttDidDisconnect<\/code> method as follows:<\/p>\n<pre>\nfunc mqttDidDisconnect(mqtt: MQTT, withError err: NSError?) {\n  NSNotificationCenter.defaultCenter().postNotificationName(\"DisconnectedNotification\",object:nil)\n}\n<\/pre>\n<p>I&#8217;ve mentioned in previous posts that I appreciate the flexibility of posting a notification and having it acted upon by whoever was listening for it.  <code>DisconnectedNotification<\/code> will be handled in our <code>main.swift<\/code> routine.<\/p>\n<h3>main.swift<\/h3>\n<p>Let&#8217;s turn our attention to <code>main.swift<\/code> which will instantiate an <code>MQTT<\/code>-based client.  At a fundamental level here is all that&#8217;s required:<\/p>\n<pre class=\"lang:swift\">\nlet client = Client(clientId:\"a-client-id\")\nclient.host = \"broker.hivemq.com\"\nclient.connect()\nclient.publish(topic:\"\/my\/topic\", withString:\"my string\")\n<\/pre>\n<p>We want our client to be a bit more robust, so we&#8217;ll add in the ability to automatically reconnect if our connection is dropped by doing something like this:<\/p>\n<pre class=\"lang:swift\">\n_ = NSNotificationCenter.defaultCenter().addObserverForName(\"DisconnectedNotification\",\n                                                            object:nil, queue:nil){_ in\n  guard client.connect() else {\n    print(\"Unable to connect to broker\")\n    exit(-1)\n  }\n}\n<\/pre>\n<p>Now, one could argue that we don&#8217;t want to <code>exit<\/code> if the connection to the broker fails.  What we <i>could<\/i> do is set a timer and then rebroadcast our <code>DisconnectedNotification<\/code>.  This will be our approach in the working code detailed further below.<\/p>\n<p>We should publish something useful to the broker, so let&#8217;s set up an <code>NSTimer<\/code> to wake up every ten seconds and obtain the CPU temperature and then post that.<\/p>\n<pre class=\"lang:swift\">\nlet reportInterval    = 10\nlet reportTemperature = NSTimer.scheduledTimer(NSTimeInterval(reportInterval), repeats:true){_ in\n  if let cpuTemperature = CPU().temperature {\n  _ = client.publish(topic:\"\/(client.clientId)\/cpu\/temperature\/value\", withString:String(cpuTemperature))\n  }\n}\nreportTemperature.fire()\nNSRunLoop.currentRunLoop().addTimer(reportTemperature, forMode:NSDefaultRunLoopMode)\nNSRunLoop.currentRunLoop().run()\n<\/pre>\n<p>It should be noted here that we set the timer up and then <code>fire()<\/code> it immediately (so we&#8217;re not waiting ten seconds for that first post).  Also, you will notice that we&#8217;re posting to the topic <code>\/&lt;i&gt;clientid&lt;\/i&gt;\/cpu\/temperature\/value<\/code>.  This is one example of an <a href=\"http:\/\/tinkerman.cat\/mqtt-topic-naming-convention\/\">MQTT topic naming convention<\/a> and meant strictly as such.  As you delve further into designing IoT applications it will become apparent that your naming convention is of considerable importance.<\/p>\n<h3>Getting the CPU Temperature<\/h3>\n<p>I love working with Linux and the wealth of information that is available in the <code>\/sys<\/code> and <code>\/proc<\/code> filesystems.  Unfortunately when you&#8217;re dealing with hardware you have to frequently tailor your code to the specific hardware it&#8217;s running on.  For example, on my x86 server the temperature of the CPU can be obtained by reading <code>\/sys\/class\/hwmon\/hwmon0\/temp1_input<\/code>.  On the BeagleBoard X15 it is at <code>\/sys\/class\/hwmon\/hwmon1\/temp1_input<\/code>.  That&#8217;s aggravating.<\/p>\n<p>We won&#8217;t bother with writing portable code for now, but you should be able to take this example and suit it to your own system&#8217;s needs:<\/p>\n<pre class=\"lang:swift\">\nstruct CPU {\n  var temperature:Double? {\n    get {\n      let BUFSIZE = 16\n      let pp      = popen(\"cat \/sys\/class\/hwmon\/hwmon0\/temp1_input\", \"r\")\n      var buf     = [CChar](repeating:0, count:BUFSIZE)\n      guard fgets(&buf, Int32(BUFSIZE), pp) != nil else {\n        pclose(pp)\n        return nil\n      }\n      pclose(pp)\n\n      let s = String(String(cString:buf).characters.dropLast())\n      if let t = Double(s) {\n        return t\/1000\n      } else {\n        return nil\n      }\n    }\n  }\n}\n<\/pre>\n<h2>Putting it All Together<\/h2>\n<p>Let&#8217;s put everything together to build a working MQTT client that posts our CPU temperature to <code>broker.hivemq.com<\/code>.  As a bonus, we&#8217;ll provide a web page that shows the CPU temperature displayed as a gauge.<\/p>\n<p>We have three files that make up our client:<\/p>\n<ul>\n<li>`Client.swift`\n<li>`CPU.swift`\n<li>`main.swift`\n<\/ul>\n<p>Each of these files should go in the <code>Sources<\/code> directory.  Let&#8217;s look at a complete implementation of each:<\/p>\n<h3>Client<\/h3>\n<p><code>Client.swift<\/code><\/p>\n<pre class=\"lang:swift\">\nimport swiftlog\nimport Foundation\nimport MQTT\n\nclass Client:MQTT, MQTTDelegate {\n\n  init(clientId:String) {\n    super.init(clientId:clientId)\n    super.delegate = self\n  }\n\n  func mqtt(mqtt: MQTT, didConnect host: String, port: Int) {\n    SLogInfo(\"MQTT client has connected to \\(host):\\(port)\")\n    NSNotificationCenter.defaultCenter().postNotificationName(\"ConnectedNotification\",\n                                                              object:nil)\n  }\n\n  func mqtt(mqtt: MQTT, didConnectAck ack: MQTTConnAck) {\n    ENTRY_LOG()\n  }\n\n  func mqtt(mqtt: MQTT, didPublishMessage message: MQTTMessage, id: UInt16) {\n    ENTRY_LOG()\n  }\n\n  func mqtt(mqtt: MQTT, didPublishAck id: UInt16) {\n    ENTRY_LOG()\n  }\n\n  func mqtt(mqtt: MQTT, didReceiveMessage message: MQTTMessage, id: UInt16 ) {\n    ENTRY_LOG()\n  }\n\n  func mqtt(mqtt: MQTT, didSubscribeTopic topic: String) {\n    ENTRY_LOG()\n  }\n\n  func mqtt(mqtt: MQTT, didUnsubscribeTopic topic: String) {\n    ENTRY_LOG()\n  }\n\n  func mqttDidPing(mqtt: MQTT) {\n    ENTRY_LOG()\n  }\n\n  func mqttDidReceivePong(mqtt: MQTT) {\n    ENTRY_LOG()\n  }\n\n  func mqttDidDisconnect(mqtt: MQTT, withError err: NSError?) {\n    SLogInfo(\"Disconnected from broker\")\n    NSNotificationCenter.defaultCenter().postNotificationName(\"DisconnectedNotification\",object:nil)\n  }\n}\n<\/pre>\n<h3>CPU<\/h3>\n<p><code>CPU.swift<\/code><\/p>\n<pre class=\"lang:swift\">\nimport Glibc\n\nstruct CPU {\n  var temperature:Double? {\n    get {\n      let BUFSIZE = 16\n      let pp      = popen(\"cat \/sys\/class\/hwmon\/hwmon0\/temp1_input\", \"r\")\n      var buf     = [CChar](repeating:0, count:BUFSIZE)\n      guard fgets(&buf, Int32(BUFSIZE), pp) != nil else {\n        pclose(pp)\n        return nil\n      }\n      pclose(pp)\n\n      let s = String(String(cString:buf).characters.dropLast())\n      if let t = Double(s) {\n        return t\/1000\n      } else {\n        return nil\n      }\n    }\n  }\n}\n<\/pre>\n<h3>main<\/h3>\n<p>Our <code>main.swift<\/code> appears to be complicated but it&#8217;s not really.  The key things to recognize is that we wait for notifications and then set timers based upon those notifications to put our client into motion.  For example, nothing good will come of trying to <code>publish<\/code> if we don&#8217;t have an MQTT connection established, so we wait for that notification (from our delegate) before attempting.  Once a connection is established we set a 10 second timer and then update the temperature <i>if we are still connected<\/i>.<\/p>\n<pre class=\"lang:swift\">\nimport swiftlog\nimport Glibc\nimport Foundation\n\nslogLevel = .Info \/\/ Change to .Verbose to get real chatty\n\nslogToFile(atPath:\"\/tmp\/pubSysTemp.log\")\n\nlet BUFSIZE = 128\nvar buffer  = [CChar](repeating:0, count:BUFSIZE)\nguard gethostname(&buffer, BUFSIZE) == 0 else {\n  SLogError(\"Unable to obtain hostname\")\n  exit(-1)\n}\n\nlet client = Client(clientId:String(cString:buffer))\nclient.host = \"broker.hivemq.com\"\nclient.keepAlive = 10\n\nlet nc = NSNotificationCenter.defaultCenter()\nvar reportTemperature:NSTimer?\n\n_ = nc.addObserverForName(\"DisconnectedNotification\", object:nil, queue:nil){_ in\n  SLogInfo(\"Connecting to broker\")\n\n  reportTemperature?.invalidate()\n  if !client.connect() {\n    SLogError(\"Unable to connect to broker.hivemq.com, retrying in 30 seconds\")\n    let retryInterval     = 30\n    let retryTimer        = NSTimer.scheduledTimer(NSTimeInterval(retryInterval),\n                                                   repeats:false){ _ in\n      nc.postNotificationName(\"DisconnectedNotification\", object:nil)\n    }\n    NSRunLoop.currentRunLoop().addTimer(retryTimer, forMode:NSDefaultRunLoopMode)\n  }\n}\n\n_ = nc.addObserverForName(\"ConnectedNotification\", object:nil, queue:nil) {_ in\n\n  let reportInterval    = 10\n  reportTemperature = NSTimer.scheduledTimer(NSTimeInterval(reportInterval),\n                                                 repeats:true){_ in\n\n    if client.connState == .CONNECTED {\n      if let cpuTemperature = CPU().temperature {\n        _ = client.publish(topic:\"\/\\(client.clientId)\/cpu\/temperature\/value\",\n                           withString:String(cpuTemperature))\n        SLogInfo(\"Published temperature to \\(cpuTemperature)\")\n      } else {\n        SLogError(\"Unable to obtain CPU temperature\")\n      }\n    } else {\n      SLogError(\"MQTT client is not connected\")\n    }\n  }\n                                                                           \n  NSRunLoop.currentRunLoop().addTimer(reportTemperature!, forMode:NSDefaultRunLoopMode)\n\n}\n\nnc.postNotificationName(\"DisconnectedNotification\", object:nil) \/\/ Kick the connection\n\nlet heartbeat = NSTimer.scheduledTimer(NSTimeInterval(30), repeats:true){_ in return}\nNSRunLoop.currentRunLoop().addTimer(heartbeat, forMode:NSDefaultRunLoopMode)\nNSRunLoop.currentRunLoop().run()\n<\/pre>\n<p>Note also the use of a heartbeat timer.  Runloops will exit if there are no input sources or timers attached to it, so we use a simple repeating timer to keep a heartbeat (and thus, the runloop) going.<\/p>\n<p>Your <code>Package.swift<\/code> file should look like:<\/p>\n<pre class=\"lang:swift\">\nimport PackageDescription\n\nlet package = Package(\n  name: \"PubSysTemp\",\n  dependencies:[\n    .Package(url:\"https:\/\/github.com\/iachievedit\/MQTT\", majorVersion:0, minor:1)\n  ]\n)\n<\/pre>\n<p>To make life easier you can grab our code from <a href=\"https:\/\/github.com\/iachievedit\/PubSysTemp\">GitHub<\/a>.  Build with <code>swift build<\/code> and then run it:<\/p>\n<pre class=\"crayon:false\">\n# git clone https:\/\/github.com\/iachievedit\/PubSysTemp\n# cd PubSysTemp\n# swift build\n# .build\/debug\/PubSysTemp\n<\/pre>\n<p><b>Remember:<\/b>  this code is written in Swift 3.0!  For more information see <a href=\"http:\/\/dev.iachieved.it\/iachievedit\/introducing-swift-3-0\/\">our post on obtaining Swift 3.0 for Linux<\/a>.<\/p>\n<h2>What&#8217;s this Broker Thing Again?<\/h2>\n<p>Think of an MQTT broker as an <code>NSNotificationCenter<\/code>.  In iOS one typically obtains a reference to the <code>NSNotificationCenter.defaultCenter()<\/code> and then posts messages to it.  Likewise, to receive a message you register to be called when a named notification is posted (topic anyone?).<\/p>\n<p>With MQTT you need to communicate with a broker.  If you&#8217;re building an IoT gateway you might run your own broker using <a href=\"http:\/\/mosquitto.org\">Mosquitto<\/a> or <a href=\"http:\/\/www.hivemq.com\">HiveMQ<\/a>.  If you&#8217;re writing a tutorial on MQTT you might take advantage of a public broker such as <code>test.mosquitto.org<\/code> or <code>broker.hivemq.com<\/code> rather than running your own (like we did!).<\/p>\n<p>In our example above we wrote an MQTT client that <i>publishes<\/i> data.  What about subscribing to that data?  This is also the purview of MQTT clients.  For our example we&#8217;ll use a nice <a href=\"https:\/\/github.com\/jpmens\/tempgauge\">temperature gauge widget<\/a> to power <a href=\"https:\/\/dev.iachieved.it\/mqttgauge\/\">our temperature display<\/a>.<\/p>\n<figure id=\"attachment_2998\" aria-describedby=\"caption-attachment-2998\" style=\"width: 404px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2016\/06\/Temp.png\" rel=\"attachment wp-att-2998\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2016\/06\/Temp.png\" alt=\"Compiling Swift Heats Things Up\" width=\"404\" height=\"405\" class=\"size-full wp-image-2998\" \/><\/a><figcaption id=\"caption-attachment-2998\" class=\"wp-caption-text\">Compiling Swift Heats Things Up<\/figcaption><\/figure>\n<p>What&#8217;s important to realize here is that the temperature gauge is actually an (JavaScript-based) MQTT client that has subscribed to receive events published to the <code>\/darthvader\/cpu\/temperature\/value<\/code> topic on <code>broker.hivemq.com<\/code>.  (<code>darthvader<\/code> is our client&#8217;s hostname.)<\/p>\n<h2>What&#8217;s Next<\/h2>\n<p>There&#8217;s much more to be done with the <a href=\"https:\/\/github.com\/iachievedit\/MQTT\">MQTT<\/a> library.  As of June 11, 2016 it successfully connects and publishes, but subscribing to a topic is another story.  That&#8217;s our next issue to tackle.<\/p>\n<p>This is just the beginning, however, for Swift on the server.  Organizations such as <a href=\"http:\/\/www.zewo.io\">Zewo<\/a> are working hard to develop libraries that can be used to develop server-side software on Linux with Swift.  In fact, our MQTT library uses Zewo&#8217;s  <a href=\"https:\/\/github.com\/VeniceX\/TCP\">VeniceX<\/a> TCP routines for network IO.  Time will tell, but I&#8217;m convinced Swift has a bright future ahead of it in more than just iOS development.<\/p>\n<p><b>Postscript<\/b>:  Please don&#8217;t comment on my lousy voltage divider.  I nearly failed EE classes and do the best I can.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Over the past few years I&#8217;ve made a living managing Internet of Things (IoT) software development projects. In that time I&#8217;ve come to learn a number of protocols for communicating sensor and telemetry information back to &#8220;the cloud&#8221;. Among the most popular in the IoT space is MQTT, a lightweight protocol that allows for publishing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3001,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[27,26,54,7],"class_list":["post-2974","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-swift","tag-linux","tag-mqtt","tag-mqtt-swift-client","tag-swift-2"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/2974"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=2974"}],"version-history":[{"count":31,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/2974\/revisions"}],"predecessor-version":[{"id":3972,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/2974\/revisions\/3972"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media\/3001"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=2974"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=2974"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=2974"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}