예제 #1
0
파일: worker.go 프로젝트: uzhinskiy/gosms
func processMessages(modem *modem.GSMModem) {
	defer func() {
		log.Info("--- deferring ProcessMessage")
	}()

	//log.Info("--- ProcessMessage")
	for {
		message := <-messages
		log.Info("processing: ", message.UUID, modem.DeviceId)

		status := modem.SendSMS(message.Mobile, message.Body)
		if strings.Contains(status, "OK") {
			message.Status = SMSProcessed
		} else if strings.Contains(status, "ERROR") {
			message.Status = SMSError
		} else {
			message.Status = SMSPending
		}
		message.Device = modem.DeviceId
		message.Retries++
		updateMessageStatus(message)
		if message.Status != SMSProcessed && message.Retries < SMSRetryLimit {
			// push message back to queue until either it is sent successfully or
			// retry count is reached
			// I can't push it to channel directly. Doing so may cause the sms to be in
			// the queue twice. I don't want that
			EnqueueMessage(&message, false)
		}
		time.Sleep(time.Microsecond * 500)
	}
}
예제 #2
0
파일: modem.go 프로젝트: ivahaev/gosms
func (m *GSMModem) SendCommand(command string, waitForOk bool) string {
	log.Info("SendCommand: ", command)
	var status string = ""
	m.Port.Flush()
	_, err := m.Port.Write([]byte(command))
	if err != nil {
		log.Error(err)
		return ""
	}
	buf := make([]byte, 32)
	var loop int = 1
	if waitForOk {
		loop = 10
	}
	for i := 0; i < loop; i++ {
		// ignoring error as EOF raises error on Linux
		n, _ := m.Port.Read(buf)
		if n > 0 {
			status = string(buf[:n])
			log.Info("SendCommand: rcvd bytes: ", n, status)
			if strings.Contains(status, "OK\r\n") || strings.Contains(status, "ERROR\r\n") {
				break
			}
		}
	}
	return status
}
예제 #3
0
파일: main.go 프로젝트: uzhinskiy/gosms
func main() {

	log.Info("main: ", "Initializing gosms")
	//load the config, abort if required config is not preset
	appConfig, err := gosms.GetConfig("conf.ini")
	if err != nil {
		log.Error("main: ", "Invalid config: ", err.Error(), " Aborting")
		os.Exit(1)
	}

	db, err := gosms.InitDB("sqlite3", "db.sqlite")
	if err != nil {
		log.Info("main: ", "Error initializing database: ", err, " Aborting")
		os.Exit(1)
	}
	defer db.Close()

	serverhost, _ := appConfig.Get("SETTINGS", "SERVERHOST")
	serverport, _ := appConfig.Get("SETTINGS", "SERVERPORT")

	_numDevices, _ := appConfig.Get("SETTINGS", "DEVICES")
	numDevices, _ := strconv.Atoi(_numDevices)
	log.Info("main: number of devices: ", numDevices)

	var modems []*modem.GSMModem
	for i := 0; i < numDevices; i++ {
		dev := fmt.Sprintf("DEVICE%v", i)
		_port, _ := appConfig.Get(dev, "COMPORT")
		_baud := 115200 //appConfig.Get(dev, "BAUDRATE")
		_devid, _ := appConfig.Get(dev, "DEVID")
		m := modem.New(_port, _baud, _devid)
		modems = append(modems, m)
	}

	_bufferSize, _ := appConfig.Get("SETTINGS", "BUFFERSIZE")
	bufferSize, _ := strconv.Atoi(_bufferSize)

	_bufferLow, _ := appConfig.Get("SETTINGS", "BUFFERLOW")
	bufferLow, _ := strconv.Atoi(_bufferLow)

	_loaderTimeout, _ := appConfig.Get("SETTINGS", "MSGTIMEOUT")
	loaderTimeout, _ := strconv.Atoi(_loaderTimeout)

	_loaderCountout, _ := appConfig.Get("SETTINGS", "MSGCOUNTOUT")
	loaderCountout, _ := strconv.Atoi(_loaderCountout)

	_loaderTimeoutLong, _ := appConfig.Get("SETTINGS", "MSGTIMEOUTLONG")
	loaderTimeoutLong, _ := strconv.Atoi(_loaderTimeoutLong)

	log.Info("main: Initializing worker")
	gosms.InitWorker(modems, bufferSize, bufferLow, loaderTimeout, loaderCountout, loaderTimeoutLong)

	log.Info("main: Initializing server")
	err = InitServer(serverhost, serverport)
	if err != nil {
		log.Error("main: ", "Error starting server: ", err.Error(), " Aborting")
		os.Exit(1)
	}
}
예제 #4
0
파일: db.go 프로젝트: ivahaev/gosms
func getPendingMessages(bufferSize int) (result []SMS, err error) {
	log.Info("getPendingMessages ")
	allMessages, err := sdb.GetAll(bucket)
	if err != nil {
		log.Error(err)
		return
	}
	result = []SMS{}
	for _, _m := range allMessages {
		sms := SMS{}
		err := json.Unmarshal(_m, &sms)
		if err != nil {
			log.Error(err)
			return nil, err
		}
		if sms.Status != SMSProcessed && sms.Retries < SMSRetryLimit {
			result = append(result, sms)
		}
		if len(result) >= bufferSize {
			break
		}
	}

	return
}
예제 #5
0
파일: worker.go 프로젝트: uzhinskiy/gosms
func InitWorker(modems []*modem.GSMModem, bufferSize, bufferLow, loaderTimeout, countOut, loaderLongTimeout int) {
	log.Info("--- InitWorker")

	bufferMaxSize = bufferSize
	bufferLowCount = bufferLow
	messageLoaderTimeout = time.Duration(loaderTimeout) * time.Minute
	messageLoaderCountout = countOut
	messageLoaderLongTimeout = time.Duration(loaderLongTimeout) * time.Minute

	messages = make(chan SMS, bufferMaxSize)
	wakeupMessageLoader = make(chan bool, 1)
	wakeupMessageLoader <- true
	messageCountSinceLastWakeup = 0
	timeOfLastWakeup = time.Now().Add((time.Duration(loaderTimeout) * -1) * time.Minute) //older time handles the cold start state of the system

	// its important to init messages channel before starting modems because nil
	// channel is non-blocking

	for i := 0; i < len(modems); i++ {
		modem := modems[i]
		err := modem.Connect()
		if err != nil {
			log.Error("InitWorker: error connecting", modem.DeviceId, err)
			continue
		}
		go processMessages(modem)
	}
	go messageLoader(bufferMaxSize, bufferLowCount)
}
예제 #6
0
파일: modem.go 프로젝트: ivahaev/gosms
func (m *GSMModem) SendSMS(mobile string, message string) string {
	log.Info("SendSMS ", mobile, message)
	mobile = strings.Replace(mobile, "+", "", -1)
	// detected a double-width char
	if len([]rune(message)) < len(message) {
		log.Info("This is UNICODE sms. Will use PDU mode")
		return m.SendPduSMS(mobile, message)
	}
	// Put Modem in SMS Text Mode
	m.SendCommand("AT+CMGF=1\r", false)

	m.SendCommand("AT+CMGS=\""+mobile+"\"\r", false)

	// EOM CTRL-Z = 26
	return m.SendCommand(message+string(26), true)

}
예제 #7
0
파일: server.go 프로젝트: uzhinskiy/gosms
// dashboard
func indexHandler(w http.ResponseWriter, r *http.Request) {
	log.Info("--- indexHandler")
	// templates.ExecuteTemplate(w, "index.html", nil)
	// Use during development to avoid having to restart server
	// after every change in HTML
	t, _ := template.ParseFiles("./templates/index.html")
	t.Execute(w, nil)
}
예제 #8
0
파일: db.go 프로젝트: ivahaev/gosms
func insertMessage(sms *SMS) error {
	log.Info("insertMessage ", sms)
	err := sdb.Save(bucket, sms.UUID, sms)
	if err != nil {
		log.Error("Error when inserting message: ", err)
	}
	return nil
}
예제 #9
0
파일: worker.go 프로젝트: uzhinskiy/gosms
func EnqueueMessage(message *SMS, newMessage bool) {
	log.Info("--- EnqueueMessage: ", message)
	if newMessage {
		log.Info("This is new message. Will insert to DB.")
		insertMessage(message)
	}
	//wakeup message loader and exit
	go func() {
		//notify the message loader only if its been to too long
		//or too many messages since last notification
		messageCountSinceLastWakeup++
		if newMessage || messageCountSinceLastWakeup > messageLoaderCountout || time.Now().Sub(timeOfLastWakeup) > messageLoaderTimeout {
			log.Info("EnqueueMessage: ", "waking up message loader")
			wakeupMessageLoader <- true
			messageCountSinceLastWakeup = 0
			timeOfLastWakeup = time.Now()
		}
		log.Info("EnqueueMessage - anon: count since last wakeup: ", messageCountSinceLastWakeup)
	}()
}
예제 #10
0
파일: modem.go 프로젝트: ivahaev/gosms
func (m *GSMModem) SendPduSMS(mobile string, message string) string {
	log.Info("SendPduSMS ", mobile, message)
	if len([]rune(message)) > 70 {
		log.Info("This is long message. Will split")
		return m.SendLongPduSms(mobile, message)
	}
	// Put Modem in SMS Binary Mode
	status := m.SendCommand("AT+CMGF=0\r", false)

	telNumber := "01" + "00" + fmt.Sprintf("%02X", len(mobile)) + "91" + encodePhoneNumber(mobile)
	encodedText := pdu.EncodeUcs2ToString(message)
	textLen := lenInHex(encodedText)
	text := telNumber + "0008" + textLen + encodedText

	status = m.SendCommand("AT+CMGS="+strconv.Itoa(lenInBytes(text))+"\r", false)
	text = "00" + text
	// EOM CTRL-Z = 26
	status = m.SendCommand(text+string(26), true)
	return status

}
예제 #11
0
파일: db.go 프로젝트: ivahaev/gosms
func GetStatusSummary() ([]int, error) {
	log.Info("GetStatusSummary")
	allMessages, err := GetMessages("")
	if err != nil {
		log.Error(err)
		return nil, err
	}
	statusSummary := make([]int, 3)
	for _, sms := range allMessages {
		statusSummary[sms.Status]++
	}
	return statusSummary, nil
}
예제 #12
0
파일: server.go 프로젝트: uzhinskiy/gosms
func InitServer(host string, port string) error {
	log.Info("--- InitServer ", host, port)

	r := mux.NewRouter()
	r.StrictSlash(true)

	r.HandleFunc("/", indexHandler)

	// handle static files
	r.HandleFunc(`/assets/{path:[a-zA-Z0-9=\-\/\.\_]+}`, handleStatic)

	// all API handlers
	api := r.PathPrefix("/api").Subrouter()
	api.Methods("GET").Path("/logs/").HandlerFunc(getLogsHandler)
	api.Methods("POST").Path("/sms/").HandlerFunc(sendSMSHandler)

	http.Handle("/", r)

	bind := fmt.Sprintf("%s:%s", host, port)
	log.Info("listening on: ", bind)
	return http.ListenAndServe(bind, nil)

}
예제 #13
0
파일: db.go 프로젝트: ivahaev/gosms
func GetMessages(filter string) (result []SMS, err error) {
	log.Info("GetMessages")
	allMessages, err := sdb.GetAll(bucket)
	if err != nil {
		log.Error(err)
		return
	}
	result = []SMS{}
	for _, _m := range allMessages {
		sms := SMS{}
		err := json.Unmarshal(_m, &sms)
		if err != nil {
			log.Error("Error when unmarshaling message: ", err)
			return nil, err
		}
		//		if sms.Status != SMSProcessed && sms.Retries < SMSRetryLimit {
		result = append(result, sms)
		//		}
	}
	return result, nil
}
예제 #14
0
파일: server.go 프로젝트: uzhinskiy/gosms
// push sms, allowed methods: POST
func sendSMSHandler(w http.ResponseWriter, r *http.Request) {
	log.Info("--- sendSMSHandler")
	w.Header().Set("Content-type", "application/json")

	//TODO: validation
	r.ParseForm()
	mobile := r.FormValue("mobile")
	message := r.FormValue("message")
	uuid := uuid.NewV4()
	sms := &gosms.SMS{UUID: uuid.String(), Mobile: mobile, Body: message, Retries: 0, CreatedAt: time.Now()}
	gosms.EnqueueMessage(sms, true)

	smsresp := SMSResponse{Status: 200, Message: "ok"}
	var toWrite []byte
	toWrite, err := json.Marshal(smsresp)
	if err != nil {
		log.Error(err)
		//lets just depend on the server to raise 500
	}
	w.Write(toWrite)
}
예제 #15
0
파일: server.go 프로젝트: uzhinskiy/gosms
// dumps JSON data, used by log view. Methods allowed: GET
func getLogsHandler(w http.ResponseWriter, r *http.Request) {
	log.Info("--- getLogsHandler")
	messages, _ := gosms.GetMessages("")
	summary, _ := gosms.GetStatusSummary()
	dayCount, _ := gosms.GetLast7DaysMessageCount()
	logs := SMSDataResponse{
		Status:   200,
		Message:  "ok",
		Summary:  summary,
		DayCount: dayCount,
		Messages: messages,
	}
	var toWrite []byte
	toWrite, err := json.Marshal(logs)
	if err != nil {
		log.Error(err)
		//lets just depend on the server to raise 500
	}
	w.Header().Set("Content-type", "application/json")
	w.Write(toWrite)
}
예제 #16
0
파일: db.go 프로젝트: ivahaev/gosms
func GetLast7DaysMessageCount() (map[string]int, error) {
	log.Info("GetLast7DaysMessageCount")
	allMessages, err := GetMessages("")
	if err != nil {
		log.Error(err)
		return nil, err
	}
	dayCount := make(map[string]int)
	fromDate := time.Now().Add(-time.Hour * 24 * 8)
	for _, sms := range allMessages {
		if sms.CreatedAt.After(fromDate) {
			createdAt := sms.CreatedAt.Format("2006-01-02")
			count, ok := dayCount[createdAt]
			if ok {
				count++
			} else {
				count = 1
			}
			dayCount[createdAt] = count
		}
	}
	return dayCount, nil
}
예제 #17
0
파일: db.go 프로젝트: ivahaev/gosms
func updateMessageStatus(sms SMS) error {
	log.Info("updateMessageStatus ", sms)
	encoded, err := sdb.Get(bucket, sms.UUID)
	if err != nil {
		log.Error("Error when getting message: ", err)
		return err
	}
	oldSms := SMS{}
	err = json.Unmarshal(encoded, &oldSms)
	if err != nil {
		log.Error("Error when unmarshaling message: ", err)
		return err
	}
	oldSms.Status = sms.Status
	oldSms.Retries = sms.Retries
	oldSms.Device = sms.Device
	oldSms.UpdatedAt = time.Now()
	err = sdb.Save(bucket, oldSms.UUID, oldSms)
	if err != nil {
		log.Error("Error when inserting message: ", err)
	}
	return err
}
예제 #18
0
파일: worker.go 프로젝트: uzhinskiy/gosms
func messageLoader(bufferSize, minFill int) {
	// Load pending messages from database as needed
	for {

		/*
		   - set a fairly long timeout for wakeup
		   - if there are very few number of messages in the system and they failed at first go,
		   and there are no events happening to call EnqueueMessage, those messages might get
		   stalled in the system until someone knocks on the API door
		   - we can afford a really long polling in this case
		*/
		timeout := make(chan bool, 1)
		go func() {
			time.Sleep(messageLoaderLongTimeout)
			timeout <- true
		}()
		log.Info("messageLoader: ", "waiting for wakeup call")
		select {
		case <-wakeupMessageLoader:
			log.Info("messageLoader: woken up by channel call")
		case <-timeout:
			log.Info("messageLoader: woken up by timeout")
		}
		if len(messages) >= bufferLowCount {
			//if we have sufficient number of messages to process,
			//don't bother hitting the database
			log.Info("messageLoader: ", "I have sufficient messages")
			continue
		}

		countToFetch := bufferMaxSize - len(messages)
		log.Info("messageLoader: ", "I need to fetch more messages", countToFetch)
		pendingMsgs, err := getPendingMessages(countToFetch)
		if err == nil {
			log.Info("messageLoader: ", len(pendingMsgs), " pending messages found")
			for _, msg := range pendingMsgs {
				messages <- msg
			}
		} else {
			log.Error(err)
		}
	}
}