Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
陳亮Royry1031
Published © MIT

Pill Machine

Smart way to take medicine without worry schedule, miss medicines or wrong pills

AdvancedFull instructions providedOver 3 days4,056
Pill Machine

Things used in this project

Hardware components

SG90 Micro-servo motor
SG90 Micro-servo motor
×2
2.4 inch USART HMI serial LCD display
×1
JQ8400 MP3 voice module
×1
3W 1.5 inches speaker
×1
Pen holder
2nd item
×1
0.4 mm thick*3mm torsion spring
16th item (50 per pack)
×8
M1*6mm screw
4th item, it is very easily broke, you should order more than 18
×18
m3*5mm screw
13th item (10 per pack)
×2
m3*8mm screw
14th item (10 per pack)
×4
Evian 500ml bottle
For pill container, you should buy one at the store near you :P
×1
2*10mm axle
1st row 1st item (20 per pack)
×8

Software apps and online services

Arduino IDE
Arduino IDE
USART HMI
for display module deployment

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Pill container cover

A simple decoration for the pill container

Wheel

Pill container wheel frame

Ejector 7mm

Pill ejector for 7mm x 7mm x 12mm pill

Ejector 8.5mm

Pill ejector for 8.5mm x 8.5mm x 5mm pill

Ejector 10mm

Pill ejector for 10mm x 10mm x 5mm pill

Plug

Plug on the spinner servo, for plug on the pill container

Gear

Pill ejector gear on the ejector servo

Internal base

Core internal base frame

Internal base support

Support between PCB and internal base part

PCB support

Support between the pen holder and PCB

Slide

Pill slide, guide the pill slide out after eject

LCD support

Support between LCD frame and slide

LCD frame

Frame for the LCD module

Schematics

USART HMI design file

Pill Slot Cutout

Print it with a transparency and cut it out.

Pill Machine main circuit board

Test it with bread board first before soldering work.

Code

PillMachino.ino

Arduino
IoT Pill Machine core program
#include <Servo.h>
#include <SoftwareSerial.h>
#include <Process.h>

#define VOICE_MODULE
#define DEMO_ONLY
//#define VIDEO_TAKING_ONLY

#define SPINNER_PIN 5
#define EJECTOR_PIN 6

#define DISPLAY_RX 8
#define DISPLAY_TX 9

#ifdef VOICE_MODULE
#define VOICE_RX 10
#define VOICE_TX 11
#endif

#define EJECT_ANGLE 35
#define REST_ANGLE 180

#define USER_INFO_URL "http://YOUR_IP:YOUR_PORT/user_info"
#define NEXT_SCHEDULE_URL "http://YOUR_IP:YOUR_PORT/next_schedule"
#define TAKEN_URL "http://YOUR_IP:YOUR_PORT/taken"

Servo spinner_servo;
Servo ejector_servo;
SoftwareSerial display_serial(DISPLAY_RX, DISPLAY_TX);
#ifdef VOICE_MODULE
SoftwareSerial voice_serial(VOICE_RX, VOICE_TX);
#endif

int pill_slot_angle[] = {15, 60, 105, 150};

byte buf_page_cmd[] = {'p', 'a', 'g', 'e', ' ', '0', 0xFF, 0xFF, 0xFF};
byte buf_set_str_cmd[] = {'s', 'c', 'h', 'e', '_', 's', 't', 'r', '.', 't', 'x', 't', '=', '"', '0', '0', ':', '0', '0', '"', 0xFF, 0xFF, 0xFF};

#ifdef VOICE_MODULE
// voice volume 1-30
//byte buf_voice_vol[] = {0xAA, 0x13, 0x01, 0x0F, 0xCD}; // command to set volume 15
//byte buf_voice_vol[] = {0xAA, 0x13, 0x01, 0x14, 0xD2}; // command to set volume 20
byte buf_voice_vol[] = {0xAA, 0x13, 0x01, 0x19, 0xD7}; // command to set volume 25
//byte buf_voice_vol[] = {0xAA, 0x13, 0x01, 0x1E, 0xDC}; // command to set volume 30

byte buf_play[4][6] = {
  {0xAA, 0x07, 0x02, 0x00, 0x01, 0xB4}, // command to play 01.mp3
  {0xAA, 0x07, 0x02, 0x00, 0x02, 0xB5}, // command to play 02.mp3
  {0xAA, 0x07, 0x02, 0x00, 0x03, 0xB6}, // command to play 03.mp3
  {0xAA, 0x07, 0x02, 0x00, 0x04, 0xB7}  // command to play 04.mp3
};
#endif

// function declaration
void ui_page(byte page);
void wait_next_schedule();
void eject_pill(byte pill_slot);
void get_user_info();
void get_next_schedule();
void push_taken();

int cur_step = 0;
int delay_time = 0;

void setup() {
  // Initialize Serial For Debug Output
  Serial.begin(9600);

  // Initialize Pill Ejection Module
  spinner_servo.attach(SPINNER_PIN);
  spinner_servo.write(0);
  ejector_servo.attach(EJECTOR_PIN);
  ejector_servo.write(REST_ANGLE);

  // Initialize Display Module
  display_serial.begin(9600);

#ifdef VOICE_MODULE
  // Initialize Voice Module
  voice_serial.begin(9600);
  voice_serial.write(buf_voice_vol, 5);
#endif

  // display loading screen
  ui_page(0);

  // Initialize Bridge
  Bridge.begin();

  // display splash screen
  ui_page(1);
}

// the loop routine runs over and over again forever:
void loop() {
  display_serial.listen();
  while (display_serial.available() > 0) {
    char in_char = display_serial.read();
    Serial.write(in_char);
    switch (in_char) { // check signal from which screen
      case '1':
        // display user info screen
        ui_page(2);
        break;
      case '2':
        // display next schedule screen
        ui_page(3);
        break;
#ifdef VIDEO_TAKING_ONLY
      case '3': // should be triggered by timer
        wait_next_schedule();
        break;
#endif
      case '4':
        if (cur_step == 0) {
          eject_pill(0);
          eject_pill(1);
          eject_pill(2);
          ui_page(5);
        } else if (cur_step == 1) {
          eject_pill(0);
          eject_pill(2);
          ui_page(6);
        } else {
          eject_pill(1);
          ui_page(7);
        }
        spinner_servo.write(0);
        break;
      case '5':
        push_taken();
        cur_step = 1;
        ui_page(3);
        break;
      case '6':
        push_taken();
        cur_step = 2;
        ui_page(3);
        break;
      case '7':
        push_taken();
        cur_step = 0;
        ui_page(3);
        break;
      default:
        break;
    }
    while (display_serial.available() > 0) {
      char c = display_serial.read();
    }
  }
}

void ui_page(byte page) {
  switch (page) {
    case 0:
      buf_page_cmd[5] = '0';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
      break;
    case 1:
      buf_page_cmd[5] = '1';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
      break;
    case 2:
      buf_page_cmd[5] = '2';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
      //TODO: get and display user info from cloud server
      //get_user_info();
#ifdef VOICE_MODULE
      voice_serial.write(buf_play[0], 6);
#endif
      break;
    case 3:
      buf_page_cmd[5] = '3';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
      buf_set_str_cmd[0] = 's';
      buf_set_str_cmd[1] = 'c';
      buf_set_str_cmd[2] = 'h';
#ifdef DEMO_ONLY
      if (cur_step == 0) {
        buf_set_str_cmd[14] = '1';
        buf_set_str_cmd[15] = '4';
        display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
      } else if (cur_step == 1) {
        buf_set_str_cmd[14] = '2';
        buf_set_str_cmd[15] = '0';
        display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
      } else {
        buf_set_str_cmd[14] = '0';
        buf_set_str_cmd[15] = '8';
        display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
      }
#else
      //TODO: get and display time from cloud schedule
      //get_next_schedule();
      //buf_set_str_cmd[14] =
      //buf_set_str_cmd[15] =
      //buf_set_str_cmd[17] =
      //buf_set_str_cmd[18] =
      //display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
#endif
#ifdef VOICE_MODULE
      voice_serial.write(buf_play[1], 6);
#endif
#ifdef VIDEO_TAKING_ONLY
      // do nothing
#else
      wait_next_schedule();
#endif
      break;
    case 4:
      buf_page_cmd[5] = '4';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
      buf_set_str_cmd[0] = 't';
      buf_set_str_cmd[1] = 'i';
      buf_set_str_cmd[2] = 'm';
#ifdef DEMO_ONLY
      if (cur_step == 0) {
        buf_set_str_cmd[14] = '1';
        buf_set_str_cmd[15] = '4';
        display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
      } else if (cur_step == 1) {
        buf_set_str_cmd[14] = '2';
        buf_set_str_cmd[15] = '0';
        display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
      } else {
        buf_set_str_cmd[14] = '0';
        buf_set_str_cmd[15] = '8';
        display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
      }
#else
      //TODO: display time from cloud schedule
      //buf_set_str_cmd[14] =
      //buf_set_str_cmd[15] =
      //buf_set_str_cmd[17] =
      //buf_set_str_cmd[18] =
      //display_serial.write(buf_set_str_cmd, sizeof(buf_set_str_cmd));
#endif
#ifdef VOICE_MODULE
      voice_serial.write(buf_play[2], 6);
#endif
      break;
    case 5:
      buf_page_cmd[5] = '5';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
#ifdef VOICE_MODULE
      voice_serial.write(buf_play[3], 6);
#endif
      break;
    case 6:
      buf_page_cmd[5] = '6';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
#ifdef VOICE_MODULE
      voice_serial.write(buf_play[3], 6);
#endif
      break;
    case 7:
      buf_page_cmd[5] = '7';
      display_serial.write(buf_page_cmd, sizeof(buf_page_cmd));
#ifdef VOICE_MODULE
      voice_serial.write(buf_play[3], 6);
#endif
      break;
    default:
      break;
  }
}

void wait_next_schedule() {
  //TODO: use timer
#ifdef DEMO_ONLY
  Serial.println("wait 10 seconds");
  delay(10000);
#else
  Serial.print("wait ");
  Serial.print(delay_time);
  Serial.println(" milliseconds");
  delay(delay_time);
#endif
  ui_page(4);
}

void eject_pill(byte pill_slot) {
  spinner_servo.write(pill_slot_angle[pill_slot]);
  delay(1000);
  ejector_servo.write(EJECT_ANGLE);
  delay(1000);
  ejector_servo.write(REST_ANGLE);
  delay(1000);
}

void get_user_info() {
  // Launch "curl" command and get Arduino ascii art logo from the network
  // curl is command line program for transferring data using different internet protocols
  Process p;        // Create a process and call it "p"
  p.begin("curl");  // Process that launch the "curl" command
  // Add the URL parameter to "curl"
  p.addParameter("--data");
  p.addParameter("id=pill_machine");
  p.addParameter(USER_INFO_URL);
  p.run();      // Run the process and wait for its termination

  // A process output can be read with the stream methods
  while (p.available()>0) {
    char c = p.read();
    Serial.print(c);
    //TODO: save user info to variable
  }
  // Ensure the last bit of data is sent.
  Serial.flush();
}

void get_next_schedule() {
  // Launch "curl" command and get Arduino ascii art logo from the network
  // curl is command line program for transferring data using different internet protocols
  Process p;        // Create a process and call it "p"
  p.begin("curl");  // Process that launch the "curl" command
  // Add the URL parameter to "curl"
  p.addParameter("--data");
  p.addParameter("id=pill_machine");
  p.addParameter(NEXT_SCHEDULE_URL);
  p.run();      // Run the process and wait for its termination

  // A process output can be read with the stream methods
  while (p.available()>0) {
    char c = p.read();
    Serial.print(c);
    //TODO: save next schedule to variable
    //delay_time =
  }
  // Ensure the last bit of data is sent.
  Serial.flush();
}

void push_taken() {
  // Launch "curl" command and get Arduino ascii art logo from the network
  // curl is command line program for transferring data using different internet protocols
  Process p;        // Create a process and call it "p"
  p.begin("curl");  // Process that launch the "curl" command
  // Add the URL parameter to "curl"
  p.addParameter("--data");
  p.addParameter("id=pill_machine");
  p.addParameter(TAKEN_URL);
  p.run();      // Run the process and wait for its termination

  // A process output can be read with the stream methods
  while (p.available()>0) {
    char c = p.read();
    Serial.print(c);
  }
  // Ensure the last bit of data is sent.
  Serial.flush();
}

IoT Pill Machine Mobile App

Credits

陳亮

陳亮

3 projects • 5 followers
Roy

Roy

1 project • 1 follower
ry1031

ry1031

1 project • 1 follower

Comments