"""
Smart Supply Chain Solution using IoT with GPS Simulation
Tracks shipment location, detects package opening, and Geofencing Violations
To view the data:
1. Go to http://www.hivemq.com/demos/websocket-client/
2. Click "Connect"
3. Under Subscriptions, click "Add New Topic Subscription"
4. In the Topic field, type "wokwi-shipment" then click "Subscribe"
Now click on the DHT22 sensor in the simulation,
change the temperature/humidity, and you should see
the message appear on the MQTT Broker, in the "Messages" pane.
https://wokwi.com/projects/415340579274051585
"""
import network
import time
from machine import Pin, I2C, ADC
import ujson
from umqtt.simple import MQTTClient
import ssd1306
import random # For simulating GPS coordinates
import ntptime # NTP for current time
import math # For geofence calculations
# MQTT Setup
MQTT_CLIENT_ID = "clientId-ME677zpl8S"
MQTT_BROKER = "mqtt-dashboard.com"
MQTT_USER = ""
MQTT_PASSWORD = ""
MQTT_TOPIC = "wokwi-shipment"
MQTT_ALERT_TOPIC = "shipment/alerts"
# LDR Sensor Setup
ldr_sensor = ADC(Pin(34))
ldr_sensor.atten(ADC.ATTN_11DB) # Full 3.3V range
ldr_sensor.width(ADC.WIDTH_10BIT)
# OLED Setup
i2c = I2C(0, scl=Pin(22), sda=Pin(21))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.text("Initializing...", 0, 0)
oled.show()
# WiFi Connection
print("Connecting to WiFi", end="")
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect('Wokwi-GUEST', '')
while not sta_if.isconnected():
print(".", end="")
time.sleep(0.1)
print(" Connected!")
# Sync time using NTP
try:
print("Syncing time with NTP...")
ntptime.settime() # Set the RTC to UTC time
print("Time synced!")
except Exception as e:
print("Failed to sync time:", e)
# MQTT Connection
print("Connecting to MQTT server... ", end="")
client = MQTTClient(MQTT_CLIENT_ID, MQTT_BROKER, user=MQTT_USER, password=MQTT_PASSWORD)
client.connect()
print("Connected!")
# Geofence Setup
GEOFENCE_CENTER = (12.9716, 77.5946) # Example: Bangalore's latitude, longitude
GEOFENCE_RADIUS = 5.0 # Radius in kilometers
def haversine(lat1, lon1, lat2, lon2):
"""Calculate the great-circle distance between two GPS points."""
R = 6371.0 # Earth's radius in kilometers
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
dlat = lat2 - lat1
dlon = lon2 - lon1
a = math.sin(dlat / 2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
# # State Ranges
# DARK_THRESHOLD = 50
# DIM_LIGHT_THRESHOLD = 300
# # State Definitions
# CLOSED = "Dark (Closed)"
# MAYBE_OPENED = "Dim Light (Maybe Opened)"
# SURELY_OPENED = "Bright Light (Opened)"
# Function to determine light state
# def get_light_state(lux_value):
# if lux_value <= DARK_THRESHOLD:
# return CLOSED
# elif DARK_THRESHOLD < lux_value <= DIM_LIGHT_THRESHOLD:
# return MAYBE_OPENED
# else:
# return SURELY_OPENED
# Function to get current time
def get_current_time():
current_time = time.localtime() # Local time in UTC
return "{:02d}:{:02d}:{:02d}".format(current_time[3], current_time[4], current_time[5])
# Initialize Variables
prev_state = None
prev_geofence_status = None
tampered = False # Tracks if tampering has occurred
is_first = True
while True:
# Add a 5-second buffer time before starting the readings
if is_first:
print("Waiting for 10 seconds to adjust sensor...")
time.sleep(10) # 5 seconds buffer to adjust LDR sensor
is_first = False
# Read light level
light_level = 1023 - ldr_sensor.read() # Inverted LDR reading for lux-like behavior
# Determine current state
current_state = "CLOSED"
#print("light level: ", light_level)
if light_level == 0:
current_state = "CLOSED"
else:
current_state = "OPENED"
tampered = True
# Simulate GPS coordinates
latitude = random.uniform(12.0, 13.0) # Replace with actual GPS readings
longitude = random.uniform(77.0, 78.0) # Replace with actual GPS readings
current_time = get_current_time()
print(f"Current Time: {current_time}")
print(f"Current Latitude: {latitude}")
print(f"Current Longitude: {longitude}\n")
# Geofence check
distance_from_center = haversine(GEOFENCE_CENTER[0], GEOFENCE_CENTER[1], latitude, longitude)
inside_geofence = distance_from_center <= GEOFENCE_RADIUS
if inside_geofence:
geofence_message = "Shipment is within the geofence."
else:
geofence_message = "Shipment has exited the geofence!"
# Check for geofence status change
if inside_geofence != prev_geofence_status:
print(geofence_message)
client.publish(MQTT_ALERT_TOPIC, geofence_message)
prev_geofence_status = inside_geofence
print("current_state: ", current_state)
print("tampered: ", tampered)
print("\n")
# Update OLED display
oled.fill(0)
oled.text(f"Light: {light_level} Lux", 0, 0)
oled.text(f"State: {current_state}", 0, 10)
oled.text(f"TAMPERED: {'YES' if tampered else 'NO'}", 0, 20) # Tampered status
oled.text(f"Lat: {latitude:.3f}", 0, 30)
oled.text(f"Lon: {longitude:.3f}", 0, 40)
oled.text(f"Time: {current_time}", 0, 50)
oled.show()
# Prepare and send MQTT message
message = ujson.dumps({
"light_level": light_level,
"state": current_state,
"latitude": round(latitude, 6),
"longitude": round(longitude, 6),
"time": current_time,
"geofence_status": "Inside" if inside_geofence else "Outside",
"tampered": tampered
})
client.publish(MQTT_TOPIC, message)
print(f"MQTT Message Sent: {message}")
time.sleep(3)