├── README.md ├── main.py └── tweet.py /README.md: -------------------------------------------------------------------------------- 1 | # micropython_esp8266_tweetbot 2 | Tweet bot for MicroPython v1.8.4 (ESP8266) 3 | 4 | This program is a Tweet bot (Twitter status updater) for MicroPython ESP8266. 5 | Confirmed working with MicroPython 1.8.4. 6 | 7 | One problem is, this program consumes a lot of heap memory, so it will not run 8 | if you add more functionality. 9 | 10 | To run, we need a modified hmac.py which is compatible with SHA1. We can 11 | obtain the code below even though it is not merged to the main stream, yet. 12 | (Thanks to the patch author.) 13 | 14 | https://github.com/micropython/micropython-lib/pull/82/files 15 | 16 | In addition, you need some authentication keys and secrets for Twitter. 17 | You can obtain 18 | them at https://dev.twitter.com/oauth/overview/application-owner-access-tokens . 19 | You can apply them to the variables in the tweet.py as following. 20 | 21 | ```python 22 | CK = '' # CONSUMER_KEY 23 | CS = '' # CONSUMER_SECRET 24 | AT = '' # ACCESS_KEY 25 | AS = '' # ACCESS_SECRET 26 | ``` 27 | 28 | Any question, please send email to github@flogics.com though I may not have time to answer clearly. 29 | 30 | Thanks you. 31 | 32 | Yokoyama, Atsushi 33 | 34 | Firmlogics (http://flogics.com) 35 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # Twitter bot for MicroPython v1.8.4 (ESP8166) 2 | # 3 | # Copyright (c) 2016, Atsushi Yokoyama, Firmlogics (http://flogics.com) 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are 8 | # met: 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following 13 | # disclaimer in the documentation and/or other materials provided 14 | # with the distribution. 15 | # * Neither the name of the nor the names of its 16 | # contributors may be used to endorse or promote products derived 17 | # from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | # import esp 32 | # esp.sleep_type(esp.SLEEP_MODEM) 33 | 34 | global sta_if 35 | 36 | def start_network(): 37 | global sta_if 38 | import network 39 | sta_if = network.WLAN(network.STA_IF) 40 | 41 | def wait_network_up(): 42 | import time 43 | while not sta_if.isconnected(): 44 | time.sleep_ms(100) 45 | print(sta_if.ifconfig()) 46 | print("Now connected") 47 | print(time.ticks_ms(), "ms") 48 | 49 | ############################################################################### 50 | 51 | def rm_tweet_txt(): 52 | import os 53 | os.remove('tweet.txt') 54 | 55 | start_network() 56 | wait_network_up() 57 | 58 | try: 59 | f = open('tweet.txt', 'r') 60 | str = f.read(4096) 61 | f.close() 62 | 63 | import usocket 64 | import ussl 65 | s=usocket.socket() 66 | addr = usocket.getaddrinfo("api.twitter.com", 443)[0][-1] 67 | s.connect(addr) 68 | s=ussl.wrap_socket(s) 69 | print(s) 70 | s.write(str) 71 | print(s.read(4096)) 72 | s.close() 73 | rm_tweet_txt() 74 | 75 | except: 76 | import tweet 77 | import esp 78 | esp.deepsleep(1) 79 | -------------------------------------------------------------------------------- /tweet.py: -------------------------------------------------------------------------------- 1 | # Twitter bot for MicroPython v1.8.4 (ESP8166) 2 | # 3 | # Copyright (c) 2016, Atsushi Yokoyama, Firmlogics (http://flogics.com) 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are 8 | # met: 9 | # * Redistributions of source code must retain the above copyright 10 | # notice, this list of conditions and the following disclaimer. 11 | # * Redistributions in binary form must reproduce the above 12 | # copyright notice, this list of conditions and the following 13 | # disclaimer in the documentation and/or other materials provided 14 | # with the distribution. 15 | # * Neither the name of the nor the names of its 16 | # contributors may be used to endorse or promote products derived 17 | # from this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 24 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 25 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 26 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | def current_time(): 32 | import time 33 | t = time.time() 34 | return t + 946684800 35 | 36 | def enc_percent(s): 37 | ret = '' 38 | for c in s: 39 | ordc = ord(c) 40 | if ordc in range(0x30, 0x39 + 1) or \ 41 | ordc in range(0x41, 0x5a + 1) or \ 42 | ordc in range(0x61, 0x7a + 1) or \ 43 | ordc in (0x2d, 0x2e, 0x5f, 0x7e): 44 | ret += c 45 | else: 46 | ret += '%%%02X' % (ordc) 47 | return ret 48 | 49 | def oauth_sign(method, url, s, vcs, vas): 50 | import hmac 51 | import ubinascii 52 | import uhashlib 53 | 54 | str = '' 55 | for t in sorted(s): 56 | if str != '': 57 | str += '&' 58 | str += "%s=%s" % (t[0], enc_percent(t[1])) 59 | str = "%s&%s&%s" % (method.upper(), enc_percent(url), enc_percent(str)) 60 | signing_key = bytearray(enc_percent(vcs) + '&' + enc_percent(vas)) 61 | hash = hmac.new(signing_key, msg=str, digestmod=uhashlib.sha1) 62 | ret = ubinascii.b2a_base64(hash.digest()).rstrip() 63 | return ret 64 | 65 | def oauth_genhead(vck, vcs, vat, vas, status): 66 | import ubinascii 67 | import os 68 | 69 | tstamp = current_time() 70 | nonce = 'nonce%d' % (tstamp) 71 | pairs = { 72 | ('status', status), 73 | ('include_entities', 'true'), 74 | ('oauth_consumer_key', vck), 75 | ('oauth_nonce', nonce), 76 | ('oauth_signature_method', 'HMAC-SHA1'), 77 | ('oauth_timestamp', str(tstamp)), 78 | ('oauth_token', vat), 79 | ('oauth_version', '1.0')} 80 | 81 | sig = oauth_sign('POST', \ 82 | 'https://api.twitter.com/1.1/statuses/update.json', \ 83 | pairs, vcs, vas).decode('utf-8') 84 | 85 | s = ''' OAuth oauth_consumer_key="%s", 86 | oauth_nonce="%s", 87 | oauth_signature="%s", 88 | oauth_signature_method="HMAC-SHA1", 89 | oauth_timestamp="%d", 90 | oauth_token="%s", 91 | oauth_version="1.0"''' % \ 92 | (vck, nonce, enc_percent(sig), tstamp, vat) 93 | return s 94 | 95 | def tweet(s): 96 | body = 'status=%s' % (enc_percent(s)) 97 | oauth_head = oauth_genhead(CK, CS, AT, AS, s) 98 | header = '''POST /1.1/statuses/update.json?include_entities=true HTTP/1.1 99 | Accept: */* 100 | Connection: close 101 | User-Agent: Pot Prant Bot v0.1 102 | Content-Type: application/x-www-form-urlencoded 103 | Authorization: 104 | %s 105 | Content-Length: %d 106 | Host: api.twitter.com 107 | ''' % (oauth_head, len(body)) 108 | return header + '\n' + body + '\n' 109 | 110 | ############################################################################### 111 | 112 | CK = '' # CONSUMER_KEY 113 | CS = '' # CONSUMER_SECRET 114 | AT = '' # ACCESS_KEY 115 | AS = '' # ACCESS_SECRET 116 | 117 | time_diff = 9 * 3600 # Time difference 118 | 119 | import time 120 | import ntptime 121 | 122 | while True: 123 | try: 124 | ntptime.settime() 125 | break 126 | except: 127 | pass 128 | 129 | t = time.localtime(time.time() + time_diff) 130 | time_str = '%02d/%02d/%02d %02d:%02d:%02d' % t[0:6] 131 | tweet_status = 'Pot plant needs water (%s)' % (time_str) 132 | s = tweet(tweet_status) 133 | f = open('tweet.txt', 'w') 134 | f.write(s) 135 | f.close() 136 | --------------------------------------------------------------------------------