Tracking LAN Uptime on My ASUS/Merlin Router – Lambda Collect

The introduction and architecture to the LAN uptime system that I have added to my ASUS/Merlin router has been described in a previous post.  This post will focus on the Lambda function that collects the data.  The code can be found at the end of this post.

This lambda function receives information passed to it from the router via API Gateway.  After initializing some variables it reads the last values stored in the DynamoDB table.  After that is it able to determine is a notification needs to be sent to the SNS topic and what that notification should be.  Lastly it saves the data into the DynamoDB table.

There were really no serious difficulties in this Lambda function.

  • The ExpirationEpochSecs DynamoDB attribute is set to a date in the future where DynamoDB will automatically delete the record via the Time to Live mechanism.  The format for this time value is the number of seconds elapsed since 12:00:00 AM January 1st, 1970 UTC.
  • When reading the DynamoDB table it is important to remember that the last record written may have been written yesterday.  So the code needs to check today first.  If no records returned, then check yesterday.  Both checks look for the latest record by limiting the return set to 1 record and using the reverse of the sort key.

Here is the code.

from time import localtime, strftime, time
from json import dumps
from boto3.dynamodb.conditions import Key, Attr
import boto3
import json

print('Loading function')

ddb = boto3.resource('dynamodb')
sns = boto3.client('sns')

def respond(err, res=None):
    return {
        'statusCode': '400' if err else '200',
        'body': err.message if err else res

def lambda_handler(event, context):
    print("Received event: " + dumps(event, indent=2))

    # initialize

    tableName = lan
    todayid = strftime('%d%m%y', localtime())
    timestamp = strftime('%Y%m%d%H%M%S', localtime())
    yesterdayid = strftime('%d%m%y', localtime(time() - 86400))
    expiration = int(time()) + 2678400 # plus 31 days in seconds
    error = False

    # get the new values
    newwiredalive = int(event['wiredalive'])
    newwiredhost = event['wiredhost']
    newwireless24alive = int(event['wireless24alive'])
    newwireless24host = event['wireless24host']
    newwireless5alive = int(event['wireless5alive'])
    newwireless5host = event['wireless5host']
    fromip = event['X-Forwarded-For']

    # read from dynamodb table lan
    tbl = ddb.Table(tableName)
    resp = tbl.query (TableName=tableName, Limit=1, ScanIndexForward=False, KeyConditionExpression=Key('DayId').eq(todayid))
    if len(resp['Items']) == 0:
        resp = tbl.query (TableName=tableName, Limit=1, ScanIndexForward=False, KeyConditionExpression=Key('DayId').eq(yesterdayid))
    if len(resp['Items']) == 0:
        oldwiredalive = 1
        oldwireless24alive = 1
        oldwireless5alive = 1
        oldwiredalive = int(itm['WiredAliveInd'])
        oldwireless24alive = int(itm['Wireless24AliveInd'])
        oldwireless5alive = int(itm['Wireless5AliveInd'])

    # check for need to notify
    notif = False
    msg = ''
    if newwiredalive != oldwiredalive:
        notif = True
        status = 'up'
        if newwiredalive == 0:
            status = 'down'
        msg = 'Wired connection to ' + newwiredhost + ' is ' + status + '.  '
    if newwireless24alive != oldwireless24alive:
        notif = True
        status = 'up'
        if newwireless24alive == 0:
            status = 'down'
        msg = msg + 'Wireless connection to ' + newwireless24host + ' is ' + status + '.  '
    if newwireless5alive != oldwireless5alive:
        notif = True
        status = 'up'
        if newwireless5alive == 0:
            status = 'down'
        msg = msg + 'Wireless connection to ' + newwireless5host + ' is ' + status + '.'

    if notif == True:
        sns.publish(TopicArn='arn:aws:sns:<region>:<account#>:<topic>', Message=msg)
    # write a new record to the table lan
    tbl.put_item (Item={
        'DayId': todayid,
        'CreateTS': timestamp,
        'ExpirationEpochSecs': expiration,
        'WiredAliveInd': newwiredalive,
        'WiredHost': newwiredhost,
        'Wireless24AliveInd': newwireless24alive,
        'Wireless24Host': newwireless24host,
        'Wireless5AliveInd': newwireless5alive,
        'Wireless5Host': newwireless5host,
        'FromIP': fromip
    return respond(None, msg)