{"id":625,"date":"2015-08-01T20:55:47","date_gmt":"2015-08-01T19:55:47","guid":{"rendered":"https:\/\/axotron.se\/blog\/?p=625"},"modified":"2026-10-24T09:24:09","modified_gmt":"2026-10-24T08:24:09","slug":"frdm-based-zigbee-humidity-sensor-node-prototype","status":"publish","type":"post","link":"https:\/\/axotron.se\/blog\/frdm-based-zigbee-humidity-sensor-node-prototype\/","title":{"rendered":"FRDM-based ZigBee Humidity Sensor Node Prototype"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I have previously written about <a href=\"https:\/\/axotron.se\/blog\/interfacing-an-xbee-module-to-a-frdm-board\/\" target=\"_blank\">how to interface an XBee ZigBee module to a FRDM-KL25Z board<\/a> and also <a href=\"https:\/\/axotron.se\/blog\/interfacing-to-an-xbee-module-from-python\/\" target=\"_blank\">how to talk to an XBee ZigBee module from a Python program on a PC<\/a>. In this article I build on these foundations and show how to make a somewhat more complex system where a Python program running on a PC sends commands to &#8211; and receives replies from &#8211; a sensor node. The sensor node has an SHT21 humidity (and temperature) sensor which is connected via I2C to the FRDM board.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here I use the API mode of the XBee module on the sensor node, instead of AT mode that I used in the <a href=\"https:\/\/axotron.se\/blog\/interfacing-an-xbee-module-to-a-frdm-board\/\" target=\"_blank\">previous blog post<\/a>. There are a few XBee libraries for e.g. Arduino and mbed available. It might be possible to use one of those, but I looked into some of them briefly and it seemed like more work to understand and adapt one of those than to write the code necessary to take care of the somewhat limited communication with the XBee module required for this system. On the Python side, I continue to use the XBee library I used before.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Preparing the XBee Modules<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The XBee ZigBee modules need to be properly configured before they can be used in this system. The one connected to the PC shall be programmed with the ZigBee API-mode Coordinator firmware and the one used in the sensor node shall have the API-mode Router firmware. A 64-bit PAN ID must be selected and both modules should be configured with this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Humidity Sensor SHT21 and I2C<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The combined humidity and temperature sensor I selected for this project is called <a href=\"http:\/\/www.sensirion.com\/fileadmin\/user_upload\/customers\/sensirion\/Dokumente\/Humidity\/Sensirion_Humidity_SHT21_Datasheet_V4.pdf\" target=\"_blank\">SHT21<\/a> and is made by <a href=\"http:\/\/www.sensirion.com\/en\/products\/humidity-temperature\/humidity-temperature-sensor-sht2x\/\" target=\"_blank\">Sensirion<\/a>. This is a reasonably priced nice little sensor (3&#215;3 mm<sup>2<\/sup>) with I2C interface and a tolerance on the relative humidity of at most 3% (2% typical) between 20% and 80% RH while the temperature is accurate to 0.4 \u00b0C (0.3 \u00b0C typical) between 5 and 60 \u00b0C.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A minor problem is that it comes in a 6-pad DFN-package (dual flat, no leads) with a pitch of 1 mm. I connected to the chip by soldering very thin wires (a single strand from a relatively thin multi-stranded wire) to the four pads that are used. I then soldered these wires to a small piece of prototype board and added a decoupling capacitor and I2C pull-up resistors (I happened to use 2.2 k\u03a9 and 4.7 k\u03a9 because I had them easily available in suitable form factors). The top side of the package needs to be away from the board to expose the opening of the sensor to the environment. This could potentially lead to short circuits between the board and the bare thin wires, but I put a piece of insulating tape under the sensor to prevent this. See photo below.<\/p>\n\n\n\n<figure class=\"wp-block-image alignnone\"><a href=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/sht21.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"481\" src=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/sht21-1024x481.jpg\" alt=\"SHT21 sensor on a prototype board with auxiliary components.\" class=\"wp-image-626\" srcset=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/sht21-1024x481.jpg 1024w, https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/sht21-300x141.jpg 300w, https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/sht21.jpg 1500w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\">SHT21 sensor (central black square) on a prototype board with a few auxiliary components.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I ended up using the pins PTC11 (J1 pin 15, SDA1) and PTC10 (J1 pin 13, SCL1) as the I2C bus on the FRDM-KL25Z. I first tried A4 (J10 pin 10, SDA1) and A5 (J10 pin 12, SCL1), but it turned out that there is a square wave on A5 that obviously ruins the I2C communication. Maybe this could be turned off, but I moved to other pins instead of investigating what caused the square ware.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The SHT21 has a bit of a quirky requirement on the I2C communication, namely that a delay of 20 \u00b5s is recommended between the ACK bit after some command and before issuing the stop condition. This turned out to be straightforward to implement using the mbed I2C library routines, see the file sht21.cpp.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I did not implement a test of the checksum that the SHT21 provides. That can be added later and the easiest is probably to use code from the <a href=\"http:\/\/www.sensirion.com\/nc\/en\/products\/humidity-temperature\/download-center\/?cid=8585&amp;did=102&amp;sechash=03a6edbf\" target=\"_blank\">SHT21 sample code application note<\/a> for this.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A photo of the complete sensor node prototype is shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image alignnone\"><a href=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/frdm_zb_sht21.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"864\" height=\"1024\" src=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/frdm_zb_sht21-864x1024.jpg\" alt=\"ZigBee sensor node based on a FRDM board, an XBee module and a SHT21 humidity sensor.\" class=\"wp-image-627\" srcset=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/frdm_zb_sht21-864x1024.jpg 864w, https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/frdm_zb_sht21-253x300.jpg 253w, https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/07\/frdm_zb_sht21.jpg 1266w\" sizes=\"auto, (max-width: 864px) 100vw, 864px\" \/><\/a><figcaption class=\"wp-element-caption\">ZigBee sensor node prototype based on a FRDM board, an XBee module and an SHT21 humidity sensor.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Communication Protocol<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To enable the PC to control the sensor node, some form of protocol needs to be devised on top of ZigBee that allows the PC to send requests to the sensor and also allows the sensor to respond.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To send data between the two XBee modules, the &#8220;Transmit Request&#8221; API command is used. This allows messages containing about 80 bytes of payload data to be sent. At the receiving end the messages show up as &#8220;RX Packet&#8221; frames.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I defined the request data packet to just contain two payload bytes, namely an ASCII acronym for the command to be executed. Currently I have two commands defined:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>&#8220;ME&#8221; &#8211; perform the measurement and return the data<\/li>\n\n\n\n<li>&#8220;ID&#8221; &#8211; return information about the sensor node<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The payload of the replies to these commands are defined as follows:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>#&lt;command&gt;#&lt;command specific data&gt;<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">For the ME command, the &lt;command specific data&gt; format is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>4 bytes &#8211; the characters &#8220;HTR &#8221; (which stands for Humidity, Temperature and Received Signal Strength Indicator (RSSI)<\/li>\n\n\n\n<li>4 bytes &#8211; a float with the humidity in %RH<\/li>\n\n\n\n<li>4 bytes &#8211; a float with the temperature in \u00b0C<\/li>\n\n\n\n<li>1 byte &#8211; the RSSI in negative dBm<\/li>\n\n\n\n<li>1 byte &#8211; error counter<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">So a reply to the ME command might look like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#ME#HTR &lt;a float with the value 47.18&gt;&lt;a float with the value 24.11&gt;&lt;a byte with the value 57&gt;&lt;a byte with the value 0&gt;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This would mean that the relative humidity is 47.18%, the temperature is 24.11 \u00b0C, the RSSI is -57 dBm and that the error counter is at zero.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the ID command, the &lt;command specific data&gt; format is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>4 bytes &#8211; ASCII characters&nbsp; containing the node type code<\/li>\n\n\n\n<li>4 bytes &#8211; ASCII characters&nbsp; containing the node revision<\/li>\n\n\n\n<li>? bytes &#8211; An ASCII string with a free text description<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">So this reply is purely in ASCII characters and a reply might look like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">#ID#HT&nbsp; 0001Humidity and temperature sensor<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This protocol can easily be expanded to more commands and different kinds of replies from different kinds of sensors.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sample Terminal Output<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Examples of text printed in the terminal windows (Tera Term for the sensor node and IPython for the Python program) are shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image alignnone\"><a href=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/ipython_api.png\"><img loading=\"lazy\" decoding=\"async\" width=\"677\" height=\"426\" src=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/ipython_api.png\" alt=\"Text printed from the Python program.\" class=\"wp-image-647\" srcset=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/ipython_api.png 677w, https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/ipython_api-300x189.png 300w\" sizes=\"auto, (max-width: 677px) 100vw, 677px\" \/><\/a><figcaption class=\"wp-element-caption\">Text printed from the Python program.<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image alignnone\"><a href=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/tera_term_mbed.png\"><img loading=\"lazy\" decoding=\"async\" width=\"547\" height=\"362\" src=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/tera_term_mbed.png\" alt=\"Text printed from the sensor node to the USB serial port.\" class=\"wp-image-648\" srcset=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/tera_term_mbed.png 547w, https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/tera_term_mbed-300x199.png 300w\" sizes=\"auto, (max-width: 547px) 100vw, 547px\" \/><\/a><figcaption class=\"wp-element-caption\">Text printed from the sensor node to the USB serial port.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Python Code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Python code running on the PC starts out by selecting a COM port to use, sets up communication with the XBee module and sends an ID command to the sensor node before it enters into the main loop. The main loop regularly sends ME commands to the sensor. It also prints dots to show that it is running and checks if the q key has been hit, in which case it exits the program.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There is also a function that is called in the background by the xbee library whenever a frame has been received from the XBee. This function takes the proper action based on what kind of frame was received. The information in replies to the ID and ME command are printed out. Also, if there is anything anomalous in the status replies to transmissions, a warning detailing the issue is printed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The 64-bit address of the sensor node is hard coded into the program (this might be OK for a quick demo or a one-off system, but should be solved in a more flexible manner in the future). The 16-bit address of the sensor node is not known at the beginning, but it is revealed in the first frame it sends back to the PC and so the Python program can store that address and use it in subsequent transmissions to decrease the burden on the ZigBee network.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The Python code can be found below.<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-1&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-1-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-1\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand Python code<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-1\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-1-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#! \/usr\/bin\/python\n\n\"\"\"\nxbee_rx.py\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nThis code is public domain and comes with absolutely no warranty. Enjoy!\n\nRegularly send measurement requests to a remote ZigBee sensor node and display\nthe data that comes back.\n\"\"\"\n\nfrom xbee import ZigBee\nimport serial\nimport serial.tools.list_ports\nimport sys\nimport time\nimport msvcrt\nimport struct\n\n# Remote node address\nlong_addr = \"\\x00\\x13\\xA2\\x00\\x40\\xB4\\x51\\x40\" # Hard coded for now\nshort_addr = '\\xff\\xfe' # Use 0xfffe as 16-bit address until it becomes known\n\n# Callback function that runs when frames are received\ndef receive_data(data):\n    global short_addr\n    if data['id'] == 'rx_explicit':\n        if short_addr == '\\xff\\xfe' and data['source_addr_long'] == long_addr:\n            short_addr = data['source_addr']\n            print 'Discovered remote 16-bit address: 0x{:02x}{:02x}'.format(ord(short_addr[0]),\n                                                                        ord(short_addr[1]))\n            # Transmission from a node\n            if data['rf_data'][:4] == '#ID#':\n                # Frame with node ID\n                print '\\nNode identifiaction frame'\n                print 'Node type: \"' + data['rf_data'][4:8] + '\"'\n                print 'Node revision: \"' + data['rf_data'][8:12] + '\"'\n                print 'Node description: \"' + data['rf_data'][12:] + '\"'\n            elif data['rf_data'][:4] == '#ME#':\n                # Reply to measure command\n                print 'Measurement data frame'\n                if data['rf_data'][4:8] == \"HTR \":\n                    humidity = struct.unpack('f', data['rf_data'][8:12])[0]\n                    temperature = struct.unpack('f', data['rf_data'][12:16])[0]\n                    rssi = ord(data['rf_data'][16])\n                    err_cnt = ord(data['rf_data'][17])\n                    print \"Humidity: {0:.2f}%, Temperature: {1:.2f} C, RSSI: -{2:d} dBm\".format(humidity, temperature, rssi)\n            else:\n                print 'Measurement from unknown node type \"{}\"'.format(data['rf_data'][4:8])\n                print data\n        else:\n            print 'Unknown RX frame type \"{}\"'.format(data['rf_data'][:4])\n            print data\n    elif data['id'] == 'tx_status':\n            # Status from a transmission\n            print '\\nTX status frame, fid={:d}'.format(ord(data['frame_id']))\n            if ord(data['retries']) != 0:\n                print 'TX Warning: {:d} retries were done'.format(ord(data['retries']))\n            if ord(data['deliver_status']) != 0:\n                print 'TX Warning: delivery failed 0x{:x}'.format(ord(data['deliver_status']))\n            if ord(data['discover_status']) != 0:\n                print 'TX Warning: discovery overhead 0x{:x}'.format(ord(data['discover_status']))\n            else:\n                print \"\\nUnknown frame type\"\n                print data\n\n# Look for a COM port that might have an XBee connected\nportfound = False\nports = list(serial.tools.list_ports.comports())\nfor p in ports:\n    # The SparkFun XBee Explorer USB board uses an FTDI chip as USB interface\n    print p\n    if \"FTDIBUS\" in p[2]:\n        print \"Found possible XBee on \" + p[0]\n        if not portfound:\n            portname = p[0]\n            portfound = True\n            print \"Using \" + portname + \" as XBee COM port.\"\n        else:\n            print \"Ignoring this port, using the first one that was found.\"\n\nif portfound:\n    ser = serial.Serial(portname, 9600)\nelse:\n    sys.exit(\"No serial port seems to have an XBee connected.\")\n\nxbee = ZigBee(ser, callback = receive_data)\ncnt = 0\ncnt_me = 100\nid = 2\n\n# Request information about remote node\nxbee.send(\"tx\", data=\"ID\", dest_addr_long=long_addr, dest_addr=short_addr)\nprint \"Press q to quit\"\n\n# Main loop\nwhile True:\n    try:\n        if msvcrt.kbhit():\n            # Ctrl-C exits the whole shell in IPython on Windows,\n            # so make a press on the q-key exit the main loop.\n            if msvcrt.getch() == 'q':\n                print \"Exiting loop\"\n                break\n            time.sleep(0.1)\n            cnt = cnt+1\n            cnt_me = cnt_me+1\n            if cnt >= 10:\n                # Show that the program is still running\n                sys.stdout.write('.')\n                sys.stdout.flush()\n                cnt = 0\n            if cnt_me >= 100:\n                # Send request for measurement data to remote node\n                xbee.send(\"tx\", data=\"ME\", dest_addr_long=long_addr, dest_addr=short_addr, frame_id=chr(id))\n                id = id+1\n                if id > 255:\n                    id = 2\n                    cnt_me = 0\n\n    except KeyboardInterrupt:\n        print \"Ctrl-C was pressed\"\n        break\n\nxbee.halt()\nser.close()\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Mbed Code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The program running on the FRDM-KL25Z in the sensor node is divided into several source code files. It works like this:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An interrupt routine receives the bytes coming from the XBee serial port and when it finds a valid frame, it stores it in an available frame buffer and marks that frame buffer as containing a frame to be processed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The main program:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>sets everything up and then enters the main loop that:<\/li>\n\n\n\n<li>regularly performs the measurements<\/li>\n\n\n\n<li>looks at the frame buffers to see if there is any that contains an unprocessed frame<\/li>\n\n\n\n<li>when it finds a new frame, it processes it (sends the reply to the command, handles the reply to an AT command or sends an ATDB command to get the RSSI value)<\/li>\n\n\n\n<li>prints status information to the PC serial port (for debug purposes)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Currently the error counter functionality is not implemented.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The program is divided into several source code files. These files together with the Python program can all be downloaded as a zip archive:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/axotron.se\/blog\/wp-content\/uploads\/2015\/08\/xbee_api_150731.zip\">XBee sensor source code, 2015-07-31<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The individual files are described further below.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">main.cpp<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This file contains the main part of the program that<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>sets up communication with the XBee module<\/li>\n\n\n\n<li>sets up the SHT21 sensor<\/li>\n\n\n\n<li>a main loop as described above<\/li>\n\n\n\n<li>code to update the variables with the most recent measurement results<\/li>\n\n\n\n<li>code to parse AT command replies from the XBee module (used to figure out RSSI), handle_at_response()<\/li>\n\n\n\n<li>code to send out the measurement data, send_humidity_temperature()<\/li>\n\n\n\n<li>code to reply to the ID command, send_id()<\/li>\n\n\n\n<li>code to parse the incoming RX frames, handle_rx()<\/li>\n<\/ul>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-2&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-2-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-2\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand main.cpp<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-2\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-2-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\n  main.cpp\n\n  Rev 0.1, 2015-07-31\n  Per Magnusson, Axotron, axotron.se\/blog\n\n  This code is public domain and comes with absolutely no warranty. Enjoy!\n\n  Sensor node based on an XBee ZigBee module, a FRDM-KL25Z development board\n  and an SHT21 humidity\/temperature sensor.\n\n  This file contains the main program that:\n  - sets everything up\n  - regularly performs the measurements\n  - processes ZigBee frames that the interrupt routine has stored in frame buffers\n  - responds to commands from other ZigBee nodes\n  - prints status information to the PC serial port\n*\/\n\n#include \"mbed.h\"\n#include \"serial_buffer.h\"\n#include \"sht21.h\"\n#include \"zigbee.h\"\n#include \"xbee_api.h\"\n\nSerial pc(USBTX, USBRX);\nSerial xbee(PTE0, PTE1);\nDigitalOut redled(LED1);\nDigitalOut greenled(LED2);\nDigitalOut blueled(LED3);\n\nuint8_t rssi; \/\/ Most recent RSSI value\nuint8_t rssi_updated; \/\/ Whether the RSSI value was updated since last time it was read\nfloat humidity = -1.0;\nfloat temperature = -1000.0;\nuint8_t id = 1; \/\/ ID used for TX packets\n\n\/\/ Update the temperature and humidity variables\nvoid update_measurements() {\n  humidity = sht21_read_humidity();\n  temperature = sht21_read_temperature();\n}\n\n\/\/ Process a frame that was a response to an AT command\nvoid handle_at_response(const uint8_t *buffer) {\n  uint32_t length;\n  uint8_t frame_type;\n  uint8_t status;\n  uint32_t at;\n\n  length = zb_get_buffer_length(buffer);\n  frame_type = buffer[ZB_OFFS_FRAME_TYPE];\n  if (frame_type != ZB_FRAME_TYPE_AT_RESPONSE) {\n    \/\/ Not the expected frame type for this handler!\n    pc.printf(\"Incorrect frame type 0x%02x for at-response handler\\n\", (uint32_t)frame_type);\n    return;\n  }\n  status = buffer[ZB_OFFS_AT_RESP_STATUS];\n  if (status != 0) {\n    \/\/ Status is not OK\n    pc.printf(\"Status is not OK (0x%02x) in at-response\\n\", (uint32_t)status);\n    return;\n  }\n\n  at = zb_get_at(buffer);\n  if (at == ZB_ATDB) {\n    \/\/ Response to ATDB command\n    if (length != 6) {\n      \/\/ Unexpected length of ATDB response\n      pc.printf(\"Unexpected length (%u) of at-response\\n\", length);\n      return;\n    }\n    rssi = buffer[ZB_OFFS_AT_RESP_DATA0];\n    rssi_updated = 1;\n    pc.printf(\"RSSI = -%u dBm\\n\", (uint32_t)rssi);\n  } else {\n    pc.printf(\"No handler for response to AT%c%c\\n\",\n\t      buffer[ZB_OFFS_AT_RESP_AT0], buffer[ZB_OFFS_AT_RESP_AT1]);\n  }\n}\n\n\/\/ Send a TX packet with humidity, temperature and RSSI information\nvoid send_humidity_temperature(const uint8_t *dest64, const uint8_t *dest16) {\n  humidity_data_t data;\n\n  \/\/ Make sure measurements are OK\n  if(humidity &lt; 0) {\n    update_measurements();\n  }\n\n  data.cmd_reply[0] = '#'; \/\/ Shows what command we are responding to\n  data.cmd_reply[1] = 'M';\n  data.cmd_reply[2] = 'E';\n  data.cmd_reply[3] = '#';\n  data.sensor_data_type[0] = 'H'; \/\/ Humidity\n  data.sensor_data_type[1] = 'T'; \/\/ Temperature\n  data.sensor_data_type[2] = 'R'; \/\/ RSSI\n  data.sensor_data_type[3] = ' ';\n  data.humidity = humidity;\n  data.temperature = temperature;\n  data.rssi = rssi;\n  data.err_cnt = 0;\n\n  id++; \/\/ Increase frame ID\n  if(id &lt; 2) {\n    id = 2; \/\/ Never let it be less than 2\n  }\n  \/\/ Send the data\n  zb_send_tx_req(id, dest64, dest16, (uint8_t *)&amp;data, sizeof(data));\n}\n\n\/\/ Send a TX packet identifying this sensor node type\nvoid send_id(const uint8_t *dest64, const uint8_t *dest16) {\n  \/\/ Data format:\n  \/\/ 4 bytes - The command this is a response to, surrounded by # signs\n  \/\/ 4 bytes - The node type code\n  \/\/ 4 bytes - The node revision\n  \/\/ ? bytes - A free text description\n  char reply[] = \"#ID#\" \"HT  \" \"0001\" \"Humidity and temperature sensor\";\n\n  id++; \/\/ Increase frame ID\n  if(id &lt; 2) {\n    id = 2; \/\/ Never let it be less than 2\n  }\n  \/\/ Send the data\n  \/\/ Do not include the trailing nul in the string\n  zb_send_tx_req(id, dest64, dest16, (uint8_t *)&amp;reply, sizeof(reply)-1);\n}\n\n\/\/ Process a frame containing an RX packet\nvoid handle_rx(const uint8_t *buffer) {\n  uint8_t frame_type;\n  uint32_t cmd;\n  uint32_t length, data_len;\n\n  pc.printf(\"RX handler\\n\");\n  length = zb_get_buffer_length(buffer);\n  frame_type = buffer[ZB_OFFS_FRAME_TYPE];\n  if (frame_type != ZB_FRAME_TYPE_RX_RECEIVED) {\n    \/\/ Not the expected frame type for this handler!\n    pc.printf(\"Incorrect frame type 0x%02x for RX handler\\n\", (uint32_t)frame_type);\n    return;\n  }\n\n  \/\/ Ignore receive options field\n  data_len = length - ZB_OFFS_RX_DATA_0 + 3;\n  if (data_len &lt; 2) {\n    pc.printf(\"Too little data in RX packet\\n\");\n    return;\n  }\n  cmd = zb_get16(buffer, ZB_OFFS_RX_DATA_0);\n  if (cmd == ZB_CMD_ME) {\n    \/\/ ME Measure command\n    \/\/ Send a response packet with the measurement data.\n    blueled = 0;\n    pc.printf(\"Sending reply to ME\\n\");\n    send_humidity_temperature(buffer+ZB_OFFS_RX_SRC64_0, buffer+ZB_OFFS_RX_SRC16_0);\n    blueled = 1;\n  } else if (cmd == ZB_CMD_ID) {\n    \/\/ ID, identify command\n    \/\/ Send a response\n    blueled = 0;\n    pc.printf(\"Sending reply to ID\\n\");\n    send_id(buffer+ZB_OFFS_RX_SRC64_0, buffer+ZB_OFFS_RX_SRC16_0);\n    blueled = 1;\n  } else {\n    redled = 0;\n    pc.printf(\"No handler for RX command %c%c\\n\",\n\t      buffer[ZB_OFFS_RX_DATA_0], buffer[ZB_OFFS_RX_DATA_0+1]);\n    redled = 1;\n  }\n}\n\nint main() {\n  uint8_t frame_type;\n  Timer rssi_timer, humidity_timer, data_timer;\n\n  redled = 1;    \/\/ led off\n  blueled = 1;   \/\/ led off\n  greenled = 0;  \/\/ led on\n  wait(1);\n  pc.printf(\"\\n\\nZigBee sensor node test program\\n\");\n  xbee.baud(9600);\n  xbee.attach(&amp;rx_interrupt, Serial::RxIrq);\n\n  greenled = 1; \/\/ led off\n\n  pc.printf(\"Setting up I2C\\n\");\n  while (!sht21_setup()) {\n    pc.printf(\"SHT21 setup failed\\n\");\n    redled = 0;\n    wait(1);\n  }\n  redled = 1;\n  pc.printf(\"SHT21 setup successful\\n\");\n\n  humidity_timer.start();\n\n  \/\/ Main loop, look for frames coming from the XBee module\n  while (1) {\n    \/\/ Check if there is any complete frame in any buffer\n    for (int ii=0; ii&lt;n_rx_buffers; ii++) {\n      if (buffer_used[ii] == 2) {\n\t\/\/ Found a complete frame, process it\n\tframe_type = rx_buffer[ii][ZB_OFFS_FRAME_TYPE];\n\tswitch (frame_type) {\n\tcase ZB_FRAME_TYPE_AT_RESPONSE:\n\t  \/\/ Response from AT command\n\t  handle_at_response(rx_buffer[ii]);\n\t  break;\n\tcase ZB_FRAME_TYPE_TX_RESPONSE:\n\t  \/\/ Response from TX command\n\t  pc.printf(\"Response from TX\\n\");\n\t  zb_send_simple_at(\"DB\", 1); \/\/ Update RSSI\n\t  break;\n\tcase ZB_FRAME_TYPE_RX_RECEIVED:\n\t  \/\/ Incoming RX frame\n\t  handle_rx(rx_buffer[ii]);\n\t  break;\n\t}\n\t\/\/ The frame has been processed, mark it as empty\n\tbuffer_used[ii] = 0;\n      }\n    }\n    \/\/ Timer controlled activities\n    if(humidity_timer.read() > 10.0) {\n      \/\/ Read humidity and temperature\n      greenled = 0;\n      update_measurements();\n      pc.printf(\"Relative humidity = %.2f%% \", humidity);\n      pc.printf(\", Temperature = %.2f C\\n\", temperature);\n      humidity_timer.reset();\n      greenled = 1;\n    }\n  }\n}<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">xbee_api.h<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This header file declares some global variables from main.cpp and typedefs a struct used for the data format in replies to the ME command.<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-3&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-3-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-3\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand xbee_api.h<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-3\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-3-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nxbee_api.h\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nThis code is public domain and comes with absolutely no warranty. Enjoy!\n\nDeclarations for the main part of the program.\n*\/\n\n#include \"mbed.h\"\n\nextern Serial pc; \nextern Serial xbee; \nextern DigitalOut redled; \nextern DigitalOut greenled; \nextern DigitalOut blueled; \n\ntypedef struct humidity_data_t {\n    char cmd_reply[4];\n    char sensor_data_type[4];\n    float humidity;\n    float temperature;\n    uint8_t rssi;\n    uint8_t err_cnt;\n} humidity_data_t;<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">serial_buffer.cpp<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This file contains the interrupt routine rx_interrupt() which is called when data arrives from the XBee serial port. The data is parsed and if it is found to be a valid ZigBee frame, it is stored in a free frame buffer. The buffer is then marked as containing a valid frame to be processed (by the main loop).<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-4&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-4-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-4\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand serial_buffer.cpp<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-4\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-4-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nserial_buffer.cpp\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nThis code is public domain and comes with absolutely no warranty. Enjoy!\n\nFrame buffers for data from the XBee module.\n\nrx_interrupt() is called when data arrives from the XBee serial port. The data is parsed \nand if it is found to be a valid ZigBee frame, it is stored in a free frame buffer.\nThe buffer is then marked as containing a valid frame to be processed (by the main loop).\n*\/\n\n\n#include \"mbed.h\" \n#include \"serial_buffer.h\"\n#include \"xbee_api.h\"\n#include \"zigbee.h\"\n\n\n\/\/ Frame buffers for XBee RX data - used by interrupt routines\n\nuint8_t rx_buffer[n_rx_buffers][buffer_size+1]; \/\/ The received frames are stored here\n\/\/ buffer_used[]: \n\/\/ 0 - not used\n\/\/ 1 - being filled up\n\/\/ 2 - filled with a frame, waiting to be processed by main loop\n\/\/ (variables with static storage are initilized to all zeros)\nint buffer_used[n_rx_buffers]; \nvolatile int rx_ptr=0;\n\n\n\/\/ Interrupt routine to read frame data from the serial port into frame buffers.\nvoid rx_interrupt() {\n    \/\/ State: \n    \/\/ 0 = waiting for start delimiter of new frame,\n    \/\/ 1 = inside frame, storing it in a buffer\n    \/\/ 2 = inside frame, dropping it\n    static int state = 0; \n    static int current_buffer;\n    static uint32_t length;\n    uint8_t cur_byte;\n    static uint8_t sum; \/\/ For checksum calculation\n    \n    while (xbee.readable()) {\n        cur_byte = ((int)xbee.getc())&amp;0xff;\n        if (state == 0) {\n            \/\/ Waiting for a start delimiter\n            if (cur_byte != ZB_START_DELIM) {\n                \/\/ Not a start delimiter. This is a bit unexpected. \n                \/\/ Just eat it and wait for the next byte.\n            } else {\n                \/\/ Got a start delimiter.\n                \/\/ Find a buffer to store it in.\n                current_buffer = -1;\n                for (int ii=0; ii &lt; n_rx_buffers; ii++) {\n                    if (!buffer_used[ii]) {\n                        \/\/ Found an empty buffer\n                        current_buffer = ii;\n                        state = 1;\n                        buffer_used[ii] = 1;\n                        rx_ptr = 0;\n                        rx_buffer[current_buffer][rx_ptr++] = cur_byte;\n                        break;\n                    }\n                }\n                if (current_buffer &lt; 0) {\n                    \/\/ There was no free buffer!\n                    \/\/ Ignore this packet and wait for a new one.\n                    pc.printf(\"Error: Discarding frame, no free buffers.\\n\");\n                    state = 2;\n                }\n            }\n        } else if (state == 1) {\n            \/\/ Copying a frame into a buffer\n            if (rx_ptr == 1) {\n                \/\/ MSB of length\n                rx_buffer[current_buffer][rx_ptr++] = cur_byte;\n            } else if (rx_ptr == 2) {\n                \/\/ LSB of length\n                rx_buffer[current_buffer][rx_ptr++] = cur_byte;\n                length = zb_get_buffer_length(rx_buffer[current_buffer]);\n                sum = 0;\n                if (length + 4 > buffer_size) {\n                    \/\/ Ooops! The frame is bigger than the buffer, this is very unexpected.\n                    \/\/ We need to discard this frame.\n                    pc.printf(\"The frame is bigger than the buffer (%u), discarding it.\\n\", length);\n                    buffer_used[current_buffer] = 0;\n                    state = 2;\n                }\n            } else if (length > 0) {\n                \/\/ This is a byte inside the frame\n                sum += cur_byte;\n                rx_buffer[current_buffer][rx_ptr++] = cur_byte;\n                length--;\n            } else {\n                \/\/ The checksum byte\n                sum += cur_byte;\n                rx_buffer[current_buffer][rx_ptr++] = cur_byte;\n                state = 0;\n                if (sum != 0xff) {\n                    \/\/ Checksum error, discard frame\n                    pc.printf(\"Frame checksum error\\n\");\n                    buffer_used[current_buffer] = 0;\n                } else {\n                    \/\/ Checksum OK, mark buffer as ready to be processed\n                    buffer_used[current_buffer] = 2;\n                    state = 0;\n                }\n            }\n        } else {\n            \/\/ Discarding incoming frame\n            if (rx_ptr==1) {\n                \/\/ MSB of length\n                length = ((uint32_t)cur_byte)&lt;&lt;8;\n            } else if (rx_ptr==2) {\n                \/\/ LSB of length\n                length += cur_byte;\n                if (length > 102) {\n                    \/\/ Length is unrealistic, we do not want to wait for \n                    \/\/ so many bytes before starting to look for a new frame.\n                    \/\/ Start looking for start of a new frame immediately.\n                    length = 0;\n                }\n            } else if (length > 0) {\n                \/\/ A byte inside the frame\n                length--;\n            } else {\n                \/\/ Finally the checksum, change state\n                state = 0;\n            }\n        }\n    }\n}\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">serial_buffer.h<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Header file for serial_buffer.cpp<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-5&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-5-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-5\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand serial_buffer.h<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-5\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-5-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nserial_buffer.h\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nThis code is public domain and comes with absolutely no warranty. Enjoy!\n\nFrame buffers for data from the XBee module.\n\nrx_interrupt() is called when data arrives from the XBee serial port. The data is parsed \nand if it is found to be a valid ZigBee frame, it is stored in a free frame buffer.\nThe buffer is then marked as containing a valid frame to be processed (by the main loop).\n*\/\n\nvoid rx_interrupt();\n\n\/\/ ZigBee frame buffers\nconst int buffer_size = 256;\nconst int n_rx_buffers = 4;\n\/\/ The received frames are stored here\nextern uint8_t rx_buffer[n_rx_buffers][buffer_size+1];\n\/\/ buffer_used[]: \n\/\/ 0 - not used\n\/\/ 1 - being filled up\n\/\/ 2 - filled with a frame, waiting to be processed by main loop\nextern int buffer_used[n_rx_buffers]; \n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">sht21.cpp<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Functions to talk to an SHT21 humidity and temperature sensor via I2C.<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-6&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-6-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-6\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand sht21.cpp<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-6\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-6-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nsht21.cpp\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nThis code is public domain and comes with absolutely no warranty. Enjoy!\n\nFunctions to talk to an SHT21 humidity and temperature sensor via I2C.\nSome of the code is adapted from https:\/\/developer.mbed.org\/handbook\/I2C\n*\/\n\n\n#include \"mbed.h\"\n#include \"xbee_api.h\"\n\nI2C i2c(PTC11, PTC10);\n \nstatic const int sht21_addr = 0x80;\nstatic const uint8_t i2c_cmd_trig_t = 0xf3;\nstatic const uint8_t i2c_cmd_trig_h = 0xf5;\nstatic const uint8_t i2c_cmd_write_reg = 0xe6;\nstatic const uint8_t i2c_cmd_read_reg = 0xe7;\nstatic const uint8_t i2c_cmd_reset = 0xfe;\n\nstatic uint8_t sht21_default_reg = 0;\n\n\/\/ Initialize the SHT21 sensor. Returns true on success.\nint sht21_setup() {\n    char data[2];\n    \n    i2c.frequency(200000); \/\/ SHT21 supports up to 400 kHz\n    \/\/ Soft reset\n    data[0] = i2c_cmd_reset;\n    if(i2c.write(sht21_addr, data, 1)) {\n        pc.printf(\"I2C Error: Did not receive ACK on soft reset.\\n\");\n        return 0;\n    }\n    wait_ms(16); \/\/ Soft reset takes less than 15 ms\n    \n    \/\/ Read user register to figure out the default bits\n    data[0] = i2c_cmd_read_reg;\n    if(i2c.write(sht21_addr, data, 1)) {\n        pc.printf(\"I2C Error: Did not receive ACK on read reg 1.\\n\");\n        return 0;\n    }\n    if(i2c.read(sht21_addr, data, 1)) {\n        pc.printf(\"I2C Error: Did not receive ACK on read reg 2.\\n\");\n        return 0;\n    }\n    \/\/ Mask out the default bit pattern for reserved bits 3, 4, 5\n    sht21_default_reg = data[0] &amp; 0x34;\n    \n    \/\/ Set it to maximum resolution\n    data[0] = i2c_cmd_write_reg;\n    data[1] = sht21_default_reg | 0x02; \/\/ max resolution, disable heater, disable OTP reload\n    if(i2c.write(sht21_addr, data, 1)) {\n        pc.printf(\"I2C Error: Did not receive ACK on write reg.\\n\");\n        return 0;\n    }\n    return 1;\n}    \n\n    \n\/\/ Read the humidity from the SHT21. \n\/\/ The humidity is returned as a float (%RH).\n\/\/ Returns a large negative value if an error occured.\nfloat sht21_read_humidity() {\n    char data[3];\n    int32_t humidity;\n    \n    \/\/ Read user register to figure out default bits\n    data[0] = i2c_cmd_trig_h;\n    if(i2c.write(sht21_addr, data, 1, true)) {\n        pc.printf(\"I2C Error: Did not receive ACK on humidity trigger.\\n\");\n        wait_us(20);\n        i2c.stop();\n        return -(1&lt;&lt;24);\n    }\n    \/\/ Need to send the stop condition a little later\n    wait_us(20);\n    i2c.stop();\n    wait_ms(30); \/\/ Wait until we are sure the conversion is done (29 ms maximum)\n    if(i2c.read(sht21_addr, data, 3)) {\n        pc.printf(\"I2C Error: Did not receive ACK on read humidity.\\n\");\n        return -(1&lt;&lt;24);\n    }\n    \/\/ Ignore checksum testing\n    humidity = ((((int32_t)data[0])&amp;0xff)&lt;&lt;8) + (data[1]&amp;0xfc); \/\/ Combine bytes and clear status bits\n    return 125.0*humidity\/65536.0 - 6.0;\n}\n\n\n\/\/ Read the temperature from the SHT21. \n\/\/ The temperature is returned as a float (degrees C).\n\/\/ Returns a large negative value if an error occured.\nfloat sht21_read_temperature() {\n    char data[3];\n    int32_t temperature;\n    \n    \/\/ Read user register to figure out default bits\n    data[0] = i2c_cmd_trig_t;\n    if(i2c.write(sht21_addr, data, 1, true)) {\n        pc.printf(\"I2C Error: Did not receive ACK on temperature trigger.\\n\");\n        wait_us(20);\n        i2c.stop();\n        return -(1&lt;&lt;24);\n    }\n    \/\/ Need to send the stop condition a little later\n    wait_us(20);\n    i2c.stop();\n    wait_ms(90); \/\/ Wait until we are sure the conversion is done (85 ms maximum)\n    if(i2c.read(sht21_addr, data, 3)) {\n        pc.printf(\"I2C Error: Did not receive ACK on read temperature.\\n\");\n        return -(1&lt;&lt;24);\n    }\n    \/\/ Ignore checksum testing\n    temperature = ((((int32_t)data[0])&amp;0xff)&lt;&lt;8) + (data[1]&amp;0xfc); \/\/ Combine bytes and clear status bits\n    return 175.72*temperature\/65536.0 - 46.85; \n}\n<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">sht21.h<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Header file for sht21.cpp<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-7&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-7-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-7\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand sht21.h<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-7\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-7-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nsht21.h\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nHeader file for routines for SHT21 I2C humidity and temperature sensor.\n*\/\n\nint sht21_setup();\nfloat sht21_read_humidity();\nfloat sht21_read_temperature();<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">zigbee.cpp<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Functions to deal with the XBee ZigBee module. This includes functions to create and send various kinds of API frames as well as to get information out of received frames.<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-8&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-8-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-8\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand zigbee.cpp<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-8\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-8-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nzigbee.cpp\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nFunctions to deal with the XBee ZigBee module.\n*\/\n\n#include \"xbee_api.h\"\n#include \"zigbee.h\"\n\n\n\/\/ Calculate and set the checksum of the ZB buffer pointed to by buffer with the length len.\n\/\/ len is the number of bytes in the buffer, including start delimiter and length.\nvoid zb_set_checksum(uint8_t *buffer, int len) {\n    uint8_t sum = 0;\n    \n    for(int ii = ZB_OFFS_LEN_LSB+1; ii &lt; len; ii++) {\n        sum += buffer[ii];\n    }\n    buffer[len] = 0xff - sum;\n}\n\n    \n\/\/ Set the length field of the ZigBee buffer. \n\/\/ len is the number of bytes in the buffer, including start delimiter and length, \n\/\/ but excluding the checksum\nvoid zb_set_len(uint8_t *buffer, uint32_t len) {\n    uint32_t len_val;\n    \n    len_val = len - 3;\n    buffer[ZB_OFFS_LEN_MSB] = len_val >> 8;\n    buffer[ZB_OFFS_LEN_LSB] = len_val &amp; 0xff;\n}\n\n\n\/\/ Send a buffer containing a ZigBee frame to the XBee module\nvoid zb_send_buffer(const uint8_t *buffer) {\n    uint32_t len;\n\n    len = zb_get_buffer_length(buffer) + 4;\n    pc.printf(\"Sending frame: \");    \n    for(int ii=0; ii &lt; len; ii++) {\n        pc.printf(\"%02x \", (uint32_t)buffer[ii]);\n        xbee.putc(buffer[ii]);\n    }\n    pc.printf(\"\\n\");    \n}\n\n\n\/\/ Send an AT command without parameter\nvoid zb_send_simple_at(const char *at, char frame_id) {\n    uint8_t buffer[8];\n    uint32_t bufp = 0;\n    \n    buffer[0] = ZB_START_DELIM;\n    buffer[ZB_OFFS_FRAME_TYPE] = ZB_FRAME_TYPE_AT;\n    buffer[ZB_OFFS_FRAME_ID] = frame_id;\n    buffer[ZB_OFFS_AT_CMD_AT0] = at[0];\n    buffer[ZB_OFFS_AT_CMD_AT1] = at[1];\n    bufp = ZB_OFFS_AT_CMD_AT1+1;\n    zb_set_checksum(buffer, bufp);\n    zb_set_len(buffer, bufp);\n    \n    zb_send_buffer(buffer);           \n}\n\n\n\/\/ Send an AT command with a single byte as parameter\nvoid zb_send_at_byte_param(const char *at, uint8_t param) {\n    uint8_t buffer[9];\n    uint32_t bufp = 0;\n    \n    buffer[0] = ZB_START_DELIM;\n    buffer[ZB_OFFS_FRAME_TYPE] = ZB_FRAME_TYPE_AT;\n    buffer[ZB_OFFS_FRAME_ID] = 0;\n    buffer[ZB_OFFS_AT_CMD_AT0] = at[0];\n    buffer[ZB_OFFS_AT_CMD_AT1] = at[1];\n    bufp = ZB_OFFS_AT_CMD_AT1+1;\n    buffer[bufp++] = param;\n    zb_set_checksum(buffer, bufp);\n    zb_set_len(buffer, bufp);\n    \n    zb_send_buffer(buffer);           \n}\n\n\n\/\/ Send a transmit request frame\nvoid zb_send_tx_req(uint8_t id, const uint8_t *dest64, const uint8_t *dest16, const uint8_t *data, int data_len) {\n    uint8_t buffer[18+82]; \/\/ How much payload can there be?\n    int bufp = 0;\n    \n    buffer[0] = ZB_START_DELIM;\n    buffer[ZB_OFFS_FRAME_TYPE] = ZB_FRAME_TYPE_TX_REQ;\n    buffer[ZB_OFFS_FRAME_ID] = id;\n    for(int ii=0; ii&lt;8; ii++) {\n        buffer[ZB_OFFS_TX_DEST64_0+ii] = dest64[ii];\n    }\n    buffer[ZB_OFFS_TX_DEST16_0] = dest16[0];\n    buffer[ZB_OFFS_TX_DEST16_1] = dest16[1];\n    buffer[ZB_OFFS_TX_RADIUS] = 0;\n    buffer[ZB_OFFS_TX_OPTIONS] = 0;\n    bufp = ZB_OFFS_TX_DATA_0;\n    for(int ii=0; ii&lt;data_len; ii++) {\n        buffer[bufp++] = data[ii];\n    }\n    zb_set_checksum(buffer, bufp);\n    zb_set_len(buffer, bufp);\n    zb_send_buffer(buffer);           \n}\n\n\n\/\/ Return two bytes (MSB and LSB) from a uint8_t array as an unsigned int\nuint32_t zb_get16(const uint8_t *buffer, int start) {\n    return (buffer[start]&lt;&lt;8) + buffer[start+1];\n}\n\n\n\/\/ Return the length field from a frame\nint zb_get_buffer_length(const uint8_t *buffer) {\n    return zb_get16(buffer, ZB_OFFS_LEN_MSB);\n}\n\n\n\/\/ Return the AT command field from an AT command response frame\nuint32_t zb_get_at(const uint8_t *buffer) {\n    return zb_get16(buffer, ZB_OFFS_AT_RESP_AT0);\n}<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">zigbee.h<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Header file with declarations and constants for ZigBee communication.<\/p>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-9&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading has-icon has-icon-right has-medium-font-size\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-9-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" id=\"accordion-item-9\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\">> Expand zigbee.h<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div aria-labelledby=\"accordion-item-9\" data-wp-bind--hidden=\"state.isHidden\" data-wp-on--beforematch=\"actions.handleBeforeMatch\" id=\"accordion-item-9-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\/*\nzigbee.h\n\nRev 0.1, 2015-07-31\nPer Magnusson, Axotron, axotron.se\/blog\n\nHeader file with declarations and constants for ZigBee communication.\n*\/\n\n\n#define ZB_BUFSIZE 150\n\/\/ ZigBee defines\n\/\/ General frame offsets\n#define ZB_START_DELIM 0x7e\n#define ZB_OFFS_LEN_MSB 1\n#define ZB_OFFS_LEN_LSB 2\n#define ZB_OFFS_FRAME_TYPE 3\n#define ZB_OFFS_FRAME_ID 4\n\/\/ AT command frame offsets\n#define ZB_OFFS_AT_CMD_AT0 5\n#define ZB_OFFS_AT_CMD_AT1 6\n#define ZB_OFFS_AT_CMD_DATA0 7\n\/\/ AT response frame offsets\n#define ZB_OFFS_AT_RESP_AT0 5\n#define ZB_OFFS_AT_RESP_AT1 6\n#define ZB_OFFS_AT_RESP_STATUS 7\n#define ZB_OFFS_AT_RESP_DATA0 8\n\n\/\/ TX request frame offsets\n#define ZB_OFFS_TX_DEST64_0 5\n#define ZB_OFFS_TX_DEST64_1 6\n#define ZB_OFFS_TX_DEST64_2 7\n#define ZB_OFFS_TX_DEST64_3 8\n#define ZB_OFFS_TX_DEST64_4 9\n#define ZB_OFFS_TX_DEST64_5 10\n#define ZB_OFFS_TX_DEST64_6 11\n#define ZB_OFFS_TX_DEST64_7 12\n#define ZB_OFFS_TX_DEST16_0 13\n#define ZB_OFFS_TX_DEST16_1 14\n#define ZB_OFFS_TX_RADIUS 15\n#define ZB_OFFS_TX_OPTIONS 16\n#define ZB_OFFS_TX_DATA_0 17\n\n\/\/ RX packet frame offsets\n#define ZB_OFFS_RX_SRC64_0 4\n#define ZB_OFFS_RX_SRC64_1 5\n#define ZB_OFFS_RX_SRC64_2 6\n#define ZB_OFFS_RX_SRC64_3 7\n#define ZB_OFFS_RX_SRC64_4 8\n#define ZB_OFFS_RX_SRC64_5 9\n#define ZB_OFFS_RX_SRC64_6 10\n#define ZB_OFFS_RX_SRC64_7 11\n#define ZB_OFFS_RX_SRC16_0 12\n#define ZB_OFFS_RX_SRC16_1 13\n#define ZB_OFFS_RX_OPTIONS 14\n#define ZB_OFFS_RX_DATA_0 15\n\n\/\/ Frame types\n#define ZB_FRAME_TYPE_AT 0x08\n#define ZB_FRAME_TYPE_TX_REQ 0x10\n#define ZB_FRAME_TYPE_AT_RESPONSE 0x88\n#define ZB_FRAME_TYPE_TX_RESPONSE 0x8b\n#define ZB_FRAME_TYPE_RX_RECEIVED 0x90\n\n\/\/ AT commands as numbers\n#define ZB_ATDB ((((uint32_t)'D')&lt;&lt;8) + 'B')\n\n\/\/ Commands to sensor nodes\n#define ZB_CMD_ME ((((uint32_t)'M')&lt;&lt;8) + 'E')\n#define ZB_CMD_ID ((((uint32_t)'I')&lt;&lt;8) + 'D')\n\nvoid zb_send_simple_at(const char *at, char frame_id=0);\nvoid zb_send_at_byte_param(const char *at, uint8_t param);\nvoid zb_send_tx_req(uint8_t id, const uint8_t *dest64, const uint8_t *dest16, const uint8_t *data, int data_len);\nuint32_t zb_get16(const uint8_t *buffer, int start);\nint zb_get_buffer_length(const uint8_t *buffer);\nuint32_t zb_get_at(const uint8_t *buffer);<\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have previously written about how to interface an XBee ZigBee module to a FRDM-KL25Z board and also how to talk to an XBee ZigBee module from a Python program on a PC. In this article I build on these foundations and show how to make a somewhat more complex system where a Python program &hellip; <a href=\"https:\/\/axotron.se\/blog\/frdm-based-zigbee-humidity-sensor-node-prototype\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">FRDM-based ZigBee Humidity Sensor Node Prototype<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-625","post","type-post","status-publish","format-standard","hentry","category-electronics"],"_links":{"self":[{"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/posts\/625","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/comments?post=625"}],"version-history":[{"count":19,"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/posts\/625\/revisions"}],"predecessor-version":[{"id":1864,"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/posts\/625\/revisions\/1864"}],"wp:attachment":[{"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/media?parent=625"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/categories?post=625"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/axotron.se\/blog\/wp-json\/wp\/v2\/tags?post=625"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}